
ㅎㅇ
4시까지는 피곤했는데
5시부터는 배고프다. 이게 언제 올라갈지는 모르지만 우선 해봅시다.
9장의 내용은 가비지컬렉션과 생성자에 대해 다룬다. 생성자는 좀 궁금했는데 한 번 들어보겠습니다.
스택&힙
JVM은 JVM을 실행시키는 운영체제로부터 메모리를 받아 자바 프로그램을 실행시킨다.
그 메모리들 중 힙은 모든 객체를 저장하고, 스택은 메서드 호출과 로컬변수의 저장을 담당한다.
스택 시나리오
메서드를 호출하면 호출 스택의 맨 위로 메서드가 올라간다. 스택에는 스택 프레임이 들어가며, 프레임 안에 코드, 로컬변수를 포함한 메서드의 정보가 들어간다. 스택의 맨 위는 현재 실행 중인 메서드이며, 메서드의 실행이 끝날 때까지 스택에 저장된다.
로컬변수 역시 메서드의 일부로 스택에 저장된다. 다만 로컬변수가 참조하는 객체는 힙에 저장된다.
인스턴스 변수는?
Dog mydog = new Dog();
라는 식으로 클래스를 호출했다고치고, Dog 객체에는 인스턴스 변수 원시타입 age가 있다고 할 때,
원시타입 인스턴스변수는 힙에 저장되며 인스턴스 변수에 값 자체가 저장된다. 그리고 그 인스턴스변수가 속하는 객체 안에 저장된다. 즉 인스턴스변수 age는 힙에서 Dog 객체 안에 저장된다.
인스턴스 변수가 원시타입이 아니라 객체일 때, 즉 인스턴스 변수가 다른 객체인 hair를 저장한다고 할 때, 객체 레퍼런스일 때는 불러온 객체 그자체가 아니라 그의 참조값이 저장된다.
이 때 참조되는 객체는 인스턴스 변수에 대입 되었을 때 힙에 형성된다. 참조되지 않고 인스턴스 변수만 선언한 경우에는 참조값을 저장할 메모리만 형성된다.
객체 생성하기
Dog mydog = new Dog();
아까 코드를 다시 가져오면.. () 때문에 이게 메서드 아닌가 싶다. (사실 처음에 나도 그랬고, 책에서도 그렇게 적혀있다.)
하지만 new Dog() 부분은 메서드가 아니라 생성자를 호출하는 부분이다. 감사하게도 생성자 호출 이후 생성자를 만드는 과정은 컴파일러가 알아서 해준다.
생성자에 대해 GPT에게 물어봤다. (책에는 한 줄 정리가 없어서..)
"생성자는 클래스의 인스턴스를 생성할 때 호출되며, 객체를 초기화하는 특이한 메서드이다."
생성자는 객체를 생성할 때 인스턴스 변수를 초기화시기 위해 사용한다.
그래서 내가 인스턴스를 생성한다고? 그럼 인스턴스 변수가 없는 클래스는 생성자가 없어도 되냐 물었더니
놀랍게도 그렇다고 한다. 단 실제로는 컴파일러가 자동으로 생성자를 만들기 때문에 그럴 필요까지는 없다고.
인스턴스 변수나 컴파일러는 0이나 null, false 같은 값을 집어넣어 인스턴스 변수를 초기화시킨다.
public Dog() {
}
끝. 우선 컴파일러가 자동으로 생성하는 생성자의 기본형은 다음과 같다.
리턴타입이 없고 생성자의 이름은 반드시 클래스 이름과 같아야한다. -> 따라서 일반 메서드의 이름을 생성자와 동일하게 하지는 말자. 엄청 헷갈린다
컴파일러가 자동으로 생성자를 만들지만 객체를 만들기 전에 필요한 준비작업을 거쳐야하는 경우도 있기 때문에 개발자가 직접 생성자를 만들어 필요한 준비작업을 미리 시행할 수 있다.
다만 저 메서드 안에 특별한 로직을 넣으면, 저 객체가 생성되고 레퍼런스 변수에 대입되기 전에 특별한 기능을 몇 개 만들수도 있다. 가령 출력문을 넣으면 객체가 만들어지고 난 후 대입되기 전에 출력이 된다던지..
직접 초기화
그렇다면 '특별한 로직'을 위해서는 프로그래머가 직접 생성자를 만들고, 그 생성자 안에 로직을 넣어야한다.
또한 개발자가 인스턴스 변수들의 초기값은 0이나 null, false가 아니라 다른 값으로 선정하고 싶다면 직접 초기화를 시켜야한다.
생성자는 최대한 간단하게 만들어야하고, 인자로 어떤 값을 넘기는 것을 지양해야한다. 인자로 값을 넘기면 그 값이 무엇인지 알아야하고 인자로 넘길 값이 정해지지 않았다면 어떤 값을 넘길지 애매해지니까..
생성자에게 인자 넘기기 - 생성자 오버로딩
public class User {
Integer id
String name
boolean isValid
//로직
}
이런 클래스가 있을 때, 유저의 입력 정보에 따라서 생성자를 바꿔서 호출할 수 있다
public User (int id) { } //id만 아는경우
public User ( ) { } //아무것도 모르는 경우
public User (boolean isValid) { }
public User (int id, String name) { }
public User (String name, int id) { }
이런식으로 생성자 오버로딩을 통하여 여러 경우에 대한 생성자를 만들어낼 수 있다.
또한 밑의 두 개의 생성자는 타입은 같으나 인자로 전달하는 변수의 순서가 다르므로 다른 생성자이다.
인자목록이 같으면 컴파일이 되지 않는다.
인자로 들어오지 않은 인스턴스변수는 자바에서 알아서 0, null, false로 초기화시킨다.
상속과 생성자
어떤 객체가 만들어지면, 그 객체의 최상위클래스에서 만들어진 클래스까지의 모든 인스턴스 변수들에 대한 공간이 부여된다. 즉 힙메모리에는 그 최상위클래스로부터 상속받은 모든 인스턴스 변수들이 들어갈 공간이 부여된다.
공간이 부여되는 것과 실제 변수가 저장되는 것은 다르다. 상속에서는 전자. 즉, 따로따로 호출해야한다.
또한 이러한 인스턴스 변수들이 객체가 만들어질 때마다 새로이 만들어지며, Type이 같더라도 객체가 다르면 다른 인스턴스 값을 가질 수 있다.
객체가 제대로 작동하기 위해서는 그 객체가 의존하는 인스턴스 변수들이 속한 상위 클래스에 대해서도 생성자가 실행되어야한다. 따라서 한 하위클래스를 실행시킬 경우 그 클래스의 상위클래스 - 상위클래스 - ... 이런식으로 생성자 연쇄반응이 일어난다.
생성자는 상속을 타고
상위 - 하위 - 하위하위 클래스 구조가 있다고쳐보자. 만약 하위하위 클래스에서 하위클래스를 new 방식으로 생성했다고하면..
생성자는 먼저 하위클래스 - 상위클래스 순으로 스택에 들어가며, 최종적으로는 Object 클래스가 최상위 스택에 들어간다.
Object()가 종료되고 스택프레임에서 제거되면 스택 프레임에서 위에 있는 순서대로 (즉, 상위 클래스 순서대로) 메서드 등이 실행된다. 즉, 하위클래스를 생성하면 하위클래스와 그 하위클래스의 모든 상위클래스까지 생성하여 스택에 쌓은 후, 가장 상위클래스인 것부터 실행시킨다.
자세한 것은 헤드퍼스트자바 9장 296쪽 참고
상위클래스 생성자 호출하기
하위클래스를 생성하면 최상위클래스부터 하위클래스까지의 모든 인스턴스 변수들을 저장할 공간 부여하지만, 실제로 상위클래스들을 저장하기 위해서는 각자 생성자를 통해 생성해야한다.
하위클래스에서 상위클래스를 호출하거나, 생성자를 호출하는 방법은 super()를 호출하는 방법이 있다. 유일한 방법이다.
super(); 만하면 상위클래스를 생성한다. 이러면 Object 생성자가 스택 맨 위에 올라가고 차례차례 종료 후 스택에서 제거되는 것을 반복하다보면 처음에 호출한 super() 생성자가 스택 맨 위에 남고 생성자호출이 종료된다.
근데 사실 상속관계에서 인스턴스 변수들은 자유롭게 사용할 수 있었는데,
이는 컴파일러가 super(); 명령어를 자동으로 처리해준다.
생성자를 만들지 않은 경우 컴파일러가 자동으로 만들어주는 생성자에 super() 명령어를 넣고,
생성가자 만들어진 경우에는 오버로드된 생성자에 super()를 호출한다.
또는 직접 생성자를 만들어 super() 메서드를 사용할 때는 생성자에 인자가 없어야하며, super()를 호출하는 명령문은 모든 생성자의 첫 번째여야한다. 무조건. 차라리 super()를 호출하지 말자. 그러면 컴파일러가 알아서 super()를 호출한다.
super()를 사용하는 좋은 예제가 헤드퍼스트자바 9장 299쪽에 있길래 가져와봤다. 문제될 시 삭제
public abstract class Animal {
private String name;
public String getName() {
return name;
}
public Animal(String theName) {
name = theName;
}
}
public class Hippo extends Animal {
public Hippo(String name) {
super(name);
}
}
public class MakeHippo {
public static void main(String[] args) {
Hippo h = new Hippo("Buffy");
System.out.println(h.getName());
}
}
이렇게 코드가 있다고 칠 때,
main 메서드의 출력문에서는 Hippo 클래스에서 Animal 클래스에서 상속받은 getName 메서드를 통해 name에 접근하도록한다.
그런데 Hippo 클래스에서는 name이라는 인스턴스 변수가 없기 때문에 Animal 부분에 의존하게 되는데, 이를 위해 super를 사용해 상위클래스에 접근해야한다.
이러면 초기화와 이름 생성까지 한 번에 된다는 장점이 있다. 물론 인자를 여러 개를 받아야할 경우에는 오버로딩을 사용하면 되고
상속과 인자
상속 관계에 있는 두 클래스에서 하위클래스가 super(); 를 사용하여 상위클래스를 초기화시킬 때, super에 인자에 들어가는 값은 오직 상위클래스에서 정의된 것만 할 수 있다.
따라서 컴파일러가 자동으로 super()를 형성할 때, 상위클래스에 생성자를 직접 만들었으나 인자를 아무것도 받지 않는 생성자가 없다면 컴파일에러가 난다.
내 생성자 호출하기 - this
오버로딩을 한다면 생성자가 많아진다는 단점이 있다. 따라서 깔끔한 가독성과 관리를 위해서는 한 생성자에 생성자 코드의 대부분 (super() 포함해서)을 몰아넣는게 좋다. 그리하면 어떤 생성자가 호출되든 하나의 진짜 생성자만 호출되면 끝이니까
this(), this(a), this(a,b) 이런식으로 호출하면 된다.
this()는 현재 객체에 대한 레퍼런스로, 생성자 안에서 첫 번째 명령문으로만 쓸 수 있다. 즉 super()와 혼용이 불가능하다.
변수 죽이기
- 로컬 변수의 죽음(?)
메서드 내에서만 생성되는 로컬변수의 경우에는 원시변수든 레퍼런스 변수든 간에 상관없이 메서드가 종료되면 사라진다.
즉, 변수가 포함된 메서드가 스택 프레임에 있을 때까지만 살아있다. 살아있는 것과 실행 중은 다르다.
- 인스턴스 변수의 죽음(?)
객체가 살아있는 동안에는 계속 쓰일 수 있다.
객체가 살아있다는게 무슨 뜻? - 레퍼런스 변수의 삶
객체는 레퍼런스 변수에 의해 호출되기 때문에 레퍼런스 변수의 삶도 정해져있다. 레퍼런스 변수가 없다면 그 객체는 딱히 사용할 이유가 없다.
객체와 레퍼런스의 삶 역시 원시 변수와 같다. 인스턴스 변수와 로컬변수로 들어갈 수 있으며, 객체에 대한 레퍼런스가 살아있다면 객체는 힙메모리 안에 있으며, 레퍼런스 변수가 종료되면 그 객체는 힙에서 버려진다.
객체가 죽는 방법은
1. 객체를 참조 중인 레퍼런스가 영역을 벗어남 (메서드나 클래스가 종료됨)
2. 레퍼런스에 새로운 객체를 대입 (같은 type의 새로운 객체 대입)
2-1
Dog dog1 = new Dog();
Dog dog2 = new Dog();
dog1 = dog2;
이렇게되면 dog1이 참조 중이던 객체는 더이상 참조되지 않아 죽는다.
3. 레퍼런스 변수를 null로 설정
객체 살인마 가비지컬렉터(GC)
버려진 객체는 가비지 컬렉션의 대싱이다. 프로그램에서 메모리가 부족해지면 가비지 컬렉터가 출발해 버려진 객체들의 전체 또는 일부를 없애버린다.
객체가 가비지컬렉션의 대상이된다는 것은 대상을 참조 중인 레퍼런스가 하나도 남지 않았을 때를 뜻한다.
소감
7장 - 8장 - 9장으로 넘어오면서 내용도 좀 복잡해지지만? 그래도 재밌다.
가비지컬렉션이니 생성자니..
특히 컴공개 때 생성자와 상속, 다형성이 좀 어려웠는데 (사실 수업을 제대로 듣진 않았다.. 교수님 죄송해요) 이 기회에 좀 개념을 명확히 하지 않았나 싶다.
특히 7~9장 내용들 (그리고 앞으로 더 추가되겠지만 다른 챕터들까지)은 반복적으로 익혀놓으면 좋을 것 같다.
다만 웹 프로그래밍에서 상속은 거의 잘 안 쓰던 것 같은데..
인텔리제이에서 자바 프로그램을 돌리는게 익숙해서 VSCODE로 자바 프로그램을 돌리려니까 좀 어색하다. 컴파일 하는게 안 끝나기도하고.. 나 졸업할 때쯤이면 인텔리제이에 돈 내야하는데 진짜 돈 내고 쓸까
'언어공부 > Java | Kotlin' 카테고리의 다른 글
| [2025백엔드] 헤드퍼스트자바 독서 #7 - 11장. 자료구조 (5) | 2025.08.13 |
|---|---|
| [2025백엔드] 헤드퍼스트자바 독서 #6 - 10장. 숫자는 정말 중요합니다 (5) | 2025.08.03 |
| [2025백엔드] 헤드퍼스트자바 독서 #4 - 8장. 심각한 다형성 (2) | 2025.07.26 |
| [2025백엔드] 헤드퍼스트자바 독서 #3. - 7장. 객체 마을에서의 더 나은 삶 (1) | 2025.07.26 |
| [2025백엔드] 헤드퍼스트자바 독서 #2 - 6장. 자바 라이브러리 사용하기 (3) | 2025.07.24 |