5

I'm building a sort of framework to avoid repetition of code, and at a certain point I need to convert a list of object Foo into a list of object Bar.

I have database entities that extend

public class BaseEntity {...}

And presentation objects that extend

public class BaseDTO<T extends BaseEntity> {...}

so

public class Foo extends BaseEntity {...}

and

public class Bar extends BaseDTO<A extends BaseEntity> {
    public Bar(Foo entity, Locale l) {
        ...
    }
}

Now to convert a list of Foo into a list of Bar is easy using streams

public abstract ClassThatUsesFooAndBar() {

    public List<Bar> convertFooToBar(List<Foo> list) {
        return list.stream().map(f -> new Bar(f, locale)).collect(...);
    }
}

But, and here is the question, these Foo and Bar are actually generics (A and B), so the class that uses Foo and Bar actually is ClassThatUsesAandB<A extends BaseEntity, B extends BaseDTO>, so that function must be abstract too and implemented as boilerplate code with the correct A and B implementations because obviously you cannot instantiate generic types.

Is there a way to use generics/streams/lambdas to create a function that can be written once, so that the implementing classes don't need to re-implement it? The function signature would be

public List<B> convertAToB(List<A> list);

I hope I've been clear enough in what I need, if you need further explanations please ask

Thank you!

4
  • 1
    Please don't use single letter class names as it's very difficult to tell them apart from type parameters. If you're stuck for random names, you can use Foo, Bar or Baz. Commented Dec 11, 2015 at 17:13
  • Actually I used A and B because at the end of the discussion they are in fact type parameters. I will try to rephrase... Commented Dec 11, 2015 at 17:16
  • can you be more specific about generics usage? you write A/B extends AnObject, but in your example A/B does not extend anything Commented Dec 11, 2015 at 17:18
  • I'm rephrasing my question to make it clearer :) Commented Dec 11, 2015 at 17:23

2 Answers 2

7

I think the simplest way is to use lambdas for the conversion.

public static <A,B> List<B> convertList(List<A> list, Function<A,B> itemConverter) {
    return list.stream().map(f -> itemConverter.apply(f)).collect(...);
}

And then you can use it like this:

List<Bar> l = convertList(fooList,foo -> new Bar(foo.getBaz()));

Or if you want to, you can extract it in its own named class:

public class Foo2BarConverter implements Function<Foo,Bar> {
    @Override
    public Bar apply(Foo f) {
       return new Bar(f.getBaz());
    }
}

As an aside, given what we can do with streaming, it seems like a bit of a waste to create a new list just to have a materialised list of Bar objects. I would probably chain whatever operation I want to do with the list straight after the conversion.

Sign up to request clarification or add additional context in comments.

7 Comments

what do you mean by wasting? you suggest to return a stream for later processing instead of creating a result list?
@AdamSkywalker Yes, it suits the functional approach better.
I got it. Anyway, name BaseDTO indicates that results most likely will be serialized and sent somewhere
@AdamSkywalker Yeah, in some cases it's unavoidable. But more often than not these patterns are a result of a naive effort to "upgrade" the code to use lambdas, usually by changing the implementation of methods but not the way they call each other. Again, sometimes this is your only choice (if your method is part of a public API and that says it should return a List for example), but it's good to be aware that where those limitations don't apply, you can go one better.
@AdamSkywalker yes, the DTO will be used in a grid, so the process is ORM gives a list of entities -> function processes and returns DTO -> list is passed to a grid that shows the result
|
2

The most difficult problem with your question is actually not the boilerplate or the streams, it's the generics. Trying to do new B is a bit of a mess. You can't do it directly, and any workaround isn't too clean.

For the boilerplate, however, you can do a bit better thanks to Java 8's default methods in interface. Consider the following interface:

public interface ConversionHandler<A,B> {

  B constructB(A a, Locale locale);

  default List<B> convertAToB(List<A> list, Locale locale) {
    return list.stream().map(a -> constructB(a, locale)).collect(Collectors.toCollection(ArrayList::new));
  }
}

The list conversion boilerplate is now done, all you have to do is implement the B construction in the subclass. However, this is still tricky if B is still generic.

public class ClassThatUsesAandB<A, B> implements ConversionHandler<A,B> {

  @Override
  public B constructB(A a, Locale locale) {
    return null; //This is tricky
  }
} 

However, if the subclass is concrete, it's quite simple

public class ConverterClass implements ConversionHandler<String,Integer> {

  @Override
  public Integer constructB(String s, Locale locale) {
    return s.length();
  }
}

So the followup you may want to search for is a good design pattern for making the construction of generic objects as maintainable and readable as possible.

1 Comment

Yes I was thinking about doing something like this, so in the subclasses I would only need to do the creation of the specific B type. I rephrased the question a bit so as you say the Bs are all generics. I'll think about it over the weekend, but I think that your answer will be the correct one in the end! Thanks :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.