I want to create a mapping from input List<T> to outputList<S>, for the method named foo(List<T>), but in a "smart" way . The way foo(List<T>) processes different types of list are very similar in nature since the input list shares the same attributes, but are from different classes.
My intention is to reuse as much of the foo() implementation with just a minor check of the input type before returning the output.
One way to do this is to implement something like the following in foo
if (items.get(0) instanceOf Cat) {
List<Kitten> kittens = items.stream().map(cat -> Kitten.builder().name(cat.getName()).build().toList();
return kittens;
}
if (items.get(0) instanceOf Dog) {
List<Puppy> puppies = items.stream().map(dog -> Puppy.builder().name(dog.getName()).build().toList();
return puppies;
}
, but it feels wrong since if I added another type like Bird I would have to add another if condition.
I suppose that another way to accomplish this if I wanted to have different return types for different types of input is by creating custom classes for a list of a specific type, i.e.
class DogList {
private List<Dog> dogs;
}
class CatList {
private List<Cat> cats;
}
class KittenList {
private List<Kitten> kittens;
}
class PuppyList {
private List<Puppy> puppies;
}
// And creating method for each type that's sth like
public KittenList foo(CatList cats) {
List<Kitten> kittens = cats.getCats().stream().map(cat ->
Kitten.builder().name(cat.getName()).build().toList();
return kittens;
}
public PuppyList foo(DogList dogs) {
List<Puppy> puppies = dogs.getCats().stream().map(cat ->
Puppy.builder().name(dogs.getName()).build().toList();
return puppies;
}
But it feels weird doing it this way since I'm creating custom classes just to wrap a list. I also am duplicating 99% of the implementation of foo.. and the implementation is almost identical here, so I would prefer to reuse the same method..
First, we need to ensure that
Cat,Dog,KittenandPuppyhave a common parent classAnimal:Then use generic methods like this:
Specify two parameters, the source list
List<R> source, and the target typeClass<T> target.We need to constrain the type
Tand typeR, because inside the method, we need to call the correspondingsetName,getNamemethod, if it is any type, then we cannot be sure that they havesetName,getNamemethod, how to ensure this? Very simple, ifTandRare required to be subtypes ofAnimal, they can be guaranteed to havesetNameandgetNamemethods.So we add constraints to the generic method<R extends Animal, T extends Animal>.In the method implementation, we convert each element (R) in the source list to the target type (T).
If you add a new type, you only need to add the corresponding type description class.
The following is the complete test code, you need to understand and deal with the details of the code yourself: