2012-03-20 101 views
5

我的问题可以概括式的这个片段:泛型和铸造到正确的类型

public interface TheClass<T> { 
    public void theMethod(T obj); 
} 

public class A { 
    private TheClass<?> instance; 

    public A(TheClass<?> instance) { 
     this.instance = instance; 
    } 

    public void doWork(Object target) { 
     instance.theMethod(target); // Won't compile! 

     // However, I know that the target can be passed to the 
     // method safely because its type matches. 
    } 
} 

A类使用的TheClass一个实例,其泛型类型不明。它具有一个方法,目标传递为Object,因为TheClass实例可以用任何类进行参数化。但是,编译器不会让我像这样传递目标,这是正常的。

我该怎么办才能绕过这个问题?

脏的解决方案是声明实例为TheClass<? super Object>,它工作正常,但在语义上是错误的...

另一种解决方案我使用之前是声明实例为原料的类型,只是TheClass,但它是坏的练习,所以我想纠正我的错误。

public class A { 
    private TheClass<Object> instance; // type enforced here 

    public A(TheClass<?> instance) { 
     this.instance = (TheClass<Object>) instance; // cast works fine 
    } 

    public void doWork(Object target) { 
     instance.theMethod(target); 
    } 
} 
+2

你为什么不还键入'A'? – 2012-03-20 10:15:45

+0

+1,一个很好的问题!等待答案...... – aProgrammer 2012-03-20 10:17:38

+0

A是我制作的开源库的一部分,许多人都使用它,而打字A意味着强大的api突破。最近我发现了这个问题,因为之前我使用原始类型为“TheClass”实例,因为我不知道这是一个糟糕的概念。它工作得很好,但被认为是不好的做法,所以我想纠正这种误用。 – 2012-03-20 10:19:06

回答

4
public class A { 
    private TheClass<Object> instance; 

    public A(TheClass<Object> instance) { 
     this.instance = instance; 
    } 

    public void do(Object target) { 
     instance.theMethod(target); 
    } 
} 

public class A<T> { 
    private TheClass<T> instance; 

    public A(TheClass<T> instance) { 
     this.instance = instance; 
    } 

    public void do(T target) { 
     instance.theMethod(target); 
    } 
} 
+0

+1,他们的代码仍然会像以前一样工作。这正是我正在输入的内容;但你先到了那里! – 2012-03-20 10:21:35

+0

您的第一个想法将我发送给解决方案,谢谢!实际上,我在实例字段的声明中强制实现了Object类型,就像你一样。不过,我仍然在构造函数参数中允许使用通配符,但将此实例强制转换为强制类型。这只会在编译时产生影响,并且不会从我的用户请求任何内容(请参阅上次编辑的问题)。 – 2012-03-20 10:31:06

1

解决的办法是也键入A。使用通配符?会使您丢失TheClass的类型信息,并且以后无法恢复。有一些丑陋的黑客,你可以做,但你最好的拍摄是也是键入A

public interface TheClass<T> { 
    public void theMethod(T obj); 
} 

public class A<T> { 
    private TheClass<T> instance; 

    public A(TheClass<T> instance) { 
     this.instance = instance; 
    } 

    public void doIt(T target) { 
     instance.theMethod(target); 
    } 
} 

它不会破坏任何API要么。

+0

+1,谢谢。输入A对我来说不是一种可能性,因为它会给图书馆增加无用的复杂性,但是你是对的,那是唯一的解决方案之一。 – 2012-03-20 10:32:24

1

原因的编译错误是?通配符表示在Java中未知类型。您可能会在声明一个具有未知通用参数的变量,但不能实例化其中的一个。这意味着在您的构造函数中传入的泛型类可能已经被创建为保存与您稍后尝试使用的内容不兼容的类型。例如:

public class A { 
    public static void main(String[] args) { 
     TheClass<String> stringHolder = null; // should constrain parameters to strings 
     A a = new A(stringHolder); 
     a.donot(Float.valueOf(13)) ; // this is an example of what could happen 
    } 

    private TheClass<?> instance; 

    public A(TheClass<?> instance) { 
     this.instance = instance; 
    } 

    public void do(Object target) { 
     instance.theMethod(target); 
    } 
} 

在这种情况下,编译器会阻止您编写容易出错的代码。正如其他人指出的那样,您应该为您的A类添加一个通用参数类型,以限制允许的类型 - 这将消除编译时错误。

一些建议阅读:Oracle Generics Trail

+0

谢谢。我在过去几天深入细致地学习了泛型理论来解决这个问题,所以我理解它的机制。如果我在使用原始类型之前知道理论,我就不会遇到这种情况。现在完成了,我试图删除原始类型,但遇到了这个问题。 – 2012-03-20 10:47:05

+0

无论如何,你的代码在运行时会失败,抛出异常并停止程序。没关系,这就像一个隐含的断言,错误会向用户显示他们的代码有错误。这不会是一个编译时错误,但它仍然是一个错误,并且不会有未定义的行为,只是使用调试堆栈跟踪新鲜而干净的崩溃:)对于您的示例,它将帮助人们理解为什么编译时实时验证很重要。 – 2012-03-20 10:49:05

+0

@AurélienRibon - 我发布的代码仅用于说明目的。我确实将'do'方法更改为'donot',但是就我纠正错​​误而言。 – Perception 2012-03-20 10:53:22