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,而是像上面那样通过添加 > 使其通用。
我有一个奇怪的问题,尽管有很多关于该主题的 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 Classpublic 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,而是像上面那样通过添加 > 使其通用。