기본적으로 Java에서는 모든 클래스가 toString() 함수를 지원한다. 따라서 라이브러리에 선언되어 있는 클래스들은 .toString() 함수만 호출하면 해당 클래스 내부에 있는 값들을 출력해 낼 수 있다. 하지만 사용자가 직접 선언한 클래스는 toString() 함수 호출만 가지고 내부 변수에 가지고 있는 값들을 확인하기가 어렵다.

 

예를 들어 아래와 같은 클래스를 선언하면

class TestClass{
    short s = 1;
    int i = 10;
    long l = 20;
    String name = "nameString";
    Long L = new Long(100);
    List<String> list = new ArrayList<String>();
    Map<String, String> map = new HashMap<String, String>();

    public TestClass(){
        list.add("string1");
        list.add("string2");
        list.add("string3");

        map.put("key1", "value1");
        map.put("key2", "value2");
        map.put("key3", "value3");
    }
}

이후 toString() 함수를 호출해보면

TestClass tc = new TestClass();
System.out.println(tc.toString()); // MyPattern.reflection.TestClass@15db9742

패키지 경로에 따라서 조금씩 다를 수는 있지만 기본적으로는 내부에 있는 변수들의 값이 나오지는 않는다. 그래서 새로 클래스를 정의할 때마다 toString() 함수를 재정의(override) 할 필요가 있다. 하지만 Java 프로그래밍을 하면서 엄청나게 많은 클래스들을 만들텐데 새로 만드는 클래스마다 각 클래스에 필요한 toString() 함수를 재정의 하기는 너무 힘든 일이다.

 

따라서 재정의를 하지 않으면서 범용으로 사용할 toString 클래스를 만들어 보자.

 

public class ToString {  
    public static String toString(Object object){
        Field []fields = object.getClass().getDeclaredFields();

        String str = "{";

        for(Field field : fields){
            field.setAccessible(true);
            try {
                String type =               
                    field.getType().toString().
                    substring(field.getType().toString().lastIndexOf(".") + 1);

                if(field.getType().toString().startsWith("class ") &&           
                   !field.getType().toString().startsWith("class java.lang.")){
                   // primitive 타입이나 라이브러리에서 제공하는 객체가 아닐때 toString을 재귀적 호출
                   str += type + " " + field.getName() + 
                          toString(field.get(object)) + " ";
                }
                else{
                   str += type + " " + field.getName() + ":" + 
                          field.get(object) + " ";   
                }
            } catch (IllegalArgumentException e) {
            } catch (IllegalAccessException e) {
            }
        }
        return str + "} ";
    }
    public static void main(String[] args) {
        AllTypes types = new AllTypes();
        System.out.println(ToString.toString(types));
        /* 실행 결과
        {short s:1 int i:1 long l:1 Short S:0 Integer I:0 Long L:0 InnerClass inner{int a:100 long b:200 String name:innerClass }  List list:[] Map map:{} } 
        */
    }
}

getClass()는 Reflection에 활용되는 클래스 객체를 가지고 오는 함수이다. 즉, object 객체는 .class 객체를 가지고 있는데, 객체의 본래 타입에 따라서 .class 객체는 모두 다르다. getDeclaredFields() 함수는 클래스 객체로부터 선언된 모든 Field 객체들을 배열 형태로 가져오는 함수다. .class 객체가 객체의 본래 타입에 따라 다르기 때문에 getDeclaredFields() 함수를 통해 나오게 되는 Field[]도 모두 다르다.

 

for문 안에 setAccessible(true) 함수 호출되는 부분의 경우는, 만약 Field가 public이 아닌 경우 캡슐화로 인하여 그 값에 접근하는 것이 불가능한데 Reflection은 이 함수를 통해서 접근이 가능하도록 만들 수 있다.(이 부분은 아직도 이슈가 많은 부분이라 프로그래머 마다 찬반 의견이 다르다. 하지만 유용성에 대해서는 아무도 부인하지는 못할 것이다.)

 

  • String getType() : type에 대한 클래스 객체(Class object)를 반환하는 함수  --> 타입을 반환하긴 하는데 primitive 타입이 아닌 경우 타입이 장황하게 출력될 수 있다. 예를 들어 Long 타입의 경우 class java.lang.Long 이라고 출력됨. 따라서 위와 같이 줄이는 부분을 만든다.
  • String getName() : 필드명을 String 객체로 반환하는 함수
  • Object get(object) : Field가 값으로 가지고 있는 객체를 반환하는 함수

약간 덧붙이자면, 일단 field가 null인 경우 동작하지 않을 수 있으므로 null 체크도 들어가는게 좋다.

그리고 배열의 경우 위의 경우에서 모두 벗어나기 때문에 제대로 출력이 안될 수 있다. 이 부분은 필요시 직접 처리하기 바란다.

만일 ToString 클래스를 사용해야 하는 것이 불편하고, toString()을 재정의 해서 사용하고 싶다고 할 때에도 이 ToString 클래스는 유용하다. 클래스마다 toString() 함수를 재정의 할 때 다음과 같이 동일하게 해주면 된다.

@Override
public String toString() {
    return ToString.toString(this);
}

 

 

 

'Java (+ Spring)' 카테고리의 다른 글

Spring: Spring MVC 설정 하기  (0) 2020.07.26
Android: tutorial page  (0) 2020.07.18
Android: Launch screen  (0) 2020.07.16
Java 라이브러리(.jar) 동적 로딩: DynamicJarLoader  (0) 2020.07.08
enum의 활용법 - 계산기  (0) 2020.07.07

+ Recent posts