Constructor 클래스 및 객체 생성
Constructor<?>
- Java 클래스의 생성자는 제네릭 클래스인
Constructor
의 인스턴스로 나타낸다 - 이 객체는 클래스 생성자의 모든 정보를 포함하고 있다.
- 파라미터의 수
- 파라미터의 타입
- 클래스에는 여러개의 생성자가 있을 수 있다.
Constructor<?> 메소드
Class.getDeclaredConstructos()
: 클래스의 모든 생성자를 반환한다. public이거나 아니거나 상관없이 모두 반환한다.Class.getConstructors()
: Public 생성자만 반환한다.
특정 생성자 매개변수 유형을 알고 있는 경우
Class.getConstructor(Class<?> ... parameterTypes)
Class.getDeclaredConstructor(Class<?> ... parameterTypes)
- 파라미터의 타입을 나타내는 타입 목록을 전달할 수 있다.
- 이 메서드는 단일 생성자를 반환한다.
- 없으면
NoSuchMethodException
예외가 발생한다.
Default 생성자
public class Artist {
// 코드상에 선언된 생성자가 없는 경우..
public Artist() {} // <<<< default 생성자가 자동으로 생성됨
}
- 이 경우 인자 없는 생성자 호출 메서드를 사용하면..
// default 생성자를 포함하는 단일 배열 반환
Constructor<?> [] constructors = Artist.class.getConstructors();
// default 생성자를 반환.
Constructor<?> defaultConstructor = Artist.class.getDeclaredConstructor();
- 클래스에 선언한 생성자가 없다면 Java는 파라미터가 없는 default 생성자를 생성한다.
- 만약 선언한 생성자를 호출하거나 클래스의
getConstructor()
를 호출하면 default 생성자를 포함한 단일 요소 배열로 반환한다.
Constructor 클래스 메서드
public static class Person {
private final Address address;
private final String name;
private final int age;
public Person() {
this.name = "익명";
this.age = 0;
this.address = null;
}
// 여러개의 생성자가 오버로드 됨..
public Person(String name) {
this.name = name;
this.age = 0;
this.address = null;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
this.address = null;
}
public Person(Address address, String name, int age) {
this.name = name;
this.age = 0;
this.address = address;
}
}
public static class Address {
private String street;
private int number;
public Address(String street, int number) {
this.street = street;
this.number = number;
}
}
- 이 클래스를 가지고 생성자를 확인해보면 아래와 같다.
public static void main(String[] args) {
printConstructorData(Person.class);
}
public static void printConstructorData(Class<?> clazz) {
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
System.out.println(String.format(
"%s 클래스는 %d 개의 선언된 생성자가 있다.",
clazz.getSimpleName(),
constructors.length)
);
for (Constructor<?> constructor : constructors) {
// 생성자가 받는 인자의 파라미터 타입을 가져온다
Class<?>[] parameterTypes = constructor.getParameterTypes();
List<String> parameterTypeNames = Arrays.stream(parameterTypes)
.map(t -> t.getSimpleName())
.collect(Collectors.toList());
System.out.println(parameterTypeNames);
}
}
실행결과
> Task :ConstructorClass.main()
Person 클래스는 4 개의 선언된 생성자가 있다.
[Address, String, int]
[String, int]
[String]
[]
Address 클래스는 1 개의 선언된 생성자가 있다.
[String, int]
Reflection을 이용해 동적으로 객체를 생성하는 방법
Reflection이 없다면..
public Object createObject(Type type, Object arg) {
switch(type) {
case Type.Employee : return new Employeee(arg);
case Type.Contractor : return new Contractor(arg);
case Type.Investor .....
// 즉, 새로운 타입에 따라 코드가 계속 변해야함.
}
}
- 또한 reflection 없이는 어느 타입에도 제네릭 메서드를 사용할 수 없다.
- 호출하고자 하는 생성자가 다양한 클래스에 제네릭 메서드를 사용하면 작업을 더 어렵게 만든다.
newInstance()
Constructor.newInstance(Object ... args)
- 생성자에 선언된 순서대로 생성자 파라미터의 타입과 순서에 따라 가변 생성자 인자를 받는다.
- 실행에 성공하면
- 적절한 생성자를 호출한다.
- 지정된 클래스의 인스턴스를 반환한다.
- 실행에 실패하면
- 실패 이유를 설명하는 적절한 예외를 던진다.
만들어보기
- 어떤 클래스의 객체든 생성하는 단일 팩토리 메서드를 실행한다
- 메서드에 전달한 파라미터에 따라 주어진 클래스에 알맞은 생성자를 찾아서 객체를 생성하는 동안 생성자를 호출하는 기능
public static Object createInstanceWithArguments(Class<?> clazz, Object ... args) throws InvocationTargetException, InstantiationException, IllegalAccessException {
for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
if(constructor.getParameterTypes().length == args.length){
return constructor.newInstance(args);
}
}
System.out.println("생성자를 찾지 못했습니다.");
return null;
}
- 객체 생성 뿐만 아니라 메서드에 전달한 인자를 기반으로 원하는 생성자를 호출할 수 있다.
public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException {
Person person = (Person) createInstanceWithArguments(Person.class);
System.out.println(person);
Person secondPerson = (Person) createInstanceWithArguments(Person.class, "아이유", 32);
System.out.println(secondPerson);
Address address = (Address) createInstanceWithArguments(Address.class, "서울시", 123);
Person thridPerson = (Person) createInstanceWithArguments(Person.class, address,"이지금", 32);
System.out.println(thridPerson);
}
실행 결과
> Task :ConstructorClass.main()
Person{address=null, name='익명', age=0}
Person{address=null, name='아이유', age=32}
Person{address=Address{street='서울시', number=123}, name='이지금', age=0}
wildcard가 아닌 제네릭으로 개선하기
- 제네릭 메서드를 사용해 개선하면 캐스팅이 필요없어진다.
public static <T> T createInstanceWithGenericAndArguments(Class<?> clazz, Object... args) throws InvocationTargetException, InstantiationException, IllegalAccessException {
for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
if (constructor.getParameterTypes().length == args.length) {
return (T) constructor.newInstance(args);
}
}
System.out.println("생성자를 찾지 못했습니다.");
return null;
}
public static void main(String[] args) {
// 캐스팅 없이 사용가능해짐
Address fourthAddress = createInstanceWithGenericAndArguments(Address.class, "서울시", 123);
Person fourthPerson = createInstanceWithGenericAndArguments(Person.class, address,"이지금", 32);
System.out.println(fourthPerson);
}