목차

Reflection; Public constructor

🗓️

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);
}