Java map 函数抛出非静态方法编译器错误

Java map function throws non-static method compiler error

我有一个奇怪的问题,尽管有很多关于该主题的 SO 问题,但我仍在努力理解 Java 中“静态上下文”的性质。

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());

第二个版本中显示的错误是“无法从静态上下文中引用非静态方法”。

长版:

对象模型: 该模型由业务类型特定的订单(例如证券交易所、支付)组成,它们通过“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 class 只是一个“包装器”class,由一个 ID 和一个名称组成。

现在是棘手的部分: DTOOrder class 有一个构造函数,它填充所有业务类型共有的字段,如流程状态转换列表、订单在其生命周期中经历的(放置、取消、执行等)。继续以流程状态转换为例,这些也被建模为数据库中的 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,它也能正常工作,但显然会在其他地方抛出错误,因为构造函数的签名已更改。我假设另一种方法是根据构造函数的调用者参数化方法或参数化父 class DtoOrder 以了解子 class 的类型,但应该有更好的方法吗?

我哪里做错了,为什么上面的版本能按预期工作?

解决该问题的一种方法是使用自己的 children.

参数化 ParentDTO Class
public class DtoOrderStex extends DtoOrder<OrderStex, DtoOrderStex> {


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

    public DtoOrderStex() {
    }
}

这提出了一个问题: 除了 child classes 中的冗余之外,这是否有任何严重的负面影响? 鉴于 class 已经是其 parent 的 child?

为什么首先需要这样做?

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

TL;DR(即,快速轻松地解决此问题的正确方法)是将 通用通配符添加到 DtoOrder class 中的 Order声明,所以 :

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

这将使构造函数中的all-in-one-line方式起作用 :

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

至于为什么这是解决方法,这是因为您已将 Order 定义为通用类型:

public class Order<DtoOrderType extends DtoOrder> 

通过不指定泛型类型,您将其声明为 raw-type.[=41=(因此也使 OrderType) ]

通常我们习惯于 List<SomeType> 是通用的,在 run-time 经历 type-erasure。此外,如果我们有像这样的旧代码:

List myRawTypeVariable = new ArrayList();

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

然而,事实证明(正如您所发现的)原始类型远不止于此,而且 type-erasure 也有 compile-time 含义。

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

The type of a constructor (§8.8), instance method (§8.4, §9.4), or non-static field (§8.3) of a raw type C that is not inherited from its superclasses or superinterfaces is the raw type that corresponds to the erasure of its type in the generic declaration corresponding to C.

请注意,这并不是将类型擦除仅限于泛型类型;所有实例方法的类型都采用它们的原始类型!

换句话说,通过不指定 Order 的泛型类型,您使 Order 成为原始类型 - 因此关闭了 [=93] 的所有泛型 type-checking =](除了从继承或接口中另行指定的方法等)。

因此,即使 getOrderExtnTransList() 被声明为返回 List<String>,因为您将 Order 用作 raw-type,它会删除 <String> 泛型并将该方法视为简单地返回一个 List (实际上是一个 List<Object> )。

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

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

然后尝试做一个 auto-complete。您会发现选项只是 Object 的成员,而不是 endsWith 的成员,等等 String.

的成员

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

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

它认为你的意思是

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

这对于 Object 来说是合适的 - 这就是为什么它抱怨 toDto 是一种 non-static 方法。

如前所述,解决方案是简单地不将 Order 视为 raw-type,而是像上面那样通过添加 使其通用。