I'm new to generics. I have an abstract class (1) with an abstract method. I have another abstract class (2) with a few abstract methods that I want shared in some DTOs (3,4). I want a concrete extension of (1) to work on anything that extends (2).
1.
//I want R to be anything here
public abstract class AggregatedEmailerRoute<R> extends RouteBuilder {
public static String EMAILER_ROUTE = "direct:aggregated-emailer-route";
@Override
public void configure() throws Exception {
from(EMAILER_ROUTE)
.bean(this, "convertToEmailBody(${body},${headers})")
.to("mock:wherever");
}
protected abstract String convertToEmailBody(List<R> body, Map<String,Object> headers);
public abstract class StagingError { //or should this be an interface?
public abstract Integer getId();
public abstract String getErrorNote();
public abstract LocalDateTime getErrorDate();
}
Two classes that extend StagingError: 3.
public class Bill extends StagingError {
private Integer id;
private String errorNote;
private LocalDateTime getErrorDate;
//other fields, getters/setters
public class Payment extends StagingError {
private Integer id;
private String errorNote;
private LocalDateTime getErrorDate;
//other fields, getters/setters
public class StagingEmailErrorRoute extends AggregatedEmailerRoute<R extends StagingError> {
@Override
public void configure() throws Exception {
super.configure();
from(jpa(Bill.class.getName())
.query("some query")
)
.to(EMAILER_ROUTE);
from(jpa(Payment.class.getName())
.query("some other query")
)
.to(EMAILER_ROUTE);
}
@Override //But here I want to work on anything that implements StagingError so this method can work on Payment OR Bill
protected String convertToEmailBody(List<T extends StagingError> body, Map<String,Object> headers) {
return
body
.stream()
.map(e -> "Staging entity ID: " e.getId()
"\n\tError: " e.getErrorNote()
"\n\tWhen: " format("%tc",e.getErrorDate().atZone(EST.get()))
"\n")
.collect(joining("\n"));
CodePudding user response:
You can write AggregatedEmailerRoute class something like this:
public abstract class AggregatedEmailerRoute<R> extends RouteBuilder {
//...
protected abstract String convertToEmailBody(List<R> body, Map<String, Object> headers);
}
And for StagingEmailErrorRoute:
public class StagingEmailErrorRoute<T extends StagingError> extends AggregatedEmailerRoute<T> {
@Override
protected String convertToEmailBody(List<T> body, Map<String, Object> headers) {
return null;
}
}
then you can pass any class that extends StaginError, in your case Payment and Bill:
I hope that is helpful.
CodePudding user response:
Understanding the concept of variance is the key to answering your question.
The definitions you provided are almost correct, though still both StagingEmailErrorRoute
and AggregatedEmailerRoute
classes need to be slightly modified to fit your expectations.
In particular, the convertToEmailBody(List<R> body, Map<String,Object> headers)
as it should meet at least these criteria:
- It has to accept a list of
R
or or a list of its subclasses. - Inside the method you need have access to the elements of
body
as instancesR
(another words you need a producer ofR
).
This is achieved by declaring the body as List<? extends R>
:
convertToEmailBody(List<? extends R> body, Map<String, Object> headers)
Then your specific subclass StagingEmailErrorRoute extends AggregatedEmailerRoute<StagingError>
is allowed to accept either StagingError
or its descendants and work with them as instances of R
(inside the stream):
public class StagingEmailErrorRoute extends AggregatedEmailerRoute<StagingError> {
@Override
protected String convertToEmailBody(List<? extends StagingError> body, Map<String, Object> headers) {
return body
.stream()
.map(e -> "Staging entity ID: " e.getId()
"\n\tError: " e.getErrorNote()
"\n\tWhen: " format("%tc",e.getErrorDate().atZone(EST.get()))
"\n")
.collect(joining("\n"));
}
}
EDIT: Example usage of StagingEmailErrorRoute
:
StagingEmailErrorRoute route = new StagingEmailErrorRoute();
//1. consume a list as a mix of `StagingError` descendants
List<StagingError> stagingErrors = new ArrayList<>();
stagingErrors.add(new Bill());
stagingErrors.add(new Payment());
route.convertToEmailBody(stagingErrors, headers);
//2. consume a list of specific subclasses of `StagingError` (i.e. Bill)
List<Bill> billErrors = new ArrayList<>();
billErrors.add(new Bill());
route.convertToEmailBody(billErrors, headers);