Displaying classes with default lambda value
Follow-up question from. The presence of such a hierarchy. Where A is the base class:
A
/ \
B C
| A | | B | | C |
| getId()| |A.getId() | |A.getId()|
|isVisible()|
and the following content:
List<A> mappings;
I would like to map all B instance IDs to B.isVisible () value and C instance IDs to TRUE
With the help of the initial question, I refined it in this format:
mappings.stream().filter(a -> a instanceof B)
.map(b -> (B)b)
.collect(Collectors.toMap(A::getId, m -> m.isVisible()));
Ugly version:
mappings.stream()
.collect(Collectors.toMap(A::getId, m ->
{
boolean isB = m instanceof B;
return isB ? ((B) m).isVisible() : true;
}));
Any help on improving it to provide a default for the more elegant version?
source to share
Your option
mappings.stream()
.collect(Collectors.toMap(A::getId, m ->
{
boolean isB = m instanceof B;
return isB ? ((B) m).isVisible() : true;
}));
not so ugly as it expresses your intention.
But you can simplify it as you don't need a local variable to store it m instanceof B
:
mappings.stream()
.collect(Collectors.toMap(A::getId, m->m instanceof B? ((B)m).isVisible(): true));
Then, generally, whenever you have a literal boolean
in a compound boolean expression, there is an alternative without it.
mappings.stream()
.collect(Collectors.toMap(A::getId, m -> !(m instanceof B) || ((B)m).isVisible()));
source to share
Perhaps you can do what you want with a helper class:
class Helper {
private final Long id;
private final boolean visible;
Helper(A a) {
this.id = a.getID();
this.visible = a instanceof B ? ((B) a).isVisible() : true;
}
Long getId() { return id; }
boolean isVisible() { return visible; }
}
Then map each element of the list to an instance Helper
and take it to the map:
Map<Long, Boolean> map = mappings.stream()
.map(Helper::new)
.collect(Collectors.toMap(Helper::getId, Helper::isVisible));
This decision simply delegates to visible
true
or to the false
class Helper
and allows you to have a clean flow pipeline.
As a side of the note ... In general, having a map with type values ββis Boolean
pointless because you can have the same semantics with Set
:
Set<Long> set = mappings.stream()
.map(Helper::new)
.filter(Helper::isVisible)
.collect(Collectors.toSet());
Then, to see if an item is visible or not, just check if it belongs to the set:
boolean isVisible = set.contains(elementId);
source to share
If you cannot change the source code, you can write a utility method isA
to describe what you want, for example:
Map<Integer, Boolean> visibility = mappings.stream().collect(toMap(
A::getId,
isA(B.class, B::isVisible, any -> true)
));
static <T, S extends T, R> Function<T, R> isA(Class<? extends S> type,
Function<? super S, R> special,
Function<T, R> general) {
return it -> type.isInstance(it) ? special.apply(type.cast(it))
: general.apply(it);
}
source to share
Your code is ugly because your hierarchy doesn't make sense. What you probably want is something like:
class A
{
abstract public boolean isVisible();
// or make it concrete and return a default if you need to
}
// B can stay the same (+ @Override)
class C extends A
{
@Override
public boolean isVisible()
{
return true;
}
}
Then you can just do:
mappings.stream()
.collect(Collectors.toMap(A::getId, m -> m.isVisible()));
source to share
I wrote this simple implementation Collector
that should do what you want:
public class AToMapCollector implements Collector<A, Map<Integer, Boolean>, Map<Integer, Boolean>>{
@Override
public Supplier<Map<Integer, Boolean>> supplier(){
return HashMap::new;
}
@Override
public BiConsumer<Map<Integer, Boolean>, A> accumulator(){
return (map, a) -> {
boolean visible = true;
if(a instanceof B){
visible = ((B) a).isVisible();
}
map.put(a.getId(), visible);
};
}
@Override
public BinaryOperator<Map<Integer, Boolean>> combiner(){
return (map1, map2) -> {
map1.putAll(map2);
return map1;
};
}
@Override
public Function<Map<Integer, Boolean>, Map<Integer, Boolean>> finisher(){
return map -> map;
}
@Override
public Set<Characteristics> characteristics(){
return EnumSet.of(Characteristics.IDENTITY_FINISH, Characteristics.UNORDERED);
}
}
which can then finally be called like this:
Map<Integer, Boolean> map = mappings.stream().collect(new AToMapCollector());
Adding a new class creation is a reuse of that collector and also increases readability instead of a multi-line lambda.
source to share