【问题标题】:JPA Abstract Inheritance with List<AbstractClass>带有 List<Abstract Class> 的 JPA 抽象继承
【发布时间】:2020-09-13 02:11:28
【问题描述】:

我的代码:

@MappedSuperclass
public abstract class Header {
    public abstract List<Line> getLines();
}

@Entity
public class ChildHeader extends Header{
    @OneToMany
    public List<ChildLine> getLines();
}
 
@MappedSuperclass
public abstract class Line {
    public abstract Header getHeader();
}

@Entity
public class ChildLine extends Line {
    @ManyToOne
    public ChildHeader getHeader();
}

问题:我收到错误消息“错误:'ChildHeader'中的'getLines()'与'Header'中的'getLines()'发生冲突;尝试使用不兼容的返回类型”

尝试的解决方案:将List&lt;Line&gt; 更改为&lt;T extends Line&gt; List&lt;T&gt; 并将List&lt;ChildLine&gt; 更改为&lt;T extends ChildLine&gt; List&lt;T&gt;

新错误:“ChildHeader”中的“getLines()”与“​​Header”中的“getLines()”发生冲突;两种方法都有相同的擦除,但都没有覆盖另一个

尝试的解决方案:尝试使用 Line[]ChildLine[] 而不是列表

新错误:“列表/数组必须用@OrderColumn(或@IndexColumn)注释”,但没有排序列。数组的顺序无关紧要,但似乎 @OneToMany 或 JPA 需要一个。

这是不可能的情况。我能做什么?

【问题讨论】:

    标签: java jpa generics inheritance


    【解决方案1】:

    由于 ChildHeader's “getLines” 没有覆盖 Header's 声明(鉴于它是一个抽象方法,最终必须完成),出于所有意图和目的,您正试图通过不同的返回类型重载该方法。

    在 Java 中,这是不允许的。

    有什么理由不能像这样覆盖 Header 的“getLines”方法:

    @Entity
    public class ChildHeader extends Header{
        @OneToMany
        public List<Line> getLines(); 
              // since this isn't an abstract class, there
              // should be an implementation here
    }
    

    由于“ChildLine”扩展了“Line”,您仍然可以返回一个只有“ChildLine's”的列表。您只需要确保在调用此类方法时,Line 列表中的那些“ChildLines”被转移到 ChildLine 列表中。

    类似的逻辑适用于线类。 ChildLine 正在尝试从 Line 重载“getHeader()”,而通过确保它们的返回类型匹配来覆盖该方法会更容易。

    【讨论】:

    • 感谢您的建议,当我需要使用任何 ChildLine 成员时,似乎我最终可能只需要按照您所说的进行转换,然后将 Lines 转换为 ChildLines .感谢您的解释,我想我认为 Java 编译器会足够聪明地认识到因为 ChildLine 扩展了 LineChildHeadergetLines() 是一个覆盖,而不是一个重载。
    【解决方案2】:

    ……这是不可能的情况……

    如果您遵循 the JLS' 关于覆盖实例方法的指导,则不会……

    8.4.8.1。覆盖(通过实例方法)

    一个实例方法mC在类C中声明或继承,覆盖C另一个方法mAA 类中声明,如果以下所有条件都为真:

    • m的签名Cm签名的子签名(§8.4.2A

    特别注意the section on method signatures

    8.4.2。方法签名

    MN 这两个方法或构造函数具有相同的签名,如果它们具有相同的名称,相同的类型参数(如果any) (§8.4.4),并且,将N的形参类型适配为M的类型形参后,形参类型相同

    你得到错误的症结在于你的方法不符合 JLS 的标准。

    ...我能做什么?...

    如果你重构了你的 Header 类……

    public abstract class Header {
        
        public abstract < L extends Line > List< L > getLines( );
    }
    

    如果你重构了你的 ChildHeader 类……

    public class ChildHeader extends Header {
        
        @Override
        @SuppressWarnings("unchecked")
        public < C extends Line > List< C > getLines(){ … }
    }
    

    那你可以what I demonstrate in this working example

    …
    Header header = new ChildHeader( );
      
    List< ChildLine > lines = header.getLines( );
      
    ChildHeader childHeader = lines.get( 0 ).getHeader( );
    …
    

    【讨论】:

    • 谢谢。似乎这就是我需要做的,因为我只需要将我的Line 转换为ChildLine,只要它们是ChildLine,就像你在这里所做的那样。我不明白的是为什么这些方法没有相同的签名? ChildLineLine,因此 List&lt;ChildLine&gt;List&lt;Line&gt;。我们在 getHeader() 方法中看到了这一点,其中覆盖按预期工作。
    • 如果由于某种原因List泛型不能使用继承,我还是不明白,因为在我的例子中我提到我尝试将List&lt;ChildLine&gt;更改为&lt;T extends ChildLine&gt; List&lt;T&gt;并更改List&lt;Line&gt;&lt;T extends Line&gt; List&lt;T&gt;。擦除后,这两种类型都应归结为List&lt;T&gt;,这再次意味着它们具有相同的签名,因此应按预期覆盖。无论如何,我可以根据需要转换类型并继续进行,只是出于好奇而提出这些问题。感谢您的帮助。
    • ...因此List&lt;ChildLine&gt;List&lt;Line&gt;“——Java 不是这样工作的。查看covariance/contravariance。 — „擦除后,这两种类型都应归结为List&lt;T&gt;“。这是不正确的。擦除后不会有任何T。毕竟删除的是T。 — „出于好奇而提出这些问题。“- 查看what the JLS says about type erasure。阅读/重读第 8.4.2 + 8.4.8.1 节。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-28
    • 2011-04-19
    • 2012-04-25
    • 1970-01-01
    • 2011-05-15
    • 1970-01-01
    相关资源
    最近更新 更多