콘텐츠로 이동

2021 06 07

2021-06-07

리플렉션

  • 정의한 Class에 접근해보기 - 모든 클래스를 로딩한 후, Class의 인스턴스가 생김
    public class Main {
        public static void main(String[] args) throws ClassNotFoundException {
            final Class<Test> classTypeTest = Test.class;
            System.out.println("classTypeTest = " + System.identityHashCode(classTypeTest));
    
            final Test test = new Test();
            final Class<? extends Test> getClassTest = test.getClass();
            System.out.println("getClassTest = " + System.identityHashCode(getClassTest));
    
            final Class<?> classForNameTest = Class.forName("Test");
            System.out.println("classForNameTest = " + System.identityHashCode(classForNameTest));
        }
    }
    >> 결과
    classTypeTest = 460141958
    getClassTest = 460141958
    classForNameTest = 460141958
    

    - 클래스 로더에서 로딩이 완료된 클래스 같은 경우, Class 타입의 인스턴스가 힙 영역에 생기게 됨 - 해당 클래스를 위와 같은 방식으로 가져올 수 있고, 보다시피 주소가 같다!!! - 해당 클래스 인스턴스로 다음과 같은 일을 할 수 있음

    //접근 지시자 public인 필드만 가져옴
    Arrays.stream(classTypeTest.getFields()).forEach(System.out::println);
    
    //접근 지시자 상관없이 필드 다 가져옴
    Arrays.stream(classTypeTest.getDeclaredFields()).forEach(System.out::println);
    
    //접근한 필드의 값을 가져와 보자 --> static이 아니라면 object가 필요하겠지?
    Arrays.stream(classForNameTest.getDeclaredFields()).forEach(f -> {
        try {
            f.setAccessible(true);
            System.out.print(f + " = ");
            System.out.println(f.get(test));
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    });
    
    //메서드를 가져와 보자 여기서는 Object에서 상속받은 친구들까지 촤르르 나옴
    Arrays.stream(getClassTest.getMethods()).forEach(System.out::println);
    
    //생성자를 가져와 보자
    Arrays.stream(getClassTest.getConstructors()).forEach(System.out::println);
    
    //부모 클래스도 가져올 수 있음
    System.out.println(getClassTest.getSuperclass());
    
    //인터페이스도 가져올 수 있음
    Arrays.stream(getClassTest.getInterfaces()).forEach(System.out::println);
    
    //접근 제어자에 대한 정보를 T/F 판별할 수 있음
    Arrays.stream(getClassTest.getDeclaredFields()).forEach(f -> {
        final int modifiers = f.getModifiers();
        System.out.println("f = " + f);
        System.out.println(Modifier.isPrivate(modifiers));
        System.out.println(Modifier.isStatic(modifiers));
    });
    

  • 어노테이션 - 기본적으로 어노테이션은 주석! (약간의 의미와 기능이 추가된) - Default로 "Class 레벨"로 어노테이션이 유지되기 때문에, 바이트 코드 컴파일 후에는 남아있지만, 런타임 환경들어가면 호로록 날라감 - @Retention(RetentionPolicy.RUNTIME) 해당 어노테이션을 통해 런타임까지 유지하게 설정 가능 - 중요 어노테이션 - @Retention: 해당 어노테이션을 언제까지 유지할 것인가? - @Inherit: 해당 어노테이션을 하위 클래스까지 전달할 것인가? - @Target: 어디에 사용할 수 있는지? - 리플렉션 - getAnnotations(): 상속받은 어노테이션까지 조회 - getDeclaredAnnotations(): 자기 자신에만 붙어있는 어노테이션 조회
    //어떤 어노테이션이 붙어있는가?
    Arrays.stream(getClassTest.getAnnotations()).forEach(System.out::println);
    
    //해당 어노테이션의 값은 무엇인가?
    Arrays.stream(getClassTest.getAnnotations()).forEach(f -> {
       if (f instanceof MyAnnotation) {
           MyAnnotation myAnnotation = (MyAnnotation) f;
           System.out.println("myAnnotation.name() = " + myAnnotation.name());
       }
    });
    
  • 리플렉션 API 활용
    - 아래와 같이 클래스의 필드, 메서드에 해당하는 정보를 호출하고 사용할 수 있음
    //생성자를 리플렉션으로 가져와서 객체 생성하기
    final Constructor<? extends Test> constructor = getClassTest.getConstructor(null);
    final Test test1 = constructor.newInstance();
    System.out.println("test1 = " + test1);
    
    final Constructor<? extends Test> stringConstructor = getClassTest.getConstructor(String.class, String.class, String.class);
    final Test test2 = stringConstructor.newInstance("hi1", "hi2", "hi3");
    System.out.println("test2 = " + test2);
    
    //Static 필드 가져와보기 (Static)
    final Field a = getClassTest.getDeclaredField("a");
    a.setAccessible(true);
    System.out.println(a.get(null)); //오브젝트 안넘겨줘도 됨
    a.set(null, "AAAAAAAAAAAAA"); //오브젝트 안넘겨줘도 됨
    System.out.println(a.get(null)); //오브젝트 안넘겨줘도 됨
    
    //인스턴스 필드 가져와보기
    final Field c = getClassTest.getDeclaredField("c");
    c.setAccessible(true);
    System.out.println(c.get(test2)); //오브젝트 안넘겨줘도 됨
    c.set(test2, "CCCCCCCCCCCCCC "); //오브젝트 안넘겨줘도 됨
    System.out.println(c.get(test2)); //오브젝트 안넘겨줘도 됨
    
    //이번엔 메서드를 가져와보자
    final Method f = getClassTest.getDeclaredMethod("f");
    f.invoke(test1);
    
    final Method param = getClassTest.getDeclaredMethod("param", int.class, int.class);
    param.invoke(test1, 1, 3);