취뽀몽

[JAVA] 자바와 객체 지향 본문

java

[JAVA] 자바와 객체 지향

허몽구 2023. 3. 16. 20:36

1. 객체 지향은 인간 지향이다.
0과 1로 대변되는 기계(컴퓨터)에 맞춰 사고하던 방식을 버리고 현실 세계를 인지하는 방식으로 프로그램을 만들자는 것이다. 
그래서 객체 지향은 직관적이다. 객체 지향을 이해하기 위해 먼저 큰 그림을 생각해보자.

세상에 존재하는 모든 것은 사물, 즉 객체이다.
각각의 사물은 고유하다.
사물은 속성을 갖는다.
사물은 행위를 한다.

그리고 사물을 하나하나 이해하기 보다는 사물을 분류(class)해서 이해하는 것이 인간의 인지법이다.

직립 보행을 하며 말을 하는 존재를 사람이라고 분류한다.
연미복, 짧은 다리, 날지 못하는 새를 펭귄이라고 분류한다.
밤하늘에 반짝이는 사물들을 별이라고 분류한다.

사람이라는 분류 안의 객체(object)들은 나이, 몸무게, 키 등의 속성(property)과 먹다, 자다, 울다 등의 행위(method)를 가지고 있다.
객체 지향 이전에는 속성과 메소드를 객체라는 단위로 묶지 않고 속성(필드) 따로, 메소드(함수) 따로 분리된 형태로 프로그램을 작성했었는데, 객체 지향에서는 우리가 주변에서 실제 사물을 인지 및 사고하는 방식대로 객체 단위의 프로그래밍이 가능하다.
객체 지향은 인간의 인지 및 사고 방식까지 프로그래믕에 접목하는 인간(개발자) 지향을 실천하고 있는 것이기에 직관적이다.

2. 객체 지향의 4대 특성
- 캡슐화 : 정보 은닉
- 상속 : 재사용
- 추상화 : 모델링
- 다형성 : 사용 편의

3. 추상화 : 모델링
추상화란 구체적인 것을 분해해서 관심 영역(애플리케이션 경계)에 있는 특성만 가지고 재조합하는 것을 말한다. 즉, 모델링이다.
객체 지향의 4대 특성은 클래스, 또는 객체를 통해 구현된다.
객체란 세상에 존재하는 유일무이한 사물을 뜻한다. 이에 대비되는 클래스는 분류 / 집합 / 같은 속성과 기능을 가진 객체를 총칭하는 개념이다.
세상에 존재하는 유일무이한 객체를 특성(속성 + 기능)에 따라 분류하니 객체를 통칭할 수 있는 집합적 개념, 즉 클래스가 나오게 된다.
새로운 사람이 태어난 것을 자바로 표현하면 다음과 같다.

사람 홍길동 = new 사람();


또 새로운 사람이 태어났는데 이번에는 이름을 허지영이라고 해보자.

사람 허지영 = new 사람();


사람이라는 클래스를 이용해 유일무이하고 새로운 하나의 사람(객체)을 만들어 허지영(객체 참조 변수)이라는 이름을 지어준 것이다.
클래스는 class, 객체는 Object이다.

클래스를 이용해 object를 만들었다는 것을 강조할 때는 인스턴스(instance)라는 표현을 쓴다.

OOP의 추상화는 모델링이다.
클래스 : 객체 = 펭귄 : 뽀로
클래스 설계에서 추상화가 사용된다.
클래스 설계를 위해서는 애플리케이션 경계부터 정해야 한다. 
객체 지향에서 추상화의 결과는 클래스다.

사실 추상화의 개념을 넓게 본다면 아래 내용도 포함된다.

상속을 통한 추상화, 구체화
인터페이스를 통한 추상화
다형성을 통한 추상화


자바는 객체 지향의 추상화를 class 키워드를 통해 지원한다.
클래스와 객체 관계를 자바에서는 다음과 같이 나타낸다.

클래스 객체_참초_변수 = new 클래스();


4. 추상화와 T 메모리
java.lang 패키지와 모든 클래스들이 T 메모리의 스태틱 영역에 배치된다. 

public class Mouse{
    public String name;
    public int age;
    public int countOfTail;

    public void sing(){
        System.out.println(name + "찍찍~~~");
    }
}


Mouse클래스를 생성해준다. 이 클래스를 통해 객체를 생성한다.

public class MouseDriver{
    public static void main(String[] args) {
        Mouse mickey = new Mouse();
        mickey.name = "미키";
        mickey.age = 85;
        mickey.countOfTail = 1;

        mickey.sing();

        mickey = null;
        
        Mouse jerry = new Mouse();

        jerry.name = "제리";
        jerry.age = 73;
        jerry.countOfTail = 1;

        jerry.sing();
    }
}

 

T메모리를 살펴보면 Mouse에서 name, age, countOfTail의 변수 저장 공간이 보이지 않는다. 그저 이름만 존재한다.
이 세 개의 속성은 Mouse 클래스에 속한 속성이 아닌 Mouse 객체에 속한 속상이기 때문이다.
객체가 생성돼야만 속성의 값을 저장하기 위한 메모리 공간이 스태틱 영역이 아닌 힙 영역에 할당된다.
- Mouse mickey : Mouse 객체에 대한 참조 변수 mickey 만든다.
- new Mouse() : Mouse 클래스의 인스턴스를 하나 만들어 힙에 배치한다.
- 대입문 : Mouse 객체에 대한 주소(포인터)를 참조 변수 mickey에 할당한다.

 

여기서 주의깊에 봐야할 점은 스태틱과 힙이다.

스태틱은 고정된 이라는 뜻을 가지고 있다.
스태틱 영역에 올라간 정보는 main() 메소드가 시작되기 전에 올라가서 main() 메소드가 종료된 후에 내려올 정도로 스태틱 영역에 단단히 고정되어 있기 때문에 스태틱 영역이라고 한다.
은 대용량 자료를 저장할 수 있도록 메모리를 사용하는 방식이다.

5. 클래스 멤버 vs. 객체 멤버 = static 멤버 vs. 인스턴스 멤버
같은 유형(클래스)의 모든 객체가 같은 값을 가지고 있다면 그 값을 클래스에 저장하는 것도 방법이다.

public class Mouse{
    public String name;
    public int age;
    public static int countOfTail = 1;

    public void sing(){
        System.out.println(name + "찍찍~~~");
    }
}

 


이제 countOfTail속성은 T 메모리의 스태틱 영역에 단 하나의 저장 공간을 갖게 된다. 

public class MouseDriver{
    public static void main(String[] args) {
        // 클래스명.countOfTail
        Mouse.countOfTail = 1;

        Mouse mickey = new Mouse();
        Mouse jerry = new Mouse();
        Mouse mightyMouse = new Mouse();
        
        // 객체명.countOfTail
        System.out.println(mickey.countOfTail);
        System.out.println(jerry.countOfTail);
        System.out.println(mightyMouse.countOfTail);
        
        // 클래스명.countOfTail
        System.out.println(Mouse.countOfTail);
    }
}


이렇게 static 키워드가 붙은 속성을 클래스 멤버 속성이라고 한다.
static이 안 붙은 속성은 객체 멤버 속성이라고 한다.
속성 뿐만 아니라 메소드도 static 키워드를 붙였느냐 안 붙였느냐에 따라 클래스 멤버 메소드객체 멤버 메소드로 분류한다.
main() 메소드를 보면 static 키워드가 항상 붙어있는 것을 볼 수 있는데, 이 메소드가 바로 클래스 멤버 메소드이기 때문이다.
클래스 멤버들은 static 키워드와 함께 사용되고 또 T 메모리의 static 영역에 상주하게 되므로 static(정적) 멤버라고도 한다.
객체 멤버들은 객체가 클래스의 인스턴스이므로 인스턴스 멤버라고도 한다.

클래스 멤버 = static 멤버 = 정적 멤버
객체 멤버 = 인스턴스 멤버


정적 속성은 해당 클래스의 모든 객체가 같은 값을 가질 때 사용하는 것이 기본이다.
정적 메소드는 객체들의 존재여부에 관계없이 쓸 수 있는 메소드이다.
정적 멤버들은 객체가 아닌 클래스에 속해 있으며, 클래스는 JVM 구동 시 T 메모리의 스태틱 영역에 바로 배치되기 때문에 객체의 존재 여부에 관계 없이 쓸 수 있다.
정적 속성인 경우 T 메모리의 스태틱 영역에 클래스가 배치될 때 클래스 내부에 메모리 공간이 확보되는 것을 보여준다.
이에 반해 객체 속성은 속성명만 있지 실제 메모리 공간을 확보하지 않는다.
객체 속성은 힙 영역에 객체가 생성되면 바로 그때 각 객체 안에 멤버 속성을 위한 메모리 공간이 할당된다.

Q. 지역 변수는 별도로 초기화해야 하는데 멤버 변수(속성)은 왜 자동으로 초기화 해줄까?
지역 변수는 한 지역에서만 쓰는 변수이지만 멤버 변수는 공유 변수의 성격을 가지고 있기 때문이다.
객체 변수는 하나의 객체 안에서 다수의 객체 메소드가 공유하는 변수이고, 클래스 변수는 전역 변수로서 프로그램 어디서든 접근 가능한 공유 변수다.

Q. 이러한 공유 변수의 초기화는 누가 해야 할까?
객체 멤버인 경우는 생성자를 통해, 정적 멤버는 정적 실행 영역을 통해 초기화하는 경우가 있긴 하지만 공유 변수를 딱히 누가 초기화해야 한다고 규정할 수는 없다.
그래서 공유 변수는 별도로 초기화를 해주지 않아도 기본값으로 초기화되는 것이다.

static 변수 = 클래스 속성, 정적 변수, 정적 속성 = 스태틱 영역에 저장
인스턴스 변수 = 객체 속성, 객체 변수 = 힙 영역에 저장
local 변수 = 지역 변수 = 스택 영역에 저장

 

6. 상속 : 재사용 + 확장
상위 클래스의 특성을 하위 클래스에서 상속하고 거기에 더해 필요한 특성을 추가, 즉 확장해서 사용할 수 있다는 의미이다.
부모 클래스 - 자식 클래스보다 상위 클래스 - 하위 클래스 또는 슈퍼 클래스 - 서브 클래스 라는 말이 옳은 표현이다.
상위 클래스로 갈수록 추상화, 일반화 됐다고 하며 하위 클래스로 갈수록 구체화, 특수화 됐다고 말한다.
하위 클래스는 상위 클래스인 점을 반드시 만족해야 한다.

1) 상속의 강력함
클래스 상속 구조에서 최상위 클래스는 Object이다. 그래서 모든 클래스는 결국 Object의 특성을 물려받는다.
그래서 어떤 클래스의 인스턴스이든 상관없이 개발자는 toString() 메소드를 사용할 수 있다는 강점이 있다.

 

2) 상속은 is a 관계를 만족해야 하는가?
is a 관계는 객체(클래스와 인스턴스)와 클래스의 관계로 오해될 소지가 많다.
때문에 is a kind of 관계를 사용하는 것이 옳다. 
하위 클래스 is a kind of 상위 클래스인 점을 사용하는 것이 옳다.

3) 다중 상속과 자바
자바가 다중 상속을 지원하지 않는 이유는 득실 관계에서 실이 더 많았기 때문에 과감히 포기했다. 
인터페이스를 도입해 다중 상속의 득은 취하고 실은 과감히 버렸다.

4) 상속과 인터페이스
다중 상속을 포기하고 대신 인터페이스를 도입한 자바에서 인터페이스는 어떤 관계를 나타낼까?
인터페이스는 be able to, 즉 "무엇을 할 수 있는" 이라는 표현 형태로 만드는 것이 좋다.

- Serializable : 직렬화할 수 있는
- Cloneable 복제할 수 있는
- Comparable : 비교할 수 있는
- Runnable : 실행할 수 있는

Q. 상위 클래스는 하위 클래스에게 물려줄 특성이 많을수록 좋을까? 적을수록 좋을까?
Q. 인터페이스는 구현을 강제할 메소드가 많을수록 좋을까? 적을수록 좋을까?

상위클래스는 물려줄 특성이 풍성할수록 좋고, 인터페이스는 구현을 강제할 메소드의 개수가 적을수록 좋다.
그 이유는 LSP(리스코프 치환 법칙)에 따른 이유라고 할 수 있다. 

리스코프 치환 법칙은 SOLID 법칙 중 하나인데, 다음에 포스팅으로 다뤄보겠다.

7. 다형성 : 사용편의성
상위 클래스와 하위 클래스 사이에서도 다형성을 이야기할 수 있고, 인터페이스와 그것의 구현 클래스 사이에서도 다형성을 이야기할 수 있지만, 가장 기본은 오버라이딩과 오버로딩이다.

오버라이딩 : 같은 메소드 이름, 같은 인자 목록으로 상위 클래스의 메소드를 재정의
오버로딩 : 같은 메소드 이름, 다른 인자 목록으로 다수의 메소드를 중복 정의

상위 클래스 타입의 객체 참조 변수를 사용하더라도 하위 클래스에서 오버라이딩(재정의)한 메소드가 호출된다.

8. 캡슐화 : 정보 은닉
1) 객체 멤버의 접근 제어자
자신의 멤버가 아닌 다른 객체의 멤버에 접근하는 경우에는 다른 객체를 생성한 후 접근해야 한다.
- public : 모두가 접근 가능
- protected - 상속 : 패키지내의 클래스에서 접근 가능
- default : 같은 패키지 내의 클래스에서 접근 가능
- private : 본인만 접근 가능

특정 객체 멤버에 대한 접근인지, 정적 멤버에 대한 접근인지에 따라 생각해야 한다.
정적 멤버인 경우 클래스명.정적멤버 형식으로 접근해야 하는데, 일관된 형식으로 접근하기 위해서이다.
객체를 생성한 경우에는 객체참조변수명.정적멤버 형태로도 접근할 수도 있다. 

2) 참조 변수의 복사

Call By Value 와 Call By Reference는 본질적으로 차이가 없다.
다만 차이라면 기본 자료형 변수는 저장하고 있는 값을 그 값 자체로 해석하는 반면, 객체 참조 변수는 저장하고 있는 값을 주소로 해석한다는 차이가 있다.
기본 자료형 변수는 저장하고 있는 값을 그 값 자체로 판단하고, 참조 변수는 저장하고 있는 값을 주소로 판단한다고 이해하는 것이 더 쉽다.
차이점 참고 블로그 : https://velog.io/@ahnick/Java-Call-by-Value-Call-by-Reference

기본 자료형 변수는 값을 값 자체로 판단한다.
참조 자료형 변수는 값을 주소, 즉 포인터로 판단한다.
기본 자료형 변수를 복사할 때, 참조 자료형 변수를 복사할 때 일어나는 일은 같다. 
즉 가지고 있는 값을 그대로 복사해서 넘겨준다.