Java/Java

Java - 생성자, 초기화 블록

Dlise 2023. 9. 25. 16:06

생성자와 초기화 블록에 대해 알아보자.

 

생성자

생성자는 인스턴스가 생성될 때 호출되는 인스턴스 초기화 메서드이다.

인스턴스가 생성될 때 필요한 동작을 위해 활용한다.

 

생성자 구조

생성자는 클래스 이름과 같은 이름을 활용하며 반환값이 없다.

반환값이 없으므로 void만 올 수 있고 이를 생략한다.

 

아래 코드에서 Car() {...}가 생성자이다.

public class ConstructorPractice {
    public static void main(String[] args) {
        Car car = new Car();
    }
}

class Car {
    Car() {
        System.out.println("Hello world");
    }
}

 

이를 실행해 보면 Hello world가 출력된다.

인스턴스가 생성되고 생성자가 동작했기 때문이다.

 

기본 생성자

생성자를 구현하지 않아도 모든 클래스는 하나의 생성자를 가진다.

컴파일러가 기본 생성자를 추가하기 때문이다.

 

기본 생성자는 '클래스명() { }'의 형태로 되어있다.

만약 생성자를 하나라도 직접 구현했다면 기본 생성자는 추가되지 않는다.

 

 

생성자 오버로딩

생성자는 오버로딩이 가능해 여러 경우를 고려해 활용할 수 있다.

 

아래는 Car 클래스에 대해 2개의 생성자를 만든 코드이다.

Car 인스턴스를 생성할 때 인자로 정수를 보내면 Car(int door)를, 인자를 비우면 Car()를 실행한다.

public class ConstructorPractice {
    public static void main(String[] args) {
        Car car1 = new Car();
        Car car2 = new Car(2);

        System.out.println("car1: " + car1.door);
        System.out.println("car2: " + car2.door);
    }
}

class Car {
    int door;
    Car() {
        door = 4;
    }

    Car(int door) {
        this.door = door;
    }
}

 

 

생성자에서 다른 생성자 호출: this()

this()를 활용해 한 생성자에서 다른 생성자를 호출할 수 있다.

 

아래 코드는 Book 클래스에 대해 Book(), Book(String, int), Book(String, int, String) 3개의 생성자를 구현했다.

이후 Book()과 Book("Hello", 200) 인스턴스를 생성했다.

public class ConstructorPractice {
    public static void main(String[] args) {
        Book book = new Book();

        System.out.println(book.toString());
    }
}

class Book {
    String name;
    int pages;
    String color;

    Book() {
        this("Default", 100);
    }

    Book(String name, int pages) {
        this(name, pages, "Blue");
    }

    Book(String name, int pages, String color) {
        this.name = name;
        this.pages = pages;
        this.color = color;
    }

    public String toString() {
        return "name: " + name + ", pages: " + pages + ", color: " + color;
    }
}

결과는 아래와 같다.

 

먼저 Book()으로 Book 인스턴스를 생성한 경우 아래 코드가 실행되었다.

    Book() {
        this("Default", 100);
    }

 

Book() 생성자의 this(String, int)는 Book(String, int)를 호출했다.

    Book(String name, int pages) {
        this(name, pages, "Blue");
    }

 

Book(String, int) 생성자는 Book(String, int, String)을 호출했다.

    Book(String name, int pages, String color) {
        this.name = name;
        this.pages = pages;
        this.color = color;
    }

최종적으로 이 생성자에서 변수에 값을 담은 것이다.

 

단, this()는 생성자의 첫 줄에만 사용할 수 있다.

만약 아래와 같이 코드를 작성하면

    Book() {
        System.out.println("Test");
        this("Default", 100);
    }

컴파일 에러가 발생한다.

첫 줄에만 사용할 수 있는 이유는 만약 뒷 줄에 this()를 호출하면 기존의 초기화 작업이 무의미해지기 때문이다.

 

 

초기화 블록

초기화 블록은 마치 변수, 메서드처럼 클래스 초기화 블록인스턴스 초기화 블록이 있다.

실제로 차이점도 static의 유무이다.

public class InitBlockPractice {
    { }         //인스턴스 초기화 블록
    static { }  //클래스 초기화 블록
}

 

클래스 초기화 블록은 클래스가 메모리에 적재될 때 1회만 실행되며

인스턴스 초기화 블록은 인스턴스가 생성될 때마다 실행된다.

 

기억해야 할 것은 초기화 블록이 생성자보다 먼저 수행된다는 점이다.

public class InitBlockPractice {
    public static void main(String[] args) {
        Book book = new Book();
        System.out.println(book.name);
    }
}

class Book {
    String name;

    {
        name = "초기화 블록";
    }

    Book() {
        name = "생성자";
    }
}

위 코드의 출력 결과는 "생성자"이다.

 

생성자와 역할이 비슷한데 초기화 블록을 활용하는 이유는 생성자에서 반복되는 코드를 줄이기 위함이다.

 

아래는 사람이 1명씩 증가할 때마다 숫자를 1씩 늘려 입력하는 코드이다.

public class InitBlockPractice {
    public static void main(String[] args) {
        Person person1 = new Person();
        Person person2 = new Person("Happy");
        Person person3 = new Person("Cake");

        System.out.println(person1.toString());
        System.out.println(person2.toString());
        System.out.println(person3.toString());
    }
}

class Person {
    static int count;
    String name;
    int num;

    {
       count++;
       num = count;
    }

    Person() {
        name = "None";
    }

    Person(String name) {
        this.name = name;
    }

    public String toString() {
        return "번호: " + num + ", 이름: " + name;
    }
}

 

만약 초기화 블록을 사용하지 않으면 Person 클래스의 생성자는 중복되는 코드를 가져야 한다.

class Person {
    static int count;
    String name;
    int num;


    Person() {
        name = "None";
        count++;
        num = count;
    }

    Person(String name) {
        this.name = name;
        count++;
        num = count;
    }

    public String toString() {
        return "번호: " + num + ", 이름: " + name;
    }
}

만약 생성자가 더욱 많아지면 유지보수 측면에서 손해이다.

 

물론 아래 코드처럼 this()를 활용할 수는 있으나 

class Person {
    static int count;
    String name;
    int num;


    Person() {
        this("None");
    }

    Person(String name) {
        this.name = name;
        count++;
        num = count;
    }

    public String toString() {
        return "번호: " + num + ", 이름: " + name;
    }
}

동일한 작업을 한다는 점을 중요히 여겨 초기화 블록으로 구분해 놓는 것이 이후의 작업에 좋다.

 

 

클래스 초기화 블록

아래 코드는 클래스 초기화 블록을 사용한 것이다.

public class InitBlockPractice {
    public static void main(String[] args) {
        Person person1 = new Person("Happy");
        Person person2 = new Person("Cake");

        System.out.println(person1.toString());
        System.out.println(person2.toString());
    }
}

class Person {
    static int count;
    String name;
    int num;

    static {
        System.out.println("클래스 초기화 블록은 1회만 실행됩니다.");
    }

    Person(String name) {
        this.name = name;
        count++;
        num = count;
    }

    public String toString() {
        return "번호: " + num + ", 이름: " + name;
    }
}

출력이 처음 1회만 된 것을 알 수 있다.