12

我即将创建一个工厂,它创建特定类型 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() 不适用于参数 ()。推断类型 I&A 不是有界参数的有效替代品

我不明白为什么。明确指出“newThing 返回某种类型 T 的东西,它确实扩展了类 A 并实现了接口 I”。当分配给 A 时一切正常(因为 T 扩展了 A)但分配给 I 却没有(因为什么?,显然返回的东西既是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 有效,而分配给 I 无效?

--

编辑:这里是完全使用 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

4 回答 4

10

至于第二个问题:

考虑这种情况:

 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- 除非您禁止这些警告。

于 2012-03-22T15:08:47.403 回答
7

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

于 2012-03-22T15:01:23.477 回答
4

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

方法的参数化签名是:

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

如果你调用 with newThing(new C())

如下

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

如果你调用 with 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 ... }

对?

我希望我已经解释了自己!这并不容易解释。

于 2012-03-22T15:43:09.270 回答
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();
    }
}
于 2012-03-22T15:30:41.943 回答