I've been exploring transforming an object from one type of class to another with java 8. What I have are a bunch of xjc generated jaxb classes. The classes don't have a sufficiently friendly structure because they map the xml more structure rather than the business object structure. I'm unwilling to edit the generated classes because I like to regenerate them whenever the schema changes without having to worry about retaining customizations.
I've got a schema something like:
<xs:element name="farm">
<xs:sequence>
<xs:element ref="animal" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:element>
<xs:element name="animal">
<xs:complexType>
<xs:sequence>
<xs:element ref="goat"/>
<xs:element ref="sheep"/>
</xs:sequence>
<xs:complexType>
</xs:element>
<xs:element name="goat">
<xs:complexType>
<xs:sequence>
goat fields
</xs:sequence>
<xs:complexType>
</xs:element>
<xs:element name="sheep">
<xs:complexType>
<xs:sequence>
sheep fields
</xs:sequence>
<xs:complexType>
</xs:element>
This generates Java something like:
class Farm
public List<Animal> getAnimals()
class Animal
public Goat getGoat()
public String getGoatField()
public Sheep getSheep()
public String getSheepField()
getGoat
and getSheep
can return null, but they can't both be null. Likewise, at least one of them must be null. This is enforced via business rules and database constraints, but not in the xml (although if anyone has a suggestion to structure the xml more like the desired VOs I'm all ears)
I'd like to transform this class into
class FarmWrapper
public ArrayList<AnimalVO> getAnimals()
//optional features tbd
//possibly public ArrayList<GoatVO> getGoats()
//possibly public ArrayList<SheepVO> getSheep()
class GoatVO extends AnimalVO
class SheepVO extends AnimalVO
My idea was to do something like this:
herd.stream()
.filter(Objects::nonNull)
.map(a -> {
Optional<AnimalVO> goatVO = Optional.ofNullable(a.getGoat())
.map(g -> new GoatVO(g.getGoatField()));
Optional<AnimalVO> sheepVO = Optional.ofNullable(a.getSheep())
.map(s -> new SheepVO(s.getSheepField()));
return goatVO.orElse(sheepVO.get());
})
.collect(Collectors.toList());
Right now I've been feeding it a list, and as soon as it encounters a null sheep, it throws a NoSuchElementException
.
I suppose I a few questions:
goatVO.orElse(sheepVO.get())
working as long as goatVO contains null and then throwing NoSuchElementException
when sheepVO contains nullWhat I'm really doing is working with generated jaxb code and trying to take the generated classes and make the friendlier to work with. Traditionally the project has used a wrapper class that transforms the generated classes to VOs via a substantial amount of null checking and int to BigInteger sort of manipulations.
Editing the generated classes (Goat, Sheep, Animal) is a non starter because I would like to retain the ability to regenerate without worrying
I think you can make your code work with a couple of adjustments:
List<AnimalVO> list = herd.stream()
.filter(Objects::nonNull)
.map(a -> Optional.ofNullable(a.getGoat())
.map(Goat::getGoatField)
.<AnimalVO>map(GoatVO::new)
.orElseGet(() -> new SheepVO(a.getSheep().getSheepField())))
.collect(Collectors.toList());
Note: I prefer method references over lambdas, so I've switched to using them.
Note the hint to the compiler in <AnimalVO>map(GoatVO::new)
. This is necessary to let the compiler know that the type you are mapping to is always an AnimalVO
, otherwise it infers that the first Optional
returns a GoatVO
and gives a compilation error in the second Optional
, which returns a SheepVO
(and SheepVO
is not a descendant of GoatVO
).
Also note that I'm using orElseGet()
method instead of orElse()
. orElseGet()
receives a Supplier
of a default value, instead of the default value itself. This means that the default value is picked lazily, only when the value of the first Optional
is not present.
EDIT: If you had more animals in your farm, i.e. apart from Goat
and Sheep
, now you have a Cow
, this is how you could do it:
List<AnimalVO> list = herd.stream()
.filter(Objects::nonNull)
.map(a -> Optional.ofNullable(a.getGoat())
.map(Goat::getGoatField)
.<AnimalVO>map(GoatVO::new)
.orElseGet(() -> Optional.ofNullable(a.getSheep())
.map(Sheep::getSheepField)
.<AnimalVO>map(SheepVO::new)
.orElseGet(() -> new CowVO(a.getCow().getCowField()))))
.collect(Collectors.toList());
Sheep extends Animal
, Goat extends Animal
)something = optionalValue.orElse(defaultValue)
which will return value contained by Optional
if the value is not null, or defaultValue
if the value in Optional
is nullgoatVO.orElse(sheepVO.get())
throws NoSuchElementException
because sheepVO
contains null
. Please note that sheepVO.get()
evaluates regardless the value in goatVO
.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