.class를 왜 사용하는 걸까?

박준규·2023년 5월 21일
0

본 글은 김영한님의 스프링 핵심 원리 - 기본편을 본 이후에 작성되었습니다.

오늘 강의를 들으면서 .class를 왜 사용하는걸까? 라는 의문이 들었다.

사실 안드로이드 코드를 짤 때 intent 이후 특정 Activity를 호출하기 위해 ::class.java라는 코드를 썼었는 데, 그때는 왜 쓰는지 몰랐다.

이번에는 그냥 넘어가기 좀 그래서 알아보았다.

내가 궁금했던 부분은 @Configuration과 @Bean을 사용하여 Spring Container에 구현체를 Bean으로 등록한 이후 AnnotationConfigApplicationContext에 AppConfig.class를 넘겨줄 때 왜..? 라는 생각이 들었다.

코드는 다음과 같다.

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

1. AnnotationConfigApplicationContext()에서 받는 인자는 무엇인가?

AnnotationConfigApplicationContext 코드를 보면 다음과 같이 되어 있다.

public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
		this();
		register(componentClasses);
		refresh();
}

Class라는 Arguement를 받고 있고, 이 componentClasses를 register()라는 method로 등록하는 것을 확인할 수 있다.

그러면 .class 객체를 넘기는 데, instance를 넘기는 건지? 아니면 다른 특정값을 넘기는건지 생각이 든다.

아! 근데 확실한 것은 instance를 넘기진 않는다. 왜냐하면 new 키워드가 없기 때문이다. 그럼 AppConfig.class가 곧 Class Type이라는 건데, Class는 무엇인가?

2. Class는 무엇인가?

Class는 다음과 같이 나타나 있다.

public final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement,
                              TypeDescriptor.OfField<Class<?>>,
                              Constable {
    private static final int ANNOTATION= 0x00002000;
    private static final int ENUM      = 0x00004000;
    private static final int SYNTHETIC = 0x00001000;

    private static native void registerNatives();
    static {
        registerNatives();
    }
    
    private Class(ClassLoader loader, Class<?> arrayComponentType) {
        // Initialize final field for classLoader.  The initialization value of non-null
        // prevents future JIT optimizations from assuming this final field is null.
        classLoader = loader;
        componentType = arrayComponentType;
    }
  • 코드를 보면 public 생성자가 없다. 다 private으로 생성된다. 그러면, 해당 객체의 생성은 당연히 JVM이 알아서 통제한다.
  • 따라서 거의 모든 Java의 자료형(참조, 원시 포함)은 Class 객체로 표현할 수 있다.

그러면 .class를 쓰면 어떻게 바뀌는가?

3 .class(::class.java)를 쓰면 어떻게 바뀌는가?

.class클래스 리터럴을 나타내는 특별한 표기법이다. 이 클래스 리터럴은 class의 메타정보를 갖고 있는 데, class 리터럴을 통해 해당 클래스의 메타 정보를 갖고 올 수 있다.

무엇인지는 좀 더 봐야하겠지만, 아래의 코드를 보면 어느 정도 감이 온다.

	@CallerSensitive
    public static Class<?> forName(String name, boolean initialize,
                                   ClassLoader loader)
        throws ClassNotFoundException
    {
        Class<?> caller = null;
        @SuppressWarnings("removal")
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            // Reflective call to get caller class is only needed if a security manager
            // is present.  Avoid the overhead of making this call otherwise.
            caller = Reflection.getCallerClass();
            if (loader == null) {
                ClassLoader ccl = ClassLoader.getClassLoader(caller);
                if (ccl != null) {
                    sm.checkPermission(
                        SecurityConstants.GET_CLASSLOADER_PERMISSION);
                }
            }
        }
        return forName0(name, initialize, loader, caller);
    }

forName이라는 method는 Class<?> 타입을 return하고 있다. 그러면 무엇을 리턴하는가?

어떤 클래스의 이름, 초기화, loader, caller를 담아 return하고 있다. 해당 정보가 바로 메타 정보로 사용되는듯 싶다.

java의 경우 .class를 사용하나, kotlin의 경우 ::class.java를 사용한다.

4. .class를 사용하면, 사용된 class의 instance가 생성되는가?

아니다. 생성되지 않는다. forName을 통해 해당 클래스의 메타정보만 갖고올 뿐 인스턴스가 생성되는 것은 아니다.


그러면 앞서 작성했던

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

는 kotlin에서 아래와 같이 사용하면 된다.

val applicationContext = AnnotationConfigApplicationContext(AppConfig::class.java)

결국 .class(::class.java)를 사용하는 이유는 해당 class의 메타 정보를 얻기 위해서 사용된다. .getClass()를 사용해도 같은 결과를 얻을 수 있으니, 참고하자.

profile
'개발'은 '예술'이고 '서비스'는 '작품'이다

0개의 댓글