[부트캠프] TIL - 인스턴스 멤버와 클래스 멤버, this와 this(), 상속
3주차 강의 인스턴스 멤버와 클래스 멤버에 생소한 내용이 많아, 천천히 정리하고, 찾아보면서 진행하고자 한다.
우선은 클래스와 인스턴스란?
알고있던것과 들은것으로 정리를 하면
클래스는 우선 요약하자면, 객체의 설계도 라고 이해하면 됩니다.
클래스의 내부에는 필드와 메소드가 존재하고 이 클래스를 가지고 생성자를 사용하여
생성하면 메모리에 객체가 할당되고 생성된 것이 인스턴스라고 하며 이 과정을 인스턴스화 라고도 부릅니다.
객체지향 프로그래밍에 대한 설명을 듣다보면 붕어빵 기계 비유를 많으 보았을 것입니다.
실생활에서도 우리는 수많은 인스턴스화를 경험하며 살고있습니다, 단순히 붕어빵을 예로 다시한번 들겠습니다.
해당 이미지를 크게 3가지로 나눠 간략히 설명하겠습니다.
1. 붕어빵 기계 틀은 클래스입니다.
2. 그리고 구워낸 붕어빵 하나하나는 각각 객체이자 인스턴스입니다.
3. 그 각각의 붕어빵(객체)들은 공통점(클래스멤버)를 가지고있습니다, 물론 차이점(인스턴스멤버)도 가지고있습니다.
이거에 대해서 하나하나 설명해 보겠습니다.
1. 붕어빵 기계 틀은 클래스입니다.
>물론입니다, 붕어빵 기계가 클래스 명이 되겠고, 멤버 변수는 속재료, 반죽재료 이런게 되겠고 생성자는 구워낸다 가 있겠네요.
2. 그리고 구워낸 붕어빵 하나하나는 각각 객체이자 인스턴스입니다.
>붕어빵 기계에 재료를 넣고 구워내는 과정을 생성자라고 할 수 있습니다, 이 생성자 호출을 통해 붕어빵 객체 하나를 생성합니다.
3. 그 각각의 붕어빵들은 공통점(클래스멤버)를 가지고있습니다, 물론 차이점(인스턴스멤버)도 가지고있습니다.
>각각 구워진 붕어빵들에겐 공통점이 있습니다 -> 반죽의 재료, 속재료(팥,슈크림)
차이점도 있을겁니다 -> 들어간 반죽의 g, 속재료의 g, 또 어떤붕어빵은 더익혀서 색이 탔을지도 모릅니다(색깔)
*여기서 3번이 메인입니다.
공통점이 바로 클래스멤버, 차이점이 인스턴스멤버 입니다.
둘 모두 클래스라는 설계도를 작성할 때에 작성했겠지만, 실제로 생성을 하고 보면 메모리에 저장되는 방식에서 차이가 존재합니다.
객체마다 모두 동일한 값을 가져야 하는 변수를 각각의 인스턴스영역에 할당한다면 메모리 낭비가 아닐 수 없습니다.
따라서 인스턴스 멤버들은 클래스멤버와는 다른 메모리 영역에 저장되는데, 참고 사진을 남깁니다.
여기에서 클래스 멤버는 Method Area에 저장됩니다.
클래스의 모든 인스턴스가 공유하는 곳이며 클래스가 메모리에 로드될 때 생성됩니다.
인스턴스 멤버는 Heap에 저장됩니다.
클래스의 인스턴스가 생성될 때! 메모리에 생성됩니다.
네, 또나왔습니다 Heap 그리고 그 옆의 Stack
이번 부트캠프 목표를 Java의 기초와 팀프로젝트 경험에 잡은 이유가 바로 이런거입니다.
여기에서 기초를 제대로 잡았느냐가 핵심이라고 저는 생각합니다.
원시타입 참조타입 얘기를했었고, 참조타입의 진짜 값은 Heap 영역에 저장된다고 했습니다,
그리고 객체는 참조타입입니다, 이야기가 맞아떨어지는것을 느끼실겁니다, 느껴야합니다.
Stack영역은 컴파일 단계에서 메모리를 할당받습니다, 즉, JVM은 java파일을 컴파일한 class 파일을
실행시키면서 클래스멤버로 선언된 것들을 Method Area에 저장합니다 프로그램이 종료될 때 까지 저장상태를 유지하고 그것을 객체들 모두가 공통으로 접근해 사용할 수 있습니다, 약간 클라우드? 개념으로 이해하면 되지않을까 합니다.
Heap영역은 실행 단계에서 메모리를 할당받습니다, 즉, 객체 생성시 Heap 영역에 인스턴스 멤버의 값들을 저장합니다,
그리고 바로 여기는 가비지 컬렉터가 작동하는곳입니다, 생성했던 영역이 사용되지않는다면 주기적으로 가비지 컬렉터가
해당 부분을 할당해제하면 관리해줍니다.
간략하게 찾아보고 알고있던것과 강의내용을 정리했지만, 이렇게 하고도 정말 많은 내용이 남아있습니다.
지역변수 부터 시작해서 영역 개념까지 기존에 대충 넘어갔던 부분들이 많이 남아있음을 깨닫습니다.
this??
this는 인스턴스 자신을 표현하는 키워드.
객체 내부의 생성자 또는 메소드에서 객체 내부 멤버에 접근하기 위해 사용된다.
주로 생성자에서 많이 볼 수 있습니다.
ex ) Car 클래스
class Car{
public String type;
public Car(String type) {
this.type = type; // 바로 이부분!
}
}
TMI 지만 생성자 내부에서 this.type = type;을 type = type;으로 작성해도 문법적 오류는 없다고합니다, 다만
this.type으로 this를 적어주는것이 개발자 간의 Convention(관습)이라고 하니 웬만하면 꼭 적어주도록 합시다.
적어주면 구분이 확실하게 됩니다, 왼쪽의 this.type 은 멤버변수인 type을 의미하고, 오른쪽의 type은 매개변수로 전달된
type을 의미합니다.
this()?
this()는 간단하게 인스턴스 자신의 생성자를 호출한다고 생각합시다. ()가 중요합니다.
개인적인 지식이지만, 생성자를 호출한다 라는 설명을 듣고 super가 떠올랐습니다.
상속 관계에서 super는 부모클래스를 의미합니다 그리고 생성자 안에서 super(); 를 본 기억이 있을겁니다,
그게 부모 클래스의 생성자를 호출하는 의미이듯이 this가 인스턴스 자신을 호출한다면, () 가 붙은 this()는
자신의 생성자를 호출한다고 볼 수 있습니다.
* 중요한점 다른 생성자를 호출할 때, 반드시 생성자의 첫 줄에 와야한다.
public Car(String model) {
this(model, "Blue", 50000000);
}
public Car(String model, String color) {
this(model, color, 100000000);
}
public Car(String model, String color, double price) {
this.model = model;
this.color = color;
this.price = price;
}
//주의사항
public Car(String model) {
System.out.println("model = " + model); //여기에 와야함.
this(model, "Blue", 50000000); //다른 생성자를 호출하는 부분, 반드시 생성자의 첫줄에 와야한다.
}
상속
조금 복잡한 내용이라 시간을 좀 들였다, 상속은 확장의 개념이다, 이것을 이해해보자.
(나머지 부분들은 이거를 이해하면 자동으로 알게된다, 모를수가없다)
그림을 표현하면 저렇게된다, 부모 클래스가 자식클래스의 부분집합이다.
ex) 포유류는 고래다 (X) , 고래는 포유류다 (O) -> 포유류가 부모클래스, 고래가 자식클래스 둘은 상속관계
왜 저런 그림이 나왔을까?
바로 생성자에 이유가 있다, 컴파일러는 클래스에 생성자가 없다면 기본생성자를 자동으로 추가해준다는 말을
들어봤을 것이다, 그뿐만이 아니라 만약 클래스가 상속관계에 있어서 부모 클래스가 존재한다면??
컴파일러는 생성자의 가장 첫줄에 super() 라고 부모클래스의 생성자를 호출하는 코드를 자동으로 추가해준다,
여기서 생성자의 가장 첫줄이 중요하다, 그게 바로 저렇게 부분집합이 된 이유가 된다.
생각을 한번 해보자 -> 자식클래스를 생성한다(생성자호출) -> super()가 실행 -> 부모클래스 인스턴스가 생성
-> 자식클래스의 인스턴스가 생성
어떻게 해봐도 자식클래스보다 인스턴스가 먼저 만들어지는 구조로 되어있다.
자 이제 메모리 구조를 보자
상위 클래스의 생성자가 먼저 호출될 뿐 아니라, 하위 클래스에서 상위 클래스의 멤버를 사용할 수 있어야 하기 때문에
이런 그림이 나오게되는것이다.
자, 여기서 인스턴스화 된 클래스는 Heap 영역에 생성된다고 또나왔다.
그리고 그 참조변수는 Stack 영역에 생성되어 Heap 영역의 주소값을 저장!! 하고 있는 모습이다.
여기서 또한번 중요한 부분이 나온다.
지금 검은화살표와 빨간 화살표가 왜 나왔는가, 참조변수는 하나인데?? 라는 의문이 있을 것이다.
예시로 설명을 하겠습니다.
//SportsCar는 Car를 상속받는다.
SportsCar car1 = new SportsCar();
Car car2 = new SportsCar(); //다형성을 허용한것.
//차를 만들면서, 이건 차야! 라고 하는게 당연하지만,
//스포츠카를 만들면서, 이건 차야! 라고도 할 수 있는것이다, 이게 다형성이다.
우리는 여기에서 멤버변수의 타입이 아닌 new SportsCar()에 집중해야한다 멤버변수의 타입이 무엇인가는 크게 중요하지않다. 왜냐하면 위의 그림으로 보자면 매개변수 타입이 SportsCar 인 car1 참조변수는 검은화살표,
매개변수 타입이 Car인 car1 참조변수는 빨간 화살표인것.
지금 두 개의 매개변수를 생성했기 때문에 위의 그림처럼 하나의 매개변수가 화살표를 가리키는건 아니지만
의미는 그러한 것이다,
그러면 이번엔 멤버변수 타입에 집중해보자.
SportsCar는 Car이기도 하기 때문에 인스턴스를 생성하고 자료형 선언시 상위 클래스인 Car로 선언이 가능하다.
단, 이때 차이점이 존재하는데
상위 클래스 타입으로 선언한 멤버변수는 하위 클래스의 멤버변수에 접근이 불가능합니다.
이거는 위의 그림대로 부분집합으로 이해하면 되겠습니다, 하위 클래스가 더 확장된 개념이므로 자신에게 포함된
상위 클래스의 멤버변수는 접근이 가능하지만, 보다 좁은 개념인 상위 클래스를 참조하고있는 멤버변수는
자신보다 확장된 범위인 상위 클래스의 멤버 변수에 접근이 불가능한것입니다.
심화과정
하지만 아직 끝나지않았습니다, 정말 심화과정, 이해하는데 한참 걸린 부분이 남았습니다.
방금 상위 클래스 타입으로 선언한 멤버변수는 하위클래스의 멤버변수에 접근이 불가능하다고 했지만.
만약 Car의 메소드를 SportsCar에서 오버라이드했고 그것을 car1과 car2가 호출한다고 했을때.
둘 다!!! SportsCar에서 오버라이드한 메소드가 호출됩니다.
가상 메소드 개념이 등장하는데, 객체를 인스턴스화 하면 멤버 변수는 항상 새롭게 생성이됩니다.
하지만 메소드의 경우는 실행해야 할 명령의 집합이기 때문에, 인스턴스가 아예 별개일지라도
같은 명령을 수행해야합니다, 따라서 메소드들은 Stack 과 Heap 영역이 아니라
Method 영역이라고 따로 존재하는곳에 저장이 됩니다. 따라서 자신의 멤버변수 타입의 함수가 아니라
생성자 타입의 함수를 호출하게됩니다. 이부분은 아직 완벽하지않아 더욱 자세한 출처를 남깁니다.