Java의 생성자

자바에서 생성자는 클래스의 인스턴스를 생성하는 데 사용됩니다. 생성자는 메서드와 거의 유사하지만 두 가지 점에서 다릅니다. – 이름은 클래스 이름과 동일하며 반환 유형이 없습니다. 때로는 생성자를 객체를 초기화하는 특수한 메서드로도 참조합니다.

자바에서의 생성자

new 키워드를 사용하여 클래스의 인스턴스를 생성할 때마다 생성자가 호출되고 해당 클래스의 객체가 반환됩니다. 생성자는 클래스에만 객체를 반환할 수 있기 때문에 자바 런타임에 의해 암시적으로 수행되며 반환 유형을 추가하지 않아야 합니다. 생성자에 반환 유형을 추가하면 클래스의 메서드가 됩니다. 이것이 자바 런타임이 일반 메서드와 생성자를 구분하는 방법입니다. 다음과 같은 코드가 Employee 클래스에 있다고 가정해 봅시다.

public Employee() {
	System.out.println("Employee Constructor");
}


public Employee Employee() {
	System.out.println("Employee Method");
	return new Employee();
}

첫 번째는 생성자입니다. 반환 유형이 없고 반환 문이 없음을 주목하세요. 두 번째는 첫 번째 생성자를 다시 호출하여 Employee 인스턴스를 가져와 반환하는 일반적인 메서드입니다. 메서드 이름이 클래스 이름과 같은 것은 혼란을 야기하므로 권장되지 않습니다.

자바에서의 생성자 유형

자바에는 세 가지 유형의 생성자가 있습니다.

  1. 기본 생성자
  2. 인수 없는 생성자
  3. 매개변수가 있는 생성자

이러한 생성자 유형을 모두 예제 프로그램과 함께 살펴보겠습니다.

자바의 기본 생성자

클래스 코드에 생성자 구현을 항상 제공할 필요는 없습니다. 생성자를 제공하지 않으면 자바는 명시적으로 생성자를 정의하지 않은 경우에 대한 기본 생성자 구현을 제공합니다. 생성자를 명시적으로 정의하지 않기 때문에 기본 생성자가 사용되고 있는 간단한 프로그램을 살펴보겠습니다.

package com.journaldev.constructor;

public class Data {

	public static void main(String[] args) {
		Data d = new Data();
	}
}
  1. 기본 생성자의 역할은 객체를 초기화하고 호출 코드에 반환하는 것입니다.
  2. 기본 생성자는 항상 인수가 없으며 기존 생성자가 정의되지 않은 경우에만 자바 컴파일러에 의해 제공됩니다.
  3. 대부분의 경우에는 다른 속성을 getter setter 메서드를 통해 액세스하고 초기화할 수 있기 때문에 기본 생성자만으로 충분합니다.

인수 없는 생성자

생성자 인자가 전혀 없는 것을 노-인자 생성자라고 합니다. 이는 기본 생성자를 오버라이드하고 리소스, 네트워크 연결, 로깅 등과 같은 몇 가지 사전 초기화 작업을 수행하는 것과 유사합니다. 자바에서 노-인자 생성자를 간단히 살펴보겠습니다.

package com.journaldev.constructor;

public class Data {
        // 노-인자 생성자
	public Data() {
		System.out.println("No-Args Constructor");
	}
	public static void main(String[] args) {
		Data d = new Data();
	}
}

이제 new Data()를 호출하면 노-인자 생성자가 호출됩니다. 아래 이미지는 이 동작을 설명하며 프로그램의 콘솔 출력을 확인하세요.

매개변수가 있는 생성자

인자와 함께 생성자를 사용하는 것은 매개변수가 있는 생성자라고 합니다. 자바에서 매개변수가 있는 생성자의 예를 살펴보겠습니다.

package com.journaldev.constructor;

public class Data {

	private String name;

	public Data(String n) {
		System.out.println("Parameterized Constructor");
		this.name = n;
	}

	public String getName() {
		return name;
	}

	public static void main(String[] args) {
		Data d = new Data("Java");
		System.out.println(d.getName());
	}

}

자바에서 생성자 오버로딩

둘 이상의 생성자가 있는 경우 이를 자바에서 생성자 오버로딩이라고 합니다. 자바 프로그램에서 생성자 오버로딩의 예를 살펴보겠습니다.

package com.journaldev.constructor;

public class Data {

	private String name;
	private int id;

	// 노-인자 생성자
	public Data() {
		this.name = "Default Name";
	}
	// 하나의 매개변수 생성자
	public Data(String n) {
		this.name = n;
	}
	// 두 개의 매개변수 생성자
	public Data(String n, int i) {
		this.name = n;
		this.id = i;
	}

	public String getName() {
		return name;
	}

	public int getId() {
		return id;
	}

	@Override
	public String toString() {
		return "ID="+id+", Name="+name;
	}
	public static void main(String[] args) {
		Data d = new Data();
		System.out.println(d);
		
		d = new Data("Java");
		System.out.println(d);
		
		d = new Data("Pankaj", 25);
		System.out.println(d);
		
	}

}

Java에서의 개인 생성자

추상, 최종, 정적 및 동기화 키워드를 생성자와 함께 사용할 수 없다는 것을 주의하세요. 그러나 클래스 개체의 인스턴스화를 제어하기 위해 액세스 수정자를 사용할 수 있습니다. publicdefault 액세스 사용은 여전히 괜찮지만, 생성자를 개인으로 만드는 이유는 무엇일까요? 이 경우 다른 클래스는 해당 클래스의 인스턴스를 생성할 수 없게 됩니다. 자바에서는 생성자를 개인으로 만들어 싱글톤 디자인 패턴을 구현하려는 경우가 있습니다. 자바는 자동으로 기본 생성자를 제공하므로 명시적으로 생성자를 만들고 개인으로 유지해야 합니다. 클라이언트 클래스는 해당 클래스의 인스턴스를 얻기 위한 유틸리티 정적 메서드로 제공됩니다. Data 클래스에 대한 개인 생성자의 예는 다음과 같습니다.

// 개인 생성자
private Data() {
	// 싱글톤 패턴 구현을 위한 빈 생성자
	// 클래스의 getInstance() 메서드 내에서 사용될 코드를 포함할 수 있음
}

자바에서의 생성자 연쇄

동일한 클래스의 생성자가 다른 생성자를 호출할 때, 이를 생성자 연쇄라고 합니다. 우리는 클래스의 다른 생성자를 호출하기 위해 this 키워드를 사용해야 합니다. 때로는 클래스 변수의 기본값을 설정하는 데 사용될 수 있습니다. 또한 다른 생성자 호출은 코드 블록에서 첫 번째 문장이어야 합니다. 무한 루프를 생성할 수 있는 재귀 호출이 있어서는 안 됩니다. 자바 프로그램에서 생성자 연쇄의 예제를 살펴보겠습니다.

package com.journaldev.constructor;

public class Employee {

	private int id;
	private String name;
	
	public Employee() {
		this("John Doe", 999);
		System.out.println("Default Employee Created");
	}
	
	public Employee(int i) {
		this("John Doe", i);
		System.out.println("Employee Created with Default Name");
	}
	public Employee(String s, int i) {
		this.id = i;
		this.name = s;
		System.out.println("Employee Created");
	}
	public static void main(String[] args) {

		Employee emp = new Employee();
		System.out.println(emp);
		Employee emp1 = new Employee(10);
		System.out.println(emp1);
		Employee emp2 = new Employee("Pankaj", 20);
		System.out.println(emp2);
	}

	@Override
	public String toString() {
		return "ID = "+id+", Name = "+name;
	}
	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

}

I have overridden the toString() method to print some useful information about Employee object. Below is the output produced by above program.

Employee Created
Default Employee Created
ID = 999, Name = John Doe
Employee Created
Employee Created with Default Name
ID = 10, Name = John Doe
Employee Created
ID = 20, Name = Pankaj

어떻게 하나의 생성자가 다른 생성자에서 호출되는지 주목하세요. 이것이 생성자 연쇄 프로세스라고 합니다.

자바 수퍼 생성자

가끔 클래스가 슈퍼클래스에서 상속됩니다. 그 경우에는 슈퍼클래스 생성자를 호출해야 할 때 super 키워드를 사용할 수 있습니다. 슈퍼클래스 생성자 호출은 자식 클래스 생성자의 첫 번째 문장이어야 합니다. 또한 자식 클래스 생성자를 인스턴스화할 때, 자바는 먼저 슈퍼클래스를 초기화한 후에 자식 클래스를 초기화합니다. 따라서 슈퍼클래스 생성자가 명시적으로 호출되지 않으면 자바 런타임에 의해 기본 생성자 또는 매개변수 없는 생성자가 호출됩니다. 이러한 개념을 몇 가지 예제 프로그램을 통해 이해해 봅시다. 아래와 같이 두 개의 클래스가 있다고 가정해 봅시다.

package com.journaldev.constructor;

public class Person {

	private int age;

	public Person() {
		System.out.println("Person Created");
	}

	public Person(int i) {
		this.age = i;
		System.out.println("Person Created with Age = " + i);
	}

}
package com.journaldev.constructor;

public class Student extends Person {

	private String name;

	public Student() {
		System.out.println("Student Created");
	}

	public Student(int i, String n) {
		super(i); // super class constructor called
		this.name = n;
		System.out.println("Student Created with name = " + n);
	}

}

이제 아래와 같이 Student 객체를 생성하면;

Student st = new Student();

위의 코드의 출력은 무엇입니까? 위 코드의 출력은 다음과 같습니다:

Person Created
Student Created

따라서 첫 번째 문장에 슈퍼 호출이 없기 때문에 Student 클래스의 매개변수 없는 생성자가 호출됩니다. 따라서 출력됩니다. Student st = new Student(34, "Pankaj");와 같이 Student 클래스의 매개변수 생성자를 사용하는 경우 출력은 다음과 같습니다:

Person Created with Age = 34
Student Created with name = Pankaj

여기서 출력이 명확합니다. 왜냐하면 우리가 명시적으로 슈퍼클래스 생성자를 호출하기 때문에 자바가 그들의 쪽에서 추가 작업을 할 필요가 없습니다.

자바 복사 생성자

Java 복사 생성자는 동일한 클래스의 객체를 매개변수로 사용하여 해당 객체의 복사본을 생성합니다. 때로는 다른 객체의 복사본이 필요하여 몇 가지 처리를 수행해야 할 때가 있습니다. 이를 다음과 같은 방법으로 수행할 수 있습니다:

  1. 구현복제
  2. 객체의깊은 복사를 위한 유틸리티 메서드 제공
  3. 복사 생성자 보유

이제 복사 생성자를 작성하는 방법을 살펴보겠습니다. 아래와 같이 Fruits 클래스가 있다고 가정해 봅시다.

package com.journaldev.constructor;

import java.util.ArrayList;
import java.util.List;

public class Fruits {

	private List<String> fruitsList;

	public List<String> getFruitsList() {
		return fruitsList;
	}

	public void setFruitsList(List<String> fruitsList) {
		this.fruitsList = fruitsList;
	}

	public Fruits(List<String> fl) {
		this.fruitsList = fl;
	}
	
	public Fruits(Fruits fr) {
		List<String> fl = new ArrayList<>();
		for (String f : fr.getFruitsList()) {
			fl.add(f);
		}
		this.fruitsList = fl;
	}
}

Fruits(Fruits fr)에 주목하십시오. 이는 객체의 복사본을 반환하기 위해 깊은 복사를 수행합니다. 복사 생성자를 사용하여 객체를 복사하는 것이 왜 더 나은지 이해하기 위해 테스트 프로그램을 살펴보겠습니다.

package com.journaldev.constructor;

import java.util.ArrayList;
import java.util.List;

public class CopyConstructorTest {

	public static void main(String[] args) {
		List<String> fl = new ArrayList<>();
		fl.add("Mango");
		fl.add("Orange");

		Fruits fr = new Fruits(fl);

		System.out.println(fr.getFruitsList());

		Fruits frCopy = fr;
		frCopy.getFruitsList().add("Apple");

		System.out.println(fr.getFruitsList());

		frCopy = new Fruits(fr);
		frCopy.getFruitsList().add("Banana");
		System.out.println(fr.getFruitsList());
		System.out.println(frCopy.getFruitsList());

	}

}

위 프로그램의 출력은 다음과 같습니다:

[Mango, Orange]
[Mango, Orange, Apple]
[Mango, Orange, Apple]
[Mango, Orange, Apple, Banana]

복사 생성자를 사용할 때 원본 객체와 해당 복사본은 서로 관련이 없으며 한쪽에서의 수정이 다른 쪽에 반영되지 않습니다. 이것으로 Java에서 생성자에 대한 설명은 모두입니다.

Source:
https://www.digitalocean.com/community/tutorials/constructor-in-java