티스토리 뷰

생성자란 메소드라기 보다는 객체의 생성과 초기화를 담당하는 특별한 기능이라고 볼 수 있다. 사실 생성자가 필요한 이유는 프로그램에서 변수를 초기화하는 것의 중요성에서 출발한다. 초기화되지 않은 필드는 프로그램이 오류를 일으키는 가장 중요한 원인 중 하나이다. 또한 그 오류는 테스트를 통해 검출되지 않는 오류로 유명하다. 그래서 객체지향프로그래밍에서는 생성자라고 하는 특별한 방법을 이용하여 객체를 만들고 나서 값을 반드시 초기화하도록 언어를 설계했다. 

생성자는 메소드와 비슷하게 생겼지만 이름이 클래스 이름과 같고 반환형이 없다. 예를 들어 다음의 학생 클래스 생성자는 매개변수로 받은 값으로 필드를 초기화하고 있다.

public Student(int id, String n, int y) {
    this.id = id;
    name = n;
    year = y;
}

새로운 학생 객체를 생성할 때 new를 이용하여 호출하면 메모리 할당 후 자동으로 생성자가 불려지게 된다. 

Student st = new Student(1, "김지연", 3);

 즉 new를 통해 할당된 학생 객체의 필드를 초기화하는 역할을 하게 된다. 학생 클래스는 한 개 이상의 생성자를 가질 수 있다.

public Student() {
    id = 0;
    name = "미정";<
    year = 1;
}

Student temp = new Student(); 와 같이 매개변수 없는 생성자를 호출하면 위의 생성자가 호출된다.

이렇게 두 개의 생성자가 다른 매개변수 리스트로 선언되면(생성자 오버로딩) 자바 컴파일러는 new에서 생성자를 호출할 때 매개변수의 수와 타입에 따라 해당하는 생성자를 찾아서 호출한다. 이것은 자바의 일반적인 메소드 오버로딩의 원칙과 같다. 

클래스를 처음 만들 때는 보통 생성자를 만들지 않는다. 그럼 어떻게 객체가 생성될까? 생성자가 없을 때는 자바 컴파일러가 디폴트 생성자라고 해서 매개변수가 없고 아무 일도 안 하는 생성자를 만들어준다. 그러면 new로 객체가 생성될 때 그 디폴트 생성자가 호출된다. 주의할 점은 생성자를 하나라도 만들게 되면(즉 매개변수가 필요해서 매개변수를 가진 생성자를 만들었다면) 디폴트 생성자는 생기지 않는다는 점이다. 그러므로 생성자 없는 클래스로 쓰다가 매개변수 있는 생성자를 추가할 때는 디폴트 생성자도 추가해 주는 것이 좋다.

다음으로 생성자가 여러 개가 되었을 때 코드 중복을 피하기 위해 주의할 점이 있다. 생성자는 필드의 초기화를 담당하게 되는데, 초기화해야 할 필드가 많거나 계산이 들어가야 하는 경우 여러 생성자가 같은 코드를 중복해서 가지게 된다. 이럴 때 코드 중복을 피하는 방법으로 this 생성자를 많이 쓴다. 위의 Student 예제에서 생성자가 할 일이 더 있는 경우 다음과 같이 바꿀 수 있다. 

public Student() { this(0, null, 1); } public Student(int id) { this(id, null, 1); } public Student(int id, String n, int y) { this.id = id; name = n; year = y; // 다른 생성자 초기화 작업... }

이 코드에서는 this 생성자를 이용해서 생성자가 매개변수를 다 가지는 생성자를 호출하는 것으로 대신하고 있다. 이렇게 하므로써 생성자의 코드 중복을 막을 수 있다. (이 때 부득이 0이나 null을 초기값으로 지정하는 코드가 들어가지만 그 정도는 무시해도 된다.)

다음으로 복사생성자에 대해서 살펴보자. 앞의 clone과 깊은 복사 부분에서 clone을 굳이 오버라이딩하기 보다는 복사 생성자를 이용하는 것이 더 나을 수도 있다고 했다. 복사 생성자란 같은 타입의 객체를 받아서 그 객체의 필드값을 새로 생긴 객체에 복사하는 생성자다. 위의 예라면 다음과 같이 복사 생성자를 만들 수 있을 것이다.

public Student(Student other) {
    this(other.id, other.name, other,year);
}<

이 때 주의할 점은 복사하는 필드 중에 객체의 값이 바뀔 수 있는 것들은 두 객체의 값이 달라질 수 있다면 깊은 복사를 해야 된다는 점이다[각주:1]. 예를 들어 위의 Student가 수강신청 배열을 가지고 있고 새로 만든 학생은 같은 수강신청을 가지고 생성되도록 만든다고 가정해 보자. 얕은복사로 필드를 대응하는 필드로 그대로 지정하면 새로 만들어지는 학생이 other의 수강신청 배열을 가리키므로 나중에 과목을 추가하거나 수정하면 other의 수강신청도 같이 바뀔 것이다. 그러므로 깊은 복사를 위해서는 다음과 같이 배열을 복사생성해 주어야 한다.

public Student(Student other) {
    this(other.id, other.name, other,year);
    enrolledList = new ArrayList<Lecture>(other.enrolledList);
}

또 하나 생성자에 대한 주의사항이라면 생성자가 너무 많은 일을 하는 것은 바람직하지 않다는 점이다. 생성자는 초기화의 역할만 하는 게 좋다. 그런데 초기화라는 것이 어디까지인가? 예를 들어 거대한 자료구조를 가지는 클래스라면 그러한 필드를 모두 생성하는 것이 맞는가? 아니면 null로 두고 다른 곳에서 필요할 때 new로 생성하는 것이 좋을까? 또는 기본으로 몇개의 값을 가지고 있게 하고 싶다면 그것을 생성자에서 하는 것이 좋은가? 즉 초기화는 필요한 필드의 객체를 생성하는 데까지일까? 아니면 초기값을 몇개 미리 넣어놓는데까지일까? 또는 다른 예로 GUI 프로그램이라면 윈도우와 메뉴와 버튼을 다 만드는 것까지 초기화인가? 생성자에서 다 하는 게 좋은가?

이것은 프로그램마다 약간씩 달라질 수 있지만 일반적인 규칙이라면 생성자는 가볍게 필드의 값을 적절한 초기값으로 지정하는 정도가 적합하다고 볼 수 있다. 아주 큰 자료구조를 가지고 있는 객체라면 그것을 생성자에서 몽땅 만들기 보다는 필요할 때 생성하여 만드는 것이 메모리도 절약되고 불필요한 일을 미리 다 하지 않는다는 점에서 더 효율적일 수 있다. (필요할 때까지 미룬다는 원칙이라 할 수 있다.)

생성자의 접근지정자에 대해 생각해 보자. 생성자가 public이면 다른 패키지에서도 그 생성자를 호출할 수 있다. 반면에 default 생성자는 같은 패키지 안에서만 생성할 수 있다. 그럼 private 생성자는 어떤 경우에 쓰일까? 그 생성자 함수는 다른 클래스 객체에서는 호출될 수 없다. 아마도 그 클래스 내에서 어디선가 그 생성자를 이용할 것이다. 그러나 그 이외의 곳에서는 그 생성자는 불려지지 않는 것을 보장한다. private 생성자는 싱글톤이라는 패턴에서 사용된다. 또한 여러 개의 생성자가 있는데, 그 중 private인 것은 다른 클래스에서는 사용하지 않는 경우도 있다. 한편 protected 생성자는 상속 클래스에서만 호출될 수 있는데, 이것은 abstract 클래스에서 쓰인다.

스택오버플로우의 질문 중에 필드 초기화를 생성자에서 하는 것이 좋은가, 아니면 선언부에서 =로 초기화하는 것이 좋은가라는 질문이 있다. 여기에 전문가들의 답은 가능하면 선언부에 =로 초기화하는 것이 좋다는 것이다. 그 이유는 생성자가 복잡해 질 필요 없고 초기화가 더 가독성(변수의 값이 무엇인가를 선언하면서 확인가능)이 더 좋다는 점이다. 그러나 계산로직이 들어가는 코드로 초기화해야 하는 경우나 생성자마다 초기값이 달라지는 경우는 반드시 생성자를 이용해서 초기화해야 한다. 여기서 한 가지 팁! 자바에서 new를 통해 생기는 객체의 메모리는 0으로 초기화된다. (모든 필드 영역의 비트가 리셋된다.) 그러므로 0이나 null로 초기화는 생략해도 된다. [각주:2]

다음으로 static 필드의 초기화에 대해 생각해 보자. static 필드는 생성자에서 초기화할 수 없다. 객체가 생기기 훨씬 전에 메모리가 잡히고 초기화되어야 하기 때문이다. static 필드도 가능하면 선언시 초기화해주면 좋지만 static 필드도 역시 큰 자료구조가 될 수 있다. 그럼 그런 자료구조를 언제 만드는 것이 좋은가? 초기화로 선언부에 = new ... 하고 바로 써주는 것도 가능하지만 모든 것을 선언부에서 초기화하기는 어려운 경우도 많다. 이런 경우 그런 초기화를 위해 static { ... } 라는 정적 코드 블록을 이용할 수 있다. 이 블록은 프로그램이 시작되기 전에 static 필드가 모두 생기고 나서 바로 실행된다. 즉 객체가 생기기 전에 (로드 시점에) 미리 실행된다. 여기에 정적 필드의 초기화 코드를 넣어두는 것이 좋다. 예를 들면 정적 리스트에 초기 설정 값을 미리 넣어두는 코드라면 static 블록에서 하기에 적합하다.



  1. 기본타입 필드와 스트링은 깊은 복사를 하지 않아도 된다. [본문으로]
  2. 불필요한 초기화 코드는 나중에 컴파일러가 모두 제거하므로 걱정하지 않아도 된다. 분명하게 하기 위해 써두는 것도 좋다. [본문으로]

'자바 프로그래밍' 카테고리의 다른 글

자바 싱글톤 패턴  (0) 2019.01.18
객체의 참조와 객체 간의 관계  (0) 2019.01.17
자바 콜렉션 (3) - 해시맵 응용 예제  (0) 2019.01.13
자바 콜렉션 (2) - HashMap  (0) 2019.01.13
자바 콜렉션 - ArrayList  (0) 2019.01.13
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함