class와 instance, object, member

muz·2021년 3월 29일
0

Java

목록 보기
7/21
post-thumbnail

🤔 프로그램의 변화

method를 사용하기 전

객체를 만들기 전, System.out.println()만을 이용하여 더하기 프로그램을 구현해보자.

public class Obejct {
    public static void main(String[] args) {
        System.out.println(2+1);
        System.out.println(2+2);
        System.out.println(2+3);
        // ...
        System.out.println(9+9);
    }
}

위의 코드를 보면 System.out.println()이 무수히 많이 반복되고 있다. 이러한 중복을 제거하기 위해서, 우리는 메소드를 사용할 수 있다.

method를 사용한 후

sum()이라는 함수를 선언하여 위의 코드를 간결화해보자.

public class Obejct {
    public static void sum(int i, int j) {
        System.out.println(i+j);
    }
    public static void main(String[] args) {
        sum(2,1);
        sum(2,2);
        sum(2,3);
        // ...
        sum(9,9);
    }
}

로직을 메소드로 만들면 중복을 제거할 수 있어 코드의 양도 줄일 수 있고, 문제가 생겼을 때 원인을 쉽게 찾을 수 있다. 그러나 같은 수를 이용하여 덧셈만이 아닌 다른 연산도 해야한다면 어떻게 해야할까? 덧셈과 평균을 구해야하는 새로 코드를 작성해보자.

public class Obejct {
    public static void sum(int i, int j) {
        System.out.println("sum : " + (i+j));
    }
    public static void avg(int i, int j) {
        System.out.println("avg : " + ((i+j)/2));
    }
    public static void main(String[] args) {

        int a = 2, b = 1;
        sum(a,b);
        avg(a,b);

        a = 3;
        b = 5;
        sum(a,b);
        avg(a,b);
        // ...

        a = 4;
        b =8;
        sum(a,b);
        avg(a,b);
    }
}

다시 코드에 반복이 생기고, 점차 복잡해지고 있다. 이에 더 많은 기능들이 추가되면 코드가 더 복잡해지고 버그도 많아질 것이다. 이러한 상황을 개선하기 위해 등장한 것이 '객체 지향'이다.

🤔 객체화

객체 지향의 핵심은 연관된 변수, 메소드를 하나의 그룹으로 묶어서 그룹핑하는 것이다. 바로 위의 코드를 보면 main에서 '변수에 값 대입과 함수 호출' 부분이 서로 연관되어 있고, 이들이 계속 반복되는 것을 볼 수 있다. 이들을 그룹핑하면 되는 것이다. 위의 코드를 객체화해보자.

class Calculator {
    int a,b; // 전역 변수 선언

    public void setOprands(int a, int b) {
        this.a = a;
        this.b = b;
    }

    public void sum() {
        System.out.println("sum : " + (this.a + this.b));
    }

    public void avg() {
        System.out.println("avg : " + ((this.a + this.b)/2));
    }

}
public class Obejct {
        public static void main(String[] args) {

        Calculator c1 = new Calculator();
        c1.setOprands(2, 1);
        c1.sum();
        c1.avg();

        Calculator c2 = new Calculator();
        c2.setOprands(3, 5);
        c2.sum();
        c2.avg();
    }
}

Class

위의 코드를 하나씩 분석해보자. 앞선 코드와는 달리 Object class 외에 'Calculator'라는 새로운 class가 생성되어있다.

class Calculator { ... }

변수 a와 b, method sum과 avg는 서로 연관되어 있는 로직이다. 이 로직들의 연관성은 '계산'을 위한 것이므로 'Calculator'라는 이름으로 그룹핑하고자 했다.

연관되어있는 변수와 메소드를 그룹핑하기 위해 class를 사용한다.

Instance

Class를 정의했다면 이를 사용해보자. 정의한 class는 'new'라는 키워드를 이용해서 사용할 수 있다.

Calculator c1 = new Calculator();

class Calculator가 설계도라면, 이를 가지고 구체적인 제품으로 생성하는 것이 new Calculator()이다. 이렇게 생성된 제품이 인스턴스(instance)이다.

  • class : 설계도
  • instance : 제품

new를 이용해서 만든 인스턴스를 변수 c1에 넣어주었는데, 이는 변수 c1을 통해 인스턴스를 조종하기 위함이다. 그냥 c1 = new Calculator()가 아닌 Calculator c1 = new Calculator()인 이유는 c1앞의 Calculator를 '데이터 타입'이라고 생각하면 된다. c1의 데이터 타입은 'Calculator라는 클래스'이다. '클래스를 만든다는 것'은 곧 '사용자 정의 데이터 타입을 만드는 것'과 같다. 일단 아래의 내용을 꼭 기억하자.

클래스를 인스턴스화 할 때는 변수에 담아야하며, 이 때 사용하는 변수의 데이터 타입은 해당하는 클래스가 된다.

객체를 만들어 사용하는 이유는 코드의 '재활용성'을 높이기 위함이다.

Calculator c1 = new Calculator();
c1.setOprands(10,20);
c1.sum();
c1.avg();

Calculator c2 = new Calculator();
c2.setOprands(20,40);
...

두 개의 인스턴스를 각각 변수 c1, c2에 담고 있다. 각각의 인스턴스는 setOprands()에서 변수 값을 설정하고 있어 인스턴스 c1과 c2는 서로 다른 변수의 값을 내부적으로 가질 수 있는 것이다.

public void setOprands(int a, int b) {
	this.a = a;
    this.b = b;
}

setOprands()를 살펴보자. this.라는 것은 클래스를 통해서 만들어진 인스턴스 자신을 가리킨다. 해당 코드에서 a와 b는 사용자에게 값을 입력받는 '매개변수'이고, 이 변수는 setOprands()내에서만 사용할 수 있는 '지역변수'이므로 이 외의 곳에서는 접근이 불가능하다. 반면 this.은 선언한 변수에 값을 설정하는 것이며, setOprands() 밖에서 선언한 변수 int a, b;Calculator{...} 클래스에서 선언한 '전역변수'이므로 해당 인스턴스 내의 모든 메소드에서 접근이 가능하다.

변수는 다른 말로 상태(State)라고도 한다. 상태가 다른 객체를 대상으로 메소드를 실행시키면 다른 결과를 나타내게 된다. 메소드는 다른 말로 행동(behave)이라고도 표현한다.

🚨 하나의 클래스를 바탕으로 서로 다른 상태를 가진 인스턴스를 만들면, 서로 다른 행동을 하게 된다.
→ 하나의 클래스가 여러 개의 인스턴스가 될 수 있다는 점이 객체 지향이 제공하는 '재활용성'이라고 볼 수 있다.

객체 지향의 객체를 하나의 작은 프로그램이라고 생각하면, 프로그램 안에 객체라는 작은 프로그램을 만드는 것이다.

🤔 멤버(member)

객체에도 구성원이 있다. 객체의 구성원은 '변수''메소드'이다. 객체를 생성하려면 클래스를 정의한 후, 클래스에 대한 인스턴스를 만들어야한다. 위의 코드에서 변수 a와 b는 인스턴스의 멤버이다. 인스턴스를 만들어야 사용할 수 있고, 인스턴스마다 서로 다른 값을 가지고 있기 때문이다.
이처럼 인스턴스가 변수와 메소드를 멤버로 가질 수 있듯이, 클래스도 변수와 메소드를 멤버로 가질 수 있다.

클래스

클래스 변수(Static Field)

Object.java에서 사용한 인스턴스 변수(Non-Static Field) a는 인스턴스마다 그 값이 달라질 수 있다. 인스턴스의 상태인 변수의 값이 인스턴스마다 다른 값을 가질 수 있다는 것은, 하나의 클래스를 여러 개의 인스턴스로 만들어서 사용할 수 있다는 점에서 좋은 기능이다.

그러나 때로는 경우에 따라서 모든 인스턴스가 같은 값을 공유하게 하고 싶을 때도 있을 것이다. 예를 들어 class Calculator{}가 사용자에게 원주율의 값 3.14를 제공하고 싶어한다면, '변수를 클래스의 멤버로' 만들면 된다.

class Calculator {
    int a,b; 
    static double PI = 3.14;

    public void setOprands(int a, int b) {
        this.a = a;
        this.b = b;
    }

    public void sum() {
        System.out.println("sum : " + (this.a + this.b));
    }

    public void avg() {
        System.out.println("avg : " + ((this.a + this.b)/2));
    }

}
public class Obejct {
        public static void main(String[] args) {

        Calculator c1 = new Calculator();
        System.out.println(c1.PI);

        Calculator c2 = new Calculator();
        System.out.println(c2.PI);
    }
}

실행 결과 c1.PIc2.PI의 값으로 3.14가 출력된다.

static double PI = 3.14;

변수나 메소드 선언 시 static을 붙이면 '클래스의 멤버'가 된다. 클래스 소속의 변수 만들었으니 직접 사용해보자. 클래스 변수에 접근하는 방법에는 2가지가 있다.

  • 인스턴스를 통해서 PI에 접근하기
    : System.out.println(c1.PI);
  • 클래스를 통해서 PI에 접근하기
    : System.out.println(Calculator.PI);

클래스를 통해 PI에 접근한 방법은 class Calculator의 sum()이나 avg() 기능은 필요없고, 오직 원주율만 필요할 때 '클래스에 직접 접근'해서 가져오는 것이므로 인스턴스가 필요없다.

클래스 변수는 변수의 변경사항을 모든 인스턴스에서 공유해야 할 때에도 사용한다. 계산 시 특정한 값을 포함시켜야 할 때를 생각해보자.

class Calculator {
    int a,b; 
    static double PI = 3.14;
    static int base = 10;

    public void setOprands(int a, int b) {
        this.a = a;
        this.b = b;
    }

    public void sum() {
        System.out.println("sum : " + (this.a + this.b + base));
    }

    public void avg() {
        System.out.println("avg : " + ((this.a + this.b + base)/2));
    }

}
public class Obejct {
        public static void main(String[] args) {

        Calculator c1 = new Calculator();
        c1.setOprands(10, 20);
        c1.sum(); // 40출력
        c1.avg(); // 20출력

        Calculator.base = 100;
        c1.sum(); // 130출력
        c1.avg(); // 65출력
    }
}

원래 base는 클래스 변수로 선언 할 당시 10을 값으로 갖고 있었는데, main()에서 Calculator.base = 100;을 해줌으로써 모든 인스턴스의 base값이 100으로 변경되었다.

🚨 클래스 변수의 용도
1. 인스턴스에 따라서 변하지 않는 값이 필요한 경우 (ex.PI)
2. 인스턴스를 생성할 필요가 없는 값을 클래스에 저장하고 싶은 경우
3. 값의 변경 사항을 모든 인스턴스가 공유해야 하는 경우

클래스 메소드

Calculator class는 인스턴스 변수인 a와 b를 이용해서 sum()과 avg()를 계산하는데, 이 때 굳이 인스턴스가 a와 b의 값을 항상 유지하고 있을 필요는 없다. 대신 sum()과 avg()를 구할 때마다 a와 b에 값을 주는 방식으로 구할 수도 있다.

class Calculator {
    // int a,b; 

    // public void setOprands(int a, int b) {
    //     this.a = a;
    //     this.b = b;
    // }

    public static void sum(int a, int b) {
        System.out.println("sum : " + (a+b));
    }

    public static void avg(int a, int b) {
        System.out.println("avg : " + ((a+b)/2));
    }

}
public class Obejct {
        public static void main(String[] args) {
        Calculator.sum(10,20); // 30
        Calculator.avg(10,20); // 15

        Calculator.sum(50,50); // 100
        Calculator.avg(50,50); // 50
    }
}

만약 메소드가 인스턴스 변수를 참조하지 않는다면, 클래스 메소드를 사용해서 불필요한 인스턴스 생성을 막을 수 있다.

클래스와 인스턴스

  1. 인스턴스 메소드는 클래스 멤버에 접근 할 수 없다.
  2. 클래스 메소드는 인스턴스 멤버에 접근 할 수 없다.
    → 인스턴스 변수는 인스턴스가 만들어지면서 생성되는데, 클래스 메소드는 인스턴스가 생성되기 전에 만들어지므로 클래스 메소드가 인스턴스 멤버에 접근하는 것은 존재하지 않는 인스턴스 변수에 접근하는 것과 같다.
class C1 {
    static int static_variable = 1; // 고정 클래스 변수 선언
    int instance_variable = 2; // 가변 클래스 변수 선언

    static void static_static() { // 고정 클래스 메소드 선언 
        System.out.println(static_variable); 
    }

    static void static_instance() { // 고정 클래스 메소드 선언
        //System.out.println(instance_variable);
        // 클래스 메소드에서는 인스턴스 변수에 접근할 수 없음
    }

    void instance_static() { // 인스턴스 클래스 메소드 선언
        System.out.println(static_variable);
        // 인스턴스 메소드에서는 클래스 변수에 접근할 수 있음
    }

    void instance_instance() { // 인스턴스 클래스 메소드 선언
        System.out.println(instance_variable);
    }
}

public class ClassMember {
    public static void main(String[] args) {
        C1 c = new C1();
        c.static_static();
        // 인스턴스를 통해 정적 메소드에 접근 -> O
        // 인스턴스 메소드가 정적 변수에 접근 -> O

        c.static_instance();
        // 인스턴스를 통해 정적 메소드에 접근 -> O
        // 정적 메소드가 인스턴스 변수에 접근 -> X

        c.instance_static();
        // 인스턴스를 통해 인스턴스 메소드에 접근 -> O
        // 인스턴스 메소드가 인스턴스 변수에 접근 -> O

        c.instance_instance();
        // 인스턴스를 통해 인스턴스 메소드에 접근 -> O
        // 인스턴스 메소드가 클래스 변수에 접근 -> O

        C1.static_static();
        // 클래스를 통해 클래스 메소드에 접근 -> O
        // 클래스 메소드가 클래스 변수에 접근 -> O

        C1.static_instance();
        // 클래스를 통해 클래스 메소드에 접근 -> O
        // 클래스 메소드가 인스턴스 메소드에 접근 -> X

        //C1.instance_static();
        // 클래스를 통해 인스턴스 메소드에 접근 -> X

        //C1.instance_instance();
        // 클래스를 통해 인스턴스 메소드에 접근 -> X
    }
}

cf.[생활코딩] https://opentutorials.org/course/1223/5440

profile
Life is what i make up it 💨

0개의 댓글