JAVA/Reflection

필드 검사와 배열 검사 with Reflection

최-코드 2024. 11. 24. 15:26

필드 클래스

  • 클래스 내의 각 필드를 나타내며, 필드명, 필드 타입 등의 정보를 가지고 있다. Field 인스턴스로 나타낼 수 있다. 
  • 필드 클래스를 얻는 방법은 아래와 같다.
    • Class.getDeclaredFields()
      • 접근 제어자에 상관없이 모든 필드를 가져온다. 이 때 상속된 필드는 제외한다.
    • Class.getFields()
      • 모든 public 필드를 얻을 수 있다. 상위 클래스로부터 상속된 필드 모두 가져온다.
    • 위의 두 가지 방법 모두 인자로 필드명을 써주면 해당하는 필드에 대한 필드 클래스를 가져올 수 있다.

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);
    }
}