I have an odd problem, where I am struggling to understand the nature of "static context" in Java, despite the numerous SO questions regarding the topic.
TL;DR:
I have a design flaw, where ...
This works:
List<OrderExtnTrans> list = orderType.getOrderExtnTransList();
this.dtoOrderExtnTransList = list.stream().map(OrderExtnTrans::toDto).collect(Collectors.toList());
But this does not:
this.dtoOrderExtnTransList = orderType.getOrderExtnTransList().stream().map(OrderExtnTrans::toDto).collect(Collectors.toList());
The error shown in the second version is "Non-static method cannot be referenced from a static context".
The long version:
Object Model: The model consists of Business Type specific orders (eg. Stock exchange, payments), which inherit from a an order entity via an "InheritanceType.JOINED" inheritance strategy. The parent order can be parameterized with the business type specific DTO object of that order, so for example DtoStockExchangeOrder. This is to enable, that JPA objects can be mapped to their DTO equivalent within the Entity, rather than in a service (which I did previously. It worked, but its "less clean").
JPA Order:
@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 Order - Business Type specific example:
@Entity
@Table(name = "ORDER_STEX")
@Inheritance(strategy = InheritanceType.JOINED)
public class OrderStex extends Order<DtoOrderStex> implements Serializable {
Likewise, DTO orders follow the same pattern, where they can be parameterized with the business type specific JPA entity, to enable the relevant mapping:
DTO Order:
public class DtoOrder<OrderType extends Order> extends DtoEntity {
DTO Order - Business Type Specific Example
public class DtoOrderStex extends DtoOrder<OrderStex> {
The DTOEntity class it inherits from is just a "wrapper" class, consisting of an ID and a name.
Now the tricky part: The DTOOrder class has a constructor which populates the fields that are common to all business types, like the list of process status transitions, an order goes through in its life cycle (placed, cancelled, executed, etc..). Staying with the example of the process status transitions, these are also modelled as JPA entities in the database, with their corresponding DTO counterparts (likewise parameterized, that part works fine).
Here the constructor:
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());
}
If I comment out the non-working version, the application behaves as expected and throws no error, so "logically", this works. But JAVA does not allow it as developed in the second version.
If instead of OrderType I use "Order", it works as well, but obviously throws errors elsewhere, because the signature of the constructor changed. I assume another approach would be to parameterise the method based on the the caller of the constructor or to parameterise the parent class DtoOrder to know the type of the child class, but there should be a better way?
What am I doing wrong, and why does the upper version work as expected?
CodePudding user response:
Thanks for an interesting question, that shows some unexpected behaviour, that is behaving according to spec.
TL;DR (ie, the correct way of quickly and easily fixing this) is to add the <?> generic wildcard to Order in the DtoOrder class declaration, so :
public class DtoOrder<OrderType extends Order<?>> extends DtoEntity {
This will make the all-in-one-line way in the constructor work :
this.dtoOrderExtnTransList = orderType.getOrderExtnTransList().stream().map(OrderExtnTrans::toDto).collect(Collectors.toList());
As for why this is the fix, this is because you have defined Order as a Generic type :
public class Order<DtoOrderType extends DtoOrder>
By NOT specifying the generic type, you are declaring it (and therefore making OrderType as well) as a raw-type.
Generally we are used to List<SomeType> being generic, that at run-time undergoes type-erasure. Further, that if we have old code like :
List myRawTypeVariable = new ArrayList();
that myRawType is a raw type, in that we can add any Object and only get Objects out.
However, it turns out that (as you have discovered) raw types go further than that, and type-erasure has compile-time implications as well.
The Java Language Specification (JLS) says this (source : 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.
Note that this is NOT limiting type erasure to ONLY those of the generic type; The types of ALL instance methods get taken to their raw types !
In other words, by not specifying the generic type of Order, you are making Order a raw type - and therefore turning off all Generic type-checking for that class (except for methods, etc otherwise specified, from inheritance or interfaces).
So even though getOrderExtnTransList() is declared as returning a List<String>, because you are using Order as a raw-type, it's dropping the <String> generic and treating that method as simply returning a List (effectively a List<Object> ).
You can confirm this by trying to insert a peek, so :
this.dtoOrderExtnTransList = orderType.getOrderExtnTransList().stream().peek(s -> s.
then try to do an auto-complete. You'll find that the options are only the members for Object, not the endsWith, etc members for String.
This, in turn, means that when it hits .map(OrderExtnTrans::toDto), instead of interpreting this for a OrderExtnTrans coming down the stream :
`.map(o -> o.toDto())`,
it thinks you mean
`.map(o -> OrderExtnTrans.toDto(o))`
which is what is appropriate for an Object coming down the stream - and that is why it complains about toDto being a non-static method.
As stated, the solution is to simply not treat Order as a raw-type, instead to make it generic by adding the <?> as above.
CodePudding user response:
One way to solve the issue is by parameterizing the ParentDTO Class with its own children.
public class DtoOrderStex extends DtoOrder<OrderStex, DtoOrderStex> {
public DtoOrderStex(OrderStex orderStex) {
super(orderStex);
}
public DtoOrderStex() {
}
}
This raises the question: Does this have any serious negative side effects, apart from the redundancy in the child classes? And why is that necessary in the first place, given that the class is already a child of its parent?
