13

I am about to create a factory which creates objects of a certain type T which extends a certain class A and another interface I. However, T must not be known. Here are the minimum declarations:

public class A { }
public interface I { }

This is the factory method:

public class F {
    public static <T extends A & I> T newThing() { /*...*/ }
}

This compiles all fine.

When I try to use the method the following works fine:

A $a = F.newThing();

...while this does not:

I $i = F.newThing();

The compiler complains:

Bound mismatch: The generic method newThing() of type F is not applicable for the arguments (). The inferred type I&A is not a valid substitute for the bounded parameter

I can't understand why. It is clearly stated that "newThing returns something of a certain type T which does extend the class A and implement the interface I". When assigning to A everything works (since T extends A) but assigning to I does not (because of what?, clearly the thing returned is both an A and an I)

Also: When returning an object, say B of the type class B extends A implements I, I need to cast it to the return type T, although B matches the bounds:

<T extends A & I> T newThing() {
    return (T) new B();
}

However, the compiler does not throw any warnings like UncheckedCast or the like.

Thus my question:

  • What is going wrong here?
  • Is there an easy away to achieve the desired behavior (i.e. assigning to a variable of static type A or I), like there is in solving the return-type-problem by casting, in the factory method?
  • Why does the assignment to A work, while to I does not?

--

EDIT: Here the complete code snippet which totally works using Eclipse 3.7, project set up for JDK 6:

public class F {
    public static class A { }
    public static interface I { }

    private static class B extends A implements I {  }

    public static <T extends A & I> T newThing() {
        return (T) new B();
}

    public static void main(String... _) {
        A $a = F.newThing();
        // I $i = F.newThing();
    }
}

EDIT: Here is a complete example with methods and invocation which does work at runtime:

public class F {
    public static class A {
        int methodA() {
            return 7;
        }
    }
    public static interface I {
        int methodI();
    }

    private static class B extends A implements I {
        public int methodI() {
            return 12;
        }
    }

    public static <T extends A & I> T newThing() {
        return (T) new B();
    }

    public static void main(String... _) {
        A $a = F.newThing();
        // I $i = F.newThing();
        System.out.println($a.methodA());
    }
}
Jeff Axelrod
  • 27,676
  • 31
  • 147
  • 246
scravy
  • 11,904
  • 14
  • 72
  • 127
  • 11
    Don't used $ in your class or variable names. It's a legal character but is informally reserved by the language to prevent name clashes. – Dunes Mar 22 '12 at 15:01
  • I think there's a mistake in your example code: `A $a = F.newThing();` does not work. Are you sure you didn't declare `class A implements I { }`? – Andrew Spencer Mar 22 '12 at 15:07
  • @AndrewSpencer yepp, totally. I edited in the complete piece of code with the line I... commented out, but A... works fine. Using Eclipse Indigo Service Release 2. – scravy Mar 22 '12 at 15:14
  • 1
    @AndrewSpencer strangely enough `A $a = F.newThing();` compiles but would throw a `ClassCastException` at runtime if `B` would not extend `A`. This even compiles with ``. – Thomas Mar 22 '12 at 15:17
  • There is a similar topic: [Why can't you have multiple interfaces in a bounded wildcard generic?](http://stackoverflow.com/questions/6643241/why-cant-you-have-multiple-interfaces-in-a-bounded-wildcard-generic) – Christophe Roussy Mar 22 '12 at 15:20
  • @Thomas it doesn't compile in Eclipse, but then I don't trust the Eclipse compiler... – Andrew Spencer Mar 22 '12 at 15:36
  • @AndrewSpencer hmm, maybe that's another generics related compiler bug (there were at least some in the past). In my Eclipse (3.7.2 with JDK 1.6.0_30) it compiled and ran. – Thomas Mar 22 '12 at 15:43

4 Answers4

10

As for the second question:

Consider this case:

 class B extends A implements I {}
 class C extends A implements I {}

Now, the following uses type inference:

<T extends A & I> T newThing() {
  return (T) new B();
}

So you could call this:

C c = F.newThing(); //T would be C here

You see that T could be anything that extends A and I you can't just return an instance of B. In the case above the cast could be written as (C)new B(). This would clearly result in an exception and thus the compiler issues a warning: Unchecked cast from B to T - unless you're supressing those warnings.

Thomas
  • 87,414
  • 12
  • 119
  • 157
7

This doesn't do what you expect it to. T extends A & I indicates that the caller can specify any type that extends A and I, and you'll return it.

Louis Wasserman
  • 191,574
  • 25
  • 345
  • 413
  • This totally does not answer any of my questions. Why does the assignment to A work then? Is there a fix? – scravy Mar 22 '12 at 15:16
  • You _must_ specify some explicit type `B` that extends `A` and implements `I`, and you must return something that extends that class. The cast you're doing won't work in practice at runtime, even if the compiler allows it. – Louis Wasserman Mar 22 '12 at 15:23
  • Hm okay. This reminds me of an issue I stumbled upon in Haskell. The cause similarly was that the caller has to specify the return type (http://stackoverflow.com/questions/9148849). Tricky. Could you edit your question so that I can remedy the vote? How can the caller specify the return type in Java? – scravy Mar 22 '12 at 15:43
  • 1
    I'm not sure what you want me to change. You'll find it almost impossible to make the caller specify the return type in Java, unless one of your method's _inputs_ is something that the method can use to produce objects of the desired type. You can't provide a factory method in Java like you would with a typeclass constraint in Haskell. – Louis Wasserman Mar 22 '12 at 15:47
  • You can do so in Java: F.newThing(); I'm playing around with it at the moment, strangely this snippet of code works (although it looks perfectly wrong): http://pastebin.ca/2131130 – scravy Mar 22 '12 at 15:49
  • You can't do it. Your code doesn't do what you think it does. The only reason that the line `I $i = F.newThing()` works is that the generic information is erased at runtime, so it doesn't actually care about the `C` type argument. If you actually tried to cast that object to a `C`, it would fail. – Louis Wasserman Mar 22 '12 at 15:59
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/9199/discussion-between-louis-wasserman-and-scravy) – Louis Wasserman Mar 22 '12 at 16:02
4

I think that one way to explain it is by replacing the type parameter with the actual type.

The parameterized signature of the methods is:

public static <T extends A & B> T newThing(){
   return ...;
}

The <T extends A & B> is what is called a type parameter. The compiler would expect that this value is actually substituted with the actual type (called type argument) when you actually use it.

In the case of your method the actual type is decided by means of type inference. That is, <T extends A & B> should be replaced by a real existing type that extends A and implements B.

So, let's say that classes C and D both extends A and implements B, then if your signature were like this:

public static <T extends A & B> T newThing(T obj){
   return obj;
}

Then, by type inference, your method would be evaluated as follows:

public static C newThing(C obj){
   return obj;
}

if you invoke with newThing(new C()).

And would be as follows

public static D newThing(D obj){
   return obj;
}

if you invoke with newThing(new D()).

This would compile just fine!

However, since you are not actually providing any kind of type to validate type inference in your method declaration, then the compiler could never be sure what is the actual type (type argument) of your type parameter <T extends A & B>.

You might expect that the actual type is C, but there may be thousands of different classes that satisfy that criteria. Which of those should the compiler use as the actual type of your type argument?

Let's say that C and D are two classes that extend A and implements B. Which of these two actual types should the compiler use as type argument for your method?

You could have even declared a type argument for which there is not even an existing type that you can use, like saying something that extends Serializable and Closable and Comparable and Appendable.

And perhaps there is not a class in the whole world that satisfies that.

As such, you must understand that the type parameter here is just a requirement for the compiler to validate the actual type that you use, a placeholder for the actual type; and this actual type must exist at the end and the compiler will use it to replace appearances of T. Therefore the actual type (type argument) must be inferable from the context.

Since the compiler cannot tell with certainty which is the actual type that you mean, basically because there is no way to determine that by type inference in this case, then you are forced to cast your type, to ensure the compiler that you know what you are doing.

As such, you could implement your method using type inference like this:

   public static <T extends A & B> T newThing(Class<T> t) throws Exception{
    return t.newInstance();
}

This way, you would be actually telling the compiler what is the actual type argument to be used.

Take into account that when the bytecodes are generated, the compiler must substitute T for a real type. There is no way to write method in Java like this

public static A & B newThing(){ return ... }

Right?

I hope I have explained myself! This is not simple to explain.

Edwin Dalorzo
  • 76,803
  • 25
  • 144
  • 205
  • @Louis Wasserman's answer was shorter, but this gives a more comprehensive explanation for anyone wondering *why* multiple bounds are allowed/disallowed where they are. Particularly in variable declarations and Collections. – MandisaW Dec 03 '12 at 20:38
  • This explains why explicit cast to T is required but not why `Why does the assignment to A work, while to I does not?` – Kshitiz Sharma May 12 '15 at 04:13
0

Simplest solution is create an abstract base class that extends and implements whatever class and interfaces you want and return that type. It doesn't matter that you're constraining your return type to extend this base class as you were already constraining the return type to its superclass.

eg.

class C {}
interface I {}

abstract class BaseClass extends C implements I {}
// ^-- this line should never change. All it is telling us that we have created a
// class that combines the methods of C and I, and that concrete sub classes will
// implement the abstract methods of C and I    


class X extends BaseClass {}
class Y extends BaseClass {}

public class F {

    public static BaseClass newThing() {
        return new X();
    }


    public static void main(String[] args) {
        C c = F.newThing();
        I i = F.newThing();
    }
}
Dunes
  • 37,291
  • 7
  • 81
  • 97