https://sundaland.tistory.com/299
리플렉션은 java.lang.relfect.Member 인터페이스를 정의하며, 이 인터페이스는 java.lang.reflect.Field, java.lang.reflect.Method, java.lang.reflect.Constructor에 의해 구현된다.
자바 언어 사양 (Specification)에 따르면 클래스의 멤버는 필드, 메서드, 중첩 클래스, 인터페이스, 열거형 타입을 포함한 클래스 본문의 상속된 구성 요소이다. 생성자는 상속되지 않기 때문에 멤버가 아니다. 이는 java.lang.reflect.Member를 구현하는 클래스와 다르다.
[ ▶ Fields ]
필드는 값과 연관된 클래스, 인터페이스 또는 열거형이다. java.lang.reflect.Field 클래스의 메서드는 필드에 대한 정보 (이름, 타입, 제어자, 및 어노테이션)을 검색할 수 있습니다.
클래스 브라우저와 같은 애플리케이션을 작성할 때 특정 클래스에 속한 필드를 찾는 것이 유용할 수 있다. 클래스의 필드는
Class.getFields()를 호출하여 식별할 수 있다.
getFields() 메서드는 접근 가능한 public 필드당 하나의 객체를 포함하는 Field 객체 배열을 반환한다.
public 필드는 다음 중 하나의 멤버인 경우 액세스할 수 있다.
- this class
- a superclass of this class
- an interface implemented by this class
- an interface extended from an interface implemented by this class
필드는 java.io.Reader.lock과 같은 클래스 (인스턴스) 필드, java.lang.Integer.MAX_VALUE와 정적 필드, 또는 java.lang.Thread.State.WAITING과 같은 열거형 상수일 수 있다.
[ ▷ Obtaining Field Types ]
필드는 기본 타입 또는 참조 타입일 수 있다. 기본 타입은 boolean, byte, short, int 등 8가지이다. 참조 타입은 인터페이스, 배열, 열거형을 포함하여 java.lang.Object의 직접 또는 간접 하위 클래스인 모든 것이다.
▼ 정규화된 클래스 이름과 필드 이름이 주어진 경우 필드의 타입과 일반 타입을 출력한다.
import java.lang.reflect.Field;
import java.util.List;
public class FieldSpy<T> {
public boolean[][] b = {{ false, false }, { true, true } };
public String name = "Alice";
public List<Integer> list;
public T val;
public static void main(String... args) {
try {
Class<?> c = Class.forName(args[0]);
Field f = c.getField(args[1]);
System.out.format("Type: %s%n", f.getType());
System.out.format("GenericType: %s%n", f.getGenericType());
// production code should handle these exceptions more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
} catch (NoSuchFieldException x) {
x.printStackTrace();
}
}
}
이 클래스의 세 public 필드 (b, name, 파라미터화된 타입 list) 타입을 검색하기 위한 샘플 출력은 아래와 같다.
$ java FieldSpy FieldSpy b
Type: class [[Z
GenericType: class [[Z
$ java FieldSpy FieldSpy name
Type: class java.lang.String
GenericType: class java.lang.String
$ java FieldSpy FieldSpy list
Type: interface java.util.List
GenericType: java.util.List<java.lang.Integer>
$ java FieldSpy FieldSpy val
Type: class java.lang.Object
GenericType: T
필드 b의 타입은 bool의 2차원 배열이다.
필드 val의 타입은 java.lang.Object로 보고된다. 제너릭은 컴파일 중에 제너릭에 대한 모든 정보를 제거하는 타입 소거를 통해 구현되기 때문이다. 따라서 T는 이 경우 java.lang.Object인 타입 변수의 상한으로 대체된다.
Field.getGenericType()은 클래스 파일에 Signature Attribute가 있는 경우 참조한다. 속성을 사용할 수 없는 경우 제너릭 도입으로 변경되지 않은 Field.getType()으로 대체한다. Foo의 일부 값에 대한 이름이 getGenericFoo인 리플렉션의 다른 메서드는 비슷하게 구현된다.
[ ▷ Retrieving and Parsing Field Modifiers ]
필드 선언의 일부가 될 수 있는 제어자는 여러가지가 있다.
- Acces modifiers : public, protected, private
- Field-specific modifiers governing runtime behavior : transient and volatile
- Modifier restricting to one instance : static
- Modifier prohibiting value modification : final
- Annotations
Field.getModifiers() 메서드는 필드에 대해 선언된 제어자 집합을 나타내는 정수를 반환하는데 사용할 수 있다. 이 정수의 제어자를 나타내는 비트는 java.lang.reflect.Modifier에 정의되어 있다.
아래의 예제는 주어진 제어자가 있는 필드를 검색하는 방법을 보여준다. 또한 Field.isSynthetic() 및 Field.isEnumConstant()를 각각 호출하여 찾은 필드가 합성 (컴파일러 생성)인지 또는 열거형 상수인지 확인한다.
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import static java.lang.System.out;
enum Spy { BLACK , WHITE }
public class FieldModifierSpy {
volatile int share;
int instance;
class Inner {}
public static void main(String... args) {
try {
Class<?> c = Class.forName(args[0]);
int searchMods = 0x0;
for (int i = 1; i < args.length; i++) {
searchMods |= modifierFromString(args[i]);
}
Field[] flds = c.getDeclaredFields();
out.format("Fields in Class '%s' containing modifiers: %s%n",
c.getName(),
Modifier.toString(searchMods));
boolean found = false;
for (Field f : flds) {
int foundMods = f.getModifiers();
// Require all of the requested modifiers to be present
if ((foundMods & searchMods) == searchMods) {
out.format("%-8s [ synthetic=%-5b enum_constant=%-5b ]%n",
f.getName(), f.isSynthetic(),
f.isEnumConstant());
found = true;
}
}
if (!found) {
out.format("No matching fields%n");
}
// production code should handle this exception more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
private static int modifierFromString(String s) {
int m = 0x0;
if ("public".equals(s)) m |= Modifier.PUBLIC;
else if ("protected".equals(s)) m |= Modifier.PROTECTED;
else if ("private".equals(s)) m |= Modifier.PRIVATE;
else if ("static".equals(s)) m |= Modifier.STATIC;
else if ("final".equals(s)) m |= Modifier.FINAL;
else if ("transient".equals(s)) m |= Modifier.TRANSIENT;
else if ("volatile".equals(s)) m |= Modifier.VOLATILE;
return m;
}
}
$ java FieldModifierSpy FieldModifierSpy volatile
Fields in Class 'FieldModifierSpy' containing modifiers: volatile
share [ synthetic=false enum_constant=false ]
$ java FieldModifierSpy Spy public
Fields in Class 'Spy' containing modifiers: public
BLACK [ synthetic=false enum_constant=true ]
WHITE [ synthetic=false enum_constant=true ]
$ java FieldModifierSpy FieldModifierSpy\$Inner final
Fields in Class 'FieldModifierSpy$Inner' containing modifiers: final
this$0 [ synthetic=true enum_constant=false ]
$ java FieldModifierSpy Spy private static final
Fields in Class 'Spy' containing modifiers: private static final
$VALUES [ synthetic=true enum_constant=false ]
일부 필드는 원래 코드에 선언되지 않았는데도 보고된다. 이는 컴파일러가 런타임 중에 필요한 일부 합성 필드를 생성하기 때문이다. 필드가 합성인지 테스트하기 위해 이 예제에서는 Field.isSynthetic()를 호출한다. 합성 필드 세트는 컴파일러에 따라 다르다. 그러나 일반적으로 사용되는 필드에는 inner 클래스 (정적 멤버 클래스가 아닌 중첩 클래스)의 this$0이 outer 클래스를 참조하고 열거형에서 암시적으로 정의된 정적 메서드 values()를 구현하는데 사용되는 $VAULES가 포함된다. 합성 클래스 멤버의 이름은 지정되지 않으며 모든 컴파일러 구현 또는 릴리스에서 동일하지 않을 수 있다. 이러한 합셩 필드는 Class.getDeclaredFields()에서 반환하는 배열에 포함되지만 합성 멤버는 일반적으로 공개되지 않으므로 Class.getField()에서 식별되지 않는다.
Field는 java.lang.reflect.AnnotatedElement 인터페이스를 구현하므로 java.lang.annotation.RetentionPolicy.RUNTIME을 사용하여 모든 런타임 어노테이션을 검색할 수 있다.
[ ▷ Getting and Setting Field Values ]
클래스의 인스턴스가 주어지면 리플렉션을 사용하여 해당 클래스의 필드 값을 수정할 수 있다. 이는 일반적인 방식으로 값을 설정할 수 없는 특수한 상황에서만 수행된다. 이러한 액세스는 일반적으로 클래스의 설계 의도를 위반하므로 최대한 신중하게 사용해야 한다.
▼ long, 배열 및 열거형 필드 타입에 대한 값을 설정하는 방
import java.lang.reflect.Field;
import java.util.Arrays;
import static java.lang.System.out;
enum Tweedle { DEE, DUM }
public class Book {
public long chapters = 0;
public String[] characters = { "Alice", "White Rabbit" };
public Tweedle twin = Tweedle.DEE;
public static void main(String... args) {
Book book = new Book();
String fmt = "%6S: %-12s = %s%n";
try {
Class<?> c = book.getClass();
Field chap = c.getDeclaredField("chapters");
out.format(fmt, "before", "chapters", book.chapters);
chap.setLong(book, 12);
out.format(fmt, "after", "chapters", chap.getLong(book));
Field chars = c.getDeclaredField("characters");
out.format(fmt, "before", "characters",
Arrays.asList(book.characters));
String[] newChars = { "Queen", "King" };
chars.set(book, newChars);
out.format(fmt, "after", "characters",
Arrays.asList(book.characters));
Field t = c.getDeclaredField("twin");
out.format(fmt, "before", "twin", book.twin);
t.set(book, Tweedle.DUM);
out.format(fmt, "after", "twin", t.get(book));
// production code should handle these exceptions more gracefully
} catch (NoSuchFieldException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
}
}
}
$ java Book
BEFORE: chapters = 0
AFTER: chapters = 12
BEFORE: characters = [Alice, White Rabbit]
AFTER: characters = [Queen, King]
BEFORE: twin = DEE
AFTER: twin = DUM
리플렉션을 통해 필드의 값을 설정하는 것은 액세스 권한 검증과 같은 다양한 작업비 발생해야 하기 때문에 일정 수준의 성능 오버헤드가 발생한다. 런타임 관점에서 볼 때 효과는 동일하며, 이 작업은 마치 클래스 코드에서 직접 값을 변경할 것처럼 원자적이다.
리플렉션을 사용하면 일부 런타임 최적화가 손실될 수 있다. 아래의 코드는 자바 가상 머신에서 최적화될 가능성이 매우 높다.
int x = 1;
x = 2;
x = 3;
Field.set*()를 사용하는 동일한 코드는 그렇지 않을 수 있다.
'Reflection' 카테고리의 다른 글
type-safe proxy object (0) | 2024.08.20 |
---|---|
Structuring method calls (0) | 2024.08.20 |
Members - Constructors (0) | 2024.08.20 |
Members - Methods (0) | 2024.08.19 |
The Reflection API - 클래스 (0) | 2024.08.16 |