https://sundaland.tistory.com/301
[ ▶ Constructors ]
생성자는 클래스의 인스턴스인 객체를 생성할 때 사용된다. 일반적으로 메서드가 호출되거나 필드에 접근되기 전에 클래스 초기화에 필요한 작업을 수행한다. 생성자는 절대 상속되지 않는다.
메서드와 유사하게, 리플렉션은 클래스의 생성자를 발견하고 검색하며, 제어자, 파라미터, 어노테이션, throw 예외와 같은 선언 정보를 얻기 위한 API를 제공한다. 또한 지정된 생성자를 사용하여 클래스의 새 인스턴스를 생성할 수도 있다. 생성자를 다룰 때 사용하는 주요 클래스는 Class와 java.lang.reflect.Constructor이다.
[ ▷ Finding Constructors ]
생성자 선언에는 이름, 제어자, 파라미터 개수 및 throw 가능한 예외 목록이 포함된다. java.lang.reflect.Constructor 클래스는 이 정보를 얻는 방법을 제공한다.
▼ 클래스의 선언된 생성자에서 주어진 타입의 파라미터가 있는 생성자를 검색하는 방법
import java.lang.reflect.Constructor;
import java.lang.reflect.Type;
import static java.lang.System.out;
public class ConstructorSift {
public static void main(String... args) {
try {
Class<?> cArg = Class.forName(args[1]);
Class<?> c = Class.forName(args[0]);
Constructor[] allConstructors = c.getDeclaredConstructors();
for (Constructor ctor : allConstructors) {
Class<?>[] pType = ctor.getParameterTypes();
for (int i = 0; i < pType.length; i++) {
if (pType[i].equals(cArg)) {
out.format("%s%n", ctor.toGenericString());
Type[] gpType = ctor.getGenericParameterTypes();
for (int j = 0; j < gpType.length; j++) {
char ch = (pType[j].equals(cArg) ? '*' : ' ');
out.format("%7c%s[%d]: %s%n", ch,
"GenericParameterType", j, gpType[j]);
}
break;
}
}
}
// production code should handle this exception more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
}
Method.getGenericParameterTypes()는 클래스 파일에 Signature 속성이 있으면 참조한다. 속성을 사용할 수 없는 경우 제너릭 도입으로 변경되지 않은 Method.getParameterType()으로 돌아간다. Reflection에서 Foo의 일부 값에 대한 getGenericFoo()라는 이름을 가진 다른 메서드는 비슷하게 구현된다. Method.get*Types()의 리턴 값에 대한 구문은 Class.getName()에 설명되어 있다.
▼ Locale 아규먼트가 있는 java.util.Formatter의 모든 생성자에 대한 출력
$ java ConstructorSift java.util.Formatter java.util.Locale
public
java.util.Formatter(java.io.OutputStream,java.lang.String,java.util.Locale)
throws java.io.UnsupportedEncodingException
GenericParameterType[0]: class java.io.OutputStream
GenericParameterType[1]: class java.lang.String
*GenericParameterType[2]: class java.util.Locale
public java.util.Formatter(java.lang.String,java.lang.String,java.util.Locale)
throws java.io.FileNotFoundException,java.io.UnsupportedEncodingException
GenericParameterType[0]: class java.lang.String
GenericParameterType[1]: class java.lang.String
*GenericParameterType[2]: class java.util.Locale
public java.util.Formatter(java.lang.Appendable,java.util.Locale)
GenericParameterType[0]: interface java.lang.Appendable
*GenericParameterType[1]: class java.util.Locale
public java.util.Formatter(java.util.Locale)
*GenericParameterType[0]: class java.util.Locale
public java.util.Formatter(java.io.File,java.lang.String,java.util.Locale)
throws java.io.FileNotFoundException,java.io.UnsupportedEncodingException
GenericParameterType[0]: class java.io.File
GenericParameterType[1]: class java.lang.String
*GenericParameterType[2]: class java.util.Locale
▼ 문자열에서 char[ ] 타입의 파라미터를 검색하는 방
$ java ConstructorSift java.lang.String "[C"
java.lang.String(int,int,char[])
GenericParameterType[0]: int
GenericParameterType[1]: int
*GenericParameterType[2]: class [C
public java.lang.String(char[],int,int)
*GenericParameterType[0]: class [C
GenericParameterType[1]: int
GenericParameterType[2]: int
public java.lang.String(char[])
*GenericParameterType[0]: class [C
Class.forName()에서 허용되는 참조 및 기본 타입의 배열을 표현하는 구문은 Class.forName()에 설명되어 있다. 첫 번째로 나열된 생성자는 public이 아니라 package-private이다. 이 생성자는 예제 코드가 public 생성자만 반환하는 Class.getConstructors()이 아니라 Class.getDeclaredConstructors()를 사용하기 때문에 반환된다.
가변적인 arity (파라미터 개수가 가변적)의 아규먼트를 검색하려면 배열 구문을 사용해야 한다.
$ java ConstructorSift java.lang.ProcessBuilder "[Ljava.lang.String;"
public java.lang.ProcessBuilder(java.lang.String[])
*GenericParameterType[0]: class [Ljava.lang.String;
▼ 소스 코드에서 ProcessBuilder 생성자의 실제 선
public ProcessBuilder(String... command)
파라미터는 java.lang.String 타입의 1차원 배열로 표현된다. 이는 Constructor.isVarArgs()를 호출하여 명시적으로 java.lang.String 배열인 파라미터와 구별할 수 있다.
▼ 제너릭 파라미터 타입으로 선언된 생성자에 대한 출력
$ java ConstructorSift java.util.HashMap java.util.Map
public java.util.HashMap(java.util.Map<? extends K, ? extends V>)
*GenericParameterType[0]: java.util.Map<? extends K, ? extends V>
생성자에 대한 예외 타입은 메서드와 비슷한 방식으로 검색할 수 있다.
[ ▷ Retrieving and Parsing Constructor Modifiers ]
자바 언어에서 생성자의 역할 때문에 의미 있는 제어자가 적다.
- Access modifiers : public, protected, private
- Annotations
▼ 지정된 액세스 제어자가 있는 주어진 클래스에서 생성자를 검색한다. 또한 생성자가 합성(컴파일러 생성)인지 가변 arity인지도 표시합니다.
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import static java.lang.System.out;
public class ConstructorAccess {
public static void main(String... args) {
try {
Class<?> c = Class.forName(args[0]);
Constructor[] allConstructors = c.getDeclaredConstructors();
for (Constructor ctor : allConstructors) {
int searchMod = modifierFromString(args[1]);
int mods = accessModifiers(ctor.getModifiers());
if (searchMod == mods) {
out.format("%s%n", ctor.toGenericString());
out.format(" [ synthetic=%-5b var_args=%-5b ]%n",
ctor.isSynthetic(), ctor.isVarArgs());
}
}
// production code should handle this exception more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
}
}
private static int accessModifiers(int m) {
return m & (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED);
}
private static int modifierFromString(String s) {
if ("public".equals(s)) return Modifier.PUBLIC;
else if ("protected".equals(s)) return Modifier.PROTECTED;
else if ("private".equals(s)) return Modifier.PRIVATE;
else if ("package-private".equals(s)) return 0;
else return -1;
}
}
package-private 액세스에 해당하는 명시적인 제어자 상수는 없으므로, package-private 생성자를 식별하려면 세 가지 액세스 제어자가 모두 없는지 확인해야 한다.
▼ java.io.File 의 private 생성자를 보여주는 출력
$ java ConstructorAccess java.io.File private
private java.io.File(java.lang.String,int)
[ synthetic=false var_args=false ]
private java.io.File(java.lang.String,java.io.File)
[ synthetic=false var_args=false ]
합성 생성자는 드믈다. 그러나 아래의 예제는 이러한 상황이 발생할 수 있는 일반적인 상황을 보여준다.
public class SyntheticConstructor {
private SyntheticConstructor() {}
class Inner {
// Compiler will generate a synthetic constructor since
// SyntheticConstructor() is private.
Inner() { new SyntheticConstructor(); }
}
public static void main(String[] args) { // Chat GTP로 추가
try {
Constructor<?>[] constructors = ConstructorAccess.class.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
System.out.println(" [ synthetic=" + constructor.isSynthetic() +
" var_args=" + constructor.isVarArgs() + " ]");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
$ java ConstructorAccess SyntheticConstructor package-private
public ConstructorAccess()
[ synthetic=false var_args=false ]
생성자는 java.lang.reflect.AnnotatedElement를 구현하는데, 이는 java.lang.annotation.RetentionPolicy.RUNTIME으로 런타임 어노테이션을 검색하는 메서드를 제공한다.
[ ▷ Creating New Class Instances ]
두 가지의 리플렉션 방법을 통해 클래스 인스턴스를 만들 수 있다. java.lang.reflect.Constructor.newInstance()와 Class.newInstance()이다. 전자가 더 선호되며 따라서 이러한 예에서 사용되는 이유는 다음과 같다.
- Class.newInstance()는 아규먼트가 없는 생성자만 호출할 수 있는 반면, Constructor.newInstance()는 파라미터 수에 관계 없이 모든 생성자를 호출할 수 있다.
- Class.newInstance()는 생성자가 throw한 모든 예외를 throw한다. 생성자가 check 되었는지 uncheck 되었는지는 상관 없다. Constructor.newInstance()는 throw한 예외를 항상 InvocationTargetException으로 래핑한다.
- Class.newInstance()는 생성자가 표시되어야 한다. Constructor.newInstance()는 특정 상황에서 private 생성자를 호출할 수 있다.
때때로 객체 생성 후에만 설정되는 내부 상태를 가져오는 것이 필요할 수 있다. 예를 들어, java.io.Console에서 사용되는 내부 문자 집합을 얻어야 하는 시나리오를 고려해야 한다. (Console의 문자 집합은 private 필드에 저장되며, 자바 가성 머신의 기본 문자 집합인 java.nio.charset.defaultCharset()이 리턴하는 값과 반드시 동일하진 않다.)
console 클래스의 private 디폴트 컨스트럭터는 자바 9 부터 사용할 수 없다.
(Class.setAccessible(true)를 호출하면, InaccessibleObjectException 오류가 발생한다.)
public class Foo {
String name;
private Foo() {
name = "hello";
}
public Foo(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
import static java.lang.System.out;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InaccessibleObjectException;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.Charset;
public class ConsoleCharset {
public static void getFooField() throws NoSuchMethodException, SecurityException {
Constructor<?>[] ctors = Foo.class.getDeclaredConstructors();
Constructor<?> ctor = null;
for(int i = 0; i < ctors.length; i++) {
ctor = ctors[i];
if (ctor.getGenericExceptionTypes().length == 0) {
break;
}
}
try {
boolean isSet = ctor.trySetAccessible();
if(isSet) {
ctor.setAccessible(true);
Foo c = (Foo)ctor.newInstance();
Field f = c.getClass().getDeclaredField("name");
f.setAccessible(true);
out.format("Console charset : %s%n", f.get(c));
out.format("Charset.defaultCharset(): %s%n",
Charset.defaultCharset());
}
// production code should handle these exceptions more gracefully
} catch (InstantiationException x) {
x.printStackTrace();
} catch (InvocationTargetException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
} catch (NoSuchFieldException x) {
x.printStackTrace();
} catch (InaccessibleObjectException x) {
x.printStackTrace();
}
}
public static void main(String... args) throws NoSuchMethodException, SecurityException {
getFooField();
}
}
Class.newInstance()는 생성자 아규먼트가 없고 이미 접근 가능한 경우에만 성공한다. 그렇지 않은 경우엔 Constructor.newInstance()를 사용해야 한다.
Console charset : hello
Charset.defaultCharset(): UTF-8
Constructor.newInstance()의 또 다른 일반적인 응용 프로그램은 인수를 취하는 생성자를 호출하는 것이다.
▼ 특정 단일 인수 생성자를 찾아서 호출한다.
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import static java.lang.System.out;
class EmailAliases {
private Set<String> aliases;
private EmailAliases(HashMap<String, String> h) {
aliases = h.keySet();
}
public void printKeys() {
out.format("Mail keys:%n");
for (String k : aliases)
out.format(" %s%n", k);
}
}
public class RestoreAliases {
private static Map<String, String> defaultAliases = new HashMap<String, String>();
static {
defaultAliases.put("Duke", "duke@i-love-java");
defaultAliases.put("Fang", "fang@evil-jealous-twin");
}
public static void main(String... args) {
try {
Constructor ctor = EmailAliases.class.getDeclaredConstructor(HashMap.class);
ctor.setAccessible(true);
EmailAliases email = (EmailAliases)ctor.newInstance(defaultAliases);
email.printKeys();
// production code should handle these exceptions more gracefully
} catch (InstantiationException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
} catch (InvocationTargetException x) {
x.printStackTrace();
} catch (NoSuchMethodException x) {
x.printStackTrace();
}
}
}
Class.getDeclaredConstructor()를 사용하여 java.util.HashMap 유형의 단일 인수가 있는 생성자를 찾는다, get*Constructor() 메서드에 대한 매게변수에는 타입 목적으로만 클래스가 필요하므로 HashMap.class를 전달하는 것 만으로도 충분하다. type erasure로 인해 아래의 expression은 true가 된다.
HashMap.class == defaultAliases.getClass()
Constructor.newInstance()와 함께 생성자를 사용하여 클래스의 새 인스턴스를 만들수 있다.
$ java RestoreAliases
Mail keys:
Duke
Fang
'Reflection' 카테고리의 다른 글
type-safe proxy object (0) | 2024.08.20 |
---|---|
Structuring method calls (0) | 2024.08.20 |
Members - Methods (0) | 2024.08.19 |
Members - Fields (0) | 2024.08.16 |
The Reflection API - 클래스 (0) | 2024.08.16 |