Java 泛型编译错误 - 类型中的方法 method(Class)不适用于参数

发布于 2024-12-14 03:47:19 字数 1170 浏览 2 评论 0原文

上周四,工作中的某人向我展示了一个编译错误,我无法以干净的方式修复该错误,从那时起它就一直困扰着我。

问题与泛型相关,我重建了生成编译错误的代码的简化版本。该错误发生在如下所示的最后一行代码中。

我一直在互联网上寻找,但似乎无法找到 Java 编译器不接受代码的合理解释。我想如果允许代码,则可能会在 Bar.operationOnBar() 中创建类转换问题,但我不知道如何实现。

有人可以告诉我为什么这不能编译吗?

public interface Interface {
}


public class Type implements Interface {
}

public class Bar<T> {
    public Bar(Class<T> clazz) {
    }

    public void operationOnBar(Class<T> arg){
    }
}

public class Foo {
    public <T> Bar<T> bar(Class<T> clazz){
        return new Bar<T>(clazz);
    }
    public static void main(String[] args) {
        Class<? extends Interface> extendsInterfaceClazz = Type.class;
        new Foo().bar(extendsInterfaceClazz).operationOnBar(Type.class);
    }
}

Foo.main()第二行出现编译错误:

The method operationOnBar(Class<capture#1-of ? extends Interface>) in the type Bar<capture#1-of ? extends Interface> is not applicable for the arguments (Class<Type>)

顺便说一句。我通过将 Type.class 向下转换为 Class 来解决这个问题,这样编译器就无法看到 Class 的泛型类型是“Type”而不是“? extends Interface”。

Last Thursday someone at work showed me a compile error that I wasn't able to fix in a clean way and it has been bothering me ever since.

The problem is generics related and I've reconstructed a simplified version of the code that generates the compile error. The error occurs in the very last line of code shown below.

I've been looking all over the interwebs but can't seem to find a decent explanation why the Java compiler doesn't accept the code. I guess that if it were to allow the code, it would be possible the create a class cast issue in Bar.operationOnBar(), but I don't see how.

Could someone please enlighten me why this doesn't compile?

public interface Interface {
}


public class Type implements Interface {
}

public class Bar<T> {
    public Bar(Class<T> clazz) {
    }

    public void operationOnBar(Class<T> arg){
    }
}

public class Foo {
    public <T> Bar<T> bar(Class<T> clazz){
        return new Bar<T>(clazz);
    }
    public static void main(String[] args) {
        Class<? extends Interface> extendsInterfaceClazz = Type.class;
        new Foo().bar(extendsInterfaceClazz).operationOnBar(Type.class);
    }
}

Compile Error on the second line of Foo.main():

The method operationOnBar(Class<capture#1-of ? extends Interface>) in the type Bar<capture#1-of ? extends Interface> is not applicable for the arguments (Class<Type>)

Btw. I've solved it by downcasting Type.class to Class, this way the compiler is unable to see that the generic type of Class is "Type" instead of "? extends Interface".

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

羁〃客ぐ 2024-12-21 03:47:19

一点建议:当您不确定编译器为什么禁止某些与泛型相关的转换时,请将有问题的泛型类替换为 List。那么很容易找到一个破坏类型安全的例子。

这种替换是正确的,因为当前 Java 没有提供一种方法来对泛型类的可能行为进行任何先验知识(即,它缺乏在声明中指定泛型类的协变和逆变的方法,如 C# 4 和 Scala 中那样) 。因此,ClassList 对于编译器可能的行为是等效的,编译器必须禁止可能导致 问题的转换还列出其他泛型类的

在你的情况下:

public class Bar<T> {
    private List<T> l;

    public Bar(List<T> l) {
        this.l = l;
    }

    public void operationOnBar(List<T> arg) {
        l.addAll(arg);
    }
}

List<Type1> l1 = new ArrayList<Type1>();
List<? extends Interface> l2 = l1;
List<Type2> l3 = Arrays.asList(new Type2());

new Foo().bar(l2).operationOnBar(l3);

Type1 t = l1.get(0); // Oops!

A little advice: when you are not sure why compiler prohibits some generic-related conversion, replace generic classes in question with List<T>. Then it would be easy to find an example that breaks type safety.

This replacement is correct since currently Java doesn't provide a way to conduct any a priory knowledge about possible behaviours of generic classes (i.e. it lacks a way to specify covariance and contravariance of generic classes in their declarations, as in C# 4 and Scala). Therefore Class<T> and List<T> are equivalent for the compiler with respect to their possible behaviours, and compiler has to prohibit conversions that can cause problems with List<T> for other generic classes as well.

In your case:

public class Bar<T> {
    private List<T> l;

    public Bar(List<T> l) {
        this.l = l;
    }

    public void operationOnBar(List<T> arg) {
        l.addAll(arg);
    }
}

List<Type1> l1 = new ArrayList<Type1>();
List<? extends Interface> l2 = l1;
List<Type2> l3 = Arrays.asList(new Type2());

new Foo().bar(l2).operationOnBar(l3);

Type1 t = l1.get(0); // Oops!
七秒鱼° 2024-12-21 03:47:19

您还可以将方法 operationOnBar 的签名更改为:

public void operationOnBar(Class<? extends Interface> arg){

You also can change the signature of the method operationOnBar to:

public void operationOnBar(Class<? extends Interface> arg){
晨与橙与城 2024-12-21 03:47:19

您会同意这不应该编译:

 1   Class<? extends Interface> clazz = AnotherType.class;
 2   new Foo().bar(clazz).operationOnBar(Type.class);

问题是 javac 有点愚蠢;当编译第 2 行时,它所知道的关于变量 clazz 的所有信息就是其声明的类型;它忘记了分配给它的具体类型。因此,在第 1 行分配给 clazz 的内容并不重要,编译器必须拒绝第 2 行。

我们可以想象一个更智能的编译器可以跟踪具体类型,然后您的代码可以被编译,因为它显然是安全和正确的。

由于情况并非如此,有时程序员比编译器更了解类型,因此程序员有必要进行强制转换来说服编译器。

You would agree that this shouldn't compile:

 1   Class<? extends Interface> clazz = AnotherType.class;
 2   new Foo().bar(clazz).operationOnBar(Type.class);

The problem is javac is a little dumb; when compiling line#2, all it knows about variable clazz is its declared type; it forgets the concrete type it was assigned to. So what is assigned to clazz at line#1 doesn't matter, compiler must reject line#2.

We can imagine a smarter compiler that can track the concrete types, then your code can be compiled, as it is obviously safe and correct.

Since that's not the case, sometimes programmers know more about types than the compiler, it is necessary that programmers do casts to convince the compiler.

一念一轮回 2024-12-21 03:47:19

处理这类问题的一般方法是为重复类型引入泛型参数,这通常意味着引入新的泛型方法(类也可以,但不是必需的)。

public static void main(String[] args) {
    fn(Type.class);
}
private static <T extends Interface> void fn(Class<T> extendsInterfaceClazz) {
    new Foo().bar(extendsInterfaceClazz).operationOnBar(extendsInterfaceClazz);
}

与这个问题不太相关,但我建议谨慎使用反射。这是非常非常罕见的好的解决方案。

The general way to deal with these sorts of problems is to introduce a generic argument for the repeated type, which generally means introducing a new generic method (a class would do as well, but isn't necessary).

public static void main(String[] args) {
    fn(Type.class);
}
private static <T extends Interface> void fn(Class<T> extendsInterfaceClazz) {
    new Foo().bar(extendsInterfaceClazz).operationOnBar(extendsInterfaceClazz);
}

Not really related to the question, but I would suggest using reflection sparingly. It is very, very rarely a good solution.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文