Java泛型:有界类型参数中的多重继承<t extends =“”a =“”&=“”i =“”> </t>

时间:2012-03-22 14:57:44

标签: java generics types bounded-wildcard

我即将创建一个工厂,它创建某种类型的对象T,它扩展了某个类A和另一个接口I.但是,T必须是未知的。以下是最低声明:

public class A { }
public interface I { }

这是工厂方法:

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

这编译所有罚款。

当我尝试使用该方法时,以下工作正常:

A $a = F.newThing();

......虽然这不是:

I $i = F.newThing();

编译器抱怨:

  

绑定不匹配:类型F的泛型方法newThing()不适用于arguments()。推断的类型I&amp; A不是有界参数的有效替代

我无法理解为什么。明确指出“newThing返回某种类型的某些类型T,它确实扩展了类A并实现了接口I”。分配给A时一切正常(因为T扩展了A)但是分配给我没有(因为是什么?,显然返回的东西都是A 一个I)

另外:当返回一个对象时,比如说class B extends A implements I类型的B,我需要将它转换为返回类型T,尽管B匹配边界:

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

但是,编译器不会抛出任何警告,如UncheckedCast等。

因此我的问题是:

  • 这里出了什么问题?
  • 是否容易实现所需的行为(即分配给静态类型A或I的变量),就像在工厂方法中通过强制转换解决返回类型问题一样?
  • 为什么分配到A工作,而我不工作?

-

编辑:这里是完整的代码片段,完全使用Eclipse 3.7,为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();
    }
}

编辑:以下是在运行时运行的方法和调用的完整示例:

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

4 个答案:

答案 0 :(得分:9)

至于第二个问题:

考虑这种情况:

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

现在,以下使用类型推断:

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

所以你可以这样称呼:

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

您看到T可以任何扩展AI您不能只返回B的实例。在上面的情况下,演员表可以写成(C)new B()。这显然会导致异常,因此编译器会发出警告:Unchecked cast from B to T - 除非你压制这些警告。

答案 1 :(得分:7)

这不符合您的预期。 T extends A & I表示调用者可以指定任何扩展AI的类型,并且您将返回它。

答案 2 :(得分:4)

我认为解释它的一种方法是将type参数替换为实际类型。

方法的参数化签名是:

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

<T extends A & B>是所谓的类型参数。当您实际使用它时,编译器会期望该值实际上被实际类型(称为类型参数)替换。

对于您的方法,实际类型是通过类型推断来决定的。也就是说,<T extends A & B>应该替换为扩展A并实现B的实际现有类型。

所以,假设C和D类都扩展了A并实现了B,那么如果你的签名是这样的:

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

然后,通过类型推断,您的方法将按如下方式进行评估:

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

如果您使用newThing(new C())调用。

如下

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

如果您使用newThing(new D())调用。

这样编译得很好!

但是,由于您实际上并未在方法声明中提供任何类型的类型推断来验证类型推断,因此编译器永远无法确定类型参数<T extends A & B>的实际类型(类型参数)是什么。

您可能希望实际类型是C,但可能有数千个不同的类满足该条件。编译器应该将哪一个用作类型参数的实际类型?

假设C和D是扩展A和实现B的两个类。编译器将这两个实际类型中的哪一个用作方法的类型参数?

你甚至可以声明一个类型参数,甚至没有你可以使用的现有类型,比如说一些扩展Serializable和Closable以及Comparable和Appendable的东西。

也许整个世界都没有一个课程能够满足这一要求。

因此,您必须了解此处的类型参数只是编译器验证您使用的实际类型(实际类型的占位符)的要求;并且这个实际类型必须存在于最后,编译器将使用它来替换T的外观。因此,实际类型(类型参数)必须可以从上下文中推断出来。

由于编译器无法确定你的意思是哪种实际类型,主要是因为在这种情况下没有办法通过类型推断确定,那么你被迫转换你的类型,以确保编译器你知道你在做什么。

因此,您可以使用类型推断来实现您的方法:

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

这样,您实际上会告诉编译器要使用的实际类型参数是什么。

考虑到生成字节码时,编译器必须用T代替实数类型。没有办法用Java编写方法

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

右?

我希望我已经解释过了自己!这不是一个简单的解释。

答案 3 :(得分:0)

最简单的解决方案是创建一个抽象基类,它扩展并实现您想要的任何类和接口并返回该类型。由于您已经将返回类型限制为其超类,因此限制返回类型以扩展此基类并不重要。

例如

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