https://sundaland.tistory.com/300
[ ▶ Method ]
메서드에는 호출될 수 있는 실행 가능한 코드가 들어있다. 메서드는 상속되고 넌-리플렉티브한 코드에서는 오버로딩, 오버라이딩, 하이딩 (Hiding)와 같은 동작이 컴파일러에 의해 적용된다. 반면 리플렉티브한 코드는 슈퍼클래스를 고려하지 않고도 메서드 선택을 특정 클래스로 제한할 수 있다. 슈퍼클래스 메서드에 액세스할 수 있지만 메서드를 선언한 클래스를 확인할 수 있다, 이는 리플렉션 없이는 프로그래밍 방식으로 발견하는 것이 불가능하며 많은 미묘한 버그의 원인이 된다.
java.lang.reflect.Method 클래스는 메서드의 제어자, 리턴 타입, 파라미터, 어노테이션 및 throw된 예외에 대한 정보를 액세스하는 API를 제공한다.
[ ▷ Obtaining Method Type Information ]
메서드 선언에는 이름, 제어자, 파라미터, 리턴 타입 및 throw 가능한 예외 목록이 포함된다. java.lang.reflect.Method 클래스는 이 정보를 얻는 방법을 제공한다.
▼ 주어진 클래스에 선언된 모든 메서드를 열거하고 주어진 이름의 모든 메서드에 대한 리턴 타입, 파라미터 타입, 예외 타입을 검색하는 방법
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import static java.lang.System.out;
public class MethodSpy {
private static final String fmt = "%24s: %s%n";
// for the morbidly curious
<E extends RuntimeException> void genericThrow() throws E {}
public static void main(String... args) {
try {
Class<?> c = Class.forName(args[0]);
Method[] allMethods = c.getDeclaredMethods();
for (Method m : allMethods) {
if (!m.getName().equals(args[1])) {
continue;
}
out.format("%s%n", m.toGenericString());
out.format(fmt, "ReturnType", m.getReturnType());
out.format(fmt, "GenericReturnType", m.getGenericReturnType());
Class<?>[] pType = m.getParameterTypes();
Type[] gpType = m.getGenericParameterTypes();
for (int i = 0; i < pType.length; i++) {
out.format(fmt,"ParameterType", pType[i]);
out.format(fmt,"GenericParameterType", gpType[i]);
}
Class<?>[] xType = m.getExceptionTypes();
Type[] gxType = m.getGenericExceptionTypes();
for (int i = 0; i < xType.length; i++) {
out.format(fmt,"ExceptionType", xType[i]);
out.format(fmt,"GenericExceptionType", gxType[i]);
}
}
// production code should handle these exceptions more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
}
▼ 파라미터화된 타입과 가변 개수의 파라미터를 갖는 메서드의 예시
$ java MethodSpy java.lang.Class getConstructor
public java.lang.reflect.Constructor<T> java.lang.Class.getConstructor
(java.lang.Class<?>[]) throws java.lang.NoSuchMethodException,
java.lang.SecurityException
ReturnType: class java.lang.reflect.Constructor
GenericReturnType: java.lang.reflect.Constructor<T>
ParameterType: class [Ljava.lang.Class;
GenericParameterType: java.lang.Class<?>[]
ExceptionType: class java.lang.NoSuchMethodException
GenericExceptionType: class java.lang.NoSuchMethodException
ExceptionType: class java.lang.SecurityException
GenericExceptionType: class java.lang.SecurityException
▼ 소스 코드에서 메서드의 실제 선
public Constructor<T> getConstructor(Class<?>... parameterTypes)
리턴 및 파라미터 타입이 일반적이다.
Method.getGenericReturnType()은 클래스 파일에서 Signature 속성이 있는 경우 이를 참조한다. 속성을 사용할 수 없는 경우 제너릭 도입으로 변경되지 않은 Method.getReturnType()으로 대체한다. Reflection에서 Foo의 일부 값에 대한 getGenericFoo()라는 이름을 가진 다른 메서드는 비슷하게 구현된다.
다음은 마지막 (유일한) 파라미터인 parametersType은 java.lang.Class 유형의 가변 arity (파라미터의 개수가 가변적)이다. 이는 java.lang.Class 유형의 1차원 배열로 표현된다. 이는 Method.isVarArgs()를 호출하여 명시적으로 java.lang.Class의 배열인 파라미터와 구별할 수 있다. Method.get*Types()의 리턴 값에 대한 구문은 Class.getName()에 설명되어 있다.
▼ 일반적인 리턴 타입을 맞는 메서드
$ java MethodSpy java.lang.Class cast
public T java.lang.Class.cast(java.lang.Object)
ReturnType: class java.lang.Object
GenericReturnType: T
ParameterType: class java.lang.Object
GenericParameterType: class java.lang.Object
Class.cast() 메서드의 제너릭 리턴 타입은 java.lang.Object로 보고된다. 이는 제너릭이 컴파일 중에 모든 제너릭 타입 정보를 소거하는 타입 소거를 통해 구현되기 때문이다. T의 삭제는 Class의 선언에 의해 정의된다.
public final class Class<T> implements ...
따라서 T는 타입 파라미터의 상한값으로 대체되며, 이 경우 java.lang.Object이다.
▼ 여러 오버로드가 있는 메서드의 출
$ java MethodSpy java.io.PrintStream format
public java.io.PrintStream java.io.PrintStream.format(java.lang.String,java.lang.Object...)
ReturnType: class java.io.PrintStream
GenericReturnType: class java.io.PrintStream
ParameterType: class java.lang.String
GenericParameterType: class java.lang.String
ParameterType: class [Ljava.lang.Object;
GenericParameterType: class [Ljava.lang.Object;
public java.io.PrintStream java.io.PrintStream.format(java.util.Locale,java.lang.String,
java.lang.Object...)
ReturnType: class java.io.PrintStream
GenericReturnType: class java.io.PrintStream
ParameterType: class java.util.Locale
GenericParameterType: class java.util.Locale
ParameterType: class java.lang.String
GenericParameterType: class java.lang.String
ParameterType: class [Ljava.lang.Object;
GenericParameterType: class [Ljava.lang.Object;
동일한 머세드 이름의 오버로드가 여러 개 발견되면 Class.getDeclaredMethods()에서 모두 반환한다. format()에는 두 개의 오버로드 (Locale이 있는 오버로드와 없는 오버로드)가 있으므로 둘 다 MethodSpy에서 표시된다.
Method.getGenericExceptionTypes()는 실제로 제너릭 예외 타입으로 메서드를 선언할 수 있기 때문에 존재한다. 그러나 제너릭 예외 타입을 캐치할 수 없기 때문에 이것은 거의 사용되지 않는다.
[ ▶ Obtaining Names of Method Parameters ]
메서드나 생성자의 정규 파라미터 이름을 얻으로면 java.lang.reflect.Executable.getParameters 메서드를 사용할 수 있다. (Method와 Constructor 클래스는 Executable 클래스를 상속받아 Exceutable.getParameters 메서드를 상속한다.) 하지만 .class 파일은 기본적으로 정규 파라미터 이름을 저장하지 않는다. 이는 .class 파일을 생성하고 사용하는 많은 도구들이 파라미터 이름이 포함된 .class 파일의 더 크 정적 및 동적 메모리 사용을 예상하지 못할 수 있기 때문이다. 특히, 이러한 도구들은 더 큰 .class 파일을 처리해야 하며, 자바 가상 머신은 더 많은 메모리를 사용하게 된다. 또한, secret나 password와 같은 일부 파라미터 이름은 보안에 민감한 메서드에 대한 정보를 노출할 수 있다.
특정 .class 파일에 정규 파라미터 이름을 저장하고, 이를 통해 Reflection API가 정규 파라미터 이름을 가져올 수 있도록 하려면, 소스 파일은 javac 컴파일러의 -parameters 옵션을 사용하여 ExampleMethods 예제를 컴파일해야 한다.
import java.util.*;
public class ExampleMethods<T> {
public boolean simpleMethod(String stringParam, int intParam) {
System.out.println("String: " + stringParam + ", integer: " + intParam);
return true;
}
public int varArgsMethod(String... manyStrings) {
return manyStrings.length;
}
public boolean methodWithList(List<String> listParam) {
return listParam.isEmpty();
}
public <T> void genericMethod(T[] a, Collection<T> c) {
System.out.println("Length of array: " + a.length);
System.out.println("Size of collection: " + c.size());
}
}
▼ 주어진 클래스의 모든 생성자와 메서드의 정규 파라미터 이름을 검색하는 방법 (각 매게변수에 대한 다른 정보도 출력)
import java.lang.reflect.*;
import java.util.function.*;
import static java.lang.System.out;
public class MethodParameterSpy {
private static final String fmt = "%24s: %s%n";
// for the morbidly curious
<E extends RuntimeException> void genericThrow() throws E {}
public static void printClassConstructors(Class c) {
Constructor[] allConstructors = c.getConstructors();
out.format(fmt, "Number of constructors", allConstructors.length);
for (Constructor currentConstructor : allConstructors) {
printConstructor(currentConstructor);
}
Constructor[] allDeclConst = c.getDeclaredConstructors();
out.format(fmt, "Number of declared constructors",
allDeclConst.length);
for (Constructor currentDeclConst : allDeclConst) {
printConstructor(currentDeclConst);
}
}
public static void printClassMethods(Class c) {
Method[] allMethods = c.getDeclaredMethods();
out.format(fmt, "Number of methods", allMethods.length);
for (Method m : allMethods) {
printMethod(m);
}
}
public static void printConstructor(Constructor c) {
out.format("%s%n", c.toGenericString());
Parameter[] params = c.getParameters();
out.format(fmt, "Number of parameters", params.length);
for (int i = 0; i < params.length; i++) {
printParameter(params[i]);
}
}
public static void printMethod(Method m) {
out.format("%s%n", m.toGenericString());
out.format(fmt, "Return type", m.getReturnType());
out.format(fmt, "Generic return type", m.getGenericReturnType());
Parameter[] params = m.getParameters();
for (int i = 0; i < params.length; i++) {
printParameter(params[i]);
}
}
public static void printParameter(Parameter p) {
out.format(fmt, "Parameter class", p.getType());
out.format(fmt, "Parameter name", p.getName());
out.format(fmt, "Modifiers", p.getModifiers());
out.format(fmt, "Is implicit?", p.isImplicit());
out.format(fmt, "Is name present?", p.isNamePresent());
out.format(fmt, "Is synthetic?", p.isSynthetic());
}
public static void main(String... args) {
try {
printClassConstructors(Class.forName(args[0]));
printClassMethods(Class.forName(args[0]));
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
}
아래 명령문은 ExampleMethods 클래스의 생성자와 메서드의 정규 파라미터 이름을 출력한다.
반드시 -parameters 컴파일러 옵션을 사용하여 ExampleMethods 예제를 컴파일해야 한다.
$java MethodParameterSpy ExampleMethods
Number of constructors: 1
Constructor #1
public ExampleMethods()
Number of declared constructors: 1
Declared constructor #1
public ExampleMethods()
Number of methods: 4
Method #1
public boolean ExampleMethods.simpleMethod(java.lang.String,int)
Return type: boolean
Generic return type: boolean
Parameter class: class java.lang.String
Parameter name: stringParam
Modifiers: 0
Is implicit?: false
Is name present?: true
Is synthetic?: false
Parameter class: int
Parameter name: intParam
Modifiers: 0
Is implicit?: false
Is name present?: true
Is synthetic?: false
Method #2
public int ExampleMethods.varArgsMethod(java.lang.String...)
Return type: int
Generic return type: int
Parameter class: class [Ljava.lang.String;
Parameter name: manyStrings
Modifiers: 0
Is implicit?: false
Is name present?: true
Is synthetic?: false
Method #3
public boolean ExampleMethods.methodWithList(java.util.List<java.lang.String>)
Return type: boolean
Generic return type: boolean
Parameter class: interface java.util.List
Parameter name: listParam
Modifiers: 0
Is implicit?: false
Is name present?: true
Is synthetic?: false
Method #4
public <T> void ExampleMethods.genericMethod(T[],java.util.Collection<T>)
Return type: void
Generic return type: void
Parameter class: class [Ljava.lang.Object;
Parameter name: a
Modifiers: 0
Is implicit?: false
Is name present?: true
Is synthetic?: false
Parameter class: interface java.util.Collection
Parameter name: c
Modifiers: 0
Is implicit?: false
Is name present?: true
Is synthetic?: false
- getType : 파라미터에 대해 선언된 타입을 식별하는 Class 객체를 반환
- getName : 파라미터의 이름을 반환. 파라미터 이름이 있는 경우 이 메서드는 .class 파일에서 제공한 이름을 반환한다. 그렇지 않은 경우 이 메서드는 argN 형식의 이름을 합성한다. 여기서 N은 파라미터를 선언하는 메서드의 디스크립터에서 파라미터의 인덱스이다. 만약 -parameters 컴파일러 옵션을 지정하고 컴파일 했다면 다음 내용이 출력된다.
public boolean ExampleMethods.simpleMethod(java.lang.String,int)
Return type: boolean
Generic return type: boolean
Parameter class: class java.lang.String
Parameter name: arg0
Modifiers: 0
Is implicit?: false
Is name present?: false
Is synthetic?: false
Parameter class: int
Parameter name: arg1
Modifiers: 0
Is implicit?: false
Is name present?: false
Is synthetic?: false
- getModifiers : 정규 파라미터가 소유한 다양한 특성을 나타내는 정수를 반환한다. 이 값은 정규 파라미터에 적용 가능한 경우 다음 값의 합계이다.
10진수 | 6진수 | 설명 |
16 | 0x0010 | The formal parameter is declared final |
4096 | 0x1000 | The formal parameter is synthetic. Alternatively, you can invoke the method isSynthetic |
32768 | 0x8000 | The parameters is implicitly declared in source code. Alternatively, you can invoke the method isImplicit |
- isImplicit : 이 파라미터가 소스 코드에서 암묵적으로 선언된 경우 true를 반환한다.
- isNamePresent : .class 파일에 따라 파라미터에 이름이 있는 경우 true를 반환한다.
- isSynthetic : 이 파라미터가 소스 코드에서 암시적으로 명시적으로 선언되지 않은 경우 true를 반환한다.
[ ▷ Implicit and Synthetic Parameters ]
일부 구조물 (Constructs)은 명시적으로 작성되지 않은 경우 소스 코드에서 암시적으로 선언된다. 예를 들어 ExampleMethods 예제는 생성자가 포함되어 있지 않다. 디폴트 생성자가 암시적으로 선언된다.
MethodParameterSpy 예제는 ExampleMethods의 암시적으로 선언된 생성자에 대한 정보를 출력한다.
Number of declared constructors: 1
public ExampleMethods()
Construct
위에서 사용한 구조물이란, 자바 프로그램 내에서 컴파일러가 생성하거나 처리하는 코드 요소나 구성 요소를 의미한다. 이 구조물에는 클래스, 메서드, 필드, 파라미터와 같은 프로그래밍 요소들이 포함될 수 있다. 구조물은 주로 프로그램의 소스 코드에서 명시적으로 작성되거나, 자바 컴파일러가 암시적으로 생성하는 코드를 가르킨다.
예를 들어, 합성 구조물 (Synthetic construct)은 컴파일러가 소스 코드에 명시적으로 작성되지 않은 코드를 생성할 떄 나타나는 구조물을 의미한다. 이는 컴파일러 특정 기능을 지원하기 위해 추가하는 코드 요소들을 말한다.
▼ MethodParameterExamples에서 발췌한 예시
public class MethodParameterExamples {
public class InnerClass { }
}
InnerClass는 비정적 중첩 클래스 또는 inner 클래스이다. 내부 클래스의 경우 생성자도 암시적으로 선언된다. 그러나 이 생성자에는 파라미터가 포함된다. 자바 컴파일러가 InnerClass를 컴파일할 때, 다음과 유사한 코드를 나타내는 .class 파일을 생성한다.
public class MethodParameterExamples {
public class InnerClass {
final MethodParameterExamples parent;
InnerClass(final MethodParameterExamples this$0) {
parent = this$0;
}
}
}
InnerClass 생성자는 InnerClass를 감싸고 있는 클래스인 MethodParameterExamples 타입의 파라미터가 포함된다. 따라서 다음을 출력한다.
public MethodParameterExamples$InnerClass(MethodParameterExamples)
Parameter class: class MethodParameterExamples
Parameter name: this$0
Modifiers: 32784
Is implicit?: true
Is name present?: true
Is synthetic?: false
- 자바 컴파일러는 내부 클래스의 생성자에 대한 정규 파라미터를 생성하여 컴파일러가 생성 표현식에서 내부 클래스의 생성자로 참조 (outer 클래스의 인스턴스에 대한 참조)를 전달할 수 있도록 한다.
- 값 32784는 InnerClass 생성자의 파라미터가 final(16)과 implicit(32768) 모두임을 의미한다.
- 자바 프로그래밍 언어에서 달러 기호 ($)를 변수 이름에 사용할 수 있다. 그러나 관례적으로 변수 이름에 달러 기호는 사용하지 않는다.
자바 컴파일러에 의해 생성된 구조물이 명시적 또는 암시적으로 소스 코드에서 선언된 구조물에 대응하지 않는 경우, 해당 구모줄은 합성 (synthetic)으로 표시된다. 단, 클래스 초기화 메서드는 예외앋. 합성 구조물은 컴파일러가 생성하는 산출물로, 다양한 구현에서 다르게 나타날 수 있다.
import java.lang.reflect.*;
import java.util.function.*;
import static java.lang.System.out;
public class MethodParameterExamples {
public class InnerClass { }
enum Colors {
RED, WHITE;
}
public static void main(String... args) {
System.out.println("InnerClass:");
MethodParameterSpy.printClassConstructors(InnerClass.class);
System.out.println("enum Colors:");
MethodParameterSpy.printClassConstructors(Colors.class);
MethodParameterSpy.printClassMethods(Colors.class);
}
}
▼ MethodParameterExamples에서 발췌한 예시
public class MethodParameterExamples {
enum Colors {
RED, WHITE;
}
}
자바 컴파일러가 열거형 (enum) 구문을 만나면, .class 파일 구조와 호환되고 열거형 구문의 예상 기낭을 제공하는 여러 메서드를 생성한다. 예를 들어, 자바 컴파일러는 열거형 구문 Colors에 대해 다음과 유사한 코드를 나타내는 .class 파일을 생성한다.
final class Colors extends java.lang.Enum<Colors> {
public final static Colors RED = new Colors("RED", 0);
public final static Colors BLUE = new Colors("BLUE", 1);
private final static Colors[] values = new Colors[]{ RED, BLUE };
private Colors(String name, int ordinal) {
super(name, ordinal);
}
public static Colors[] values(){
return values;
}
public static Colors valueOf(String name){
return (Colors)java.lang.Enum.valueOf(Colors.class, name);
}
}
자바 컴파일러는 이 열거형 구문에 대해 세 가지 생성자와 메서드를 생성한다. Colors(String name, int ordinal), Colors[] values(), 그리고 Colors valueOf(String name). values와 valueOf 메서드는 암시적으로 선언된다. 따라서 이들의 정규 파라미터 이름도 암시적으로 선언된다.
Colors valueOf(String name) 열거형 생성자는 디폴트 생성자이며, 암시적으로 선언된다. 그러나 이 생성자의 정규 파라미터 (name, ordinal)는 암시적으로 선언되지 않는다. 이러한 정규 파라미터는 명시적이거나 암시적으로 선언되지 않았기 때문에, 합성 (synthetic) 파라미터이다. (열거형 구문의 디폴트 생성자의 정규 파라미터는 암시적으로 선언되지 않는다. 이는 다른 자바 컴파일러가 이 생성자에 대해 다른 정규 파라미터를 지정할 수 있기 때문이다. 컴파일러가 열거형 상수를 사용하는 표현식을 컴파일할 때, 암시적으로 선언된 공용 (static) 필드에만 의존하며, 생성자나 이러한 상수가 초기화되는 방식에는 의존하지 않는다.)
▼ MethodParamenterExample의 Colors 열거형 구문에 대한 출력
enum Colors:
Number of constructors: 0
Number of declared constructors: 1
Declared constructor #1
private MethodParameterExamples$Colors()
Parameter class: class java.lang.String
Parameter name: $enum$name
Modifiers: 4096
Is implicit?: false
Is name present?: true
Is synthetic?: true
Parameter class: int
Parameter name: $enum$ordinal
Modifiers: 4096
Is implicit?: false
Is name present?: true
Is synthetic?: true
Number of methods: 2
Method #1
public static MethodParameterExamples$Colors[]
MethodParameterExamples$Colors.values()
Return type: class [LMethodParameterExamples$Colors;
Generic return type: class [LMethodParameterExamples$Colors;
Method #2
public static MethodParameterExamples$Colors
MethodParameterExamples$Colors.valueOf(java.lang.String)
Return type: class MethodParameterExamples$Colors
Generic return type: class MethodParameterExamples$Colors
Parameter class: class java.lang.String
Parameter name: name
Modifiers: 32768
Is implicit?: true
Is name present?: true
Is synthetic?: false
[ ▷ Retrieving and Parsing Method Modifiers ]
메서드 선언에는 아래와 같은 몇 가지 제어자가 포함될 수 있다.
- Access modifiers : public, protected, private
- Modifier restricting to one instance : static
- Modifier prohibiting value modification : final
- Modifier requirinng override : abstract
- Modifier preventing reentrancy : synchronized
- Modifier indicating implementation in another programming language : native
- Annotations
MethodModifierSpy 예제는 주어진 이름을 가진 메서드의 제어자를 나열한다. 또한, 해당 메서드가 합성 메서드 (컴파일러가 생성한 메서드), 가변 아규먼트 메서드, 제너릭 인터페이스릴 지원하기 위해 컴파일러가 생성한 브리지 메서드인지 여부를 표시한다.
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import static java.lang.System.out;
public class MethodModifierSpy {
private static int count;
private static synchronized void inc() { count++; }
private static synchronized int cnt() { return count; }
public static void main(String... args) {
try {
Class<?> c = Class.forName(args[0]);
Method[] allMethods = c.getDeclaredMethods();
for (Method m : allMethods) {
if (!m.getName().equals(args[1])) {
continue;
}
out.format("%s%n", m.toGenericString());
out.format(" Modifiers: %s%n",
Modifier.toString(m.getModifiers()));
out.format(" [ synthetic=%-5b var_args=%-5b bridge=%-5b ]%n",
m.isSynthetic(), m.isVarArgs(), m.isBridge());
inc();
}
out.format("%d matching overload%s found%n", cnt(),
(cnt() == 1 ? "" : "s"));
// production code should handle this exception more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
}
$ java MethodModifierSpy java.lang.Object wait
public final void java.lang.Object.wait()
throws java.lang.InterruptedException
Modifiers: public final
[ synthetic=false var_args=false bridge=false ]
public final void java.lang.Object.wait(long,int)
throws java.lang.InterruptedException
Modifiers: public final
[ synthetic=false var_args=false bridge=false ]
public final native void java.lang.Object.wait(long)
throws java.lang.InterruptedException
Modifiers: public final native
[ synthetic=false var_args=false bridge=false ]
3 matching overloads found
$ java MethodModifierSpy java.lang.StrictMath toRadians
public static double java.lang.StrictMath.toRadians(double)
Modifiers: public static strictfp
[ synthetic=false var_args=false bridge=false ]
1 matching overload found
$ java MethodModifierSpy MethodModifierSpy inc
private synchronized void MethodModifierSpy.inc()
Modifiers: private synchronized
[ synthetic=false var_args=false bridge=false ]
1 matching overload found
$ java MethodModifierSpy java.lang.Class getConstructor
public java.lang.reflect.Constructor<T> java.lang.Class.getConstructor
(java.lang.Class<T>[]) throws java.lang.NoSuchMethodException,
java.lang.SecurityException
Modifiers: public transient
[ synthetic=false var_args=true bridge=false ]
1 matching overload found
$ java MethodModifierSpy java.lang.String compareTo
public int java.lang.String.compareTo(java.lang.String)
Modifiers: public
[ synthetic=false var_args=false bridge=false ]
public int java.lang.String.compareTo(java.lang.Object)
Modifiers: public volatile
[ synthetic=true var_args=false bridge=true ]
2 matching overloads found
Method.isVarArgs( )는 Class.getConstructor( )에 대해 true를 반환한다. 이는 메서드 선언이 다음과 같다는 것을 나타낸다.
public Constructor<T> getConstructor(Class<?>... parameterTypes)
▼ 이렇게는 할 수 없다!!
public Constructor<T> getConstructor(Class<?> [] parameterTypes)
String.compareTo( )의 출력에 두 개의 메서드가 포함되어 있다. 하나는 String.java에 선언된 메서드이다.
public int compareTo(String anotherString);
그리고 두 번째 합성 메서드 또는 컴파일러가 생성한 브리지 메서드이다. 이는 String이 파라미터화된 인터페이스 Comparable를 구현하기 때문에 발생한다. 타입 소거 동안, 상속된 메서드 Comparable.compareTo( )의 아규먼트 타입이 java.lang.Object에서 java.lang.String으로 변경된다. 소거 후 Comparable과 String의 CompareTo 메서드의 파라미터 타입이 더 이상 일치하지 않으므로, 오버라이딩이 발생할 수 없다. 다른 모든 상황에서는 인터페이스가 구현되지 않았기 때문에 컴파일 타임 오류가 발생하지만, 브리지 메서드의 추가로 이 문제를 방지할 수 있다.
Method는 java.lang.reflect.AnnotatedElement를 구현한다. 따라서 java.lang.annotation.RetentionPolicy.RUNTIME을 가진 모든 런타임 어노테이션을 가져올 수 있다.
[ ▷ Invoking Methods ]
리플렉션은 클래스의 메서드를 호출할 수 있는 방법을 제공한다. 일반적으로 이는 non-reflective 코드에서 클래스 인스턴스를 원하는 타입으로 캐스팅할 수 없는 경우에만 필요하다. 메서드는 java.lang.reflect.Method.invoke( )를 사용하여 호출된다. 첫 번쨔 인자는 이 특정 메서드가 호출될 객체 인스턴스이다. (메서드가 static인 경우 첫 번째 인자는 null이어야 한다.) 이후 아큐먼트들은 이 특정 메서드의 파라미터들이다. 만약 이 특정 메서드가 예외를 던지면, 이 예외는 java.lang.reflect.InvocationTargerException으로 래핑된다. 메서드의 원래 예외는 예외 체이능 메커니즘의 InvocationTargetException.getCause( ) 메서드를 사용하여 가져올 수 있다.
[ ▷ Finding and Invoking a Method with a Specific Declaration ]
특정 클래스에서 private 테스트 메서드를 리플렉션을 사용해 호출하는 테스트 스위트를 고려했을때, 아래의 예제는 test 문자열로 시작하고, 리턴 타입이 boolean이며, 단일 Locale 파라미터를 가지는 클래스의 public 메서드를 검색한 다음, 일치하는 각 메서드를 호출한다.
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Locale;
import static java.lang.System.out;
import static java.lang.System.err;
public class Deet<T> {
private boolean testDeet(Locale l) {
// getISO3Language() may throw a MissingResourceException
out.format("Locale = %s, ISO Language Code = %s%n", l.getDisplayName(),
l.getISO3Language());
return true;
}
private int testFoo(Locale l) { return 0; }
private boolean testBar() { return true; }
public static void main(String... args) {
if (args.length != 4) {
err.format("Usage: java Deet <classname> <langauge> <country> <variant>%n");
return;
}
try {
Class<?> c = Class.forName(args[0]);
Object t = c.newInstance();
Method[] allMethods = c.getDeclaredMethods();
for (Method m : allMethods) {
String mname = m.getName();
if (!mname.startsWith("test")
|| (m.getGenericReturnType() != boolean.class)) {
continue;
}
Type[] pType = m.getGenericParameterTypes();
if ((pType.length != 1)
|| Locale.class.isAssignableFrom(pType[0].getClass())) {
continue;
}
out.format("invoking %s()%n", mname);
try {
m.setAccessible(true);
Object o = m.invoke(t, new Locale(args[1], args[2], args[3]));
out.format("%s() returned %b%n", mname, (Boolean) o);
// Handle any exceptions thrown by method to be invoked.
} catch (InvocationTargetException x) {
Throwable cause = x.getCause();
err.format("invocation of %s failed: %s%n",
mname, cause.getMessage());
}
}
// production code should handle these exceptions more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
} catch (InstantiationException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
}
}
}
Deet는 클래스에 명시적으로 선언된 모든 메서드를 반환하는 getDeclaredMethods( )를 호출한다. 또한 Class.isAssignableFrom( )을 사용하여 찾은 메서드의 파라미터가 원하는 호출과 호환되는지 확인한다. 기술적으로는 Locale이 finale이므로 아래 문장이 참인지 테스트할 수 있다.
Locale.class == pType[0].getClass()
하지만 Class.isAssignableFrom( )이 더 일반적이다.
$ java Deet Deet ja JP JP
invoking testDeet()
Locale = Japanese (Japan,JP),
ISO Language Code = jpn
testDeet() returned true
$ java Deet Deet xx XX XX
invoking testDeet()
invocation of testDeet failed:
Couldn't find 3-letter language code for xx
먼저 testDeet( )만이 코드에서 적용된 선언 제한을 충족한다. testDeet( )에 잘못된 아규먼트가 전달되면, unchecked java.util.MissingResourceException을 던진다. 리플렉션에서는 checked 예외와 unchecked 예외의 처리에 차이가 없다.
모두 InvocationTargetException으로 래핑된다.
[ ▷ Invoking Methods with a Variable Number of Arguments ]
Method.invoke( )를 사용하여 메서드에 가변 아규먼트를 전달할 수 있다. 이해해야 할 핵심 개념은 가변 아규먼트 메서드가 마치 가변 아규먼트가 배열에 패킹된 것처럼 구현된다는 점이다.
▼ 특정 클래스에서 main( ) 진입점을 호출하고 런타임에 결정된 인수 집합을 전달하는 방법을 보여준다.
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
public class InvokeMain {
public static void main(String... args) {
try {
Class<?> c = Class.forName(args[0]);
Class[] argTypes = new Class[] { String[].class };
Method main = c.getDeclaredMethod("main", argTypes);
String[] mainArgs = Arrays.copyOfRange(args, 1, args.length);
System.out.format("invoking %s.main()%n", c.getName());
main.invoke(null, (Object)mainArgs);
// 실제 코드에서는 이러한 예외를 더 우아하게 처리해야 합니다.
} catch (ClassNotFoundException x) {
x.printStackTrace();
} catch (NoSuchMethodException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
} catch (InvocationTargetException x) {
x.printStackTrace();
}
}
}
main( ) 메서드를 찾기 위해 코드는 String 배열을 단일 매게변수로 가지는 "main"이라는 이름의 메서드를 검색한다. main( )이 정적 (static)이르모, null이 Method.invoke( )의 첫 번째 인자이다. 두 번쨰 인자는 전달할 인수 배열이다.
$ java InvokeMain Deet Deet ja JP JP
invoking Deet.main()
invoking testDeet()
Locale = Japanese (Japan,JP),
ISO Language Code = jpn
testDeet() returned true
'Reflection' 카테고리의 다른 글
type-safe proxy object (0) | 2024.08.20 |
---|---|
Structuring method calls (0) | 2024.08.20 |
Members - Constructors (0) | 2024.08.20 |
Members - Fields (0) | 2024.08.16 |
The Reflection API - 클래스 (0) | 2024.08.16 |