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?

+3


source to share


6 answers


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()));

      

+7


source


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);

      

+3


source


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);
}

      

+3


source


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()));

      

+2


source


Can C map to null on a stream and then return true to null? Like this:

mappings.stream().map(a -> a instanceof C ? (B)null : (B)a)
                 .collect(Collectors.toMap(A::getId, m==null || m.isVisible()));

      

+1


source


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.

0


source







All Articles