[java] Java가 제네릭을 수정하지 않았는지 왜 신경 써야합니까?

이것은 후보자가 Java 언어에 추가되기를 바라는 내용으로 최근 인터뷰에서 묻는 질문으로 나왔습니다. 일반적으로 Java가 제네릭을 수정 하지 않은 것이 고통으로 알려져 있지만, 밀어 붙였을 때 후보는 실제로 그들이 거기에 있었다면 그가 달성 할 수 있었던 종류의 일을 내게 말할 수 없었습니다.

분명히 원시 유형은 Java (및 안전하지 않은 검사)에서 허용되기 때문에 제네릭을 파괴 List<Integer>하고 (예를 들어) 실제로를 포함 하는 것으로 끝날 수 있습니다 String. 이것은 유형 정보가 수정 되었다면 불가능하게 될 수있었습니다. 하지만 이것보다 더 있어야합니다 !

사람들 이 정말로하고 싶은 일의 예를 게시 할 수 있습니까? 내 말은, 분명히 당신은 List런타임 에 a의 유형을 얻을 수 있지만, 그것으로 무엇을 하시겠습니까?

public <T> void foo(List<T> l) {
   if (l.getGenericType() == Integer.class) {
       //yeah baby! err, what now?

편집 : 대답은 주로 Class매개 변수로 전달해야 할 필요성에 대해 우려하는 것 같습니다 (예 EnumSet.noneOf(TimeUnit.class):). 나는 이것이 가능하지 않은 곳에서 무언가를 더 찾고 있었다 . 예를 들면 :

List<?> l1 = api.gimmeAList();
List<?> l2 = api.gimmeAnotherList();

if (l1.getGenericType().isAssignableFrom(l2.getGenericType())) {
    l1.addAll(l2); //why on earth would I be doing this anyway?



답변

내가이 “필요”를 발견 한 몇 번부터, 궁극적으로이 구조로 귀결됩니다.

public class Foo<T> {

    private T t;

    public Foo() {
        this.t = new T(); // Help?
    }

}

기본 생성자 T가 있다고 가정하면 C #에서 작동 합니다. 으로 런타임 유형을 가져오고 생성자를 가져올 수도 있습니다.typeof(T)Type.GetConstructor()

일반적인 Java 솔루션은 Class<T>as 인수 를 전달하는 것입니다.

public class Foo<T> {

    private T t;

    public Foo(Class<T> cls) throws Exception {
        this.t = cls.newInstance();
    }

}

(메서드 인자도 괜찮 기 때문에 반드시 생성자 인자로 전달할 필요는 없습니다. 위는 예시 일 뿐이며 try-catch간결성을 위해 생략되었습니다)

다른 모든 제네릭 형식 구조의 경우 약간의 리플렉션을 사용하여 실제 형식을 쉽게 확인할 수 있습니다. 아래 Q & A는 사용 사례와 가능성을 보여줍니다.


답변

가장 일반적으로 저를 물어 뜯는 것은 여러 일반 유형에 걸쳐 다중 디스패치를 ​​이용할 수 없다는 것입니다. 다음은 불가능하며 최상의 솔루션이되는 경우가 많습니다.

public void my_method(List<String> input) { ... }
public void my_method(List<Integer> input) { ... }


답변

형식 안전성 이 마음에 듭니다. 매개 변수화 된 유형으로 다운 캐스팅하는 것은 수정 된 제네릭 없이는 항상 안전하지 않습니다.

List<String> myFriends = new ArrayList();
myFriends.add("Alice");
getSession().put("friends", myFriends);
// later, elsewhere
List<Friend> myFriends = (List<Friend>) getSession().get("friends");
myFriends.add(new Friend("Bob")); // works like a charm!
// and so...
List<String> myFriends = (List<String>) getSession().get("friends");
for (String friend : myFriends) print(friend); // ClassCastException, wtf!? 

또한 추상화는 유출이 적습니다 . 적어도 유형 매개 변수에 대한 런타임 정보에 관심이있을 수있는 것입니다. 오늘날 일반 매개 변수 중 하나의 유형에 대한 런타임 정보가 필요한 경우 Class함께 전달해야합니다 . 이렇게하면 외부 인터페이스는 구현에 따라 달라집니다 (매개 변수에 대해 RTTI를 사용하는지 여부).


답변

코드에서 일반 배열을 만들 수 있습니다.

public <T> static void DoStuff() {
    T[] myArray = new T[42]; // No can do
}


답변

이것은 오래된 질문이고 많은 답변이 있지만 기존 답변은 표시되지 않은 것 같습니다.

“reified”는 단지 실제를 의미하고 보통 유형 삭제의 반대를 의미합니다.

Java Generics와 관련된 큰 문제 :

  • 이 끔찍한 권투 요구 사항은 기본 형식과 참조 형식 간의 연결을 끊습니다. 이것은 수정 또는 유형 삭제와 직접 관련이 없습니다. C # / Scala는이 문제를 해결합니다.
  • 자기 유형이 없습니다. 이러한 이유로 JavaFX 8은 “빌더”를 제거해야했습니다. 유형 삭제와는 전혀 관련이 없습니다. Scala는이 문제를 해결하지만 C #에 대해서는 확실하지 않습니다.
  • 선언 측 유형 변형이 없습니다. C # 4.0 / Scala에는이 기능이 있습니다. 유형 삭제와는 전혀 관련이 없습니다.
  • 과부하 void method(List<A> l)method(List<B> l) . 이것은 유형 삭제로 인한 것이지만 매우 사소합니다.
  • 런타임 유형 반영을 지원하지 않습니다. 이것이 유형 삭제의 핵심입니다. 컴파일 타임에 프로그램 로직을 많이 확인하고 증명하는 고급 컴파일러를 좋아한다면 리플렉션을 가능한 한 적게 사용해야하며 이러한 유형의 삭제는 문제가되지 않아야합니다. 좀 더 고르지 않고 스크립 티하고 동적 유형 프로그래밍을 좋아하고 가능한 한 많은 논리가 올바른 것을 증명하는 컴파일러에별로 신경 쓰지 않는다면 더 나은 반영을 원하고 유형 삭제를 수정하는 것이 중요합니다.

답변

직렬화는 수정으로 더 간단합니다. 우리가 원하는 것은

deserialize(thingy, List<Integer>.class);

우리가해야 할 일은

deserialize(thing, new TypeReference<List<Integer>>(){});

보기 흉하고 펑키하게 작동합니다.

다음과 같은 말을하는 것이 정말 도움이되는 경우도 있습니다.

public <T> void doThings(List<T> thingy) {
    if (T instanceof Q)
      doCrazyness();
  }

이런 것들은 자주 물지는 않지만, 일어날 때 물기도합니다.


답변

Java Geneircs에 대한 나의 노출은 상당히 제한적이며 다른 답변이 이미 언급 한 점을 제외하고 Java Generics and Collections 책에 설명 된 시나리오가 있습니다. 하고 Maurice Naftalin과 Philip Walder의 , 여기서 수정 된 제네릭이 유용합니다.

유형을 수정할 수 없기 때문에 매개 변수화 된 예외를 가질 수 없습니다.

예를 들어 아래 양식의 선언은 유효하지 않습니다.

class ParametericException<T> extends Exception // compile error

이는 catch 절이 throw 된 예외가 주어진 유형과 일치하는지 여부를 확인 하기 때문 입니다. 이 검사는 인스턴스 테스트에 의해 수행되는 검사와 동일하며 유형을 수정할 수 없기 때문에 위 형식의 문은 유효하지 않습니다.

위의 코드가 유효했다면 아래 방식으로 예외 처리가 가능했을 것입니다.

try {
     throw new ParametericException<Integer>(42);
} catch (ParametericException<Integer> e) { // compile error
  ...
}

이 책은 또한 Java 제네릭이 C ++ 템플릿이 정의 된 방식 (확장)과 유사하게 정의되면 최적화를위한 더 많은 기회를 제공하므로보다 효율적인 구현으로 이어질 수 있다고 언급합니다. 그러나 이것보다 더 많은 설명을 제공하지 않으므로 지식이 풍부한 사람들의 설명 (포인터)이 도움이 될 것입니다.