Spring Framework/Reflection / / 2024. 8. 19. 10:00

리플렉션: 멤버 탐구 - 필드(Fields)

[튜토리얼]

 

리플렉션은 java.lang.reflect.Member 인터페이스를 정의하며, 

이 인터페이스는 java.lang.reflect.Field, java.lang.reflect.Method, java.lang.reflect.Constructor에 의해 구현됩니다. 이 레슨에서는 이러한 객체들에 대해 논의할 것입니다. 각 멤버에 대해, 선언 및 타입 정보를 검색하는 관련 API, 멤버에 고유한 작업(예를 들어, 필드의 값을 설정하거나 메서드를 호출하는 것), 일반적으로 발생하는 오류를 설명할 것입니다. 각 개념은 일부 예상되는 리플렉션 용도와 유사한 코드 샘플 및 관련 출력과 함께 설명됩니다.


참고: 자바 언어 사양(Java Language Specification), Java SE 7 에디션에 따르면 클래스의 멤버는 필드, 메서드, 중첩 클래스, 인터페이스, 열거형 타입을 포함한 클래스 본문의 상속된 구성 요소입니다. 생성자는 상속되지 않기 때문에 멤버가 아닙니다. 이는 java.lang.reflect.Member를 구현하는 클래스와 다릅니다.


 

Fields

필드는 타입과 값을 가집니다. java.lang.reflect.Field 클래스는 주어진 객체의 필드에 대한 타입 정보를 접근하고 값을 설정하고 가져오는 메서드를 제공합니다.

Methods

메서드는 리턴 값, 파라미터를 가지며 예외를 던질 수 있습니다. java.lang.reflect.Method 클래스는 파라미터와 리턴 값의 타입 정보를 얻기 위한 메서드를 제공합니다. 또한 주어진 객체에서 메서드를 호출하는 데도 사용될 수 있습니다.

Constructors

리플렉션 API에서 생성자는 java.lang.reflect.Constructor에 정의되어 있으며, 메서드에 대한 API와 유사합니다. 그러나 두 가지 주요 예외가 있습니다. 첫째, 생성자는 리턴 값이 없습니다. 둘째, 생성자를 호출하면 주어진 클래스의 새 인스턴스가 생성됩니다.

 

Fields

필드는 값과 연관된 클래스, 인터페이스 또는 열거형입니다. java.lang.reflect.Field 클래스의 메서드는 필드에 대한 정보(예: 이름, 타입, 제어자 및 어노테이션)를 검색할 수 있습니다. ( Classes 레슨의 " Examining Class Modifiers and Types " 섹션에서는 어노테이션을 검색하는 방법을 설명합니다.) 또한 필드의 값을 동적으로 접근하고 수정할 수 있는 메서드도 있습니다. 이러한 작업은 다음 섹션에서 다룹니다:

클래스 브라우저와 같은 애플리케이션을 작성할 때 특정 클래스에 속한 필드를 찾는 것이 유용할 수 있습니다. 클래스의 필드는 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, long, char, float, double 등 8가지입니다. 참조 타입은 인터페이스, 배열, 열거형을 포함하여 java.lang.Object의 직접 또는 간접 하위 클래스인 모든 것입니다. 

FieldSpy 예제는 정규화된 클래스 이름과 필드 이름이 주어진 경우 필드의 타입과 일반 타입을 출력합니다.

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차원 배열입니다. 타입 이름의 신택스는 Class.getName()에 설명되어 있습니다. 

필드 val의 타입은 java.lang.Object로 보고됩니다. 제네릭은 컴파일 중에 제네릭 타입에 대한 모든 정보를 제거하는 타입 소거를 통해 구현되기 때문입니다. 따라서 T는 이 경우 java.lang.Object인 타입 변수의 상한으로 대체됩니다. 

Field.getGenericType()은 클래스 파일에 Signature Attribute가 있는 경우 참조합니다. 속성을 사용할 수 없는 경우 제네릭 도입으로 변경되지 않은 Field.getType()으로 대체합니다. Foo의 일부 값에 대한 이름이 getGenericFoo인 리플렉션의 다른 메서드는 비슷하게 구현됩니다.

 

Retrieving and Parsing Field Modifiers

필드 선언의 일부가 될 수 있는 제어자는 여러 가지가 있습니다.

  • Access modifiers: public, protected, and 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 에 정의되어 있습니다. 

FieldModifierSpy  예제는 주어진 제어자가 있는 필드를 검색하는 방법을 보여줍니다. 또한 Field.isSynthetic()  Field.isEnumCostant() 를 각각 호출하여 찾은 필드가 합성(컴파일러 생성)인지 또는 열거형 상수인지도 확인합니다.

 

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()를 구현하는 데 사용되는 $VALUES가 포함됩니다. 합성 클래스 멤버의 이름은 지정되지 않으며 모든 컴파일러 구현 또는 릴리스에서 동일하지 않을 수 있습니다. 이러한 합성 필드와 다른 합성 필드는 Class.getDeclaredFields()에서 반환하는 배열에 포함되지만 합성 멤버는 일반적으로 공개되지 않으므로 Class.getField()에서 식별되지 않습니다. 

Field는 java.lang.reflect.AnnotatedElement 인터페이스를 구현하므로 java.lang.annotation.RetentionPolicy.RUNTIME을 사용하여 모든 런타임 어노테이션을 검색할 수 있습니다. 어노테이션을 얻는 예는 클래스 제어자 및 타입 검토 섹션을 참조하세요.

 

Getting and Setting Field Values

클래스의 인스턴스가 주어지면 리플렉션을 사용하여 해당 클래스의 필드 값을 설정할 수 있습니다. 이는 일반적인 방식으로 값을 설정할 수 없는 특수한 상황에서만 수행됩니다. 이러한 액세스는 일반적으로 클래스의 설계 의도를 위반하므로 최대한 신중하게 사용해야 합니다. 

Book 클래스는 long, 배열 및 열거형 필드 타입에 대한 값을 설정하는 방법을 보여줍니다. 다른 프리미티브 타입을 가져오고 설정하는 방법은 Field에 설명되어 있습니다.

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

 


참고: 리플렉션을 통해 필드의 값을 설정하는 것은 액세스 권한 검증과 같은 다양한 작업이 발생해야 하기 때문에 일정 수준의 성능 오버헤드가 발생합니다. 런타임 관점에서 볼 때 효과는 동일하며, 이 작업은 마치 클래스 코드에서 직접 값을 변경한 것처럼 원자적입니다. 

리플렉션을 사용하면 일부 런타임 최적화가 손실될 수 있습니다. 예를 들어, 다음 코드는 Java 가상 머신에서 최적화될 가능성이 매우 높습니다.

int x = 1;
x = 2;
x = 3;

 

Field.set*()를 사용하는 동일한 코드는 그렇지 않을 수 있습니다.



  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유