JAVA/Reflection
필드 검사와 배열 검사 with Reflection
최-코드
2024. 11. 24. 15:26
필드 클래스
- 클래스 내의 각 필드를 나타내며, 필드명, 필드 타입 등의 정보를 가지고 있다. Field 인스턴스로 나타낼 수 있다.
- 필드 클래스를 얻는 방법은 아래와 같다.
- Class.getDeclaredFields()
- 접근 제어자에 상관없이 모든 필드를 가져온다. 이 때 상속된 필드는 제외한다.
- Class.getFields()
- 모든 public 필드를 얻을 수 있다. 상위 클래스로부터 상속된 필드 모두 가져온다.
- 위의 두 가지 방법 모두 인자로 필드명을 써주면 해당하는 필드에 대한 필드 클래스를 가져올 수 있다.
- Class.getDeclaredFields()
synthetic 필드
- 클래스에 명시하여 선언한 필드외에도 자바 컴파일러가 내부 사용을 위해 인위적으로 필드를 생성한 필드를 말한다.
- 실행할 때 Reflection을 이용해 찾지 않는 한 보이지 않는 필드이다.
- 컴파일러에 특정한 이름을 갖고 주로 변경하지 않는다.
- synthetic 필드인지 확인하려면 Field.isSynthetic()을 이용하면 된다.
- 변경을 가할 시에 예기치 못한 에러가 발생할 수 있으므로 변경을 피해야 한다.
Field.get(Object arg)
- A 클래스에 속한 B 필드에 대해 A 클래스에 대한 인스턴스를 건네면 해당 인스턴스에서 B 필드 값을 가져온다.
- 예를 들어 Movie 클래스가 있고 price라는 필드가 있을 때 amazon Movie 객체의 price의 값이 1000일 때 priceField.get(amazon)과 같이 인스턴스를 넣으면 1000의 값이 반환된다.
- 정적 필드에 대해서는 인스턴스를 넣어도 무시하고 클래스 자체에서 값을 가져오기에 null을 넣어도 정상적으로 출력한다.
Example
public class Main {
public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException {
Movie movie = new Movie("Lord of the Rings", 2001, 12.99, true, Category.ADVENTURE);
// printDeclaredFieldsInfo(movie.getClass(), movie);
Field minPriceStaticField = Movie.class.getDeclaredField("MINIMUM_PRICE");
System.out.printf("static MINIMUM_PRICE value : %f\n", minPriceStaticField.get(null));
}
public static <T> void printDeclaredFieldsInfo(Class<? extends T> clazz, T instance) throws IllegalAccessException {
for (Field field : clazz.getDeclaredFields()) {
System.out.printf("Field name : %s type : %s\n", field.getName(), field.getType().getName());
System.out.printf("Is synthetic field : %s\n", field.isSynthetic());
System.out.printf("Field value is : %s\n", field.get(instance));
System.out.println();
}
}
public static class Movie extends Product {
...
public class MovieStats {
...
}
}
public static class Product {
...
}
public enum Category {
...
}
}
제한된 필드 접근 : 제한된 생성자 접근과 마찬가지로 field.setAccessible(true) 해주면 된다. 같은 원리로 동작한다.
배열과 배열 요소 타입 식별
- 자바에서 배열은 특수한 클래스이기에 Class<?>로 배열에 대한 정보를 추출할 수 있다.
자바의 배열은 JVM에 의해 내부적으로 특별한 클래스 형태로 구현된다.
- Class.isArray()를 통해 배열 클래스인지 확인할 수 있다.
- Class.getComponentType()을 통해 배열 요소의 타입을 알 수 있다. 만약 배열이 아닌 것에 사용하면 null값을 반환한다.
java.lang.reflect.Array Class
- 배열 객체에서 데이터를 얻을 수 있게 도와주는 헬퍼 클래스로, 모든 메소드가 static 메소드로 이뤄져 있다.
- Array.getLength(Object arrayObject) : 유형 및 차원에 관계없이 모든 배열의 길이를 알려준다.
- Array.get(Object arrayObject, int index) : 주어진 인덱스의 특정 요소를 알려준다.
Example
public class Main {
public static void main(String[] args) {
int[] oneDimensionalArray = {1, 2};
double[][] twoDimensionalArray = {{1.5, 2.5}, {3.5, 4.5}};
// inspectArrayObject(twoDimensionalArray);
inspectArrayValues(twoDimensionalArray);
}
public static void inspectArrayValues(Object arrayObject) {
int arrayLength = Array.getLength(arrayObject);
for (int i = 0; i < arrayLength; i++) {
Object arrayElement = Array.get(arrayObject, i);
if (arrayElement.getClass().isArray()) {
inspectArrayValues(arrayElement);
} else {
System.out.print(arrayElement + " ");
}
}
}
public static void inspectArrayObject(Object arrayObject) {
Class<?> clazz = arrayObject.getClass();
System.out.printf("Is array : %s\n", clazz.isArray());
Class<?> arrayComponentType = clazz.getComponentType();
System.out.printf("This is an array of type : %s\n", arrayComponentType.getTypeName());
}
}
활용 Example
public class Main {
public static void main(String[] args) throws IllegalAccessException {
Address address = new Address("Main Street", (short) 1);
Company company = new Company("Udemy", "San Francisco", new Address("Harrison Street", (short) 600));
Person person = new Person("John", true, 29, 100.555f, address, company);
String json1 = objectToJson(person);
System.out.println(json1);
Actor actor1 = new Actor("Elijah Wood", new String[]{"Lord of the Rings", "The Good Son"});
Actor actor2 = new Actor("Ian McKellen", new String[]{"X-Men", "Hobbit"});
Actor actor3 = new Actor("Orlando Bloom", new String[]{"Pirates of the Caribbean", "Kingdom of Heaven"});
Movie movie = new Movie("Lord of the Rings", 8.8f, new String[]{"Action", "Adventure", "Drama"},
new Actor[]{actor1, actor2, actor3});
String json2 = objectToJson(movie);
System.out.println(json2);
}
public static String objectToJson(Object instance) throws IllegalAccessException {
Field[] fields = instance.getClass().getDeclaredFields();
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("{");
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
field.setAccessible(true);
if (field.isSynthetic()) continue;
stringBuilder.append(formatStringValue(field.getName()));
stringBuilder.append(":");
if (field.getType().isPrimitive()) {
stringBuilder.append(formatPrimitiveValue(field.get(instance), field.getType()));
} else if (field.getType().equals(String.class)) {
stringBuilder.append(formatStringValue(field.get(instance).toString()));
} else if (field.getType().isArray()) {
stringBuilder.append(arrayToJson(field.get(instance)));
} else {
stringBuilder.append(objectToJson(field.get(instance)));
}
if (i != fields.length - 1) stringBuilder.append(",");
}
stringBuilder.append("}");
return stringBuilder.toString();
}
private static String arrayToJson(Object arrayInstance) throws IllegalAccessException {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("[");
int arrayLength = Array.getLength(arrayInstance);
Class<?> componentType = arrayInstance.getClass().getComponentType();
for (int i = 0; i < arrayLength; i++) {
Object element = Array.get(arrayInstance, i);
if (componentType.isPrimitive()) {
stringBuilder.append(formatPrimitiveValue(element, componentType));
} else if (componentType.equals(String.class)) {
stringBuilder.append(formatStringValue(element.toString()));
} else {
stringBuilder.append(objectToJson(element));
}
if (i != arrayLength - 1) stringBuilder.append(",");
}
stringBuilder.append("]");
return stringBuilder.toString();
}
private static String formatPrimitiveValue(Object instance, Class<?> type) {
if (type.equals(boolean.class) || type.equals(int.class)
|| type.equals(long.class) || type.equals(short.class)) {
return instance.toString();
} else if (type.equals(double.class) || type.equals(float.class)) {
return String.format("%.2f", instance);
}
throw new RuntimeException(String.format("Unsupported field type: %s", type.getName()));
}
private static String formatStringValue(String value) {
return String.format("\"%s\"", value);
}
}