developer tip

Java가 제네릭을 수정하지 않는다는 점에 왜주의해야합니까?

optionbox 2020. 8. 17. 08:47
반응형

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와 관련된 큰 문제 :

  • This horrible boxing requirement and disconnect between primitives and reference types. This isn't directly related to reification or type erasure. C#/Scala fix this.
  • No self types. JavaFX 8 had to remove "builders" for this reason. Absolutely nothing to do with type erasure. Scala fixes this, not sure about C#.
  • No declaration side type variance. C# 4.0/Scala have this. Absolutely nothing to do with type erasure.
  • Can't overload void method(List<A> l) and method(List<B> l). This is due to type erasure but is extremely petty.
  • No support for runtime type reflection. This is the heart of type erasure. If you like super advanced compilers that verify and prove as much of your program logic at compile time, you should use reflection as little as possible and this type of type erasure shouldn't bother you. If you like more patchy, scripty, dynamic type programming and don't care so much about a compiler proving as much of your logic correct as possible, then you want better reflection and fixing type erasure is important.

Serialization would be more straightforward with reification. What we would want is

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

What we have to do is

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

looks ugly and works funkily.

There are also cases where it would be really helpful to say something like

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

These things don't bite often, but they do bite when they happen.


My exposure to Java Geneircs is quite limited, and apart from the points other answers have already mentioned there is a scenario explained in the book Java Generics and Collections, by Maurice Naftalin and Philip Walder, where the reified generics are useful.

Since the types are not reifiable, it is not possible to have Parameterized exceptions.

For example the declaration of below form is not valid.

class ParametericException<T> extends Exception // compile error

This is because the catch clause checks whether the thrown exception matches a given type. This check is same as the check performed by instance test and since the type is not reifiable the above form of statement is invalid.

If the above code was valid then exception handling in the below manner would have been possible:

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

The book also mentions that if Java generics are defined similar to the way C++ templates are defined (expansion) it may lead to more efficient implementation as this offers more opportunities for optimization. But doesn't offer any explanation more than this, so any explanation (pointers) from the knowledgeable folks would be helpful.


Arrays would probably play much nicer with generics if they were reified.


I have a wrapper that presents a jdbc resultset as an iterator, (it means I can unit test database-originated operations a lot easier through dependency injection).

The API looks like Iterator<T> where T is some type that can be constructed using only strings in the constructor. The Iterator then looks at the strings being returned from the sql query and then tries to match it to a constructor of type T.

In the current way that generics are implemented, I have to also pass in the class of the objects that I will be creating from my resultset. If I understand correctly, if generics were reified, I could just call T.getClass() get its constructors, and then not have to cast the result of Class.newInstance(), which would be far neater.

Basically, I think it makes writing APIs (as opposed to just writing an application) easier, because you can infer a lot more from objects, and thereby less configuration will be necessary...I didn't appreciate the implications of annotations until I saw them being used in things like spring or xstream instead of reams of config.


One nice thing would be avoiding boxing for primitive (value) types. This is somewhat related to the array complaint that others have raised, and in cases where memory use is constrained it could actually make a significant difference.

There are also several types of problems when writing a framework where being able to reflect over the parameterized type is important. Of course this can be worked around by passing a class object around at runtime, but this obscures the API and places an additional burden on the user of the framework.


It's not that you will achieve anything extraordinary. It will just be simpler to understand. Type erasure seems like a hard time for beginners, and it ultimately requires one's understanding on the way the compiler works.

My opinion is, that generics are simply an extra that saves a lot of redundant casting.


Something that all the answers here have missed that is constantly a headache for me is since the types are erased, you cannot inherit a generic interface twice. This can be a problem when you want to make fine grained interfaces.

    public interface Service<KEY,VALUE> {
           VALUE get(KEY key);
    }

    public class PersonService implements Service<Long, Person>,
        Service<String, Person> //Can not do!!

Here's one that's caught me today: without reification, if you write a method that accepts a varargs list of generic items ... callers can THINK they're typesafe, but accidentally pass in any-old crud, and blow up your method.

Seems unlikely that would happen? ... Sure, until ... you use Class as your datatype. At this point, your caller will happily send you lots of Class objects, but a simple typo will send you Class objects that don't adhere to T, and disaster strikes.

(NB: I may have made a mistake here, but googling around "generics varargs", the above appears to be just what you'd expect. The thing that makes this a practical problem is the use of Class, I think - callers seem to be less careful :( )


For instance, I'm using a paradigm that uses Class objects as a key in maps (it's more complex than a simple map - but conceptually that's what's going on).

e.g. this works great in Java Generics (trivial example) :

public <T extends Component> Set<UUID> getEntitiesPossessingComponent( Class<T> componentType)
    {
        // find the entities that are mapped (somehow) from that class. Very type-safe
    }

e.g. without reification in Java Generics, this one accepts ANY "Class" object. And it's only a tiny extension of the previous code :

public <T extends Component> Set<UUID> getEntitiesPossessingComponents( Class<T>... componentType )
    {
        // find the entities that are mapped (somehow) to ALL of those classes
    }

The above methods have to be written out thousands of times in an individual project - so the possibility for human error becomes high. Debugging mistakes is proving "not fun". I'm currently trying to find an alternative, but don't hold much hope.

참고URL : https://stackoverflow.com/questions/1927789/why-should-i-care-that-java-doesnt-have-reified-generics

반응형