简体   繁体   中英

Generics type inference fails?

Example A

Study the following snippet:

public class ExampleA {
   static class Pair<F,S> { }

   static <F,S> Pair<F,S> anyPair() { return null; }

   static <F,S> void process(Pair<F,S> p1, Pair<F,S> p2) { return; }

   public static void main(String[] args) {
      Pair<String,Integer> p = anyPair();

      process(p, anyPair()); // doesn't compile
   }
}

Can someone explain why type inference works for the assignment to local variable p but not for the second actual parameter to process ?


Example B

This is perhaps simpler to understand:

public class ExampleB {     
   public static <E> void process(Set<E> s1, Set<E> s2) { return; }

   public static void main(String[] args) {
      process(new HashSet<String>(), Collections.emptySet()); // doesn't compile
   }
}

Same question: why doesn't it compile?

I'd hope that Collections.emptySet() would just work for ANY parameterized Set type.

Your second call to anyPair() does not have any way to determine it's types and so it defaults to <Object, Object> .

The compiler is breaking process(p, anyPair()); into it's pieces and processing each individually. When it does this, it needs to process the arguments first to determine their types, which can then be used when processing process .

When it goes to process anyPair() there is no type information available for that piece, because it does not know that it is part of process at that point. It defaults to <Object, Object> , which then causes a type mismatch when looking at process .

The same thing happens with your second example. Collections.emptySet() needs to be processed by itself, but has no way of determining the Types needed.

There are 2 ways to solve this:

The first is to give the compiler the information it needs for type inference the same way you did with the first call to anyPair() , by storing it in a temporary variable with the correct type.

The second (thanks to @BalusC) is to use ExampleA.<String, Integer>anyPair() . This syntax explicitly sets the types needed without having to look beyond the invocation.

Why:

Collections.emptySet() is trying to infer the type to return. It's unable because E could be Object or String . Both are valid matches for process. Generic parameters are always invariant by default, not contravariant. That means that Integer extends Number but List<Integer> does not extend List<Number> . It does extend List<? extends Number> List<? extends Number> , however.

Solutions:

Use an assignment to infer the type:

public <E> void process(Set<E> s1, Set<E> s2) { return; }

public void main(String[] args) {
   Set<String> s = Collections.emptySet();  // add this line
   process(new HashSet<String>(), s);
}

Explicitly write the type:

public <E> void process(Set<E> s1, Set<E> s2) { return; }

public void main(String[] args) {
   process(new HashSet<String>(), Collections.<String>emptySet()); //notice <String>
}

Allow contravariance explicitly (this is probably not what you want, however):

public <E> void process(Set<E> s1, Set<? super E> s2) { // added "super"
  return;
}

public void main(String[] args) {
   process(new HashSet<String>(), Collections.emptySet());
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM