【问题标题】:Bound mismatch error on generics type argument for a generic class that extends another generic class扩展另一个泛型类的泛型类的泛型类型参数的绑定不匹配错误
【发布时间】:2020-07-14 18:12:20
【问题描述】:

我对 java 中的泛型和继承有疑问。

我想从“createManager”方法中实例化一个“ObjectManager”类,该方法可以从给定类型创建实例。这个“ObjectManager”有一些实用方法(例如:“getDescription”)。

此方法允许我从一个类型实例化一个“ObjectManager”,然后获取例如它的描述(此处为哈希)。

如果输入类型可分配给类型“Foo”,我还想为“FooManager”专门化“ObjectManager”,以便我可以覆盖“getDescription”方法。因此,我在“createManager”方法中测试输入的类型,以了解我是创建“ObjectManager”还是“FooManager”。

代码有效,但我不知道对应于“new FooManager(type)”的输出的通用类型。在我看来,对于 Eclipse 快速修复,类型应该是 但这会导致错误:

Bound mismatch:类型 T 不是 FooManager

类型的有界参数 的有效替代品

我的代码:

import java.lang.reflect.InvocationTargetException;
import java.util.Objects;

public class GenericBug {
    private GenericBug() {}
    
    public static void main(String[] args) {
        ObjectManager<Foo> objectManager1 = createManager(Foo.class);
        System.out.println(objectManager1.getDescription());
        
        ObjectManager<Object> objectManager2 = createManager(Object.class);
        System.out.println(objectManager2.getDescription());
    }
    
    private static <T> ObjectManager<T> createManager(Class<T> type) {
        if(Foo.class.isAssignableFrom(type))
            return new FooManager<T>(type);   //Error: Bound mismatch
        return new ObjectManager<T>(type);
    }
}

class ObjectManager<T>{
    public T val;
    public ObjectManager(Class<T> type) {
        try {
            val = type.getConstructor().newInstance();
        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
            e.printStackTrace();
        }
    }
    
    public String getDescription() {
        return "Hash: " + Objects.hash(val);
    }
}

class FooManager<T extends Foo> extends ObjectManager<T>{

    public FooManager(Class<T> type) {
        super(type);
    }
    
    @Override
    public String getDescription() {
        return super.getDescription() + " value: " + val.getVal();
    }
}

class Foo{
    private double val = Math.random();
    public Foo() {}

    public double getVal() {
        return val;
    }
}

所以我的问题是:为什么会出现这个错误?如何在没有警告的情况下修复它?而且,作为奖励,为什么 Eclipse 提供了一个无法编译的解决方案?这是 Eclipse 错误吗?

提前感谢您的回答。

【问题讨论】:

  • 您会收到此错误,因为 FooManager 需要一个扩展 Foo 的类型,但 createFooManager() 中的 T 并没有“明确地”这样做,检查并不重要对于isAssignableFrom(),从编译器的角度来看,createFooManager() 中的T 仅扩展了Object,因此会出现错误。
  • 我学到的关于 OO 的第一件事就是,当你发现自己使用 if 语句来确定要使用哪个对象时,你就知道自己做错了。实例化。
  • 您的应用程序是否只会创建 FooFooManager 类型的实例? Bar 类型的对象在语义上是 Foos 呢?那你不是需要一个对应的BarManager吗?然后,您必须在 if 检查中添加另一个分支。对?对我来说,将一个名为 createFooManager() 的方法投入使用以创建 语义 没有 的东西有点粗略。 >Foo - 关于他们。
  • 首先,感谢您的回复。我想我理解了这个警告,这是最初的目标。关于方法“createFooManager”的名称,这是我在创建此示例时的错误……正如 Eugene 建议的那样,它应该被称为“createManager”。我会在我的例子中更正它。
  • 我还指出,我做了这个极简主义的例子来说明我的问题。实际上,它与 RMI 上下文中的任务委派问题有关。在我的情况下,我不仅有一个 if,而是一个根据类类型注册管理器的映射,并且我的解决方案的用户可以动态添加其他管理器。所以我需要一种独特的方法来创建它。在我看来,这会使重复数据删除器提案无效,但他无法从我的示例中推断出来......我的错......请注意:有时我也可以在不使用此方法的情况下创建管理器,如果我知道我想要哪个管理器使用。

标签: java generics inheritance


【解决方案1】:

发生该错误是因为您没有编译时保证 T 是 Foo 的实例。您只有运行时保证 Foo 可以从 T 分配,编译器并不关心。您可以通过抑制它们来真正地使这个编译没有警告:

@SuppressWarnings({ "unchecked", "rawtypes" })
private static <T> ObjectManager<T> createFooManager(Class<T> type) {
    if(Foo.class.isAssignableFrom(type))
        return new FooManager(type);   //Error: Bound mismatch
    return new ObjectManager<T>(type);
}

这可能不是您要寻找的答案,但我认为您无法在不压制警告的情况下摆脱警告。我从这些警告的必然性中推断出最初的方法是不正确的。为什么需要传入一个类作为参数?如果类参数在编译时已知,您可以简单地为不同的类调用不同的方法。如果在编译时该类是未知的,那么您将永远无法从该泛型参数中受益。

【讨论】:

  • 我也在我的主帖中发布了我的答案,但我也在此处发布了它:就我而言,我在编译时不知道该类。因此我不能调用不同的方法。我同意你的观点,当我调用“createManager”时,我无法从该通用参数中受益。
  • 但是 ObjectManager 有通用参数还有两个原因: (1) 避免在 'return super.getDescription() + " value: " + val.getVal();' 行中转换变量 val .如果没有泛型参数,则 val 的类型将是 Object。 (2)在其他地方不用'createManager'而只用ObjectManager(因为我们不需要这些点的特殊性)并且能够利用泛型参数的优势。就我而言,我只需要在运行时而不是在编辑阶段的特异性。
【解决方案2】:

问题是您通过isAssignableFrom 混合了泛型的编译时安全性和运行时检查。

我不知道更好的方法:

private static <T> ObjectManager<T> createFooManager(Class<T> type) {
    if (Foo.class.isAssignableFrom(type)) {
        return (ObjectManager<T>) getIt((Class<? extends Foo>) type);
    }
    return new ObjectManager<>(type);

}

private static <R extends Foo> ObjectManager<R> getIt(Class<R> cls) {
    return new FooManager<>(cls);
}

不过,这会引发unchecked warning

【讨论】:

  • 使用 if 测试选择要创建的对象通常被认为是“代码气味”面向对象编程。另外,关于:“这将引发未经检查的警告,尽管…”,我的回答是demonstrates how to avoid that
【解决方案3】:

...为什么会出现这个错误?...“

javac 对您的原始代码报告的错误为您提供了以下原因:

...GenericBug.java:14: error: type argument T#1 is not within bounds of type-variable T#2
            return new FooManager<T>(type);   //Error: Bound mismatch
                                  ^
  where T#1,T#2 are type-variables:
    T#1 extends Object declared in method <T#1>createFooManager(Class<T#1>)
    T#2 extends Foo declared in class FooManager
1 error

T#1 is not within bounds of type-variable T#2 因为:

  1. &lt;T&gt; ObjectManager&lt;T&gt; createFooManager(...) 表示您将 Taka T#1)定义为 no 界限。
  2. FooManager&lt;T extends Foo&gt; 的类型参数 T (akaT#2) 使用边界 @987654333 定义@。并且泛型方法的 T 并没有像泛型类所要求的那样扩展 Foo。所以它超出范围

„...如何解决它...“

您最初的 Foo.class.isAssignableFrom(type) 方法让我觉得不是非常面向对象。所以基本上,I've fixed it this way

...
private static <T> ObjectManager<T> createObjectManager(Class<T> type) {
        return new ObjectManager<>(type);
} 

private static <S extends Foo> ObjectManager<S> createFooManager(Class<S> type) {
        return new FooManager<>(type);
}
...   

您的 IRL 用例是什么并不明显。所以我不得不做出一些假设。就像我假设您很可能将它与 Foo 的更专业的实现一起使用。所以我介绍了一个 FooJr 来演示解决方案如何处理这个问题:

    ObjectManager<? extends Foo> objectManager1 = createFooManager(Foo.class);
    System.out.println(objectManager1.getDescription());
    
    ObjectManager<?> objectManager2 = createObjectManager(Object.class);
    System.out.println(objectManager2.getDescription());

    objectManager1 = createFooManager(FooJr.class);
    System.out.println(objectManager1.getDescription());        

    objectManager2 = createObjectManager(FooJr.class);
    System.out.println(objectManager2.getDescription());

说一种解决方案比另一种解决方案“更好”或“更差”是主观的;也许是个人品味的问题。但是可以用 objectivity (pun 意图) 说,一种解决方案可以比另一种解决方案更面向对象。有些解决方案肯定比其他解决方案更安全。

“……没有警告?……”

This solution 被证明是更安全的类型。使用 -Xlint:unchecked 运行它。编译器不会报告任何未经检查的警告。这不需要任何@SuppressWarnings(...)

„...为什么 Eclipse 提供了无法编译的解决方案?...“

Eclipse 只是向您展示了它的最佳猜测,它认为解决问题的方法是什么。 IDE 的本质是给某些东西一个 PASS,javac 或 JLS 绝对不允许。

【讨论】:

  • 通常情况下,“更好”解决方案——在 OO 设计最佳实践环境中——是创建另一个方法。考虑一下:应用程序是否只会创建 FooFooManager 类型的实例? Bar 类型的对象在语义上是 Foos 呢?那你不是需要一个对应的BarManager吗?对我来说,将一个名为 createFooManager() 的方法压入服务以创建 语义 没有 Foo - 关于他们。
  • 你刚刚创建了一个新方法,这改变了他/她必须做的公开暴露方法的合同“ -​​ 原始 的公共 API createFooManager() 没有改变。因此,对于该方法的消费者来说,什么都不会改变。我的解决方案使应用程序更安全。 AND 它更接近于引导 OOD 最佳实践。
  • OP 的方法返回一个 ObjectManager&lt;T&gt;。您的答案方法返回 ObjectManager&lt;T&gt;。我的回答方法返回一个ObjectManager&lt;S&gt;。有什么不同? OP 的方法采用 Class&lt;T&gt;。我的回答方法采用Class&lt;S&gt;。没有不同。 “这意味着调用者也应该改变” - 请问?告诉我——使用一小段代码——调用者必须以什么方式改变?我不反对您的评论。我正在 (1) 寻求知识 (2) 确保 正确 OO 知识可供人们使用像我一样正在寻求正确 OO 知识。
  • ...OP 方法:createObjectMapper 被两个客户端调用...“- 从来没有一个名为 createObjectMapper() 的 OP 方法 i> 也不是名为 createObjectManager() 的 OP 方法。只有 createFooManager()。不要相信我的话。看看the revision history你自己。 — „...您引入了一种新方法:createFooManager 并编辑了 createObjectManager...的内部结构...“。这就是我所说的不正确 OO 知识。我没有介绍createFooManager()。它一直都在。查看帖子历史记录?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多