繁体   English   中英

Java泛型:有界类型参数中的多重继承<T extends A & I>

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

我即将创建一个工厂,它创建某种类型的对象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和A不是有界参数的有效替代

我不明白为什么。 明确指出“newThing返回某种类型的某些类型T,它确实扩展了类A并实现了接口I”。 当分配给一个一切正常(因为T延伸的),但分配给我并没有(因为什么?,清楚地返回的东西既是 A I)

另外:当返回一个对象时,比如B class B extends A implements I ,我需要将它转换为返回类型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());
    }
}

至于第二个问题:

考虑这种情况:

 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可以是扩展A 任何东西I不能只返回B的实例。 在上面的情况下,演员表可以写成(C)new B() 这显然会导致异常,因此编译器会发出警告: Unchecked cast from B to T - 除非您正在抑制这些警告。

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

我认为解释它的一种方法是将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 ... }

对?

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

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

例如。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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