简体   繁体   中英

Java Generics: Multiple Inheritance in Bounded Type Parameters <T extends A & I>

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 (ie 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());
    }
}

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.

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.

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.

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();
    }
}

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