리플렉션 API는 무엇을 위한 것입니까?
Java의 리플렉션 메커니즘을 통해 개발자는 이름을 몰라도 런타임에 클래스, 인터페이스, 필드 및 메서드에 대한 정보를 변경하고 얻을 수 있습니다.
Reflection API를 사용하면 새 개체를 만들고, 메서드를 호출하고, 필드 값을 가져오거나 설정할 수도 있습니다.
리플렉션을 사용하여 수행할 수 있는 모든 작업의 목록을 작성해 보겠습니다.
- 객체의 클래스 식별/결정
- 클래스 수정자, 필드, 메소드, 상수, 생성자 및 수퍼클래스에 대한 정보 얻기
- 구현된 인터페이스에 속하는 메서드 찾기
- 프로그램이 실행될 때까지 클래스 이름을 알 수 없는 클래스의 인스턴스를 만듭니다.
- 이름으로 인스턴스 필드의 값 가져오기 및 설정
- 이름으로 인스턴스 메서드 호출
거의 모든 최신 Java 기술은 리플렉션을 사용합니다. 오늘날 대부분의 Java/Java EE 프레임워크 및 라이브러리의 기반이 됩니다. 예를 들면 다음과 같습니다.
- 웹 애플리케이션 구축을 위한 Spring 프레임워크
- JUnit 테스트 프레임 워크
이전에 이러한 메커니즘을 접해본 적이 없다면 왜 이 모든 것이 필요한지 묻고 있을 것입니다. 대답은 매우 간단하지만 매우 모호합니다. 리플렉션은 유연성과 응용 프로그램 및 코드를 사용자 정의하는 기능을 크게 향상시킵니다.
그러나 항상 장단점이 있습니다. 몇 가지 단점을 언급해 보겠습니다.
- 애플리케이션 보안 위반. 리플렉션을 사용하면 해서는 안 되는 코드에 액세스할 수 있습니다(캡슐화 위반).
- 보안 제한. 리플렉션에는 보안 관리자를 실행하는 시스템에서 사용할 수 없는 런타임 권한이 필요합니다.
- 성능이 낮습니다. Java의 리플렉션은 로드할 클래스를 찾기 위해 클래스 경로를 스캔하여 동적으로 유형을 결정합니다 . 이로 인해 프로그램의 성능이 저하됩니다.
- 유지하기 어렵다. 리플렉션을 사용하는 코드는 읽고 디버그하기 어렵습니다. 유연성이 떨어지고 유지 관리가 더 어렵습니다.
Reflection API를 사용하여 클래스 작업
모든 리플렉션 작업은 java.lang.Class 객체로 시작됩니다. 객체의 각 유형에 대해 java.lang.Class 의 불변 인스턴스가 생성됩니다. 개체 속성 가져오기, 새 개체 만들기 및 메서드 호출을 위한 메서드를 제공합니다.
java.lang.Class 작업을 위한 기본 메서드 목록을 살펴보겠습니다 .
방법 | 행동 |
---|---|
문자열 getName(); | 클래스의 이름을 반환 |
int getModifiers(); | 액세스 수정자를 반환합니다. |
패키지 getPackage(); | 패키지에 대한 정보를 반환합니다. |
클래스 getSuperclass(); | 상위 클래스에 대한 정보를 반환합니다. |
클래스[] getInterfaces(); | 인터페이스 배열을 반환합니다. |
생성자[] getConstructors(); | 클래스 생성자에 대한 정보를 반환합니다. |
Fields[] getFields(); | 클래스의 필드를 반환합니다. |
필드 getField(String 필드명); | 클래스의 특정 필드를 이름으로 반환 |
메소드[] getMethods(); | 메서드 배열을 반환합니다. |
이들은 클래스, 인터페이스, 필드 및 메소드에 대한 데이터를 가져오는 가장 중요한 메소드입니다. 필드 값을 가져오거나 설정하고 비공개 필드 에 액세스할 수 있는 메서드도 있습니다 . 잠시 후에 살펴보겠습니다.
바로 지금 우리는 java.lang.Class 자체를 가져오는 것에 대해 이야기할 것입니다. 세 가지 방법이 있습니다.
1. Class.forName 사용
실행 중인 애플리케이션에서 클래스를 가져오려면 forName(String className) 메서드를 사용해야 합니다 .
이 코드는 리플렉션을 사용하여 클래스를 만드는 방법을 보여줍니다. 작업할 수 있는 Person 클래스를 만들어 봅시다 .
package com.company;
public class Person {
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
예제의 두 번째 부분은 리플렉션을 사용하는 코드입니다.
public class TestReflection {
public static void main(String[] args) {
try {
Class<?> aClass = Class.forName("com.company.Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
이 접근 방식은 클래스의 전체 이름을 알고 있는 경우에 가능합니다. 그런 다음 정적 Class.forName() 메서드를 사용하여 해당 클래스를 가져올 수 있습니다. 이 방법은 기본 유형에 사용할 수 없습니다.
2. .class 사용하기
유형을 사용할 수 있지만 인스턴스가 없는 경우 유형 이름에 .class를 추가하여 클래스를 가져올 수 있습니다. 이것은 기본 유형의 클래스를 얻는 가장 쉬운 방법입니다.
Class aClass = Person.class;
3. .getClass() 사용
객체를 사용할 수 있는 경우 클래스를 가져오는 가장 쉬운 방법은 object.getClass() 를 호출하는 것입니다 .
Person person = new Person();
Class aClass = person.getClass();
마지막 두 접근 방식의 차이점은 무엇입니까?
코딩 시 관심 있는 클래스 개체를 알고 있는 경우 A.class를 사용하십시오 . 사용 가능한 인스턴스가 없으면 .class 를 사용해야 합니다 .
클래스의 메서드 가져오기
클래스의 메서드를 반환하는 메서드인 getDeclaredMethods() 및 getMethods() 를 살펴보겠습니다 .
getDeclaredMethods()는 public, private, default 및 protected 메서드를 포함하지만 상속된 메서드는 포함하지 않고 Class 객체가 나타내는 클래스 또는 인터페이스의 모든 선언된 메서드에 대한 메서드 객체를 포함하는 배열을 반환 합니다 .
getMethods() 는 Class 객체가 나타내는 클래스 또는 인터페이스의 모든 공개 메서드(클래스 또는 인터페이스에 의해 선언된 메서드와 수퍼클래스 및 수퍼인터페이스에서 상속된 메서드)에 대한 Method 객체를 포함하는 배열을 반환 합니다 .
각각의 작동 방식을 살펴보겠습니다.
getDeclaredMethods() 부터 시작하겠습니다 . 다시 두 방법의 차이점을 이해하는 데 도움이 되도록 아래에서 추상 Numbers 클래스 로 작업합니다 . Method 배열을 List<String> 으로 변환하는 정적 메서드를 작성해 보겠습니다 .
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class TestReflection {
public static void main(String[] args) {
final Method[] declaredMethods = Number.class.getDeclaredMethods();
List<String> actualMethodNames = getMethodNames(declaredMethods);
actualMethodNames.forEach(System.out::println);
}
private static List<String> getMethodNames(Method[] methods) {
return Arrays.stream(methods)
.map(Method::getName)
.collect(Collectors.toList());
}
}
이 코드를 실행한 결과는 다음과 같습니다.
shortValue
intValue
longValue
float floatValue;
이중값
이들은 Number 클래스 내부에 선언된 메소드입니다 . getMethods()는 무엇을 반환합니까? 예제에서 두 줄을 변경해 보겠습니다.
final Method[] methods = Number.class.getMethods();
List<String> actualMethodNames = getMethodNames(methods);
이렇게 하면 다음과 같은 메서드 집합이 표시됩니다.
shortValue
intValue
longValue
float floatValue;
doubleValue
대기
대기
대기 toString hashCode getClass notify notifyAll
_
모든 클래스가 Object 를 상속하기 때문에 메서드는 Object 클래스의 공용 메서드도 반환합니다.
클래스의 필드 가져오기
getFields 및 getDeclaredFields 메서드 는 클래스의 필드를 가져오는 데 사용됩니다. 예를 들어 LocalDateTime 클래스를 살펴보겠습니다 . 코드를 다시 작성합니다.
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class TestReflection {
public static void main(String[] args) {
final Field[] declaredFields = LocalDateTime.class.getDeclaredFields();
List<String> actualFieldNames = getFieldNames(declaredFields);
actualFieldNames.forEach(System.out::println);
}
private static List<String> getFieldNames(Field[] fields) {
return Arrays.stream(fields)
.map(Field::getName)
.collect(Collectors.toList());
}
}
이 코드를 실행한 결과 LocalDateTime 클래스에 포함된 필드 집합을 얻습니다.
MAX
serialVersionUID
날짜
시간
이전의 메서드 검사와 유사하게 코드를 약간 변경하면 어떤 일이 발생하는지 살펴보겠습니다.
final Field[] fields = LocalDateTime.class.getFields();
List<String> actualFieldNames = getFieldNames(fields);
산출:
최대
이제 이러한 방법의 차이점을 알아 보겠습니다.
getDeclaredFields 메소드 는 이 메소드로 표시되는 클래스 또는 인터페이스에 의해 선언된 모든 필드에 대한 Field 객체 의 배열을 반환합니다.수업물체.
getFields 메소드는 클래스 또는 인터페이스의 모든 공용 필드에 대한 Field 객체 배열을 반환합니다 .수업물체.
이제 LocalDateTime 내부를 살펴보겠습니다 .
클래스의분그리고MAX필드는 공용이므로 getFields 메서드를 통해 볼 수 있습니다. 대조적으로,날짜,시간,직렬 버전 UID메서드에는 private 수정자가 있습니다 . 즉, getFields 메서드를 통해 표시되지 않지만 getDeclaredFields를 사용하여 가져올 수 있습니다 . 이것이 비공개 필드 에 대한 필드 개체에 액세스하는 방법입니다 .
다른 방법에 대한 설명
이제 Class 클래스 의 몇 가지 메서드에 대해 이야기할 시간입니다 .
방법 | 행동 |
---|---|
getModifiers | 클래스에 대한 수정자 가져오기 |
getPackage | 클래스가 포함된 패키지 가져오기 |
getSuperclass | 상위 클래스 가져오기 |
getInterfaces | 클래스에 의해 구현된 인터페이스 배열 가져오기 |
getName | 정규화된 클래스 이름 얻기 |
getSimpleName | 클래스 이름 얻기 |
getModifiers()
수정자는 다음을 사용하여 액세스할 수 있습니다.수업물체.
수정자는 public , static , interface 등과 같은 키워드입니다 . getModifiers() 메서드를 사용하여 수정자를 얻습니다 .
Class<Person> personClass = Person.class;
int classModifiers = personClass.getModifiers();
이 코드는정수비트 필드인 변수입니다. 각 액세스 수정자는 해당 비트를 설정하거나 지워서 켜거나 끌 수 있습니다. java.lang.reflect.Modifier 클래스 의 메서드를 사용하여 수정자를 확인할 수 있습니다 .
import com.company.Person;
import java.lang.reflect.Modifier;
public class TestReflection {
public static void main(String[] args) {
Class<Person> personClass = Person.class;
int classModifiers = personClass.getModifiers();
boolean isPublic = Modifier.isPublic(classModifiers);
boolean isStatic = Modifier.isStatic(classModifiers);
boolean isFinal = Modifier.isFinal(classModifiers);
boolean isAbstract = Modifier.isAbstract(classModifiers);
boolean isInterface = Modifier.isInterface(classModifiers);
System.out.printf("Class modifiers: %d%n", classModifiers);
System.out.printf("Is public: %b%n", isPublic);
System.out.printf("Is static: %b%n", isStatic);
System.out.printf("Is final: %b%n", isFinal);
System.out.printf("Is abstract: %b%n", isAbstract);
System.out.printf("Is interface: %b%n", isInterface);
}
}
Person 선언이 어떻게 생겼는지 기억해 보세요.
public class Person {
…
}
다음과 같은 결과를 얻습니다.
공개 여부: true
정적 여부: false
최종 여부: false
추상 여부: false
인터페이스 여부: false
클래스를 추상화하면 다음과 같습니다.
public abstract class Person { … }
이 출력:
공개 여부: true
정적 여부: false
최종 여부: false
추상 여부: true
인터페이스 여부: false
액세스 한정자를 변경했습니다. 즉, Modifier 클래스 의 정적 메서드를 통해 반환되는 데이터도 변경했습니다 .
getPackage()
클래스만 알면 해당 패키지에 대한 정보를 얻을 수 있습니다.
Class<Person> personClass = Person.class;
final Package aPackage = personClass.getPackage();
System.out.println(aPackage.getName());
getSuperclass()
Class 객체가 있으면 부모 클래스에 액세스할 수 있습니다.
public static void main(String[] args) {
Class<Person> personClass = Person.class;
final Class<? super Person> superclass = personClass.getSuperclass();
System.out.println(superclass);
}
우리는 잘 알려진 Object 클래스를 얻습니다.
class java.lang.Object
그러나 클래스에 다른 상위 클래스가 있는 경우 대신 표시됩니다.
package com.company;
class Human {
// Some info
}
public class Person extends Human {
private int age;
private String name;
// Some info
}
여기에서 부모 클래스를 얻습니다.
class com.company.Human
get인터페이스()
클래스에 의해 구현된 인터페이스 목록을 얻는 방법은 다음과 같습니다.
public static void main(String[] args) {
Class<Person> personClass = Person.class;
final Class<?>[] interfaces = personClass.getInterfaces();
System.out.println(Arrays.toString(interfaces));
}
Person 클래스 를 변경하는 것을 잊지 마십시오 .
public class Person implements Serializable { … }
산출:
클래스는 많은 인터페이스를 구현할 수 있습니다. 그래서 우리는 배열을 얻습니다.수업사물. Java Reflection API에서 인터페이스는 다음으로도 표시됩니다.수업사물.
참고: 메서드는 부모 클래스가 아닌 지정된 클래스에 의해 구현된 인터페이스만 반환합니다. 클래스에 의해 구현된 전체 인터페이스 목록을 얻으려면 현재 클래스와 상속 체인의 모든 조상을 모두 참조해야 합니다.
getName() & getSimpleName() & getCanonicalName()
프리미티브, 중첩 클래스, 익명 클래스 및 String 클래스 와 관련된 예제를 작성해 보겠습니다 .
public class TestReflection {
public static void main(String[] args) {
printNamesForClass(int.class, "int class (primitive)");
printNamesForClass(String.class, "String.class (ordinary class)");
printNamesForClass(java.util.HashMap.SimpleEntry.class,
"java.util.HashMap.SimpleEntry.class (nested class)");
printNamesForClass(new java.io.Serializable() {
}.getClass(),
"new java.io.Serializable(){}.getClass() (anonymous inner class)");
}
private static void printNamesForClass(final Class<?> clazz, final String label) {
System.out.printf("%s:%n", label);
System.out.printf("\tgetName()):\t%s%n", clazz.getName());
System.out.printf("\tgetCanonicalName()):\t%s%n", clazz.getCanonicalName());
System.out.printf("\tgetSimpleName()):\t%s%n", clazz.getSimpleName());
System.out.printf("\tgetTypeName():\t%s%n%n", clazz.getTypeName());
}
}
우리 프로그램의 결과:
getName()): int
getCanonicalName()): int
getSimpleName()): int
getTypeName(): int
String.class(일반 클래스):
getName()): java.lang.String
getCanonicalName() ): java.lang.String
getSimpleName()): String
getTypeName(): java.lang.String
java.util.HashMap.SimpleEntry.class(중첩 클래스):
getName()): java.util.AbstractMap$SimpleEntry
getCanonicalName( )): java.util.AbstractMap.SimpleEntry
getSimpleName()): SimpleEntry
getTypeName(): java.util.AbstractMap$SimpleEntry
new java.io.Serializable(){}.getClass()(익명 내부 클래스):
getName() ): TestReflection$1
getCanonicalName()): null
getSimpleName()):
getTypeName(): TestReflection$1
이제 프로그램의 출력을 분석해 보겠습니다.
-
getName()은 엔터티의 이름을 반환합니다.
-
getCanonicalName()은 Java 언어 사양에 정의된 기본 클래스의 표준 이름을 반환합니다. 기본 클래스에 정식 이름이 없는 경우(즉, 로컬 또는 익명 클래스이거나 요소 유형에 정식 이름이 없는 배열인 경우) null을 반환합니다.
-
getSimpleName()은 소스 코드에 지정된 기본 클래스의 간단한 이름을 반환합니다. 기본 클래스가 익명인 경우 빈 문자열을 반환합니다.
-
getTypeName()은 이 유형의 이름에 대한 정보 문자열을 반환합니다.
GO TO FULL VERSION