본문 바로가기
Java/About Java

[Java] 생성자

by seaweed_one 2023. 1. 31.
728x90

안녕하세요. 씨위드입니다.
오늘은 포스팅 주제로 생성자를 가지고 왔는데요.
같이 일하던 친구가 본인은 처음부터 스프링과 그루비만 사용하여 생성자를 어떻게 사용하는 건지 모르겠다는 고민을 이야기한 적이 있었습니다.
저는 퓨어 자바를 사용하여 개발 중이기 때문에 생성자를 만들 일이 아주아주 많습니다.
해서 생성자에 대하여 정리해보려고 합니다.

생성자(constructor)란?

모든 클래스에는 반드시 하나 이상의 생성자가 있어야 합니다.
그럼 생성자란 무엇일까요?
생성자란 인스턴스가 생성될 때마다 호출되는 초기화 메서드입니다.
인스턴스 변수의 초기화 또는 인스턴스 생성 시 수행할 작업에 사용하는데요.
몇 가지를 제외하고는 기본적으로는 일반 메서드와 동일합니다.

아직 이해가 조금 힘드신가요?
그럼 클래스를 하나 생성해 보겠습니다.

Seaweed sw = new Seaweed();

생성자 Seaweed() 가 사용되었습니다.
생성자를 이용해 클래스를 생성하면 어떤 일이 일어날까요?

  1. new 연상에 의해 힙 메모리 영역에 Seaweed 클래스의 인스턴스가 생성됩니다.
  2. 생성자 (Seaweed()) 가 호출되어 수행됩니다.
  3. new 연산의 결과로 생성된 Seaweed 클래스의 주소가 참조변수 sw에 저장됩니다.

그럼 이쯤에서 여러분은 한 가지 의문이 드실 수도 있습니다.

모든 클래스는 반드시 하나 이상의 생성자가 존재해야 한다면서요???
저는 생성자가 없는 java class를 본 적이 있는데요..?

맞습니다. 생성자가 없는 클래스들도 존재합니다.
생성자를 만들지 않으면 어떻게 될까요?
클래스에 생성자가 하나도 없는 경우 컴파일러가 기본 생성자를 추가합니다.
반대로 말하면 생성자가 하나라도 있으면 컴파일러는 생성자를 추가하지 않겠죠?

생성자의 조건

생성자를 만드는 데는 많은 조건이 필요하지 않습니다.

  • 생성자의 이름은 클래스명과 동일해야 합니다.
  • 생성자는 리턴값이 없습니다.
  • 생성자는 리턴값이 없음에도 void를 사용하지 않습니다.

간단하죠?

생성자의 종류

기본생성자

기본생성자는 매개변수가 없는 생성자를 말합니다.

public class Seaweeed {
  public Seaweeed () {
  }
}

매개변수가 있는 생성자

public class Seaweeed {
  private String name;
  private int age;
  
  public Seaweeed (String name, int age) {
    this.name = name;
    this.age = age;
  }
}

파라미터가 있는 생성자의 예시입니다.
위에서 생성자도 몇 가지 조건을 제외하고는 메서드와 동일하다고 말씀드렸죠?
생성자 또한 매개변수로 값을 받아 변수 초기화에 사용할 수 있습니다.
인스턴스를 생성한 후 변수의 값을 변경하는 것보다 생성자의 매개변수로 전달하는 편이 보다 직관적인 코드를 작성할 수 있겠죠?
아래의 예시를 보면 매개변수가 있는 생성자를 사용하는 것의 이점을 보다 명확하게 아실 수 있을 실겁니다.

//기본생성자를 사용한 코드 
Seaweed sw = new Seaweed();
sw.name = "one";
sw.age = 0;

//매개변수가 있는 생성자를 사용한 코드 
Seaweed sw = new Seaweed("one" , 0);

코드가 훨씬 간결하고 명확해졌습니다.

인스턴스 복사를 위한 생성자

사실 매개변수가 있는 생성자와 모양은 같습니다.
다만 사용하고 있는 인스턴스와 같은 상태의 인스턴스를 하나 더 만들고자 할 때 사용할 수 있는 생성자입니다.
예시는 아래와 같습니다.

Seaweed(Seaweed sw){
  name = sw.name;
  age = sw.age;
}

Seaweed 클래스의 참조변수를 매개변수로 선언한 생성자를 만들었습니다.
이 생성자는 매개변수로 넘어온 참조변수가 가리키는 Seaweed 클래스의 name, age 값을 자기 자신에게 복사했습니다.
내부의 값을 자세하게 알지 못해도 똑같은 인스턴스를 생성할 수 있는 것이죠.
그럼 생성자를 사용해 볼까요?

Class Test{
  public static void main(String[] agrs){
    Seaweed sw = new Seaweed("one", 20);
    Seaweed sw2 = new Seaweed(sw);
    System.out.println(sw.name + "," + sw.age);
    System.out.println(sw2.name + "," + sw2.age);


    sw.age = 30;
    System.out.println(sw.name + "," + sw.age);
    System.out.println(sw2.name + "," + sw2.age); //어떤 값이 출력될까요?
  }

생성자를 사용하는 예시 코드를 작성해 봤습니다.
마지막 프린트 문에서는 과연 어떤 값이 출력될까요?
sw2는 sw를 복사하여 생성되었으므로 초기에는 서로 같은 상태를 갖습니다.
하지만 서로 독립적인 인스턴스이고 별도의 메모리공간에 존재하기 때문에 sw의 값이 변경되더라도 sw2는 영향을 받지 않습니다.

this()

생성자 간에 호출이 가능할까요?
가능합니다.
대신 두 가지 조건이 필요합니다.

  • 생성자의 이름으로 클래스명대신 this를 사용해야 합니다.
  • 생성자에서 다른 생성자 호출 시 반드시 첫 줄에서 호출해야 합니다.

왜 생성자 내에서 생성자를 호출할 경우 첫 줄에서 호출하라고 하는 것일까요?
생성자 내에서 초기화 작업 도중 다른 생성자 호출 시 호출된 생성자 내에서도 멤버 변수들의 값을 초기화할 것이기 때문에 다른 생성자를 호출하기 이전의 작업이 무의미해질 수 있기 때문입니다.
물론 아래와 같이 코드를 작성할 일은 없지만 여러분들의 이해를 돕기 위해 간단한 예시를 작성해 보았습니다.

public class Seaweeed {
  private String name;
  private String home;
  private int age;
  
  public Seaweed(){
    this("one");
  }
  
  public Seaweeed (String name) {
    this(name, "soule"); //20살이 되고싶습니다.
  }
  
  public Seaweed(String name, String home){
    this(name, home, 20)
  }
  
  public Seaweed(String name, String home, int age){
    this.name = name;
    this.home = home;
    this.age = age;
  }
}

열심히 생성자를 만들었으니 이제 클래스를 생성해 볼까요?

Seaweed sw = new Seaweed();
//sw.name? one
//sw.home? soule
//sw.age? 20

Seaweed sw2 = new Seaweed("one");
//sw2.name? one
//sw2.home? soule
//sw2.age? 20

Seaweed sw3 = new Seaweed("one", "busan");
//sw3.name? one
//sw3.home? busan
//sw3.age? 20

Seaweed sw4 = new Seaweed("one", "gwangju", 30);
//sw4.name? one
//sw4.home? gwangju
//sw4.age? 30

보시는 것처럼 Seaweed 클래스 생성 시 아무 옵션도 주지 않으면 기본적으로 서울에 사는 20살의 one 인 Seaweed가 생성됩니다.
같은 클래스 내의 생성자들은 서로 관계가 깊은 경우가 많습니다.
해서 필요시 서로 호출하도록 하여 연결을 해준다면 더 좋은 코드를 작성할 수 있습니다.
또한 수정 시에도 보다 간결하겠죠.

this

public class Seaweeed {
  private String name;
  private String home;
  private int age;
  
  public Seaweed(String name, String home, int seaweedAge){
    this.name = name;
    this.home = home;
    age = seaweedAge; //this.age = seaweedAge 
  }
}

위의 예시에서 this를 발견하셨나요?
변수 name과 home 에는 'this'가 붙어있습니다.
왜일까요?

매개변수로 받은 값과 인스턴스 변수의 이름이 같아 구별을 할 수가 없습니다.
해서 인스턴스 변수의 앞에 'this'를 붙여 구별을 해주는 것이죠.
만약 인스턴스 변수 앞의 'this'를 제거한다면 어떻게 될까요? home = home;처럼 말이죠.
이런 경우 둘 다 지역변수로 간주하게 됩니다.

그럼 this를 붙이면 왜 구분이 가능할까요?
'this' 는 참조변수로 인스턴스 자신을 가리킵니다.
인스턴스의 주소가 저장되어 있죠.
사실 모든 인스턴스메서드에는 관련된 인스턴스를 가리키는 참조변수 'this'가 지역변수로 숨어있습니다.

비교를 위해 파라미터로 받아오는 age의 값은 다르게 네이밍 해보았습니다.
이런 경우 'this'를 사용하지 않아도 괜찮습니다.
다만 주석으로 표기한 것처럼 명시적으로 'this'를 붙이는 경우도 많기 때문에 적절하게 사용하셨으면 합니다. (저의 경우 생성자내에서는 웬만하면 this를 붙입니다.)

참고로 저희 교수님께서는 꼭 'this'를 사용하라고 하시는 편이셨고, 현장에서 다른 분들의 코드를 보면 굳이 사용하지 않는 경우도 많습니다.

이렇게 생성자에 대하여 알아보았는데요.
가장 기본적으로 여겨지는 생성자이지만 정리를 하니 양이 꽤 됩니다.
여러분께 도움이 되었길 바라며 이만 마치겠습니다.
감사합니다~

728x90

'Java > About Java' 카테고리의 다른 글

[Java] 얕은 복사 & 깊은 복사 & 방어적 복사  (0) 2023.02.13
[Java] 객체 - 클래스  (0) 2023.02.06
[Java] Java Memory Leak  (0) 2023.01.11
[Java] Primitive Type & Reference Type  (0) 2023.01.09
[Java] JNA? JNI?  (0) 2023.01.04