java-squid / effective-java

effective java 3e study
105 stars 38 forks source link

[아이템 65] 리플렉션보다는 인터페이스를 사용하라 #67

Closed ghojeong closed 3 years ago

kses1010 commented 3 years ago

리플렉션의 자바 문서: Class Class <T> https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html

102092 commented 3 years ago

리플렉션 성능에 관한 글입니다. 가볍게 볼만해요 https://yjacket.tistory.com/73

102092 commented 3 years ago

한번 언급했던 것 같은데, Spring에서는 사용되는 어노테이션들이 이 리플렉션을 기반으로 동작하는 걸로 알고 있습니다. 그에 대한 흐름을 아래 포스팅에서 조금 파악할 수 있을듯 합니다! https://sas-study.tistory.com/271

102092 commented 3 years ago

p374.. 이 프로그램을 컴파일 하면... 비검사 형변환 경고가 뜬다. 하지만 Class<? extends Set> 으로 형변환은 심지어 명시한 클래스가 Set을 구현하지 않았더라도 성공할 것이다.

  1. 왜 성공하는 걸까요?
  2. 왜 인스턴스를 생성하려 할 때 오류가 발생할까요?
kses1010 commented 3 years ago

현재 백기선님 더 자바 강의를 보고있는데 https://sas-study.tistory.com/271 링크와 똑같습니다. 호눅스가 추천하셨던 백기선님 강의 더 자바, 코드를 조작하는 다양한 방법 좋으니 만약 여유가 있으시면 꼭 보는걸 추천합니다.

kses1010 commented 3 years ago

리플렉션 정리(백기선님 강의)

리플렉션 사용시 주의해야할 사항

사용되는 곳

tmdgusya commented 3 years ago

한번 언급했던 것 같은데, Spring에서는 사용되는 어노테이션들이 이 리플렉션을 기반으로 동작하는 걸로 알고 있습니다. 그에 대한 흐름을 아래 포스팅에서 조금 파악할 수 있을듯 합니다! https://sas-study.tistory.com/271

와 진짜 좋은 글이네요. 한번 리플렉션을 이번주에 간단하게나마 예시로 공부해봐야겠다는 생각이드네요.

tmdgusya commented 3 years ago

저번에 David 가 리플렉션을 이용해서 Annotation 이 동작한다고 들었던것 같은데, 이번 기회에 공부해봐도 좋겠네요.

kses1010 commented 3 years ago

리플렉션을 이용해서 Annotation 정보를 가져오거나 조작할 수 있다고 생각할 수 있군요

ghojeong commented 3 years ago

p374.. 이 프로그램을 컴파일 하면... 비검사 형변환 경고가 뜬다. 하지만 Class<? extends Set> 으로 형변환은 심지어 명시한 클래스가 Set을 구현하지 않았더라도 성공할 것이다.

  1. 왜 성공하는 걸까요?
  2. 왜 인스턴스를 생성하려 할 때 오류가 발생할까요?

질문 뒤늦게 확인해서 답변 올립니다.

Class<? extends Set<String>> cl = (Class<? extends Set<String>>) Class.forName("java.lang.String"); // 1번

Constructor<? extends Set<String>> cons = cl.getDeclaredConstructor(); // 2번

Set<String> s = cons.newInstance(); // 3번

위 코드의 런타임에서 왜 1번과 2번은 성공하고 3번은 실패하는 걸까요?

저는 인터페이스가 구현하기를 요구하는 메서드가 인스턴스 내에 존재하느냐의 유무로 달라진다고 이해했습니다.

가령 Class.forName("java.lang.String") 은 비록 Class<? extends String> 타입을 반환할 테지만, 명시적으로 형변환을 하고 있고, 그에 대해 Class 클래스가 가져야 하는 필드와 메서드를 모두 가지고 있으므로, 런타임에서의 실행에서도 에러를 일으키지 않습니다. 성공을 왜 하느냐에 대한 이해는 제너릭을 전부 지우니 더 직관적으로 이해가 가더군요.

Class cl = Class.forName("java.lang.String"); // 1번
Constructor cons = cl.getDeclaredConstructor(); // 2번

위 코드는 정상작동합니다. 제너릭이 없어서 허전한것 말고는, 코드 상으로도 이해해보려 했을 떄 전혀 문제될게 없어보입니다. 기존의 1번과 2번 코드는 제너릭을 명시하고, 명시적인 형변환 코드를 추가했을 뿐입니다. 즉 컴파일 타임에서의 명시를 강화했을 뿐이지, 로직상으로 달라진게 하나도 없습니다. 그리고 왜 정상작동하는지에 대해서는 너무나 직관적이고 명확합니다. 가능한 타입에, 가능한 인스턴스를 할당했을 뿐입니다.

다만, 3번의 실패는 이야기가 다릅니다. 1번과 2번은 제너릭이 다를 뿐 인터페이스는 동일합니다.(Class<? extends Set> 과 Class<? extends String> 둘 다 어쩄든 Class) 3번은 제너릭이 아니라 인터페이스가 아예 다릅니다. (String 과 Set 은 진짜 아예 다르다)

java.lang.ClassCastException: class java.lang.String cannot be cast to class java.util.Set (java.lang.String and java.util.Set are in module java.base of loader 'bootstrap')

즉 성공과 실패여부의 차이는 아래와 같다고 이해했습니다.

추가: 컴파일 타임에서의 타입 캐스팅

Class cl = Class.forName("java.util.HashSet"); // 1번

Constructor<? extends Set> cons = cl.getDeclaredConstructor(); // 2번

Set s = cons.newInstance(); // 3번

위 코드는 정상 작동합니다. 단 Constructor<? extends Set> 에서 <? extends Set> 제너릭을 제거하면, cons.newInstance() 를 했을 떄 컴파일 타임 중 캐스팅이 실패하므로 명시를 해주어야 합니다.

// 실패
Class cl = Class.forName("java.util.HashSet");
Constructor cons = cl.getDeclaredConstructor();
Set s = cons.newInstance();

// 성공
Class cl = Class.forName("java.util.HashSet");
Constructor cons = cl.getDeclaredConstructor();
Set s = (Set) cons.newInstance();

즉 컴파일 타임 중의 타입 캐스팅은 명시만 한다면, generic 이 없거나 이상하더라도, 타입 캐스팅으로 인한 에러는 발생하지 않는 것을 알 수 있습니다.

참고

kses1010 commented 3 years ago

https://docs.oracle.com/javase/9/docs/api/java/lang/Class.html newInstance() 는 deprecated 되었으니 생성자를 통해서 만드는것을 추천합니다.

ghojeong commented 3 years ago

Class 에도 newInstance 있다는거 처음 알았는데 감사합니다!