[튜토리얼]
Constructors
생성자는 클래스의 인스턴스인 객체를 생성할 때 사용됩니다. 일반적으로 메서드가 호출되거나 필드에 접근되기 전에 클래스 초기화에 필요한 작업을 수행합니다. 생성자는 절대 상속되지 않습니다.
메서드와 유사하게, 리플렉션은 클래스의 생성자를 발견하고 검색하며, 제어자, 파라미터, 애노테이션, 던지는 예외와 같은 선언 정보를 얻기 위한 API를 제공합니다. 또한 지정된 생성자를 사용하여 클래스의 새 인스턴스를 생성할 수도 있습니다. 생성자를 다룰 때 사용하는 주요 클래스는 Class 와 java.lang.reflect.Constructor 입니다. 생성자와 관련된 일반적인 작업은 다음 섹션에서 다룹니다:
- Finding Constructors 특정 파라미터를 가진 생성자를 검색하는 방법을 보여줍니다.
- Retrieving and Parsing Constructor Modifiers 생성자 선언의 제어자와 생성자에 대한 다른 정보를 얻는 방법을 보여줍니다.
- Creating New Class Instances 생성자를 호출하여 객체의 인스턴스를 생성하는 방법을 보여줍니다.
- Troubleshooting 생성자를 찾거나 호출할 때 발생할 수 있는 일반적인 오류를 설명합니다.
Finding Constructors
생성자 선언에는 이름, 제어자, 파라미터 개수 및 throw 가능한 예외 목록이 포함됩니다. java.lang.reflect.Constructor 클래스는 이 정보를 얻는 방법을 제공합니다.
ConstructorSift 예제는 클래스의 선언된 생성자에서 주어진 타입의 파라미터가 있는 생성자를 검색하는 방법을 보여줍니다.
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.getName()에 설명되어 있습니다. 첫 번째로 나열된 생성자는 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>
생성자에 대한 예외 타입은 메서드와 비슷한 방식으로 검색할 수 있습니다. 자세한 내용은 Obtaining Method Type Information 섹션에 설명된 MethodSpy 예제를 참조하세요.
Retrieving and Parsing Constructor Modifiers
자바 언어에서 생성자의 역할 때문에 메서드보다 의미 있는 제어자가 적습니다.
- Access modifiers: public, protected, and private
- Annotations
ConstructorAccess 예제는 지정된 액세스 제어자가 있는 주어진 클래스에서 생성자를 검색합니다. 또한 생성자가 합성(컴파일러 생성)인지 가변 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 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(); }
}
}
$ java ConstructorAccess SyntheticConstructor package-private
SyntheticConstructor(SyntheticConstructor$1)
[ synthetic=true var_args=false ]
내부 클래스의 생성자는 외부 클래스의 private 생성자를 참조하므로 컴파일러는 package-private 생성자를 생성해야 합니다. 파라미터 타입 SyntheticConstructor$1은 임의적이며 컴파일러 구현에 따라 달라집니다. 합성 또는 non-public 클래스 멤버의 존재에 따라 달라지는 코드는 이식할 수 없습니다.
생성자는 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.Charset.defaultCharset()이 리턴하는 값과 반드시 동일하지는 않습니다.) ConsoleCharset 예제는 이것을 어떻게 달성할 수 있는지를 보여줍니다.
import java.io.Console;
import java.nio.charset.Charset;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import static java.lang.System.out;
public class ConsoleCharset {
public static void main(String... args) {
Constructor[] ctors = Console.class.getDeclaredConstructors();
Constructor ctor = null;
for (int i = 0; i < ctors.length; i++) {
ctor = ctors[i];
if (ctor.getGenericParameterTypes().length == 0)
break;
}
try {
ctor.setAccessible(true);
Console c = (Console)ctor.newInstance();
Field f = c.getClass().getDeclaredField("cs");
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();
}
}
}
참고:
Class.newInstance() 는 생성자가 아규먼트가 없고 이미 접근 가능한 경우에만 성공합니다. 그렇지 않은 경우, 위의 예제에서처럼 Constructor.newInstance() 를 사용해야 합니다.
Example output for a UNIX system:
$ java ConsoleCharset
Console charset : ISO-8859-1
Charset.defaultCharset() : ISO-8859-1
Example output for a Windows system:
C:\> java ConsoleCharset
Console charset : IBM437
Charset.defaultCharset() : windows-1252
Another common application of Constructor.newInstance() is to invoke constructors which take arguments. The RestoreAliases example finds a specific single-argument constructor and invokes it:
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();
}
}
}
This example uses Class.getDeclaredConstructor() to find the constructor with a single argument of type java.util.HashMap. Note that it is sufficient to pass HashMap.class since the parameter to any get*Constructor() method requires a class only for type purposes. Due to type erasure, the following expression evaluates to true:
HashMap.class == defaultAliases.getClass()
The example then creates a new instance of the class using this constructor with Constructor.newInstance().
$ java RestoreAliases
Mail keys:
Duke
Fang
'Spring Framework > Reflection' 카테고리의 다른 글
Java의 배열과 열거형: 리플렉션을 통한 심층 탐구 (0) | 2024.08.22 |
---|---|
Java 동적 프록시 클래스 활용법 (0) | 2024.08.20 |
리플렉션: 멤버 탐구 - 메서드(Methods) (0) | 2024.08.19 |
리플렉션: 멤버 탐구 - 필드(Fields) (0) | 2024.08.19 |
리플렉션 API: 클래스 탐구하기 (2) | 2024.08.19 |