为了账号安全,请及时绑定邮箱和手机立即绑定

Java 函数式编程:如何将 for 循环中的 if-else 阶梯转换为函数式风格?

Java 函数式编程:如何将 for 循环中的 if-else 阶梯转换为函数式风格?

暮色呼如 2023-03-31 16:46:42
期望是从输入列表中派生 3 个列表itemIsBoth,,。如何将下面的代码转换为功能样式?(我知道这段代码在命令式风格中已经足够清晰了,但我想知道声明式风格是否真的无法处理这样一个简单的例子)。谢谢。aItemsbItemsitemsfor (Item item: items) {    if (item.isA() && item.isB()) {        itemIsBoth.add(item);    } else if (item.isA()) {        aItems.add(item);    } else if (item.isB()){        bItems.add(item)    }}
查看完整描述

5 回答

?
MMMHUHU

TA贡献1834条经验 获得超8个赞

问题标题相当广泛(转换 if-else 阶梯),但由于实际问题询问的是特定场景,所以让我提供一个示例,至少可以说明可以做什么。


因为该if-else结构基于应用于项目的谓词创建了三个不同的列表,所以我们可以将此行为更明确地表达为分组操作。要开箱即用,唯一需要做的额外工作是使用标记对象折叠多个布尔谓词。例如:


class Item {

    enum Category {A, B, AB}


    public Category getCategory() {

        return /* ... */;

    }

}

那么逻辑可以简单的表达为:


Map<Item.Category, List<Item>> categorized = 

    items.stream().collect(Collectors.groupingBy(Item::getCategory));

其中每个列表都可以从给定类别的地图中检索。


如果无法更改 class Item,则可以通过移动 enum 声明和分类方法使其超出类Item(该方法将成为静态方法)来实现相同的效果。


查看完整回答
反对 回复 2023-03-31
?
一只萌萌小番薯

TA贡献1795条经验 获得超7个赞

另一种使用 Vavr 并只对项目列表进行一次迭代的解决方案可以使用以下方法实现foldLeft:


list.foldLeft(

    Tuple.of(List.empty(), List.empty(), List.empty()), //we declare 3 lists for results

    (lists, item) -> Match(item).of(

        //both predicates pass, add to first list

        Case($(allOf(Item::isA, Item::isB)), lists.map1(l -> l.append(item))),

        //is a, add to second list

        Case($(Item::isA), lists.map2(l -> l.append(item))),

        //is b, add to third list

        Case($(Item::isB), lists.map3(l -> l.append(item)))

    ))

);

它将返回一个包含三个结果列表的元组。


查看完整回答
反对 回复 2023-03-31
?
慕森王

TA贡献1777条经验 获得超3个赞

既然你提到了 vavr 作为标签,我将提供一个使用 vavr 集合的解决方案。


import static io.vavr.Predicates.allOf;

import static io.vavr.Predicates.not;


...


final Array<Item> itemIsBoth = items.filter(allOf(Item::isA,     Item::isB));

final Array<Item> aItems     = items.filter(allOf(Item::isA, not(Item::isB)));

final Array<Item> bItems     = items.filter(allOf(Item::isB, not(Item::isA)));

该解决方案的优点是简单易懂,一目了然,而且它的功能与 Java 一样。缺点是它将遍历原始集合三次而不是一次。这仍然是一个O(n),但乘数为 3。在非关键代码路径和小型集合中,为了代码清晰度而牺牲几个 CPU 周期可能是值得的。


当然,这也适用于所有其他 vavr 集合,因此您可以替换Array为List、Vector、Stream等。


查看完整回答
反对 回复 2023-03-31
?
慕运维8079593

TA贡献1876条经验 获得超5个赞

摆脱 the 的另一种方法if-else是将它们替换为Predicateand Consumer:


Map<Predicate<Item>, Consumer<Item>> actions = 

  Map.of(item.predicateA(), aItems::add, item.predicateB(), bItems::add);

actions.forEach((key, value) -> items.stream().filter(key).forEach(value));

因此,您需要增强Item这两种方法predicateA()并使用您在和predicateB()中实现的逻辑isA()isB()


顺便说一句,我仍然建议使用你的if-else逻辑。


查看完整回答
反对 回复 2023-03-31
?
侃侃尔雅

TA贡献1801条经验 获得超15个赞

当然可以。功能方式是使用声明方式。


在数学上你正在设置一个Equivalence relation,然后,你可以写


Map<String, List<Item>> ys = xs

    .stream()

    .collect(groupingBy(x -> here your equivalence relation))

一个简单的例子说明了这一点


public class Main {


    static class Item {

        private final boolean a;

        private final boolean b;


        Item(boolean a, boolean b) {

            this.a = a;

            this.b = b;

        }


        public boolean isB() {

            return b;

        }


        public boolean isA() {

            return a;

        }

    }


    public static void main(String[] args) {

        List<Item> xs = asList(new Item(true, true), new Item(true, true), new Item(false, true));

        Map<String, List<Item>> ys = xs.stream().collect(groupingBy(x -> x.isA() + "," + x.isB()));

        ys.entrySet().forEach(System.out::println);

    }

}

带输出


true,true=[com.foo.Main$Item@64616ca2, com.foo.Main$Item@13fee20c]

false,true=[com.foo.Main$Item@4e04a765]


查看完整回答
反对 回复 2023-03-31
  • 5 回答
  • 0 关注
  • 136 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信