Why can't you have multiple interfaces in a bounded wildcard generic?
Asked Answered
S

5

86

I know there's all sorts of counter-intuitive properties of Java's generic types. Here's one in particular that I don't understand, and which I'm hoping someone can explain to me. When specifying a type parameter for a class or interface, you can bound it so that it must implement multiple interfaces with public class Foo<T extends InterfaceA & InterfaceB>. However, if you're instantiating an actual object, this doesn't work anymore. List<? extends InterfaceA> is fine, but List<? extends InterfaceA & InterfaceB> fails to compile. Consider the following complete snippet:

import java.util.List;

public class Test {

  static interface A {
    public int getSomething();
  }

  static interface B {
    public int getSomethingElse();
  }

  static class AandB implements A, B {
    public int getSomething() { return 1; }
    public int getSomethingElse() { return 2; }
  }

  // Notice the multiple bounds here. This works.
  static class AandBList<T extends A & B> {
    List<T> list;

    public List<T> getList() { return list; }
  }

  public static void main(String [] args) {
    AandBList<AandB> foo = new AandBList<AandB>(); // This works fine!
    foo.getList().add(new AandB());
    List<? extends A> bar = new LinkedList<AandB>(); // This is fine too
    // This last one fails to compile!
    List<? extends A & B> foobar = new LinkedList<AandB>();
  }
}

It seems the semantics of bar should be well-defined -- I can't think of any loss of type-safety by allowing an intersection of two types rather than just one. I'm sure there's an explanation though. Does anyone know what it is?

Sealed answered 10/7, 2011 at 19:0 Comment(2)
Two points: first it would look more proper like "extends A, B". Second that A and B can be not an interface, but a class (of course this could be detected by the compiler).Sherise
Nope. The & character is the standard way to denote multiple bounds. "class AandBList<T extends A & B>" is just how the language works, although I agree it would be more intuitive to use <T extends A, B> to match public interface A extends C, D. And, in generic type bounds, you use extends regardless of whether it's an interface or a class. Confusing, I know, but that's the way it currently is.Sealed
I
45

Interestingly, interface java.lang.reflect.WildcardType looks like it supports both upper bounds and lower bounds for a wildcard arg; and each can contain multiple bounds

Type[] getUpperBounds();
Type[] getLowerBounds();

This is way beyond what the language allows. There's a hidden comment in the source code

// one or many? Up to language spec; currently only one, but this API
// allows for generalization.

The author of the interface seems to consider that this is an accidental limitation.

The canned answer to your question is, generics is already too complicated as it is; adding more complexity might prove to be the last straw.

To allow a wildcard to have multiple upper bounds, one has to scan through the spec and make sure the entire system still works.

One trouble I know would be in the type inference. The current inference rules simply can't deal with intersection types. There's no rule to reduce a constraint A&B << C. If we reduced it to

    A<<C 
  or
    A<<B

any current inference engine has to go through major overhaul to allow such bifurcation. But the real serious problem is, this allows multiple solutions, but there's no justification to prefer one over another.

However, inference is not essential to type safety; we can simply refuse to infer in this case, and ask programmer to explicitly fill in type arguments. Therefore, difficulty in inference is not a strong argument against intercection types.

Insincere answered 11/7, 2011 at 3:19 Comment(4)
Very thoughtful answer. And quite an interesting find about java.lang.reflect.WildcardType. Thank you so much :)Sealed
I really don't understand this answerVisa
Do you mean "intersection types"?Unicorn
why then is it ok for type arguments to have multiple bounds?Boomkin
C
28

From the Java Language Specification:

4.9 Intersection Types An intersection type takes the form T1 & ... & Tn, n>0, where Ti, 1in, are type expressions. Intersection types arise in the processes of capture conversion (§5.1.10) and type inference (§15.12.2.7). It is not possible to write an intersection type directly as part of a program; no syntax supports this. The values of an intersection type are those objects that are values of all of the types Ti, for 1in.

So why is this not supported? My guess is, what should you do with such a thing? - let's suppose it were possible:

List<? extends A & B> list = ...

Then what should

list.get(0);

return? There's no syntax to capture a return value of A & B. Adding something into such a list would not be possible either, so it's basically useless.

Candlenut answered 10/7, 2011 at 20:15 Comment(5)
Thanks for the link! That's really helpful. I guess what I would do with it is that A a = list.get(0); and B b = list.get(0); would both be safe. I guess I can always refactor to get a common superinterface though.Sealed
>>>There's no syntax to capture a return value of A & B. Adding something into such a list would not be possible either, so it's basically useless. If you extend you can not put in that list anyway. and to get, you can simply say A a = list.get(0) or B b = list.get(0). I don't think that there is type theoretical problem with this, it is only java limitation. On the other hand <? super A&B> is almost meaningless.Ventilator
Agreed with @OpDeCirkel. This already occurs if you have a type SomeClass<T extends A & B> which declares a method returning List<T>, and then call that method on a variable of type SomeClass<?>. So +1 for the JLS reference but -1 for the guess.Astrometry
Suppose A and B are interfaces, and all of J, K, L implement both A and B (among other interfaces they implement). Then list could contain elements of J, K, and L and list.get(0) could return them. Indeed, the Java Spec correctly notes that there's no way to express this concept directly, but J, K, and L utilize it indirectly through multiple inheritance of interfaces.Jarad
-1 As Bohemian points out, it is possible with slightly different syntax.Garrott
A
13

No problem... just declare the type you need in the method signature.

This compiles:

public static <T extends A & B> void main(String[] args) throws Exception
{
    AandBList<AandB> foo = new AandBList<AandB>(); // This works fine!
    foo.getList().add(new AandB());
    List<? extends A> bar = new LinkedList<AandB>(); // This is fine too
    List<T> foobar = new LinkedList<T>(); // This compiles!
}
Austria answered 10/7, 2011 at 21:5 Comment(2)
This is true, but it does not explain why the value for the type argument can not be other than Reference Type as defined in JSL, Reference Type is defined hereVentilator
Sorry, i meant: WildcardBounds can not be other then extends ReferenceType | super ReferenceTypeVentilator
T
2

Good question. It took me a while to figure out.

Lets simplify your case: You are trying to do the same as if you declare a class that extends 2 interfaces, and then a variable that has as a type those 2 interfaces, something like this:

  class MyClass implements Int1, Int2 { }

  Int1 & Int2 variable = new MyClass()

Of course, illegal. And this is equivalent to what you try to do with generics. What you are trying to do is:

  List<? extends A & B> foobar;

But then, to use foobar, you would need to use a variable of both interfaces this way:

  A & B element = foobar.get(0);

Which is not legal in Java. This means, you are declaring the elements of the list as beeing of 2 types simultaneously, and even if our brains can deal with it, Java language cannot.

Theurer answered 10/7, 2011 at 20:12 Comment(3)
True, but by being both types simultaneously, we can safely treat it as one or the other, independently at different times. Both A a = list.get(0); and B b = list.get(0); would be valid.Sealed
"would need to use" is wrong, you are not forced to do so, I don't see a restriction, it is also legal to do this: class C<T extends A & B> { public T foo() {};} where you return a variable with multiple bounds.Liberalism
The issue comes with directly using the result and not storing it in a variable. For instance, what methods are available for list.get(0).*?Adjacency
I
-2

For what it's worth: if anyone's wondering this because they would truly like to use this in practice, I've worked around it by defining an interface that contains the union of all methods in all the interfaces and class that I'm working with. i.e. I was trying to do the following:

class A {}

interface B {}

List<? extends A & B> list;

which is illegal - so instead I did this:

class A {
  <A methods>
}

interface B {
  <B methods>
}

interface C {
  <A methods>
  <B methods>
}

List<C> list;

This still isn't as useful as being able to type something as List<? extends A implements B>, e.g. if someone adds or removes methods to A or B, the typing of the list will not be updated automatically, it requires a manual change to C. But it's worked for my needs.

Illustrator answered 10/4, 2017 at 23:15 Comment(2)
If you do this, why not class C extends A implements B? Sure, you need to manually update C, when new methods come into A or B, but at least you're sure, that no one will forget to update C, which, in your case, is absolutely possible. (You won't forget, because NoClassDefFound compile time error).Livialivid
@Livialivid I can't remember the details of my use case but it's possible I needed C to be an interface because I needed to attach it to a class that was already forced to extend something besides A.Illustrator

© 2022 - 2024 — McMap. All rights reserved.