오늘의 학습 포인트: 리플렉션(Reflection)이란 무엇인가? Class 클래스 객체 획득 방법 Field(get/set) Method(invoke) Constructor(newInstance) 내성(Introspection) BeanInfo readMethod writeMethod
1. 리플렉션(Reflection)
Java에서 리플렉션은 프로그램 실행 시간에 동적으로 클래스의 객체, 메서드 및 속성을 검사, 획득하고 조작하는 능력을 의미합니다. 리플렉션을 통해 실행 시간에 임의의 클래스 정보를 얻을 수 있으며, 클래스 이름, 메서드, 필드 등을 포함하며 실행 시간에 객체를 생성하고 메서드를 호출하며 속성에 접근할 수 있습니다.
리플렉션: 프로그램 실행 중에 동적으로 클래스에 정의된 속성, 메서드 및 생성자 메커니즘을 얻는 기술 리플렉션의 핵심은 Class 클래스입니다. 프로그램에서 사용되는 모든 클래스는 고유한 Class 객체를 가집니다 리플렉션 API: Field, Method, Constructor API 패키지명: java.lang.reflect 리플렉션은 클래스의 캡슐화를 깨뜨릴 수 있습니다. 상황에 따라 장단점이 있으니 주의해야 합니다
객체가 있으려면 먼저 클래스가 있어야 하며, static은 클래스 속성과 메서드를 수정합니다 Java에는 클래스 내용이 저장되어 있으며, 이 내용도 객체여야 합니다 Java의 각 클래스는 메모리에 저장되며, 각 메모리는 객체입니다 이러한 객체들은 클래스에 선언된 속성, 메서드 및 생성자를 기록합니다 Java는 이러한 클래스를 Class라는 유형으로 추상화합니다
1.1 Class 클래스
Class 객체는 new로 생성할 수 없습니다
이 클래스의 객체에는 클래스에 정의된 내용(속성/메서드/생성자)이 저장됩니다
클래스 객체 획득 방법 (3가지)
클래스 이름으로 클래스 객체 획득
Class clazz= SampleClass.class;
객체로부터 클래스 객체 획득
clazz=new SampleClass().getClass();
Class 클래스의 forName 메서드를 통한 획득
clazz=Class.forName("com.sample.reflection.SampleColor");
1.2 클래스 속성 획득
Java에서 클래스 속성을 기록하는 클래스는 Field라 합니다 fName 변수가 가리키는 객체는 Sample 클래스의 name 속성입니다
Field fName=c.getField("name");
Sample 클래스 객체의 name 속성 값 획득
Object objectName=fName.get(sample);
속성 값 주입
fName.set(sample,"홍길동");
getField getFields는 클래스에서 public으로 선언된 속성만 획득할 수 있습니다
비public 속성은 getDeclaredField()를 사용하여 획득할 수 있습니다. getDeclaredField는 정의된 속성을 획득합니다
Field fCode=c.getDeclaredField("code");
fCode.set(sample,"10001");
Object objCode=fCode.get(sample);//리플렉션을 통해 sample 객체의 code 속성 값 획득
Field fGender=c.getDeclaredField("gender");
fGender.set(sample,"여성");
부모 클래스 속성 획득: 클래스 객체의 getSuperclass 메서드를 호출하여 자식 클래스 객체로 부모 클래스 객체를 생성하고, 속성 획득은 자식 클래스와 유사합니다
Class<?> super = c.getSuperclass();
리플렉션으로 private 속성에 접근하려면 먼저 접근 권한을 획득해야 합니다. 현재 권한 외의 속성에 접근하려면 먼저 권한을 획득해야 합니다
권한은 setAccessible 메서드를 통해 설정하며, true로 설정하면 됩니다
Field fAddress=c.getDeclaredField("address");
fAddress.setAccessible(true);
fAddress.set(sample,"서울");
전체 코드 예제:
public class ReflectionDemo {
public static void main(String[] args) throws ClassNotFoundException,
NoSuchFieldException, IllegalAccessException {
//클래스 객체에는 클래스에 정의된 내용(속성, 메서드, 생성자)이 저장됩니다
//클래스 객체 획득
Class c=Sample.class;
c=new Sample().getClass();
c=Class.forName("com.sample.reflection.Sample");
//클래스 속성 획득
//Java에서 클래스 속성을 기록하는 클래스는 Field입니다
//fName 변수가 가리키는 객체는 Sample 클래스의 name 속성입니다
Field fName=c.getField("name");
Sample sample=new Sample();
sample.name="김철수";
System.out.println(sample.name);
//Sample 클래스 객체의 name 속성 값 획득
Object objectName=fName.get(sample);
System.out.println(objectName+"---------");
//속성 값 주입
fName.set(sample,"이영희");
System.out.println(sample.name);
System.out.println("------------------");
//getField getFields는 클래스에서 public으로 선언된 속성만 획득할 수 있습니다
Field fCode=c.getDeclaredField("code");
fCode.set(sample,"10001");
Object objCode=fCode.get(sample);//리플렉션을 통해 sample 객체의 code 속성 값 획득
System.out.println(objCode);
Field fGender=c.getDeclaredField("gender");
Field fAddress=c.getDeclaredField("address");
fGender.set(sample,"여성");
//리플렉션으로 private 속성에 접근하려면 먼저 접근 권한을 획득해야 합니다
fAddress.setAccessible(true);
fAddress.set(sample,"부산");
System.out.println(fAddress.get(sample));
}
}
class Sample {
public String name;
protected String code;
String gender;
private String address;
static int maxAge;
public static final transient String test=null;
public Sample(){}
public Sample(String name){
this.name=name;
}
}
1.3 리플렉션을 사용하여 객체 속성 설정
리플렉션을 통해 인스턴스 획득, 해당 클래스의 객체 생성
T t=tClass.newInstance();//클래스 객체의 무인자 생성자를 통해 객체 생성
public class ReflectionPropertySetter {
public static <T> T createInstance(Class<T> targetClass, Map<String, Object> values) throws
InstantiationException, IllegalAccessException {
//리플렉션을 통해 인스턴스 획득, 해당 클래스의 객체 생성
T instance=targetClass.newInstance();//클래스 객체의 무인자 생성자를 통해 객체 생성
//리플렉션을 통해 클래스에 정의된 속성 획득
Field[] fields=targetClass.getDeclaredFields();
//System.out.println(Arrays.toString(fields));
for (Field field:fields){
//속성 이름 획득
String fieldName=field.getName();
//Map에서 해당 속성의 키-값 쌍 획득, 속성에 해당하는 값
Object value=values.get(fieldName);
//속성 접근 권한 설정
field.setAccessible(true);
//값 주입
field.set(instance,value);
}
return instance;
}
public static void main(String[] args) throws
InstantiationException, IllegalAccessException {
Map<String, Object> propertyMap=new HashMap<>();
propertyMap.put("studentId","S1001");
propertyMap.put("studentName","박지성");
propertyMap.put("studentGender","남성");
Student student=createInstance(Student.class,propertyMap);
System.out.println(student);
}
}
class Student{
private String studentId;
private String studentName;
private String studentGender;
@Override
public String toString() {
return "Student{" +
"studentId='" + studentId + '\'' +
", studentName='" + studentName + '\'' +
", studentGender='" + studentGender + '\'' +
'}';
}
}
1.4 클래스 메서드 획득
public 메서드 획득: getMethod() getDeclaredMethod로 비공개 메서드 획득
Method methodA=c.getMethod("processA");
Method methodB=c.getMethod("processB",int.class,int.class);
메서드 호출: 객체.메서드이름() 객체 지향 방식 method.invoke(객체) 리플렉션 invoke의 반환값은 모두 Object 타입입니다
methodA.invoke(sample);
methodB.invoke(sample,23,45);
전체 코드 예제:
public class MethodReflectionDemo {
public static void main(String[] args) throws NoSuchMethodException,
InstantiationException, IllegalAccessException, InvocationTargetException {
//클래스 객체 획득
Class<Sample> c= Sample.class;
//리플렉션으로 메서드 획득 Method
Sample sample=(Sample) c.newInstance();
//public 메서드 획득 getDeclaredMethod로 비공개 메서드 획득
Method methodA=c.getMethod("processA");
//메서드 호출 객체.메서드이름()
//method.invoke(객체) 리플렉션
methodA.invoke(sample);
Method methodB=c.getMethod("processB",int.class,int.class);
//sample.processB(12,34)
methodB.invoke(sample,23,45);
}
}
public class Sample {
public String name;
protected String code;
String gender;
private String address;
static int maxAge;
public static final transient String test=null;
public Sample(){}
public Sample(String name){
this.name=name;
}
public void processA(){
System.out.println("processA 실행");
}
public void processB(int a,int b){
System.out.println("processB 실행");
System.out.println("두 파라미터 값: "+a+","+b);
}
}
1.5 생성자 획득
(1) 무인자 생성자
클래스 객체.newInstance() 먼저 Constructor<Sample> con=c.getConstructor(); 그 다음 con.newInstance();
위의 두 방법 모두 가능합니다
(2) 인자 있는 생성자
Constructor<Sample> con=c.getConstructor(String.class);
Sample instance=con.newInstance("김민준");
이 방법만 가능하며, 전달되는 인자는 가변 인자입니다
public class ConstructorReflectionDemo {
public static void main(String[] args) throws NoSuchMethodException,
InvocationTargetException, InstantiationException, IllegalAccessException,
NoSuchFieldException, IntrospectionException {
//리플렉션으로 생성자 획득
Class<Sample> c=Sample.class;
c.newInstance();//클래스 객체의 무인자 생성자를 통해 객체 생성
//무인자 생성자 획득
Constructor<Sample> con=c.getConstructor();
con.newInstance();
con=c.getConstructor(String.class);
Sample instance=con.newInstance("박지민");
//System.out.println(instance.name);
}
}
1.6 수정자(Modifier)
<strong>Modifier 클래스의 메서드를 사용하여 메서드, 속성, 생성자의 수정자 판별</strong>
Field f=c.getDeclaredField("test");
int modifier=f.getModifiers();
System.out.println(modifier);
boolean isStatic=Modifier.isStatic(modifier);
System.out.println(isStatic);
2. 내성(Introspection)
<strong>리플렉션을 통해 구현되며, 내성은 캡슐화를 깨지 않습니다</strong>
2.1 BeanInfo 획득
BeanInfo beanInfo=Introspector.getBeanInfo(c);
여기서 c는 클래스 객체입니다
2.2 속성의 쓰기 메서드와 읽기 메서드 획득
PropertyDescriptor[] propertyDescriptors=beanInfo.getPropertyDescriptors();
String propertyName=propertyDescriptors[0].getName();//속성 이름 획득
System.out.println("속성 이름: "+propertyName);
Method readMethod=propertyDescriptors[0].getReadMethod();//속성의 getter 메서드
Method writeMethod=propertyDescriptors[0].getWriteMethod();//속성의 setter 메서드
2.3 객체 생성 및 획득된 메서드 호출
Sample sample=c.newInstance();
writeMethod.invoke(sample,"김하늘");