【问题标题】:Java map function throws non-static method compiler errorJava map 函数抛出非静态方法编译器错误
【发布时间】:2022-01-26 08:09:49
【问题描述】:

我有一个奇怪的问题,我很难理解 Java 中“静态上下文”的本质,尽管关于这个主题有很多 SO 问题。

TL;DR:

我有一个设计缺陷,其中...

这行得通:

List<OrderExtnTrans> list = orderType.getOrderExtnTransList();
this.dtoOrderExtnTransList = list.stream().map(OrderExtnTrans::toDto).collect(Collectors.toList());

但这不是:

this.dtoOrderExtnTransList = orderType.getOrderExtnTransList().stream().map(OrderExtnTrans::toDto).collect(Collectors.toList());

第二个版本显示的错误是“Non-static method cannot be referenced from a static context”。

长版:

对象模型: 该模型由特定于业务类型的订单(例如证券交易所、支付)组成,这些订单通过“InheritanceType.JOINED”继承策略从订单实体继承。 父订单可以使用该订单的业务类型特定 DTO 对象进行参数化,例如 DtoStockExchangeOrder。这是为了使 JPA 对象可以映射到实体中的 DTO 等效项,而不是服务中(我之前做过。它有效,但它“不太干净”)。

JPA 订单:

@Entity
@Table(name = "ORDER_BASE")
@Inheritance(strategy = InheritanceType.JOINED)
public class Order<DtoOrderType extends DtoOrder> implements Serializable {

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "order", orphanRemoval = true)
    private List<OrderExtnTrans> orderExtnTransList = new ArrayList<>();

}

JPA 订单 - 业务类型特定示例:

@Entity
@Table(name = "ORDER_STEX")
@Inheritance(strategy = InheritanceType.JOINED)
public class OrderStex extends Order<DtoOrderStex> implements Serializable {

同样,DTO 订单遵循相同的模式,可以使用业务类型特定的 JPA 实体对其进行参数化,以启用相关映射:

DTO 订单:

public class DtoOrder<OrderType extends Order> extends DtoEntity {

DTO 订单 - 业务类型特定示例

public class DtoOrderStex extends DtoOrder<OrderStex> {

它继承的 DTOEntity 类只是一个“包装”类,由一个 ID 和一个名称组成。

现在是棘手的部分: DTOOrder 类有一个构造函数,它填充所有业务类型共有的字段,如流程状态转换列表、订单在其生命周期中经历的(放置、取消、执行等)。继续以流程状态转换为例,这些也被建模为数据库中的 JPA 实体,以及它们对应的 DTO 对应物(同样参数化,该部分工作正常)。

这里是构造函数:

public DtoOrder(OrderType orderType) {
    super(orderType);
    // this is the part from above, which works (but it shows a warning: Unchecked assignment: 'java.util.List' to 'java.util.List<com.tradingvessel.core.persistence.model.OrderExtnTrans>' )
    List<OrderExtnTrans> list = orderType.getOrderExtnTransList();
    this.dtoOrderExtnTransList = list.stream().map(OrderExtnTrans::toDto).collect(Collectors.toList());
    // this is how I would have expected it to work, but it does not, with the error shown above: "Non-static method cannot be referenced from a static context"
    this.dtoOrderExtnTransList = orderType.getOrderExtnTransList().stream().map(OrderExtnTrans::toDto).collect(Collectors.toList());
}

如果我注释掉非工作版本,应用程序将按预期运行并且不会引发错误,因此“从逻辑上讲”,这是可行的。但是JAVA不允许它像第二版那样开发。

如果我使用“Order”而不是 OrderType,它也可以正常工作,但显然会在其他地方抛出错误,因为构造函数的签名发生了变化。我假设另一种方法是根据构造函数的调用者对方法进行参数化,或者对父类 DtoOrder 进行参数化以了解子类的类型,但应该有更好的方法吗?

我做错了什么,为什么高版本能按预期工作?

【问题讨论】:

  • 所以忘记使用变量list的工作的表面差异,主要区别在于工作的使用OrderExtnTrans但失败的使用OrderExtnAdd。您能否编辑您的问题以显示更多这些类 - 特别是显示它们的 toDto 方法(特别是如果一个是 static
  • @racraman 抱歉,这是一个复制粘贴错误,它们在实际代码中是同一个类。稍后我将添加更多详细信息
  • dtoOrderExtnTransList 的类型是什么,getOrderExtnTransList() 的返回是什么?
  • 您能否编辑您的问题以获得可重现的示例,我们没有正确的类并且缺少某些方法。我怀疑您的原始类和类型无法正确推断。
  • 请出示OrderType class,谢谢

标签: java spring hibernate dictionary jpa


【解决方案1】:

感谢您提出一个有趣的问题,该问题显示了一些意外行为,其行为符合规范。

TL;DR(即快速轻松地解决此问题的正确方法)是在 DtoOrder 类声明中的 Order 中添加 > 通用通配符,因此:

public class DtoOrder<OrderType extends Order<?>> extends DtoEntity {

这将使构造函数中的一体化方式工作:

​this.dtoOrderExtnTransList = orderType.getOrderExtnTransList().stream().map(OrderExtnTrans::toDto).collect(Collectors.toList());

至于为什么会这样修复,这是因为您已将 Order 定义为 Generic 类型:

public class Order<DtoOrderType extends DtoOrder> 

通过不指定泛型类型,您将其声明为(因此也使OrderType)为原始类型

通常我们习惯于List&lt;SomeType&gt; 是通用的,在运行时会进行类型擦除。此外,如果我们有类似的旧代码:

List myRawTypeVariable = new ArrayList();

myRawType 是一个原始类型,因为我们可以添加任何Object 并且只能得到Objects。

但是,事实证明(正如您所发现的)原始类型比这更进一步,并且类型擦除也具有编译时含义。

Java 语言规范 (JLS) 这么说(来源:https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.8

构造函数的类型(第 8.8 节)、实例方法(第 8.4 节、第 9.4 节)或 未从其继承的原始类型 C 的非静态字段(第 8.3 节) 超类或超接口是对应于的原始类型 在对应于 C 的泛型声明中擦除其类型。

请注意,这并不是将类型擦除仅限于泛型类型; ALL 实例方法的类型都被转换为它们的原始类型!

换句话说,通过不指定Order 的泛型类型,您正在使Order 成为原始类型 - 因此关闭该类的所有泛型类型检查(除了方法等另有指定,从继承或接口)。

因此,即使 getOrderExtnTransList() 被声明为返回 List&lt;String&gt;,因为您使用 Order 作为原始类型,它也会删除 &lt;String&gt; 泛型并将该方法视为简单地返回 List(实际上是List&lt;Object&gt;)。

您可以尝试插入peek 来确认这一点,因此:

   ​this.dtoOrderExtnTransList = orderType.getOrderExtnTransList().stream().peek(s -> s.

然后尝试自动完成。您会发现选项只是Object 的成员,而不是endsWithString 的成员。

反过来,这意味着当它到达.map(OrderExtnTrans::toDto) 时,而不是将其解释为OrderExtnTrans 顺流而下:

 `.map(o -> o.toDto())`, 

它认为你的意思是

  `.map(o -> OrderExtnTrans.toDto(o))` 

这适合 Object 顺流而下 - 这就是为什么它抱怨 toDto 是一种非静态方法。

如上所述,解决方案是简单地不将Order 视为原始类型,而是通过添加 > 使其成为通用类型。

【讨论】:

  • 好的,这可能是我在 Stackoverflow 上得到的最酷的答案,谢谢! “您可以通过尝试插入 peek 来确认这一点,然后尝试执行自动完成。您会发现选项只是 Object 的成员,而不是 String 的 endsWith 等成员。”昨天晚些时候我注意到了这一点,它给了我一个提示,即问题出在类型推断上,但我没有看到如何解决这个问题。正如您所描述的,该解决方案当然有效。
【解决方案2】:

解决此问题的一种方法是使用自己的子级参数化 ParentDTO 类。

public class DtoOrderStex extends DtoOrder<OrderStex, DtoOrderStex> {


    public DtoOrderStex(OrderStex orderStex) {
        super(orderStex);
    }

    public DtoOrderStex() {
    }
}

这就提出了一个问题: 除了子类的冗余之外,这是否有任何严重的负面影响? 既然该类已经是其父类的子类,那么为什么首先需要这样做?

【讨论】:

    猜你喜欢
    • 2022-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-28
    • 2016-02-21
    • 2018-09-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多