본문 바로가기
Java/└ Reflect

Reflect) 클래스 객체 - 동적으로 객체 생성, 메소드/필드 제어

by 박채니 2022. 8. 3.

안녕하세요, 코린이의 코딩 학습기 채니 입니다.

 

개인 포스팅용으로 내용에 오류 및 잘못된 정보가 있을 수 있습니다.


Reflect (투영/반사하다)

- 클래스 객체를 통해서 클래스 정보를 열람

- 객체 생성 / 메소드 호출 / 필드 값 처리 제어 가능

 

클래스 객체

- 클래스 당 하나 씩 만들어지는 객체로, 클래스의 모든 정보를 가지고 있음

- new 연산자 호출 시에도 이 클래스 객체를 베이스로 객체 생성

 

객체는 클래스에 의해서 생성됩니다.

이 때, .class에서 바로 객체가 생성되는 것이 아닌 내부적으로 클래스 정보를 가진 instance가 생성됩니다.

따라서 객체 생성 시, .class를 바탕으로 객체가 만들어지는 것이 아닌 .class를 베이스로 만들어진 class객체를 바탕으로 객체가 생성됩니다. (내부적으로 참조하는 객체)

 

매번 new 연산자를 이용하여 객체를 생성하였지만, class객체를 이용하여 객체를 생성해내보려고 합니다.

 


Sample

public class Sample {
	private int num;
	private String str;
	
	public Sample() {
		super();
		// TODO Auto-generated constructor stub
	}
	
	public Sample(int num, String str) {
		super();
		this.num = num;
		this.str = str;
	}
	
	public int getNum() {
		return num;
	}
	
	public void setNum(int num) {
		this.num = num;
	}
	
	public String getStr() {
		return str;
	}
	
	public void setStr(String str) {
		this.str = str;
	}
	
	@Override
	public String toString() {
		return "Sample [num=" + num + ", str=" + str + "]";
	}
}

객체 생성

 

클래스 객체 생성

클래스.class

객체.getClass()

Class.forName()

private void test1() throws ClassNotFoundException {
    Sample s1 = new Sample();

    Class<Sample> clz1 = Sample.class;
    Class<Sample> clz2 = (Class<Sample>) s1.getClass();
    Class<Sample> clz3 = (Class<Sample>) Class.forName("com.ce.app.reflection.api.Sample");

    System.out.println(clz1 == clz2);
    System.out.println(clz2 == clz3);
}

@콘솔출력값
true
true

클래스 당 하나의 클래스 객체가 생성되므로 true가 출력됩니다.

 

기본생성자 생성

getDeclaredConstructor() - 생성자 생성

newInstance - 파라미터 값 대입

// 기본 생성자
Constructor<Sample> const1 = clz1.getDeclaredConstructor(null);
Sample s2 = const1.newInstance(null);
System.out.println(s2);

@콘솔출력값
Sample [num=0, str=null]

getDeclaredConstructor()를 이용하여 생성자를 생성해주었고, 이 때 매개인자로 null을 넘겨주어 파라미터가 없는 기본 생성자를 생성해주었습니다.

기본 생성자이므로, 생성자 매개인자가 존재하지 않기 때문에 newInstance()에서도 null을 넘겨주었습니다.

 

파라미터 생성자 생성

// 파라미터 생성자
Class[] paremterTypes = {int.class, String.class};
Constructor<Sample> const2 = clz2.getDeclaredConstructor(paremterTypes); // int, String을 받는 생성자
Object[] initArgs = {100, "helloworld"};
Sample s3 = const2.newInstance(initArgs);
System.out.println(s3);

@콘솔출력값
Sample [num=100, str=helloworld]

이번에는 파라미터가 있는 생성자를 만든 후, newInstance로 해당하는 값들을 대입해주었습니다.

배열로 넘겨준 것을 확인할 수 있으며, getDeclaredConstructor()의 경우 Class를 매개인자로 받기 때문에 int형의 class, String형의 class 객체를 넘겨주었습니다.

 


메소드 제어

private void test2() {
    Class<Sample> clz = Sample.class;
    Method[] methods = clz.getDeclaredMethods();
    for(Method method : methods) {
        System.out.println(method);
    }
}

@콘솔출력값
public java.lang.String com.ce.app.reflection.api.Sample.toString()
public void com.ce.app.reflection.api.Sample.setStr(java.lang.String)
public int com.ce.app.reflection.api.Sample.getNum()
public void com.ce.app.reflection.api.Sample.setNum(int)
public java.lang.String com.ce.app.reflection.api.Sample.getStr()

Sample 클래스가 가지고 있는 메소드들이 출력되는 것을 확인할 수 있으며, 메소드명은 패키지명 포함 full name이 출력됩니다.

 

하나의 메소드 가져오기

getDeclaredMethod() - 메소드 가져오기

invoke() - 메소드 동적 실행

Sample sample = clz.getDeclaredConstructor(null).newInstance(null); // 기본생성자
Method setNum = clz.getDeclaredMethod("setNum", int.class);			// Sample의 setNum, 매개인자
Object returnValue = setNum.invoke(sample, 123); // sample 객체의 setNum에 123을 대입해라!

System.out.println(returnValue);
System.out.println(sample);

@콘솔출력값
null
Sample [num=123, str=null]

매개인자가 int고 메소드명이 'setNum'인 메소드를 리턴해주었습니다. (오버로딩된 메소드를 구별하기 위하여 매개인자의 자료형을 정의해줌)

invoke()를 이용하여 sample 객체의 setNum에 123을 대입해준 후 출력해보았습니다.

또한 invoke()는 리턴 타입을 반환해주는데, setNum은 리턴타입이 void라서 null이 출력!

 

getNum() 가져와보기

Sample sample = clz.getDeclaredConstructor(null).newInstance(null); // 기본생성자

Method setNum = clz.getDeclaredMethod("setNum", int.class);			// Sample의 setNum, 매개인자
Object returnValue = setNum.invoke(sample, 123); // sample 객체의 setNum에 123을 대입해라!

Method getNum = clz.getDeclaredMethod("getNum");
returnValue = getNum.invoke(sample);

System.out.println(returnValue);
System.out.println(sample);

@콘솔출력값
123
Sample [num=123, str=null]

 


필드 제어

getDeclaredField()

Class<Sample> clz = (Class<Sample>) Class.forName("com.ce.app.reflection.api.Sample");
Sample sample = clz.getDeclaredConstructor(int.class, String.class).newInstance(123, "wow~");

Field num = clz.getDeclaredField("num");

System.out.println(num);
System.out.println(sample);

@콘솔출력값
private int com.ce.app.reflection.api.Sample.num
Sample [num=123, str=wow~]

int, String을 받는 생성자를 만든 후 123, 'wow~' 값을 각각 대입해주었습니다.

getDeclaredField()를 이용하여 'num' 필드를 리턴 받았으며 'num'필드의 정보가 출력되는 것을 확인할 수 있습니다.

(private 필드이지만 접근은 가능)

 

필드 값 가져오기

get()

Class<Sample> clz = (Class<Sample>) Class.forName("com.ce.app.reflection.api.Sample");
Sample sample = clz.getDeclaredConstructor(int.class, String.class).newInstance(123, "wow~");

Field num = clz.getDeclaredField("num");
Object value = num.get(sample);
System.out.println(value);

System.out.println(num);
System.out.println(sample);

@콘솔출력값
Exception in thread "main" java.lang.IllegalAccessException: class com.ce.app.reflection.api.ReflectionMain cannot access a member of class com.ce.app.reflection.api.Sample with modifiers "private"
	at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:361)
	at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:591)
	at java.base/java.lang.reflect.Field.checkAccess(Field.java:1075)
	at java.base/java.lang.reflect.Field.get(Field.java:416)
	at com.ce.app.reflection.api.ReflectionMain.test3(ReflectionMain.java:22)
	at com.ce.app.reflection.api.ReflectionMain.main(ReflectionMain.java:14)

get() 메소드를 이용해 sample 객체의 'num'필드의 값을 가져올 수 있지만, 'num'필드는 private 필드이기 때문에 직접 접근이 불가하여 IllegalAccessException이 발생하였습니다.

 

private 필드에 직접 접근하기

setAccessible(true)

Class<Sample> clz = (Class<Sample>) Class.forName("com.ce.app.reflection.api.Sample");
Sample sample = clz.getDeclaredConstructor(int.class, String.class).newInstance(123, "wow~");

Field num = clz.getDeclaredField("num");

num.setAccessible(true);

Object value = num.get(sample);
System.out.println(value);

System.out.println(num);
System.out.println(sample);

@콘솔출력값
123
private int com.ce.app.reflection.api.Sample.num
Sample [num=123, str=wow~]

setAccessible를 true로 설정하여 private인 'num'필드에 직접 접근 가능하도록 하였습니다.

그 결과 'num'필드의 값인 123이 출력되는 것을 확인할 수 있습니다.