Home > Enterprise >  How do I extend a parameterized abstract class with a more specific generic type?
How do I extend a parameterized abstract class with a more specific generic type?

Time:01-04

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:

sample code

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:

  1. It has to accept a list of R or or a list of its subclasses.
  2. Inside the method you need have access to the elements of body as instances R (another words you need a producer of R).

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);
  •  Tags:  
  • Related