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!
A stream is a sequence of objects that supports various methods which can be pipelined to produce the desired result. The features of Java stream are – A stream is not a data structure instead it takes input from the Collections, Arrays or I/O channels.
Generics means parameterized types. The idea is to allow type (Integer, String, … etc., and user-defined types) to be a parameter to methods, classes, and interfaces. Using Generics, it is possible to create classes that work with different data types.
Using generics, primitive types can not be passed as type parameters. In the example given below, if we pass int primitive type to box class, then compiler will complain. To mitigate the same, we need to pass the Integer object instead of int primitive type.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With