Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java generics and streams

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!

like image 247
Luca Avatar asked Dec 11 '15 17:12

Luca


People also ask

What are streams in Java?

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.

What are generics in Java?

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.

Can Java generics be applied to primitive 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.


2 Answers

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.

like image 132
biziclop Avatar answered Oct 03 '22 06:10

biziclop


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.

like image 35
Mshnik Avatar answered Oct 03 '22 08:10

Mshnik