https://sundaland.tistory.com/269
[ ▶ Uses of Reflection ]
리플랙션은 JVM (자바 가상 머신)에서 실행되는 애플리케이션의 런타임 동작을 검사하거나 수정하는 기능이 필요한 프로그램에서 일반적으로 사용된다. 이는 비교적 고급 기능이므로 언어의 기본 사항을 잘 이해하고 있는 개발자만 사용해야 한다. 이러한 주의사항을 염두에 두고 리플랙션은 강력한 기술이며 응용 프로그램이 그렇지 않으면 불가능한 작업을 수행할 수 있도록 할 수 있다.
리플렉션이란 자바와 같은 프로그래밍 언어에서 프로그램이 런타임 시점에 자기 자신을 검사하고 수정할 수 있는 기능을 말한다. 리플랙션을 통해 클래스, 메서드, 필드 등의 구조적인 요소에 대한 정보를 동적으로 탐색하고 조작할 수 있다. 즉 프로그램이 자신의 코드에 대한 메타데이터를 읽고, 이 메타데이터를 바탕으로 실행 시 동적으로 행동을 바꿀 수 있게 해준다.
자바에서 리플랙션은 주로 java.lang.relect 패키지에 포함된 클래스를 사용하여 구현된다. 이 패키지에서는 자바, 메서드, 필드, 생성자들의 클래스가 포함되어 있으며, 이를 통해 클래스의 구조를 조사하거나 메서드를 호출하는 등의 작업을 할 수 있다.
1. 클래스 및 객체 탐색
- 리플렉션을 통해 클래스 이름만 알고 있어도 해당 클래스의 모든 메서드, 필드, 생성자 등에 접근할 수 있다.
- 예시로 Class.forName("com.example.MyClass")를 통해 런타임 시 클래스 이름으로 MyClass의 정보를 가져올 수 있다.
2. 메서드 호출
- 리플랙션을 사용하면 특정 객체의 메서드를 런타임에 동적으로 호출할 수 있다. 예를 들어 Method.invoke()를 사용하여 메서드를 호출할 수 있다.
- 이를 통해 컴파일 시점에는 어떤 메서드를 호출할지 알 수 없더라도, 런타임 시에 메서드를 선택하고 실행할 수 있다.
3. 필드 및 메서드 접근 수정
- 리플렉션을 사용하면 private 필드나 메서드에 접근할 수 있다. 예를 들어 setAccessible(true)를 사용하여 접근 제한을 해제할 수 있다.
4. 클래스 생성자 호출
- 리플렉션을 통해 특정 클래스의 생성자를 호출하여 객체를 동적으로 생성할 수 있다.
리플렉션의 장점
- 유연성 : 리플렉션을 사용하면 컴파일 시점에 결정되지 않은 동작을 런타임 시점에 동적으로 구현할 수 있다. 예를 들어 플러그인 시스템을 구현할 때 반사가 유용하다,
- 동적 클래스 로딩 : 런타임 시에 특정 조건에 따라 클래스를 로드하고, 그 클래스의 메서드를 실행할 수 있다.
리플렉션의 단점
- 성능 저하 : 리플렉션을 사용하는 코드는 일반적인 메서드 호출보다 느리다. 런타임에 동적으로 클래스나 메서드를 탐색하고 호출하기 때문에 성능 오버헤드가 발생한다.
- 안정성 저하 : 컴파일 시점에서 타입 체크가 이루어지지 않기 때문에, 잘못된 메서드 호출이나 필드 접근 등에서 런타임 오류가 발생할 가능성이 높다.
- 보안 위험 : 리플렉션을 통해 접근 제한이 있는 필드나 메서드에 접근할 수 있기 때문에, 잘못 사용하면 보안 문제가 발생할 수 있다.
리플렉션은 매우 강력한 기능이지만, 필요할때만 신중하게 사용하는 것이 좋다. 프로그램의 복잡성을 증가시키고, 성능 및 안정선 문제를 야기할 수 있기 때문이다.
[ ▶ 확정성 기능 (Extensibility Features) ]
애플리케이션은 외부에서 정의된 사용자 크래스의 인스턴스를 완전한 이름을 사용하여 생성함으로써 확장성 객체를 활용할 수 있다.
[ ▷ Class Browsers and Visual Development Enviroments ]
클래스 브라우저는 클래스의 멤버를 나열할 수 있어야 한다. 비주얼 개발 환경은 리플렉션을 통해 얻을 수 있는 타입 정보를 활용하여 개발자가 올바른 코드를 작성할 수 있게 도울 수 있다.
[ ▷ Debuggers and Test Tools ]
디버거는 클래스의 private 멤버를 검사할 수 있어야 한다. 테스트 하네스는 리플렉션을 사영하여 클래스에 정의된 API를 체계적으로 호출함으로써 테스트 스위트에서 높은 수준의 코드 커버리지를 보장할 수 있다.
[ ▷ Drawbacks of Reflection, 리플렉션의 단점 ]
리플렉션은 강력하지만 무분별하게 사용해서는 안된다. 리플렉션을 사용하지 않고 작업을 수행할 수 있다면, 이를 피하는 것이 바람직하다. 리플렉션을 통해 코드를 접근할 때 다음과 같은 우려 사항을 염두에 두어야한다.
1. 성능 오버헤드
리플렉션은 동적으로 해석되는 타입을 포함하기 때문에 특정 자바 가상 머신 최적화를 수행할 수 없다. 결과적으로 리플렉션 작업은 비리플렉션 작업보다 성능이 느리며, 성능에 민감한 애플리케이션에서 자주 호출되는 코드 섹션에서는 피해야 한다.
2. 보안 제한
리플렉션은 런타임 권한을 필요로 하며, 이는 보안 관리자가 있는 환경에서는 제공되지 않는 않을 수 있다. 이는 애플릿과 같이 제한된 컨텍스트에서 실행해야 하는 코드에 대해 중요한 고려사항이다.
3. 내부 노출
리플렉션은 private 필드 및 메서드 접근과 같이 비리플렉션 코드에서는 불법적인 작업을 수행할 수 있으므로, 예상치 못한 부작용을 초래할 수 있으며, 이는 코드의 기능을 저해하고 이식성을 파괴할 수 있다. 리플렉션 코드는 추상을 깨트리므로 플랫폼 업그레이드 시 동작이 변경될 수 있다.
[ ▶ 클래스 (Classes) ]
모든 타입은 참조 타입 또는 기본 타입 중 하나이다. 클래스, 열거형, 배열 (모두 java.lang.Object를 상속)뿐만 아니라 인터페이스도 참조 타입이다. 참조 타입의 예로는 java.lang.String, 모든 기본 타입의 래퍼 클래스, 인터페이스 java.io.Serializable, enum javax.swing.SortOreder 등이 있다. 기본 타입에는 boolean, byte, short, int, long, char, float, double이 있다.
모든 객체 타입에 대해, 자바 가상 머신은 해당 객체의 런타임 속성 (멤버 및 타입 정보 포함)을 검사할 수 있는 메서드를 제공하는 java.lang.Class의 immutable 인스턴스를 인스턴스화한다. Class는 새로운 클래스 및 객체를 생성할 수 있는 기능도 제공한다. 가장 중요한 것은 모든 리플랙션 API의 진입점이다.
[ ▷ Retrieving Class Objects ]
모든 리플렉션 작업의 진입점은 java.lang.Class이다.
java.lang.reflect.ReflectPermission을 제외하고, java.lang.reflect 패키지의 클래스들은 public 생성자를 가지고 있지 않다. 이러한 클래스에 접근하기 위해서는 Class의 적절한 메서드를 호출해야한다. 객체, 클래스, 이름, 타임 또는 기존의 Class에 접근할 수 있는지 여부에 따라 Class를 얻는 여러가지 방법이 있다.
[ ▷ Object.getClass() ]
객체의 인스턴스가 존재하는 경우, 가장 간단하게 Class 객체를 얻는 방법은 Object.getClass() 메서드를 호출하는 것이다. 물론 이는 모든 참조 타입에 대해서만 동작하며, 모든 참조 타입은 Object로부터 상속받는다.
Class c = "foo".getClass();
// String 클래스에 대한 Class 객체를 반환합니다.
Class c = System.console().getClass();
// 가상 머신과 연관된 고유한 콘솔이 있으며, 이는 정적 메서드 System.console()에 의해 반환됩니다.
// getClass()가 반환하는 값은 java.io.Console에 해당하는 Class입니다.
// 해당 코드는 현재 에러가 발생한다!
enum E { A, B }
Class c = E.A.getClass();
// A는 enum E의 인스턴스입니다. 따라서 getClass()는 열거형 타입 E에 해당하는 Class를 반환합니다.
byte[] bytes = new byte[1024];
Class c = bytes.getClass();
// 배열도 객체이므로 배열 인스턴스에 대해 getClass()를 호출할 수 있습니다.
// 반환된 Class는 byte 타입의 구성 요소를 갖는 배열에 해당합니다.
import java.util.HashSet;
import java.util.Set;
Set<String> s = new HashSet<String>();
Class c = s.getClass();
// 이 경우, java.util.Set은 java.util.HashSet 타입의 객체에 대한 인터페이스입니다.
// getClass()가 반환하는 값은 java.util.HashSet에 해당하는 클래스입니다.
[ ▷ The .class Syntax ]
타입 사용이 가능하지만 인스턴스가 없는 경우, 타입 이름에 ".class"를 붙여서 Class 객체를 얻을 수 있다. 이것은 원시 타입의 Class를 얻는 가장 쉬운 방법이다.
boolean b;
Class c = b.getClass(); // 컴파일 타임 에러
Class c = boolean.class; // 올바른 코드
boolean은 원시 타입이므로 boolean.getClass() 문장은 컴파일 에러가 발생한다. .class 구문은 boolean 타입에 해당하는 Class를 반환한다.
Class c = java.io.PrintStream.class;
변수 c는 java.io.PrintStream 타입에 해당하는 Class가 된다.
Class c = int[][][].class;
.class 구문을 사용하여 주어진 타입의 다차원 배열에 해당하는 Class를 가져올 수 있다.
[ ▷ Class.forName() ]
클래스의 전체 이름 (fully-qualified name)이 제공된 경우, 정적 메서드 Class.forName()을 사용하여 해당 클래스의 Class 객체를 얻을 수 있다. 이 방법은 원시 타입에는 사용할 수 없다. 배열 클래스의 이름 구문은 Class.getName()에 의해 설명된다. 이 구문은 참조 타입과 원시 타입 모두 적용된다.
▼ 이 문장은 주어진 전체 이름으로부터 클래스를 생성한다.
Class c = Class.forName("com.duke.MyLocaleServiceProvider");
여기서 말하는 전체 이름 (fully-qualified name)은 패키지명까지 포함한 것을 말한다. 예를 들어 클래스 MyLocaleServiceProvider가 com.duke 패키지에 있다면, 이 클래스의 전체 이름은 com.duke. MyLocaleServiceProvider가 된다.
전체 이름을 사용하는 이유는 자바에서 동일한 이름을 가진 클래스가 여러 패키지에 있을 수 있기 때문에, 패키지명을 포함함으로써 정확하게 어떤 클래스를 참조하는지 명확히 하기 위함이다.
예를 들어 Class c = Class.forName("com.duke.MyLocaleServiceProvider"); 는 come.duke 패키지에 있는 MyLocaleServiceProvider 클래스를 로드한다. 전체 이름을 통해 자바 런타임은 해당 클래스를 정확히 식별하고 로드할 수 있다.
Class cDoubleArray = Class.forName("[D");
Class cStringArray = Class.forName("[[Ljava.lang.String;");
변수 cDoubleArray는 기본 타입 double의 배열에 해당하는 Class를 포함한다. (double[ ].class와 동일). cStringArray 변수는 String 2차원 배열에 해당하는 Class를 포함한다. (String[ ][ ].class와 동일)
Class.forName("[D")에서 [ 는 배열을 나타내는 특수한 표기법이다. 이 표기법은 자바의 내부 클래스 이름 규칙을 따른다. 자바에서 배열의 타임을 표현할 때는 앞에 대괄호 [ 를 사용하여 배열임을 나타낸다.
- [D는 원시타입 dobule의 1차원 배열을 나타낸다. D는 double 타입을 의미한다.
- double[ ] 타입은 [D로 표현된다.
- [ [ Ljava.lang.String은 String 타입의 2차원 배열울 나타낸다. 첫 번째 [ 는 배열임을 나타내고, 두 번째 [ 는 다차원 배열임을 나타낸다. L은 참조타임을 나타내며, Ljava.lang.String은 String 클래스를 의미한다.
해당 표기법으로 Class.forName 메서드를 통해 배열 타입의 Class 객체를 얻을 수 있다.
Class cDoubleArray = Class.forName("[D"); // double [ ] 타입의 Class 객체
Class cStringArray = Class.forName("[[Ljava.lang.String;" // String [ ][ ] 타입의 Class 객체
위 코드는 각각 double 배열과 String 2차원 배열의 Class 객체를 반환한다.
[ ▷ TYPE Field for Primitive Type Wrappers ]
.class 구문은 기본 타입의 Class 객체를 얻는 더 편리하고 선호되는 방법이다. 그러나 기본 타입의 Class 객체를 얻는 또 다른 방법으로, 각 기본 타입과 void 에는 자바의 java.lang 패키지에 해당하는 래퍼 클래스가 있으며, 이 클래스들은 원시 타입을 참조 타입으로 박싱하는데 사용된다. 각 래퍼 클래스에는 해당 기본 타입의 Class와 동일한 TYPE이라는 필드가 포함되어 있다.
Class c = Double.TYPE;
java.lang.Double 클래스는 기본 타입 double을 객체가 필요한 경우 래핑하는데 사용된다.
Double.TYPE의 값은 double.class와 동일하다.
Class c = Void.TYPE;
void.Type은 void.class와 동일하다.
[ ▷ Methods that Return Classes ]
리플렉션 API 중 몇 가지는 클래스를 반환하지만, 이러한 API는 직접적으로나 간접적으로 이미 클래스 객체를 얻은 경우에만 접근할 수 있다.
Class.getSuperclass()
주어진 클래스의 슈퍼 클래스를 반환한다.
Class c = java.lang.String.class.getSuperclass();
java.lang.String의 슈퍼 클래스는 javax.lang.Object이다.
Class.getClasses()
클래스의 모든 public 클래스, 인터페이스 및 enum을 상속된 멤버를 포함하여 반환한다.
Class<?>[] cs = Character.class.getClasses();
for (Class clazz:cs)
System.out.println(clazz.getTypeName());
java.lang.Character$UnicodeBlock
java.lang.Character$UnicodeScript
java.lang.Character$Subset
Character 클래스에는 두 개의 static nested 클래스 Character.Subset와 Character.UnicodeBlock 그리고 Charcter.UnicodeScript가 포함되어 있다.
Class.getDeclaringClass()
- java.lang.reflect.Field.getDeclaringClass()
- java.lang.reflect.Method.getDeclaringClass()
- java.lang.reflect.Constructor.getDeclaringClass()
이 멤버들이 선언된 클래스를 반환한다. 익명 클래스 선언은 선언된 클래스가 없지만 포함된 클래스는 있을 수 있다.
import java.lang.reflect.Field;
Field f = System.class.getField("out");
Class c = f.getDeclaringClass();
out 필드는 System 클래스에 선언되어 있다.
public class MyClass {
static Object o = new Object() {
public void m() {}
};
static Class<?> c = o.getClass().getEnclosingClass();
}
o에 의해 정의된 익명 클래스의 선언 클래스는 null이다.
Class.getEnclosingClass()
해당 클래스의 outer 클래스를 리턴한다.
Class c = Thread.State.class.getEnclosingClass();
Thread.State enum을 포함하고 있는 클래스는 Thread이다.
public class MyClass {
static Object obj = new Object() {
public void m() {}
};
static Class<?> c = obj.getClass().getEnclosingClass();
}
obj에 의해 정의된 익명 클래스는 MyClass에 포함된다.
[ ▷ Examining Class Modifiers and Types ]
클래스는 런타임 동작에 영향을 미치는 하나 이상의 제어자로 선언될 수 있다.
- 액세스 제어자 : public, protected, private
- override (재정의)를 요구하는 제어자 : abstract
- 하나의 인스턴스로 제한하는 제어자 : static
- 값 수정을 금지하는 제어자 : final
- 엄격한 부동 소수점 동작을 강제하는 제어자 : strictfp
- Annotations
모든 액세서 제어자가 모든 클래스에 허용되는 것은 아니다. 예를 들어 인터페이스는 final일 수 없고, 열거형 (enum)은 abstract일 수 없다. java.lang.relfect.Modifier에는 모든 가능한 제어자에 대한 선언이 포함되어 있다. 또한 Class.getModifiers()가 반환하는 제어자 세트를 디코드할 수 있는 메서드도 포함되어 있다.
ClassDeclarationSpy 예제는 제어자, 제너릭 타입 파라미터, 구현된 인터페이스, 상속 경로를 포함하여 클래스 선언 구성 요소를 얻는 방법을 보여준다. Class는 java.lang.relfect.AnnotatedElement 인터페이스를 구현하기 때문에 런타임 오너테이션을 쿼리하는 것도 가능하다.
import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import static java.lang.System.out;
public class ClassDeclarationSpy {
public static void main(String... args) {
try {
Class<?> c = Class.forName(args[0]);
out.format("Class:%n %s%n%n", c.getCanonicalName());
out.format("Modifiers:%n %s%n%n",
Modifier.toString(c.getModifiers()));
out.format("Type Parameters:%n");
TypeVariable[] tv = c.getTypeParameters();
if (tv.length != 0) {
out.format(" ");
for (TypeVariable t : tv)
out.format("%s ", t.getName());
out.format("%n%n");
} else {
out.format(" -- No Type Parameters --%n%n");
}
out.format("Implemented Interfaces:%n");
Type[] intfs = c.getGenericInterfaces();
if (intfs.length != 0) {
for (Type intf : intfs)
out.format(" %s%n", intf.toString());
out.format("%n");
} else {
out.format(" -- No Implemented Interfaces --%n%n");
}
out.format("Inheritance Path:%n");
List<Class> l = new ArrayList<Class>();
printAncestor(c, l);
if (l.size() != 0) {
for (Class<?> cl : l)
out.format(" %s%n", cl.getCanonicalName());
out.format("%n");
} else {
out.format(" -- No Super Classes --%n%n");
}
out.format("Annotations:%n");
Annotation[] ann = c.getAnnotations();
if (ann.length != 0) {
for (Annotation a : ann)
out.format(" %s%n", a.toString());
out.format("%n");
} else {
out.format(" -- No Annotations --%n%n");
}
// production code should handle this exception more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
private static void printAncestor(Class<?> c, List<Class> l) {
Class<?> ancestor = c.getSuperclass();
if (ancestor != null) {
l.add(ancestor);
printAncestor(ancestor, l);
}
}
}
Class 클래스의 오브젝트 메서드 getCanonicalName
기본 클래스의 정규 이름을 자바 언어 사양에서 정의한 대로 리턴한다. 기본 클래스에 정규 이름이 없는 경우 null을 리턴한다.
기본 클래스의 정규 이름 (canonical name)이란, 클래스의 고유하고 표준화된 이름을 말한다. 이는 패키지 이름을 포함한 전체 이름으로, 자바 언어 사양에 따라 정의된다. 예를 들어 java.util.ArrayList는 ArrayList 클래스의 정규 이름이다. 정규 이름이 없는 클래스의 예는 아래와 같다.
- 지역 클래스 : 메서드 내부에 정의된 클래스이다.
- 익명 클래스 : 이름이 없는 즉석에서 정의된 클래스이다.
- 배열 클래스 : 배열 타입의 클래스이다. 예시로 String[ ]은 정규이름이 없다. 정규 이름은 클래스의 명확한 식별을 위해 사용되며, 리플렉션을 통해 클래스를 다룰 때 중요한 역할을 한다.
[ ▷ Modifier Class ]
Modifier 클래스는 클래스 및 멤버 액세스 제어자를 디코드하기 위한 정적 메서드와 상수를 제공한다. 제어자 세트는 서로 다른 제어자를 나타내는 고유한 비트 위치가 있는 정수로 표현된다. 제어자를 나타내는 상수 값은 자바 가상 머신 사양 섹션 4.1, 4.4, 4.5, 4.7의 표에서 가져온다.
▼ java.util.ConcurrentNavigableMap의 실제 선언
$ java ClassDeclarationSpy java.util.concurrent.ConcurrentNavigableMap
public interface ConcurrentNavigableMap<K,V>
extends ConcurrentMap<K,V>, NavigableMap<K,V>
Class:
java.util.concurrent.ConcurrentNavigableMap
Modifiers:
public abstract interface
Type Parameters:
K V
Implemented Interfaces:
java.util.concurrent.ConcurrentMap<K, V>
java.util.NavigableMap<K, V>
Inheritance Path:
-- No Super Classes --
Annotations:
-- No Annotations --
ConcurrentNavigableMap은 인터페이스이다, 인터페이스는 암묵적으로 abstract이다. 컴파일러는 모든 인터페이스에 대해 이 제어자를 추가한다. 또한 이 선언에는 두 개의 제너릭 타입 타입파라미터 K와 V가 포함되어 있다. 예저 크드는 이 파라미터의 이름만 출력하지만, java.lang.reflect.TypeVariable의 메서드를 사용하여 추가 정보를 검색할 수 있다. 인터페이스는 위와 같이 다른 인터페이스를 구현할 수도 있다.
위 출력 중에 Modifiers: public abstract interface 부분에 대한 설명으로 "인터페이스는 암묵적으로 abstarct이다. 컴파일러는 모든 인터페이스에 대해 이 제어자를 추가한다." 라고 언급하고 있다.
$ java ClassDeclarationSpy "[Ljava.lang.String;"
Class:
java.lang.String[]
Modifiers:
public abstract final
Type Parameters:
-- No Type Parameters --
Implemented Interfaces:
interface java.lang.Cloneable
interface java.io.Serializable
Inheritance Path:
java.lang.Object
Annotations:
-- No Annotations --
배열은 런타임 객체이므로 모든 타입 정보는 자바 가상 머신에 의해 정의된다. 특히 배열은 Clonable 및 java.io.Serialization을 구현하여, 배열의 직접적인 슈퍼클래스는 항상 Object이다.
$ java ClassDeclarationSpy java.io.InterruptedIOException
Class:
java.io.InterruptedIOException
Modifiers:
public
Type Parameters:
-- No Type Parameters --
Implemented Interfaces:
-- No Implemented Interfaces --
Inheritance Path:
java.io.IOException
java.lang.Exception
java.lang.Throwable
java.lang.Object
Annotations:
-- No Annotations --
상속 경로 (Inheritance Path)에서 java.io.InterruptedIOExpection이 checked 예외임을 알 수 있다. 왜냐하면 RuntimeException이 없기 때문이다.
$ java ClassDeclarationSpy java.security.Identity
Class:
java.security.Identity
Modifiers:
public abstract
Type Parameters:
-- No Type Parameters --
Implemented Interfaces:
interface java.security.Principal
interface java.io.Serializable
Inheritance Path:
java.lang.Object
Annotations:
@java.lang.Deprecated()
이 출력은 java.security.Identiy가 java.lang.Deprecated 어노테이션을 가지고 있음을 보여준다. 이는 리플렉션 코드를 통해 deprecated API를 감지하는데 사용할 수 있다.
모든 어노테이션이 리플렉션을 통해 사용할 수 있는 것은 아니다. RUNTIME 보유 정책을 가진 어노테이션만이 접근 가능하다. 언어에 사전 정의된 세 가지 어노테이션 중 @Deprecated, @Override, @SuppressWarning 중에서 @Deprecated만 런타임에 사용할 수 있다.
[ ▶ Discovering Class Members ]
Class에서 필드, 메서드, 생성자에 접근하기 위한 두 가지 카테고리의 메서드가 있다. 이 멤버들을 열거하는 메서드와 특정 멤버를 검색하는 메서드이다. 또한 클래스에 직접 선언된 멤버에 접근하는 메서드와 슈퍼인터페이스 및 슈퍼클래스에서 상속된 멤버를 검색하는 메서드는 구별된다.
Class Methods for Locating Fields | |||
Class API | List of members? | Inherited members? | Private members? |
getDeclaredField() | N | N | Y |
getField() | N | Y | N |
getDeclaredFields() | Y | N | Y |
getFields() | Y | Y | N |
Class Methods for Locating Methods | |||
Class API | List of members? | Inherited members? | Private members? |
getDeclaredMethod() | N | N | Y |
getMethod() | N | Y | N |
getDeclaredMethods() | Y | N | Y |
getMethods() | Y | Y | N |
Class Methods for Locating Constructors | |||
Class API | List of members? | Inherited members? | Private members? |
getDeclaredConstructor() | N | N/A (1) | Y |
getConstructor() | N | N/A (1) | N |
getDeclaredConstructors() | Y | N/A (1) | Y |
getConstructors() | Y | N/A (1) | N |
(1) Constructors are not inherited
주어진 클래스 이름과 관심 있는 멤버의 표시를 바탕으로, ClassSpy 예제는 get*s() 메서드를 사용하여 상속된 멤버를 포함한 모든 public elements 목록을 결정한다.
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Member;
import static java.lang.System.out;
enum ClassMember { CONSTRUCTOR, FIELD, METHOD, CLASS, ALL }
public class ClassSpy {
public static void main(String... args) {
try {
Class<?> c = Class.forName(args[0]);
out.format("Class:%n %s%n%n", c.getCanonicalName());
Package p = c.getPackage();
out.format("Package:%n %s%n%n",
(p != null ? p.getName() : "-- No Package --"));
for (int i = 1; i < args.length; i++) {
switch (ClassMember.valueOf(args[i])) {
case CONSTRUCTOR:
printMembers(c.getConstructors(), "Constructor");
break;
case FIELD:
printMembers(c.getFields(), "Fields");
break;
case METHOD:
printMembers(c.getMethods(), "Methods");
break;
case CLASS:
printClasses(c);
break;
case ALL:
printMembers(c.getConstructors(), "Constuctors");
printMembers(c.getFields(), "Fields");
printMembers(c.getMethods(), "Methods");
printClasses(c);
break;
default:
assert false;
}
}
// production code should handle these exceptions more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
private static void printMembers(Member[] mbrs, String s) {
out.format("%s:%n", s);
for (Member mbr : mbrs) {
if (mbr instanceof Field)
out.format(" %s%n", ((Field)mbr).toGenericString());
else if (mbr instanceof Constructor)
out.format(" %s%n", ((Constructor)mbr).toGenericString());
else if (mbr instanceof Method)
out.format(" %s%n", ((Method)mbr).toGenericString());
}
if (mbrs.length == 0)
out.format(" -- No %s --%n", s);
out.format("%n");
}
private static void printClasses(Class<?> c) {
out.format("Classes:%n");
Class<?>[] clss = c.getClasses();
for (Class<?> cls : clss)
out.format(" %s%n", cls.getCanonicalName());
if (clss.length == 0)
out.format(" -- No member interfaces, classes, or enums --%n");
out.format("%n");
}
}
printMembers() 메서드는 다소 불편한데, 이는 java.lang.reflect.Member 인터페이스가 가장 초기의 리플렉션 구현 이후 존재했기 때문에 제네릭이 도입될 때 더 유용한 getGenericString() 메서드를 포함하도록 수정될 수 없었기 때문이다.
가능한 대안으로 테스트 및 캐스트를 추가하거나, 이 메서드를 printConstructors(), printFields(), printMethods()로 대체하거나, Member.getName()의 상대적으로 간결한 결과에 만족하는 것이다.
$ java ClassSpy java.lang.ClassCastException CONSTRUCTOR
Class:
java.lang.ClassCastException
Package:
java.lang
Constructor:
public java.lang.ClassCastException()
public java.lang.ClassCastException(java.lang.String)
생성자는 상속되지 않기 때문에, 바로 상위 클래스은 RuntimeException 및 다른 상위 클래스에 정의된 예외 연결 메커니즘 생성자 (Throwable 파라미터를 가진 생성자)는 찾을 수 없다.
자바에서 자식 클래스는 부모 클래스의 생성자를 자동으로 상속하지 않는다. 부모 클래스의 생성자는 자식 클래스에서 명시적으로 호출해야한다. 이를 위해 자식 클래스의 생성자에서 super() 키워드를 사용하여 부모 클래스의 생성자를 호출할 수 있다.
'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 |
Members - Fields (0) | 2024.08.16 |