【问题标题】:Mapping classes with lambda to default value将具有 lambda 的类映射到默认值
【发布时间】:2018-01-03 05:52:31
【问题描述】:

跟进来自this 的问题。 有这样的层次结构。其中 A 是基类:

       A 
      / \
     B   C

  |   A    |   |   B       |   |  C      |  
  | getId()|   |A.getId()  |   |A.getId()|
               |isVisible()| 

以及以下内容:

List<A> mappings;

我想将 B 实例的所有 ID 映射到 B.isVisible() 的值,并将 C 实例的 ID 映射到 TRUE

在最初的问题的帮助下,我将其改进为以下格式:

mappings.stream().filter(a -> a instanceof B)
                 .map(b -> (B)b)
                 .collect(Collectors.toMap(A::getId, m -> m.isVisible()));

丑陋的版本是:

mappings.stream()                       
        .collect(Collectors.toMap(A::getId, m ->
                        {
                            boolean isB = m instanceof B;
                            return isB ? ((B) m).isVisible() : true;
                        }));

对改进它以提供更优雅版本的默认 true 有任何帮助吗?

【问题讨论】:

  • A类包含getId方法,还是我错了?
  • @OlimpiuPOP 那你为什么打电话给B::getId
  • 你的例子有点混乱。在上一个 lambda 中,您将变量命名为 m,但您使用的是 mapping instanceof B。请检查您的示例以避免与您的问题无关的问题。 .filter(a -&gt; instanceof B) 也应该是 .filter(a -&gt; a instanceof B)
  • 您应该能够将m -&gt; { boolean isB = m instanceof B; return isB ? ((B) m).isVisible() : true; } 减少到m -&gt; m instanceof B ? ((B) m).isVisible() : true(尽管目前还不清楚这是否是您想要的)。
  • 说真的 - 你所拥有的并不丑陋。你不会得到更好的建议。 Pshemo 是对的 - 这是你应该做的唯一的事情,以使你的代码更好......

标签: java lambda java-8


【解决方案1】:

你的变种

mappings.stream()                       
        .collect(Collectors.toMap(A::getId, m ->
                        {
                            boolean isB = m instanceof B;
                            return isB ? ((B) m).isVisible() : true;
                        }));

没有那么难看,因为它表达了你的意图。

但是你可以简化它,因为你不需要一个局部变量来保存m instanceof B

mappings.stream()                       
        .collect(Collectors.toMap(A::getId, m->m instanceof B? ((B)m).isVisible(): true));

然后,根据经验,只要您在复合布尔表达式中有 boolean 字面量,就有一个没有它的替代方案。

mappings.stream()                       
        .collect(Collectors.toMap(A::getId, m -> !(m instanceof B) || ((B)m).isVisible()));

【讨论】:

  • @Eugene:也许值得发布所有组合的完整表格以进行演示......
  • @Eugene: 不,不必要的复杂布尔表达式是一个反复出现的事情,不仅在 Stackoverflow 上,所以我认为,这样的表格提醒开发人员更简单的替代方案并证明我的临时规则有效,也就是说,如果您在任何其他上下文中看到truefalse,而不是作为唯一常量,它应该总是响铃,可能有一些价值。
  • 好的,我同意。但这应该是一个单独的 Q/A...
  • @Holger 如果我要根据您的经验为 Sonar 或 FindBugs 实现警报,我也会认为常量谓词是有效的,即 t -&gt; true,我的意思是,除了 boolean variable = true .
  • @Federico Peralta Schaffner:这就是为什么我说“只要你在 compound 布尔表达式中有布尔文字”。唯一的文字不算数。简化规则甚至可以评估为布尔文字,例如a &amp;&amp; falsefalse
【解决方案2】:

如果不能改源码,可以写一个实用方法isA来描述你想要的,例如:

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

【讨论】:

  • 你好,梁!不错的方法,非常实用
  • @FedericoPeraltaSchaffner 谢谢,受到您的回答的启发。 :)
  • @FedericoPeraltaSchaffner 你的记忆力很好。谢谢你记得我,我的朋友。 :)
  • 请接受 FedericoPeraltaSchaffner 的回答,我的回答灵感来自他。
  • 但是你的好多了,不需要辅助类
【解决方案3】:

也许你可以用帮助类做你想做的事:

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

然后,将列表中的每个元素映射到Helper 的实例并收集到映射:

Map<Long, Boolean> map = mappings.stream()
    .map(Helper::new)
    .collect(Collectors.toMap(Helper::getId, Helper::isVisible));

此解决方案只是将visibletrue 还是false 委托给Helper 类,并让您拥有一个干净的流管道。

附带说明...一般来说,使用类型为 Boolean 的映射是没有意义的,因为您可以拥有与 Set 相同的语义:

Set<Long> set = mappings.stream()
    .map(Helper::new)
    .filter(Helper::isVisible)
    .collect(Collectors.toSet());

然后,要知道某个元素是否可见,只需检查它是否属于集合:

boolean isVisible = set.contains(elementId);

【讨论】:

  • 首先,起来!我们是一样的。但我改用了实用方法。
【解决方案4】:

您的代码很难看,因为您的层次结构没有意义。你可能想要的是这样的:

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

那么就可以这样做了:

mappings.stream()
        .collect(Collectors.toMap(A::getId, m -> m.isVisible()));

【讨论】:

  • 是的,我也这么认为,但我无法更改层次结构。谢谢
【解决方案5】:

可能将 C 映射到流中的 null,然后在 null 上返回 true?像这样:

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

【讨论】:

  • m==null || m.isVisible()
  • .map(a -&gt; a instanceof C ? (B)null : (B)b) 应该是.map(a -&gt; a instanceof B ? (B)a : (B)null) 还是我错了?
【解决方案6】:

我已经编写了这个简单的 Collector 实现,它应该可以满足您的需求:

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

最终可以这样调用:

Map<Integer, Boolean> map = mappings.stream().collect(new AToMapCollector());

添加一个全新的类是这个收集器的可重用性,也增加了可读性而不是多行 lambda。

【讨论】:

  • 这比 much 更简单的 lambda 表达式有什么好处?我看不到这样做的好处...
  • 我确实退出了原始问题,而不是来自现在提供简单 lambda 表达式的 cmets
猜你喜欢
  • 2019-08-17
  • 2016-12-13
  • 2015-01-13
  • 1970-01-01
  • 2018-12-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多