Reflection; Class Object

🗓️

Java Reflection 이 뭐지?

  1. JVM 기능
  2. 런타임 동안 클래스와 객체 정보를 추출할 수 있음
  3. Reflection API로 접근 가능
  4. JDK에 포함된 클래스 집합

Reflection이 할 수 있는 것

  • 소스 코드를 수정하지 않고 새로운 프로그램 순서를 만들 수 있음.
  • 실행하고 있는 클래스와 객체에 동적으로 코드 삽입.
  • 실제로 사용하는 곳
    • 로깅 프레임워크
    • ORM
    • Spring Framework
    • JUnit
    • Jackson

Spring Framework

  • @Autowired
  • @Configuration
  • @Bean
@Configuration
public class Config {
    @Bean
    public Connection createConnection() {
        //..
    }

    @Bean
    public Session createSession() {
        //..
    }
}
public class Client {
    @Autowired
    public Client (Connection connection, Session session) {
        this.connection = connection;
        this.session = session;
    }

    public void invoke() {
        //..
    }
}
  • bean을 정의하고 의존성을 주입하는 과정이 reflection을 통해 이뤄짐.

Jackson library

  • Reflection을 사용해 클래스를 확인하고 필드를 분석한다.
public class Listen {
    private String server;
    private int port;
    private String upstream;
    private List<String> alias;

    // getter..

    // setter..
}
{
    "server" : "canxan",
    "port" : 443,
    "upstream" : "192.168.1.1"
    "alias" :[
        "canxan.com",
        "www.canxan.com"
    ]
}
Listen listen = objectMapper.readValue("...", Listen.class);

String listenJson = objectMapper.writeValueAsString(listen);

Reflection 을 사용하면서 주의해야 할 점

  • 런타임에서 보이지 않는 구조에 접근할 수 있음
  • 목적에 맞지 않게 사용되면 코드를 변경해야 하는데 코드가 늦게 실행되거나 실행되면 위험한 상황이 발생함
  • 보통 reflection에서 문제가 발생하면 어플리케이션에 심각한 문제가 생김
  • “With great power, comes great responsibility”

Class<?> 오브젝트

  • 객체의 런타임에 관한 정보를 포함하고 있음
  • 어플리케이션의 클래스 정보
  • 메서드와 필드 정보
  • 상속 또는 인터페이스의 정보

Class<?> 에 접근하기

1. Object.getClass()

String stringObject = "아이유";

Singer singer = new Singer();

Map<String, Integer> map = new HashMap<>();


Class<String> stringClass = stringObject.getClass();

Class<Singer> singerClass = singer.getClass();

Class<?> mapClass = map.getClass();
  • 마지막의 Map 객체는 인터페이스가 아니라 HashMap 클래스를 나타냄.
    변수의 런타임 타입이기 때문임.
  • Primitive 타입은 객체가 아님.

2. 타입 이름에 ‘.class’ 붙이기

  • 클래스에 인스턴스가 없을 때 사용됨
  • 이때 primitive 타입에 대해서 얻을 수 있음.
Class booleanType = boolean.class;

Class intType = int.class;

Class doubleType = double.class;
  • 메서드 파라미터나 클래스 맴버를 확인할때 원시타입일 수 있기 때문에.

3. Class.forName()

  • 정적 메서드
  • 동적으로 패키지명이 포함된 클래스를 찾을 수 있음.
  • Primitive 타입은 여전히 사용할 수 없음
  • 잘못 입력하는 경우 ClassNotFoundException 발생
  • 이때가 가장 위험함.
Class<?> stringType = Class.forName("java.lang.String");

Class<?> singerType = Class.forName("artist.Singer");

Class<?> genreType = Class.forName("artist.Singer$Genre");


package artist;

class Singer {
    static class Genre {
        //..
    }
}
  • 사용자 정의 구성 파일 (예 : bean xml) 에서 전달될때 많이 사용 됨.
<bean id="artist" class="artist.Singer">
    <property name="gender" value="woman" />
</bean>
Class<?> artist = Class.forName(value);
  • 외부 라이브러리인 경우 컴파일 할때 사용되기도 함.
  • 컴파일 후 실행 시 해당 클래스가 어플리케이션의 클래스 경로에 추가됨.

Java wildcard

Class<?>
  • Java 제네릭에서는 Integer 클래스는 Number 클래스를 상속받고, Stirng 클래스는 CharSequence 클래스를 구현하지만 List<Integer>List<Number>를 상속 받는게 아님.
  • 그러나 wildcard 라고 부르는 List<?>는 제네릭 타입이면 전부 해당하는 상위 타입임.
  • 즉, List<T>List<?>를 상속받음.
  • 이는 Class<T>Class<?> 관계도 마찬가지임!
    • Class<?>를 사용하면 모든 파라미터 타입의 클래스 객체를 설명할 수 있다.
    • Class<?>는 모든 타입의 Class<T>에 대한 상위 타입임.

언제 쓰는건데?

컴파일 하는 동안 컴파일러가 제네릭 타입을 정확하게 알 수 없을 때

Class<?> singerType = Class.forName("artist.Singer");

클래스의 제네릭 파라미터가 제네릭 타입이라면 wildcard를 사용해야 함.

Map<String, Integer> map = new HashMap<>();

Class<Singer> singerClass = singer.getClass();

wildcard 를 사용해서 클래스가 메서드로 전달되는걸 제한할 수 있음

// Collection 을 상속받는 타입으로만 제한
public List<String> findAllMethods(Class<? extends Collection> clazz) {
    //..
}

Class<?> 사용해보기

package o.e._01;

import java.util.HashMap;
import java.util.Map;

public class FirstReflection {

    public static void main(String[] args) throws ClassNotFoundException {
        Class<String> stringClass = String.class;

        Map<String, Integer> mapObject = new HashMap<>();

        Class<?> hashMapClass = mapObject.getClass();

        Class<?> squareClass = Class.forName("o.e._01.FirstReflection$Square");

        printClassInfo(stringClass, hashMapClass, squareClass);
    }
    private static void printClassInfo(Class<?> ... classes) {
        for (Class<?> clazz : classes) {
            System.out.println(String.format("클래스 이름 : %s, 클래스 패키지 명 : %s",
                    clazz.getSimpleName(),
                    clazz.getPackageName()));
            Class<?>[] implementedInterfaces = clazz.getInterfaces();

            for (Class<?> implementedInterface : implementedInterfaces) {
                System.out.println(String.format("%s 클래스 implement : %s",
                        clazz.getSimpleName(),
                        implementedInterface.getSimpleName()));
            }

            System.out.println();
            System.out.println();
        }
    }

    private static class Square implements Drawable {
        @Override
        public int gerNumberOfCorners() {
            return 4;
        }
    }

    private static interface Drawable {
        int gerNumberOfCorners();
    }

    private enum Color {
        BLUE,
        RED,
        GREEN
    }
}
> Task :FirstReflection.main()
클래스 이름 : String, 클래스 패키지 명 : java.lang
String 클래스 implement : Serializable
String 클래스 implement : Comparable
String 클래스 implement : CharSequence
String 클래스 implement : Constable
String 클래스 implement : ConstantDesc


클래스 이름 : HashMap, 클래스 패키지 명 : java.util
HashMap 클래스 implement : Map
HashMap 클래스 implement : Cloneable
HashMap 클래스 implement : Serializable


클래스 이름 : Square, 클래스 패키지 명 : o.e._01
Square 클래스 implement : Drawable
  • Map의 경우 런타임에서 인터페이스가 아닌 HashMap클래스가 됨.
var circleObject = new Drawable() {
    @Override
    public int gerNumberOfCorners() {
        return 0;
    }
};

printClassInfo(stringClass, hashMapClass, squareClass,
        Collection.class, boolean.class, int[][].class, Color.class,
        circleObject.getClass());

//...


    private static void printClassInfo(Class<?> ... classes) {
        for (Class<?> clazz : classes) {
            System.out.println(String.format("클래스 이름 : %s, 클래스 패키지 명 : %s",
                    clazz.getSimpleName(),
                    clazz.getPackageName()));
            Class<?>[] implementedInterfaces = clazz.getInterfaces();

            for (Class<?> implementedInterface : implementedInterfaces) {
                System.out.println(String.format("%s 클래스 implement : %s",
                        clazz.getSimpleName(),
                        implementedInterface.getSimpleName()));
            }

            System.out.println("배열 : " + clazz.isArray());
            System.out.println("원시타입 : " + clazz.isPrimitive());
            System.out.println("열거타입 : " + clazz.isEnum());
            System.out.println("인터페이스 : " + clazz.isInterface());
            System.out.println("익명클래스 : " + clazz.isAnonymousClass());

            System.out.println();
            System.out.println();
        }
    }
클래스 이름 : Collection, 클래스 패키지 명 : java.util
Collection 클래스 implement : Iterable
배열 : false
원시타입 : false
열거타입 : false
인터페이스 : true
익명클래스 : false


클래스 이름 : boolean, 클래스 패키지 명 : java.lang
배열 : false
원시타입 : true
열거타입 : false
인터페이스 : false
익명클래스 : false


클래스 이름 : int[][], 클래스 패키지 명 : java.lang
int[][] 클래스 implement : Cloneable
int[][] 클래스 implement : Serializable
배열 : true
원시타입 : false
열거타입 : false
인터페이스 : false
익명클래스 : false


클래스 이름 : Color, 클래스 패키지 명 : o.e._01
배열 : false
원시타입 : false
열거타입 : true
인터페이스 : false
익명클래스 : false


클래스 이름 : , 클래스 패키지 명 : o.e._01
 클래스 implement : Drawable
배열 : false
원시타입 : false
열거타입 : false
인터페이스 : false
익명클래스 : true