【问题标题】:Java generics, Interface and Class inheritanceJava泛型、接口和类继承
【发布时间】:2019-01-02 23:14:25
【问题描述】:

考虑抽象类:

public abstract class Animal { ...}

和界面:

public interface Canine<T extends Animal> {...}

我已经定义了具体的类:

public class Dog extends Animal implements Canine<Dog> {...}
public class Wolf extends Animal implements Canine<Wolf> {...}

我想构建一个repository 类来访问动物数据库并返回它们。我是这样定义的:

public interface Repository {
    Option<Dog>  findById(String id, Class<Dog>  type);
    Option<Wolf> findById(String id, Class<Wolf> type);

注意:Option 取自 vavr

此存储库用于以下类:

public abstract AbstractFinderClass<T extends Animal & Canine<T>> {

    private Class<T> animalType;

    public AbstractFinderClass(Class<T> animalType) {
        this.animalType = animalType;
    }

    public Option<T> findMyAnimal(String id) {
        return repository.findById(id, this.animalType);
    }
}

这又以具体形式实现:

public class DogFinder extends AbstractFinderClass<Dog> {
    public DogFinder() {
        super(Dog.class);
    }
}

现在,return repository.findById(id, this.animalType) 行导致两个错误:

  1. 在第二个参数上,this.animalTypeClass&lt;T&gt; 类型,而预期类型是 Class&lt;Dog&gt;,这些显然是不兼容的;
  2. 返回类型应为Option&lt;T&gt;,而我得到Option&lt;Dog&gt;

恐怕我遗漏了一些“小”细节,因为我希望 DogT 兼容。 你能帮我解决这个问题吗?

【问题讨论】:

  • 我想知道Repository 类应该如何实现。 findById 方法不应该具有相同的擦除类型吗?
  • 不是在存储库类中重载相同的方法,你能把它变成一个单一的泛型方法吗?基本上是Option&lt;A&gt; findById&lt;A&gt;(String id, Class&lt;A&gt; type); .
  • 我认为您遇到了错误,因为T 可能是您的Repository 中未定义的类型,因此您将尝试执行不存在的方法。跨度>
  • 如果DogFinder 应该是AbstractFinderClass 的具体实现,为什么它没有extends 子句?我们不能确定您的TDog。事实上,为什么它甚至有Dog 作为泛型参数?这会造成与Dog 类的混淆。您的代码实际上不会编译。我建议minimal reproducible example
  • 您所要求的基本上是 Bloch 的“Typesafe Heterogenous Container”实现——您应该查看详细描述此方法的 Effective Java。在这种特殊情况下,您可能会发现 this answer to a related question 很有用。

标签: java generics


【解决方案1】:

第一个问题是DogFinder 有一个不必要的类型参数。它是一个寻狗器,因此它所找到的类型参数是多余的(非常规命名的类型参数Dog 可能表明存在问题)。应该是:

class DogFinder extends AbstractFinderClass<Dog> {
    public DogFinder() {
        super(Dog.class);
    }
}

其次,您的Repository 类型具有绑定到特定类型的方法。这没有什么意义,因为您希望它是通用的。因此,您可以只使用一种方法,(可选)使存储库本身具有通用性(在解决签名冲突问题的过程中):

interface Repository<T extends Animal> {
    Option<T> findById(String id, Class<T> type);
}

第三,除非我们缺少上下文,我相信您的 Canine 类型不需要是通用的(除非事情必须复杂):

interface Canine {
}

如果您需要专门的犬类探测器,您可以简单地更改您的存储库类,如下所示:

abstract class CanineFinderClass<T extends Animal & Canine>
    implements Repository<T> {...}

附带说明,DogFinder 存储库是多余的,除非它提供特殊的狗方法,例如 findAllPuppies()。否则,使AbstractFinderClass 具体化就足够了,因为类型是通用的(只是一个例子):

class AnimalFinderClass<T extends Animal> implements Repository<T> {

    Repository<T> repository;

    private Class<T> animalType;


    public AbstractFinderClass(Class<T> animalType) {
        this.animalType = animalType;
    }

    public Option<T> findMyAnimal(String id) {
        return repository.findById(id, this.animalType);
    }
}

【讨论】:

  • 类型参数 应该从 Repository 类移到 findById 方法,因为 Repository 适用于所有动物类型。
  • @AlexeiKaigorodov 这个想法是给定的存储库可以使用任何动物类型,不是所有动物类型。所以在实例化时,类型参数将定义哪种动物类型。所以当重用repo时,你会得到相同的类型(否则animalType参数必须在每个方法调用中传递)
  • 主题启动器的想法是存储库适用于所有类型,而 AbstractFinderClass 适用于任何类型。否则,不需要 2 个不同的类,每个类都使用某种具体的动物类型。
  • @AlexeiKaigorodov 这正是我的旁注的重点(我怀疑我们说的是同一件事)。给定类型可以与任何动物类型一起使用(因此该类型本身是通用的),但是当创建存储库的实例时,它会绑定到一个具体的动物类型(因此该方法不是通用的)。 OP 最大的问题是 DogFinder 上的(可能是偶然的)类型参数
  • 我对@9​​87654335@ 的额外参数不好,抱歉:我错误地删除了一段代码:/ 查看更新的问题。
猜你喜欢
  • 1970-01-01
  • 2021-10-27
  • 1970-01-01
  • 2020-12-01
  • 2014-03-05
  • 2012-01-27
  • 2021-10-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多