본문 바로가기

Language/Java

Java Reflection(2)

Field - 필드 변수에 접근

Java 클래스에서 필드는 클래스나 인터페이스 안에서 선언된 변수를 의미하며 이는 정적 상수나 객체 변수같이 클래스 내에서 선언된 변수가 될 수 있다.
Field 클래스는 필드를 나타내며 필드의 이름과 타입 등 다양한 특성 정보를 가지고 있다.

 

이러한 Field 객체를 생성하는 것에는 Constructor를 생성하는 것처럼 Field 객체도 여러가지 방법이 존재한다.

Class.getDeclaredFields()

클래스에 선언된 접근 제어자에 상관없이 모든 필드를 배열로 반환한다. - 상속받은 필드는 제외한다.

 

Class.getFields()

public으로 선언된 모든 필드를 배열로 반환한다. - 상속받은 필드도 포함한다.

 

Class.getDeclaredField(fieldName) | Class.getField(fieldName) 

필드의 이름을 알 때, 필드 이름을 전달해 필드 객체를 만들 수 있다.  

 

class Dog extends Animal {
  private DogType type;
  protected String home;

}

class Animal {
  public int age;
  public String name;
}

enum DogType {
  PUPPY,
  ADULT,
  SENIOR
}

위와 같은 객체가 있다 할 때 이들의 필드를 출력해보자.

  private static void printDeclaredFieldsInfo(Class<?> clazz) {
    System.out.println("Declared Fields");
    for (Field field : clazz.getDeclaredFields()) {
      System.out.println("Field name : " + field.getName() + " | type : " + field.getType().getName());
    }
  }

필드를 출력하는 함수를 만들고 Dog.class를 출력해 보았을 때

Field name : type | type : reflectionTest.test.DogType
Field name : home | type : java.lang.String

상속 받은 필드를 제외한 모든 필드가 잘 나온다.


synthetic 필드

클래스에 명시하여 선언한 필드 외에도 Java 컴파일러가 내부 사용을 위해 인위적으로 생성하는 특별한 필드이다.
실행할 때, Reflection을 사용해 찾지 않는 한 보이지 않는다.

 

이러한 필드는 우리 눈에는 보이지 않는 연결 관계가 있을 때 컴파일러에 의해 생성된다.

 

비 정적 내부 클래스를 생각해보자 비 정적 내부 클래스는 부모 클래스에서 정의된 필드를 가져다 쓸 수 있다. 이는 즉 부모 클래스와 우리 눈에는 보이지 않는 연결 관계가 있다는 것을 알 수 있다.

class Dog extends Animal {
  private DogType type;
  private String name;
  private int age;

  public class DogState {
    private String state;
    public String getInfo() {
      return String.format("name : %s | age : %d | state : %s", name, age, state);
    }
  }
}

위와 같이 Dog클래스의 내부 클래스로 DogState가 있을 때 DogState의 필드 정보를 출력해보자. 또한 Field객체의 isSynthetic() 메서드를 사용해서 synthetic 필드인지도 확인해보자.

Field name : state | type : java.lang.String
isSynthetic : false

Field name : this$0 | type : reflectionTest.test.Dog
isSynthetic : true

this$0이라는 Dog 타입의 synthetic 필드가 선언되어있다. 이렇게 내부적으로 두 클래스가 연결되어 있다는 것을 알 수 가 있다. 


지금까지 나온 개념들을 이용해서 .주어진 클래스 인스턴스의 필드 값을 읽어보자.

private static <T> void  printDeclaredFieldsInfo(T instance, Class<? extends T> clazz) throws IllegalAccessException {
  for (Field field : clazz.getDeclaredFields()) {
    field.setAccessible(true);
    System.out.println("Field name : " + field.getName() + " | type : " + field.getType().getName());
    System.out.println("Field value : " + field.get(instance));
    field.setAccessible(false);
  }
}

 

값을 읽을 인스턴스와 해당 인스턴스의 클래스 정보를 받아서 각각의 필드 정보를 출력한다.

private으로 선언된 필드의 값 정보를 읽기 위해서는 Accessible의 값을 true로 바꾸어 주어야 읽을 수 있다. 

Dog dog = new Dog("예삐", 2, DogType.ADULT);
printDeclaredFieldsInfo(dog, Dog.class);

 

인스턴스 정보와 클래스 정보를 넘긴 결과를 확인해보면

Field name : name | type : java.lang.String
Field value : 예삐

Field name : age | type : int
Field value : 2

Field name : type | type : reflectionTest.test.DogType
Field value : ADULT

담은 정보를 이상 없이 잘 읽어온 것을 확인할 수 있다.


이번엔 배열에 대해 알아보자. 배열은 Class API를 사용해 모든 차원의 배열에 관한 기본 정보를 알 수 있다.

 

Class.isArray()

함수를 통해 객체가 배열임을 확인하고

 

getComponentType()

메서드로 배열의 원소의 타입을 알 수 있다. 

 

위의 함수와 메서드로 배열의 유형을 알수가 있다.

 

이제 Reflection을 이용해 런타임으로 배열 객체의 개별 요소를 다루는 법을 알아보자.

 Array 클래스

Array 클래스는 배열 객체에서 데이터를 얻을 수 있게 도와주는 정적 메서드만을 가지는 헬퍼 클래스이다. 

 

Array.getLength(Object objectArray)

유형 및 차원에 관계 없이 배열의 길이를 알려준다.

 

Array.get(Object objectArray, int index)

주어진 인덱스의 값을 알려준다. 

 

Array.newInstance(Class<?> componentType, int length)

컴포넌트 타입과, 배열의 길이를 매개변수로 받아 해당 타입의 배열을 생성한다. 

 

Array.set(Object objectArray, int index, Object value)

배열과 인덱스, 값을 받아 해당 배열의 알맞는 인덱스에 값을 넣는다.

 

핵심이라 할 수 있는 함수와 메서드들을 알게 됐다. 

위의 메서드들을 이용해서 배열 정보를 출력해보자.

Object[] objectArray = {1, "banana", 0.5, -0.1f, 'a', false, new int[]{1, 2, 3}};

Class<?> clazz = objectArray.getClass();
System.out.println("Array type: " + clazz.getComponentType().getSimpleName());
inspectArrayValue(objectArray);

위와 같은 Object 배열있고 해당 Object배열을 inspectArrayValue라는 함수를 통해서 내용을 출력해보자.

public static void inspectArrayValue(Object arrayObject) {
  int arrayLength = Array.getLength(arrayObject);
  System.out.print("[");

  for(int i = 0 ;i < arrayLength; i++){
    Object element = Array.get(arrayObject, i);
    if(element.getClass().isArray()){
      inspectArrayValue(element);
    }else{
      System.out.print(element + "(" + element.getClass().getSimpleName() + ")");
    }
    if(i < arrayLength - 1){
      System.out.print(", ");
    }
  }
  System.out.print("]");
}

위와 같은 결과로 잘 나오는 것을 알 수 있다.


Reflection API를 사용한 JSON 직렬화

이번에는 Reflection Api를 사용해서 Java 객체를 JSON 객체로 직렬화를 진행해보겠다.

어떤 객체가 올지 모르는 상황이기에 Reflection은 해당 작업에 있어서 완벽한 도구라 할 수 있다.

public static String objectToJson(Object instance, int indentSize) throws IllegalAccessException {
        Field[] fields = instance.getClass().getDeclaredFields();
        StringBuilder stringBuilder = new StringBuilder();

        stringBuilder.append(indent(indentSize));
        stringBuilder.append("{");
        stringBuilder.append("\n");

        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
            field.setAccessible(true);

            if (field.isSynthetic()) {
                continue;
            }

            stringBuilder.append(indent(indentSize + 1));
            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), indentSize + 1));
            } else {
                stringBuilder.append(objectToJson(field.get(instance), indentSize + 1));
            }

            if (i != fields.length - 1) {
                stringBuilder.append(",");
            }
            stringBuilder.append("\n");
        }

        stringBuilder.append(indent(indentSize));
        stringBuilder.append("}");
        return stringBuilder.toString();
    }

    private static String arrayToJson(Object arrayInstance, int indentSize) throws IllegalAccessException {
        StringBuilder stringBuilder = new StringBuilder();

        int arrayLength = Array.getLength(arrayInstance);

        Class<?> componentType = arrayInstance.getClass().getComponentType();

        stringBuilder.append("[");
        stringBuilder.append("\n");

        for (int i = 0; i < arrayLength; i++) {
            Object element = Array.get(arrayInstance, i);

            if (componentType.isPrimitive()) {
                stringBuilder.append(indent(indentSize + 1));
                stringBuilder.append(formatPrimitiveValue(element, componentType));
            } else if (componentType.equals(String.class)) {
                stringBuilder.append(indent(indentSize + 1));
                stringBuilder.append(formatStringValue(element.toString()));
            } else {
                stringBuilder.append(objectToJson(element, indentSize + 1));
            }

            if (i != arrayLength - 1) {
                stringBuilder.append(",");
            }
            stringBuilder.append("\n");
        }

        stringBuilder.append(indent(indentSize));
        stringBuilder.append("]");
        return stringBuilder.toString();
    }

    private static String indent(int indentSize) {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < indentSize; i++) {
            stringBuilder.append("\t");
        }
        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("%.02f", instance);
        }

        throw new RuntimeException(String.format("Type : %s is unsupported", type.getTypeName()));
    }

    private static String formatStringValue(String value) {
        return String.format("\"%s\"", value);
    }
Request.Header[] header = new Request.Header[]{
  new Request.Header("Content-Type", "application/json;charset=UTF-8"),
  new Request.Header("Accept", "application/json"),
};
Request request = new Request(header, new Actor[]{actor1, actor2});

String requestJson = objectToJson(request, 0);

System.out.println(requestJson);
{
    "header":[
	    {
            "name":"Content-Type",
            "value":[
                "application/json;charset=UTF-8"
            ]
        },
	    {
            "name":"Accept",
            "value":[
                "application/json"
            ]
	    }
    ],
	"body":[
	    {
            "name":"Elijah Wood",
            "knownForMovies":[
			    "Lord of the Rings",
			    "The Good Son"
            ]
	    },
	    {
            "name":"Ian McKellen",
            "knownForMovies":[
                "X-Men",
                "Hobbit"
            ]
	    }
	]
}

 

 

'Language > Java' 카테고리의 다른 글

Java native 키워드로 Rust코드와 연동하기  (2) 2024.09.19
Java Modifier(제어자)  (1) 2024.09.17
Java Reflection(3)  (1) 2024.09.15
Java - unchecked casting  (1) 2024.09.13
Java Reflection(1)  (0) 2024.08.21