Java:嵌套递归泛型

7

我有一组继承自某个基本实体的类。这组类中的某些类也可以相互继承,形成嵌套层次结构。

我的目标是让所有的类都能够访问一个创建自身新实例的方法。我希望在我的基本实体中实现此方法,以便所有继承类都可以继承它。

以下是定义为我的模式的三个示例类:

BaseEntity.java

public abstract class BaseEntity<E extends BaseEntity> {

    Class<E> clazz;

    public BaseEntity(Class<E> clazz) {
        this.clazz = clazz;
    }

    public E getNewInstance() throws IllegalAccessException, InstantiationException {
        return clazz.newInstance();
    }

}

Collection.java

public class Collection<E extends Collection> extends BaseEntity<E> {

    public Collection() {
        super(Collection.class); 
        // compiler error: BaseEntity (java.lang.Class<E>) in BaseEntity cannot be applied to
        //                            (java.lang.Class<Collection>)
    }

    public Collection(Class<E> clazz) {
        super(clazz);
    }

}

Document.java

public class Document extends Collection<Document> {

    public Document() {
        super(Document.class);
    }

}

通过这个设置,我希望能够做到以下操作:

Collection c = new Collection();
c = c.getNewInstance(); // compiler error

Document d = new Document();
d = d.getNewInstance();

Collection cd = new Document();
cd = cd.getNewInstance(); // compiler error

请注意,Collection.java 的默认构造函数存在编译错误。我不确定造成这种情况的原因,我认为这也导致了示例主方法中的编译错误。我做错了什么,如何解决?

请注意,这是一个虚构的示例,涉及我正在尝试解决的更大问题。我明白这个实现看起来有些愚蠢。


3
一些流行的Java库类名冲突引起了混淆... - Foosh
2个回答

8

Collection<E...>是一种通用类型,但你的Collection c是一个原始类型。这意味着它的所有方法都将被视为原始类型,这意味着它们将返回任何通用类型的擦除。

你的基类被声明为BaseEntity<E extends BaseEntity>,这意味着在这个方法中:

E getNewInstance()

擦除是指将数据从存储设备中删除的过程。
BaseEntity getNewInstance();

这意味着c.getNewInstance()返回一个BaseEntity,而不是一个Collection,这就是你编译错误的原因。
另一方面,Document并不是一个泛型类。这意味着擦除在编译时并不重要(对于这些目的),而getNewInstance()返回E表示的类型,在这种情况下是Document。因此,d.getNewInstance()的返回类型为Document,所以该行代码可以编译通过。
顺便说一下:每当您使用递归泛型时,应确保在递归中考虑到泛型。例如,在这一行中:
BaseEntity<E extends BaseEntity>

您定义了一个泛型类 BaseEntity,但是在 E extends BaseEntity 中立即忽略了其泛型。该行代码应该改为:

BaseEntity<E extends BaseEntity<E>>

完全明白,解释得非常好。 我觉得我的问题的一部分在于我很难理解递归泛型。 BaseEntity<E extends BaseEntity<E>> 是我难以掌握的一个例子。 但我只需要花更多时间去理解,再次感谢。 - user2066880
你能解释一下 BaseEntity<E extends BaseEntity<E>>BaseEntity<E extends BaseEntity<?>> 之间的区别吗?它们似乎都是解决递归泛型的有效方式,但我注意到编译器处理它们的方式有所不同。 - user2066880
1
这比注释要复杂一些——可能值得跟进一个问题(我没有找到任何关于“带通配符的Java递归泛型”的内容)。但基本上区别在于当你的泛型类本身使用E上的方法时。考虑它是否有一个方法,该方法接受E entity并调用entity.getNewInstance()。使用extends BaseEntity<E>,您将获得一个E。使用extends BaseEntity<?>,您将获得一个BaseEntity<?> - yshavit
еңЁиҝҷйҮҢдҪҝз”ЁBaseEntity<E>иҖҢдёҚжҳҜBaseEntity<E extends BaseEntity<E>>жҳҜжІЎжңүж„Ҹд№үзҡ„гҖӮ - newacct

3

这个构造函数存在问题。

public Collection() {
    super(Collection.class);
}

超类构造函数期望一个 Class<E>,但是类字面量 Collection.class 是一个 Class<Collection>。这些类型不兼容,因为 E 可能是一个 Collection,一个 Document 或者任何其他可能扩展 Collection 的东西。
任何像 Document 这样扩展 Collection 的类都必须提供自己的类,因此它将调用另一个接受 Class<E>Collection 构造函数,因此我认为 Collection() 构造函数没有任何用处。我会将其删除。
此外,在 E 的上限中,您使用了您试图使通用的类的原始形式。请使用
public abstract class BaseEntity<E extends BaseEntity<E>> {

并且

public class Collection<E extends Collection<E>> extends BaseEntity<E> {

Collection 类型是泛型的,因此您必须指定与传递给 Collection 构造函数的参数匹配的泛型类型参数。

Collection<Document> c = new Collection<Document>(Document.class);
c = c.getNewInstance();

文档本身并不是通用的,所以这段代码仍然可以正常工作:

Document d = new Document();
d = d.getNewInstance();

即使是直接创建一个文档,也必须将 Document 作为类型参数提供给 Collection,因为 DocumentCollection<Document>
Collection<Document> cd = new Document();
cd = cd.getNewInstance();

非常有帮助。我理解了主方法中的差异。我仍然遇到一个问题,即使在更改类头后,我的“Collection”默认构造函数仍然具有相同的编译器错误。 - user2066880
1
我认为Collection的默认构造函数是无用的,并建议将其删除。当你必须使用具体的子类时,使用Collection<Collection>有什么好处呢?此外,这会迫使你编写Collection<Collection<Collection>>,从而强制你编写 Collection<Collection<Collection<Collection>>> ,以及更多层次的泛型无限循环。 - rgettman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接