JAVA

[Java] 자바 생성자의 초기화 방법

Stark97 2024. 9. 21. 15:52
반응형

 

 
 

자바 생성자는 어떻게 초기화될까?

 

1. 생성자에서 모든 필드를 초기화할 필요가 없는 이유

자바에서 생성자는 객체가 생성될 때 특정 필드를 초기화하기 위해 사용된다.

그러나 모든 필드를 반드시 생성자에서 초기화해야 하는 것은 아니다.

 

다음과 같은 이유로 일부 필드는 생성자에서 초기화하지 않아도 된다.

  1. 선택적 초기화
    • 일부 필드는 기본값으로 초기화해도 문제가 없는 경우가 있다.
    • 예를 들면 숫자형 필드는 기본적으로 0, 객체형 필드는 null로 초기화된다.

  2. 다중 생성자
    • 여러 생성자를 정의하여 다양한 방식으로 객체를 생성할 수 있다.
    • 각 생성자에서 초기화할 필드를 선택적으로 지정할 수 있다.

  3. 필드 초기화 블록 또는 선언 시 초기화
    • 필드를 선언할 때 기본값을 지정할 수 있다.
    • 초기화 블록을 사용하여 공통적으로 초기화할 수도 있다.

기본값 초기화

  • 자바에서는 클래스의 인스턴스 필드(멤버 변수)는 자동으로 기본값으로 초기화된다. 이는 final 키워드와 상관없이 적용된다.
  • 기본값은 필드의 데이터 타입에 따라 다르게 설정된다.

기본형 타입의 초기값

  • byte, short, int, long: 0
  • float, double: 0.0
  • char: '\u0000' (null 문자)
  • boolean: false

참조형 타입의 초기값

  • 객체형 (예: String, 사용자 정의 클래스 등): null

 

2. 다른 클래스를 멤버 변수로 가질 때의 초기화

다른 클래스를 멤버 변수로 가질 때

  • 다른 클래스를 멤버 변수로 선언하면, 해당 변수는 객체의 레퍼런스(reference)를 저장하게 된다. 만약 생성자에서 이를 초기화하지 않으면 기본값인 null로 설정된다. 이는 메모리 상에 실제 객체가 생성되지 않았음을 의미한다.

예제

public class Address {

    private String city;
    private String street;

    public Address(String city, String street) {
        this.city = city;
        this.street = street;
    }

    // getters and setters...

    @Override
    public String toString() {
        return city + ", " + street;
    }
    
}
public class Person {

    private String name;
    private int age;
    private Address address; // 다른 클래스를 멤버 변수로 선언

    // 생성자에서 address를 초기화하지 않음
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
        // address는 기본값인 null로 남게 됩니다.
    }

    public void displayInfo() {
        System.out.println("Name: " + name);         // 정상 출력
        System.out.println("Age: " + age);           // 정상 출력
        System.out.println("Address: " + address);   // null 출력
    }

    public static void main(String[] args) {
        Person person = new Person("홍길동", 30);
        person.displayInfo();
    }
    
}

출력은 다음과 같다.

Name: 홍길동
Age: 30
Address: null

 

3. 생성자 오버로딩 (Constructor Overloading)

생성자 오버로딩

  • 자바에서는 같은 이름의 생성자를 여러 개 정의할 수 있으며, 이를 생성자 오버로딩이라고 한다. 객체를 생성할 때 전달하는 인자의 개수와 타입에 따라 호출되는 생성자가 결정된다. 한 번 객체를 생성할 때는 하나의 생성자만 호출되며, 다른 생성자들은 독립적으로 존재한다.

  • 호출에 대해 조금 더 상세히 설명하자면 자바에서 클래스에 여러 개의 생성자를 오버로딩(overloading)하여 정의한 경우, 객체를 생성할 때 원하는 생성자 하나만 선택적으로 호출할 수 있으며, 나머지 생성자들에는 전혀 영향을 미치지 않는다. 다시 말해, 하나의 생성자를 사용한다고 해서 다른 생성자들을 함께 사용해야 하거나, 자동으로 호출되는 일은 없다.

 

예제

public class User {

    private String username;
    private String email;
    private int age;

    // username만 초기화하는 생성자
    public User(String username) {
        this.username = username;
    }

    // username과 email을 초기화하는 생성자
    public User(String username, String email) {
        this.username = username;
        this.email = email;
    }

    // 모든 필드를 초기화하는 생성자
    public User(String username, String email, int age) {
        this.username = username;
        this.email = email;
        this.age = age;
    }

    // getters and setters...
}

생성자 선택 시 초기화되는 필드

  1. 첫 번째 생성자 (User(String username)):
    • username 필드만 초기화된다.
    • email과 age 필드는 기본값으로 초기화된다.
      • email: null
      • age: 0

  2. 두 번째 생성자 (User(String username, String email)):
    • username과 email 필드가 초기화된다.
    • age 필드는 기본값인 0으로 초기화된다.

  3. 세 번째 생성자 (User(String username, String email, int age)):
    • 모든 필드 (username, email, age)가 초기화된다.

위의 예제코드 실행 예시

public class Main {

    public static void main(String[] args) {
    
        // 첫 번째 생성자 사용: username만 초기화
        User user1 = new User("alice");
        System.out.println(user1.getUsername()); // 출력: alice
        System.out.println(user1.getEmail());    // 출력: null
        System.out.println(user1.getAge());      // 출력: 0

        // 두 번째 생성자 사용: username과 email 초기화
        User user2 = new User("bob", "bob@example.com");
        System.out.println(user2.getUsername()); // 출력: bob
        System.out.println(user2.getEmail());    // 출력: bob@example.com
        System.out.println(user2.getAge());      // 출력: 0

        // 세 번째 생성자 사용: 모든 필드 초기화
        User user3 = new User("carol", "carol@example.com", 25);
        System.out.println(user3.getUsername()); // 출력: carol
        System.out.println(user3.getEmail());    // 출력: carol@example.com
        System.out.println(user3.getAge());      // 출력: 25
        
    }
    
}

 

4. 주의할 점

final 필드의 초기화

  • final로 선언된 필드는 반드시 생성자에서 초기화하거나 선언 시에 초기값을 지정해야 한다. 그렇지 않으면 컴파일 오류가 발생한다.
public class ImmutablePerson {

    private final String name;
    private final int age;

    // 모든 final 필드를 초기화하는 생성자
    public ImmutablePerson(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 기본 생성자를 만들면 오류 발생
    // public ImmutablePerson() {} // 컴파일 오류
}

로컬 변수와 인스턴스 변수의 차이

  • 인스턴스 변수(필드): 클래스 내에 선언된 변수로, 객체가 생성될 때 자동으로 기본값으로 초기화된다.
  • 로컬 변수: 메서드 내에 선언된 변수로, 자동으로 초기화되지 않는다. 반드시 사용하기 전에 초기화를 해야 한다.
public void method() {

    int localVar;
    // System.out.println(localVar); // 컴파일 오류: 초기화되지 않은 변수 사용

    localVar = 10;
    System.out.println(localVar); // 정상 출력: 10
    
}

객체의 일관성 유지

  • 모든 필드를 생성자에서 초기화하지 않으면 객체의 상태가 일관되지 않을 수 있다. 특히, 중요한 필드가 null 상태로 남아있으면 NullPointerException 등의 문제가 발생할 수 있다. 따라서, 객체의 상태를 항상 유효하게 유지하려면 필요한 필드를 생성자에서 초기화하는 것이 좋다.

5. 예제 코드

기본 필드 초기화 예제

public class Example {

    private int number;
    private boolean flag;
    private String text;
    private double decimal;
    private Object obj;

    // 생성자에서 필드를 초기화하지 않음
    public Example() {
        // 모든 필드는 기본값으로 초기화됩니다.
    }

    public void displayValues() {
        System.out.println("number: " + number);   // 출력: number: 0
        System.out.println("flag: " + flag);       // 출력: flag: false
        System.out.println("text: " + text);       // 출력: text: null
        System.out.println("decimal: " + decimal); // 출력: decimal: 0.0
        System.out.println("obj: " + obj);         // 출력: obj: null
    }

    public static void main(String[] args) {
        Example example = new Example();
        example.displayValues();
    }
    
}

출력은 다음과 같다.

number: 0
flag: false
text: null
decimal: 0.0
obj: null

내부 클래스 멤버 변수 초기화 예제

public class Company {

    private String companyName;
    private Employee manager; // 내부 클래스를 멤버 변수로 선언

    // 내부 클래스
    public class Employee {
    
        private String name;
        private int employeeId;

        public Employee(String name, int employeeId) {
            this.name = name;
            this.employeeId = employeeId;
        }

        @Override
        public String toString() {
            return name + " (ID: " + employeeId + ")";
        }
        
    }

    // 생성자에서 manager를 초기화하지 않음
    public Company(String companyName) {
        this.companyName = companyName;
        // manager는 기본값인 null로 남게 됩니다.
    }

    public void displayInfo() {
        System.out.println("Company: " + companyName);
        System.out.println("Manager: " + (manager != null ? manager : "미정"));
    }

    public static void main(String[] args) {
        Company company = new Company("TechCorp");
        company.displayInfo();
    }
    
}

출력은 다음과 같다.

Company: TechCorp
Manager: 미정

 

6. 요약 (정리본)

  1. final이 아닌 필드는 생성자에서 초기화하지 않아도 기본값으로 자동 초기화된다.
    • 기본형 타입: 0, false 등
    • 참조형 타입: null

  2. 다른 클래스를 멤버 변수로 가지는 경우
    • 초기화하지 않으면 해당 변수는 null이 된다.
    • null 상태에서 메서드 호출 시 NullPointerException 발생 가능.
    • 생성자, 초기화 블록, 또는 선언 시 초기값 할당을 통해 null 상태를 방지하는 것이 좋다.

  3. 로컬 변수와 인스턴스 변수
    • 인스턴스 변수: 자동 초기화됨.
    • 로컬 변수: 반드시 사용 전에 초기화해야 함.

  4. 객체의 일관성 유지
    • 객체의 중요한 멤버 변수(필드)는 반드시 초기화하여 객체의 일관성을 유지하고 잠재적인 오류를 방지해야 한다.

  5. 생성자 오버로딩
    • 여러 생성자를 정의하여 필요한 필드만 선택적으로 초기화할 수 있다.
    • 하지만, 필요한 필드를 초기화하지 않으면 일부 필드가 기본값으로 남을 수 있으므로 주의해야 한다.

 

반응형