Why can't I use a type argument in a type parameter with a few constraints?

So, I understand that the following does not work, but why does not work?

interface Adapter<E> {} class Adaptulator<I> { <E, A extends I & Adapter<E>> void add(Class<E> extl, Class<A> intl) { addAdapterFactory(new AdapterFactory<E, A>(extl, intl)); } } 

The add() method gives me a compilation error: "It is not possible to specify any additional related Adapter <E> when the first binding is a type parameter" (in Eclipse), or "the type parameter cannot follow other boundaries" (in IDEA), make a choice.

Clearly, you are simply not allowed to use a parameter of type I there, before & , and that. (And before you ask, this will not work if you switch them because there is no guarantee that I not a specific class.) But why not? I have looked through the Angelika Langer FAQ and cannot find the answer.

Usually, when the restriction of some generics seems arbitrary, this is due to the fact that you created a situation where the type system cannot actually provide the correctness. But I don’t see which case will break what I am trying to do here. I would say maybe something has to do with sending the method after deleting the styles, but there is only one add() method, so it doesn't like any ambiguity ...

Can someone demonstrate this problem for me?

+42
java generics constraints
Oct 13 '08 at 10:21
source share
4 answers

I am also not sure why there is a limitation. You can try sending a friendly email to the Java 5 Generics developers (mainly Gilad Bracha and Neal Gafter).

My assumption is that they only wanted to maintain an absolute minimum of intersection types (which is essentially a few limitations) to make the language no more complicated than necessary. An intersection cannot be used as a type annotation; a programmer can only express an intersection when it appears as the upper bound of a type variable.

And why was this case even supported? The answer is that multiple borders allow you to control erasure, which allows you to maintain binary compatibility when creating existing classes. As explained in section 17.4 of the book by Naphthalene and Wadler, the max method would logically have the following signature:

 public static <T extends Comparable<? super T>> T max(Collection<? extends T> coll) 

However, this erases:

 public static Comparable max(Collection coll) 

Which does not match the historical signature of max and makes old customers break. For multiple boundaries, only the leftmost boundary is taken into account for erasure, therefore, if max specified with the following signature:

 public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) 

Then erasing his signature becomes:

 public static Object max(Collection coll) 

which is equal to the signature max before Generics.

It seems plausible that the Java developers only cared about this simple case and limited other (more advanced) applications of intersection types because they are simply not sure about the complexity that it can bring. Therefore, the reason for this design decision should not be a possible security problem (as follows from this question).

More discussion of intersection types and generic restrictions in the upcoming OOPSLA paper .

+22
Oct 13 '08 at 12:25
source share

Two possible reasons for the ban on this:

  • Complexity

    . Sun bug 4899305 suggests that a binding containing a type parameter plus additional parameterized types will allow the use of even more complex mutually recursive types than already exist. In short, Bruno's answer .

  • The ability to specify illegal types. In particular, it doubles the common interface with different parameters . I cannot come up with a non-contrived example, but:

      / ** Contains a Comparator <String> that also implements the given type T. * /
     class StringComparatorHolder <T, C extends T & Comparator <String>> {
     private final C comparator;
     // ...
     }
     
     void foo (StringComparatorHolder <Comparator <Integer>,?> holder) {...} 

Now holder.comparator is a Comparator<Integer> and a Comparator<String> . It’s not clear to me how many problems this can cause for the compiler, but this is clearly not good. Suppose, in particular, that Comparator had this method:

  void sort (List <? extends T> list); 

Our hybrid Comparator<Integer> / Comparator<String> now has two methods with the same erase:

  void sort (List <? extends Integer> list);
 void sort (List <? extends String> list); 

This is for such reasons that you cannot specify this type directly:

  <T extends Comparator <Integer> & Comparator <String>> void bar () {...} 
  java.util.Comparator cannot be inherited with different arguments:
     <java.lang.Integer> and <java.lang.String> 

Since <A extends I & Adapter<E>> allows you to do the same thing indirectly, that too.

+11
Oct 17 '08 at 3:16
source share

Here is another quote from JLS :

The form of restriction is limited (only the first element can be a class or type variable, and only one type variable can appear in this binding) to exclude some awkward situations that arise .

What exactly are these uncomfortable situations, I do not know.

+9
Oct 13 '08 at 15:55
source share

This probably doesn't answer the root question, but just want to point out that the specification explicitly prohibits it. A google search for error messages led me to this blog post which further points to jls 4.4 :

A deal consists of either a variable of type or a class or type of interface T, followed by additional types of interfaces I1, ..., In.

So, if you use the type parameter as bound, you cannot use any other binding, as indicated in the error message.

Why restriction? I have no idea.

+1
Oct 13 '08 at 11:33
source share



All Articles