클래스, 인터페이스, 필드, 메소드의 런타임 애트리뷰트를 확인하거나 수정할 수 있게 하는 기능
단순히 확인하는 것을 넘어서, 객체를 생성하고, 메소드를 호출하고, 필드를 수정할 수 있다
외부 의존성이 필요하지 않다. 자바는 기본적으로 java.lang.reflect 패키지의 클래스들로 리플렉션 관련 기능들을 제공한다
기본 내장 패키지로 리플렉션 관련된 다양한 클래스를 제공한다. 예를 들어 Field 클래스가 있다.
Field : 클래스나 인터페이스의 필드 한개에 대한 정보와 동적 접근을 제공하는 클래스. 정적, 인스턴스 변수 모두 리플렉트 될 수 있다
public class Person {
private String name;
private int age;
}
public class Reflection {
@Test
public void testGetDeclaredFields() {
Object person = new Person();
// 객체의 필드 정보를 가저온다
Field[] fields = person.getClass().getDeclaredFields();
List<String> actualFieldNames = getFieldNames(fields);
assertTrue(Arrays.asList("name", "age")
.containsAll(actualFieldNames));
}
private List<String> getFieldNames(Field[] fields) {
List<String> fieldNames = new ArrayList<>();
// 필드 정보에서 필드명을 가져온다
for (Field field : fields) {
fieldNames.add(field.getName());
}
return fieldNames;
}
}
// output
[name, age]
Field와 같이 AccessibleOjbect를 상속하는 Contructor, Method 클래스가 존재한다. 리플렉션 클래스들에 대한 자세한 설명은 아래에서 한다 Class는 리플렉션 클래스들을 반환하는 메소드들을 제공한다. 리플렉션 API를 활용하기위해 필수적이다
아래와 같은 객체들이 있을때 다양한 메소드를 사용할 수 있다
public interface Eating {
String eats();
}
public abstract class Animal implements Eating {
public static String CATEGORY = "domestic";
private String name;
protected abstract String getSound();
...
}
public class Goat extends Animal {
@Override
protected String getSound() {
return "bleat";
}
@Override
public String eats() {
return "grass";
}
...
}
@Test
public void givenObject_whenGetsClassName_thenCorrect() {
Object goat = new Goat("goat");
Class<?> clazz = goat.getClass();
assertEquals("Goat", clazz.getSimpleName());
assertEquals("com.baeldung.reflection.Goat", clazz.getName());
assertEquals("com.baeldung.reflection.Goat", clazz.getCanonicalName());
}
@Test
public void givenClass_whenRecognisesModifiers_thenCorrect() {
// 클래스가 거대할 경우 생성하지 않고 아래처럼 할 수 있다
Class<?> goatClass = Class.forName("com.baeldung.reflection.Goat");
Class<?> animalClass = Class.forName("com.baeldung.reflection.Animal");
int goatMods = goatClass.getModifiers();
int animalMods = animalClass.getModifiers();
assertTrue(Modifier.isPublic(goatMods));
assertTrue(Modifier.isAbstract(animalMods));
assertTrue(Modifier.isPublic(animalMods));
}
getModifiers()는 정수를 반환한다. 정수는 리눅스의 chmod처럼 접근 지정자의 플래그 비트를 나타낸다
java.lang.reflect.Modifier 클래스가 .getModifiers()의 결과값을 분석해주는 Modifier.isPulblic() 정적 메소드를 제공한다
@Test
public void givenClass_whenGetsPackageInfo_thenCorrect() {
Goat goat = new Goat("goat");
Class<?> goatClass = goat.getClass();
Package pkg = goatClass.getPackage();
assertEquals("com.baeldung.reflection", pkg.getName());
}
@Test
public void givenClass_whenGetsSuperClass_thenCorrect() {
Goat goat = new Goat("goat");
String str = "any string";
Class<?> goatClass = goat.getClass();
Class<?> goatSuperClass = goatClass.getSuperclass();
assertEquals("Animal", goatSuperClass.getSimpleName());
assertEquals("Object", str.getClass().getSuperclass().getSimpleName());
}
@Test
public void givenClass_whenGetsImplementedInterfaces_thenCorrect(){
Class<?> goatClass = Class.forName("com.baeldung.reflection.Goat");
Class<?> animalClass = Class.forName("com.baeldung.reflection.Animal");
Class<?>[] goatInterfaces = goatClass.getInterfaces();
Class<?>[] animalInterfaces = animalClass.getInterfaces();
assertEquals(1, animalInterfaces.length);
assertEquals("Eating", animalInterfaces[0].getSimpleName());
}
public class Bird extends Animal {
private boolean walks;
public Bird() {
super("bird");
}
public Bird(String name, boolean walks) {
super(name);
setWalks(walks);
}
public Bird(String name) {
super(name);
}
public boolean walks() {
return walks;
}
// standard setters and overridden methods
}
리플렉션을 통해 생성자를 확인하는 것은 물론, 런타임에 객체를 생성할 수도 있다
복수개 생성자가 존재할시, 시그니쳐가 다른 점을 이용해 특정 생성자를 선택할 수도 있다
객체를 생성하고 클래스를 가져오는 것의 비용이 너무 클 경우, 아래처럼 forName을 통해 Class객체를 생성할 수도 있다
@Test
public void givenClass_whenGetsAllConstructors_thenCorrect() {
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Constructor<?>[] constructors = birdClass.getConstructors();
assertEquals(3, constructors.length);
}
getConstructor 메소드에 인자를 지정해 특정 생성자를 가져올 수 있다. 일치하는 생성자가 없을시 NoSuchMethodException이 발생한다 @Test
public void givenClass_whenGetsEachConstructorByParamTypes_thenCorrect(){
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Constructor<?> cons1 = birdClass.getConstructor();
Constructor<?> cons2 = birdClass.getConstructor(String.class);
Constructor<?> cons3 = birdClass.getConstructor(String.class, boolean.class);
}
Constructor 객체를 통해 인스턴스화 할 수 있다 @Test
public void givenClass_whenInstantiatesObjectsAtRuntime_thenCorrect() {
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Constructor<?> cons1 = birdClass.getConstructor();
Constructor<?> cons2 = birdClass.getConstructor(String.class);
Constructor<?> cons3 = birdClass.getConstructor(String.class,
boolean.class);
// 생성자 객체를 통해 생성할 수 있다
Bird bird1 = (Bird) cons1.newInstance();
Bird bird2 = (Bird) cons2.newInstance("Weaver bird");
Bird bird3 = (Bird) cons3.newInstance("dove", true);
assertEquals("bird", bird1.getName());
assertEquals("Weaver bird", bird2.getName());
assertEquals("dove", bird3.getName());
assertFalse(bird1.walks());
assertTrue(bird3.walks());
}
setAccessible을 설정하면 private 생성자에 접근할 수도 있다public class Person {
private String name;
private int age;
private Person() {}
}
public class Reflection {
@Test
public void testGetDeclaredFields() {
Class<?> personClass = Class.forName("Person");
Constructor<?> constructor = personClass.getDeclaredConstructor();
constructor.setAccessible(true);
Person person = (Person) constructor.newInstance();
}
}
Class에서는 모든 필드를 가져오는걸 보여줬지만, 이름으로 특정 필드를 가져올 수도 있다 @Test
public void givenClass_whenGetsPublicFieldByName_thenCorrect() {
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Field field = birdClass.getField("CATEGORY");
assertEquals("CATEGORY", field.getName());
}
getDeclaredFields를 사용하면 private 필드들도 가져올 수 있다 @Test
public void givenClass_whenGetsDeclaredFields_thenCorrect(){
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Field[] fields = birdClass.getDeclaredFields();
assertEquals(1, fields.length);
assertEquals("walks", fields[0].getName());
}
@Test
public void givenClassField_whenGetsType_thenCorrect() {
Field field = Class.forName("com.baeldung.reflection.Bird")
.getDeclaredField("walks");
Class<?> fieldClass = field.getType();
assertEquals("boolean", fieldClass.getSimpleName());
}
@Test
public void givenClassField_whenSetsAndGetsValue_thenCorrect() {
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Bird bird = (Bird) birdClass.getConstructor().newInstance();
Field field = birdClass.getDeclaredField("walks");
field.setAccessible(true);
assertFalse(field.getBoolean(bird));
assertFalse(bird.walks());
field.set(bird, true);
assertTrue(field.getBoolean(bird));
assertTrue(bird.walks());
}
@Test
public void givenClassField_whenGetsAndSetsWithNull_thenCorrect(){
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Field field = birdClass.getField("CATEGORY");
field.setAccessible(true);
assertEquals("domestic", field.get(null));
}
자바 리플렉션을 활용하면 메소드를 런타임에 호출할 수도 있다. 생성자를 특정했던 것처럼, 오버로딩된 메소드를 특정할 수 있다
getMethods는 클래스와 부모 클래스의 모든 public 메소드를 반환한다
getDeclaredMethods는 클래스의 public 메소드만 반환한다
@Test
public void givenClass_whenGetsAllPublicMethods_thenCorrect(){
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Method[] methods = birdClass.getMethods();
List<String> methodNames = getMethodNames(methods);
List<String> expectedMethodNames = Arrays.asList("equals", "notifyAll", "hashCode", "walks", "eats", "toString");
assertTrue(methodNames.containsAll(expectedMethodNames));
}
@Test
public void givenClass_whenGetsOnlyDeclaredMethods_thenCorrect(){
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
List<String> actualMethodNames = getMethodNames(birdClass.getDeclaredMethods());
List<String> expectedMethodNames = Arrays.asList("setWalks", "walks", "getSound", "eats");
assertEquals(expectedMethodNames.size(), actualMethodNames.size());
assertTrue(expectedMethodNames.containsAll(actualMethodNames));
assertTrue(actualMethodNames.containsAll(expectedMethodNames));
}
@Test
public void givenMethodName_whenGetsMethod_thenCorrect() throws Exception {
Bird bird = new Bird();
Method walksMethod = bird.getClass().getDeclaredMethod("walks");
Method setWalksMethod = bird.getClass().getDeclaredMethod("setWalks", boolean.class);
assertTrue(walksMethod.canAccess(bird));
assertTrue(setWalksMethod.canAccess(bird));
}
@Test
public void givenMethod_whenInvokes_thenCorrect() {
Class<?> birdClass = Class.forName("com.baeldung.reflection.Bird");
Bird bird = (Bird) birdClass.getConstructor().newInstance();
Method setWalksMethod = birdClass.getDeclaredMethod("setWalks", boolean.class);
Method walksMethod = birdClass.getDeclaredMethod("walks");
// 메소드를 호출한다
boolean walks = (boolean) walksMethod.invoke(bird);
assertFalse(walks);
assertFalse(bird.walks());
setWalksMethod.invoke(bird, true);
boolean walks2 = (boolean) walksMethod.invoke(bird);
assertTrue(walks2);
assertTrue(bird.walks());
}
Guide to Java Reflection. (2021). https://www.baeldung.com/java-reflection