在Java中简化流上的Lambda表达式

时间:2018-03-22 04:00:59

标签: java lambda java-stream

我正在尝试自学如何在Java中使用lambda表达式而不是常规的外部迭代类型解决方案。我做了一个简短的程序,我使用整数流并为它们分配等级字母。我没有使用典型的“switch”语句,而是使用了这些lambda表达式。有什么办法可以简化吗?似乎有更好的方法来做到这一点。它有效,但看起来并不整洁。

    grades.stream()
          .mapToInt(Integer::intValue)
          .filter(x -> x >= 90)
          .forEach(x -> System.out.println(x + " -> A"));

    grades.stream()
          .mapToInt(Integer::intValue)
          .filter(x -> x >= 80 && x <= 89)
          .forEach(x -> System.out.println(x + " -> B"));

    grades.stream()
          .mapToInt(Integer::intValue)
          .filter(x -> x >= 70 && x <= 79)
          .forEach(x -> System.out.println(x + " -> C"));

    grades.stream()
          .mapToInt(Integer::intValue)
          .filter(x -> x >= 60 && x <= 69)
          .forEach(x -> System.out.println(x + " -> D"));

    grades.stream()
          .mapToInt(Integer::intValue)
          .filter(x -> x >= 50 && x <= 59)
          .forEach(x -> System.out.println(x + " -> F"));

grades是一个ArrayList。

有什么方法可以将逻辑全部合并到一个流语句中?我试过让他们跟随彼此,但不断得到语法错误。我错过了什么?我试着查看IntStream和Stream的文档,但是找不到我想要的东西。

8 个答案:

答案 0 :(得分:5)

只需创建一个单独的函数来映射成绩字母并在单个流中调用它:

static char gradeLetter(int grade) {
    if (grade >= 90) return 'A';
    else if (grade >= 80) return 'B';
    else if (grade >= 70) return 'C';
    else if (grade >= 60) return 'D';
    else return 'F';
}

grades.stream()
      .mapToInt(Integer::intValue)
      .filter(x -> x >= 50)
      .forEach(x -> System.out.println(x + " -> " + gradeLetter(x)));

如果您特别想按年级排序结果,可以在流的开头添加排序:

.sorted(Comparator.comparing(x -> gradeLetter(x)))

答案 1 :(得分:3)

如果您打开使用第三方库,此解决方案将适用于Java Streams和Eclipse Collections

List<Integer> grades = List.of(0, 40, 51, 61, 71, 81, 91, 100);
grades.stream()
        .mapToInt(Integer::intValue)
        .mapToObj(new IntCaseFunction<>(x -> x + " -> F")
                .addCase(x -> x >= 90, x -> x + " -> A")
                .addCase(x -> x >= 80 && x <= 89, x -> x + " -> B")
                .addCase(x -> x >= 70 && x <= 79, x -> x + " -> C")
                .addCase(x -> x >= 60 && x <= 69, x -> x + " -> D")
                .addCase(x -> x >= 50 && x <= 59, x -> x + " -> F"))
        .forEach(System.out::println);

这输出以下内容:

0 -> F
40 -> F
51 -> F
61 -> D
71 -> C
81 -> B
91 -> A
100 -> A

我使用了Java 9工厂方法用于List和来自Eclipse Collections的IntCaseFunction。如果没有其他情况匹配,我传递给构造函数的lambda将是默认情况。

数字范围也可以由IntInterval的实例表示,可以使用contains作为方法参考进行测试。以下解决方案将在Java 10中使用局部变量类型推断。

var mapGradeToLetter = new IntCaseFunction<>(x -> x + " -> F")
        .addCase(x -> x >= 90, x -> x + " -> A")
        .addCase(IntInterval.fromTo(80, 89)::contains, x -> x + " -> B")
        .addCase(IntInterval.fromTo(70, 79)::contains, x -> x + " -> C")
        .addCase(IntInterval.fromTo(60, 69)::contains, x -> x + " -> D")
        .addCase(IntInterval.fromTo(50, 59)::contains, x -> x + " -> F");

var grades = List.of(0, 40, 51, 61, 71, 81, 91, 100);
grades.stream()
        .mapToInt(Integer::intValue)
        .mapToObj(mapGradeToLetter)
        .forEach(System.out::println);

如果我删除所有PredicatesFunctions并删除IntCaseFunction并使用三元运算符替换方法引用调用实例方法,则以下操作将起作用。< / p>

@Test
public void grades()
{
    var grades = List.of(0, 40, 51, 61, 71, 81, 91, 100);
    grades.stream()
            .mapToInt(Integer::intValue)
            .mapToObj(this::gradeToLetter)
            .forEach(System.out::println);
}

private IntObjectPair<String> gradeToLetter(int grade)
{
    var letterGrade =
            grade >= 90 ? "A" :
            grade >= 80 ? "B" :
            grade >= 70 ? "C" :
            grade >= 60 ? "D" : "F";
    return PrimitiveTuples.pair(grade, letterGrade);
}

我在这里使用了对的toString()实现,因此以下将是输出。

0:F
40:F
51:F
61:D
71:C
81:B
91:A
100:A

可以使用对中包含的数字等级和字母等级在forEach中自定义输出。

注意:我是Eclipse Collections的提交者。

答案 2 :(得分:1)

这是一个没有开关/ if的解决方案,否则。

它具有各种条件和动作(打印逻辑)之间的映射

Map<Predicate<Integer>, Consumer<Integer>> actions = new LinkedHashMap<>();
//Beware of nulls in your list - It can result in a NullPointerException when unboxing
actions.put(x -> x >= 90, x -> System.out.println(x + " -> A"));
actions.put(x -> x >= 80 && x <= 89, x -> System.out.println(x + " -> B"));
actions.put(x -> x >= 70 && x <= 79, x -> System.out.println(x + " -> C"));
actions.put(x -> x >= 60 && x <= 69, x -> System.out.println(x + " -> D"));
actions.put(x -> x >= 50 && x <= 59, x -> System.out.println(x + " -> F"));


grades.forEach(x -> actions.entrySet().stream()
                    .filter(entry -> entry.getKey().apply(x))
                    .findFirst()
                    .ifPresent(entry -> entry.getValue().accept(x)));

注意:

  1. 与传统的开关/ if相比,它不优雅。
  2. 对于每个等级而言,它可能无法有效地通过地图中的每个条目(这不是一个大问题,因为地图只有几个映射)。
  3. 摘要:我更愿意使用switch / if坚持原始代码。

答案 3 :(得分:1)

您可能希望将标记转换为等级(int - &gt; String)并循环遍历您的arraylist。

使用流作为循环数据的机制和翻译逻辑的方法

这是一个简单的例子

private static void getGradeByMarks(Integer marks) {
    if(marks > 100 || marks < 0) {
        System.err.println("Invalid value " + marks);
        return;
    }
    // whatever the logic for your mapping is
    switch(marks/10) {
        case 9:
            System.out.println(marks + " --> " + "A");
            break;
        case 8:
            System.out.println(marks + " --> " + "B");
            break;
        case 7:
            System.out.println(marks + " --> " + "C");
            break;
        case 6:
            System.out.println(marks + " --> " + "D");
            break;
        default:
            System.out.println(marks + " --> " + "F");
            break;
    }
}

public static void main(String[] args) {
    List<Integer> grades = Arrays.asList(90,45,56,12,54,88,-6);

    grades.forEach(GradesStream::getGradeByMarks);

}

答案 4 :(得分:0)

您可以在forEach语句中添加这些条件,例如

grades.stream()
.mapToInt(Integer::intValue)
.forEach(x -> {
    if (x >= 90) 
        System.out.println(x + " -> A");
    else if (x >= 80) 
        System.out.println(x + " -> B");
    else if (x >= 70)
        System.out.println(x + " -> C");
    ......
    // other conditions
});

答案 5 :(得分:0)

没有办法去&#34;分叉&#34;流入多个流,如果这是你所追求的。如果您想要在流操作中编写所有逻辑,那么您当前编写它的方式是您可以做的最好的方法。

我认为这里最简单的方法就是编写正常生成字符串的逻辑。如果您想要了解它,可以使用mapToObj,如下所示:

    grades.stream()
        .mapToInt(Integer::intValue)
        .mapToObj(x -> {
            if (x >= 90) {
                return "A";
            }
            //etc
        })
        .forEach(System.out::println);

答案 6 :(得分:0)

以下代码产生与原始代码完全相同的输出,即以相同的顺序。

grades.stream()
  .filter(x -> x >= 50)
  .collect(Collectors.groupingBy(x -> x<60? 'F': 'E'+5-Math.min(9, x/10)))
  .entrySet().stream()
  .sorted(Map.Entry.comparingByKey())
  .flatMap(e -> e.getValue().stream().map(i -> String.format("%d -> %c", i, e.getKey())))
  .forEach(System.out::println);

如果您不需要该订单,可以省略分组和排序步骤:

grades.stream()
  .filter(x -> x >= 50)
  .map(x -> String.format("%d -> %c", x, x<60? 'F': 'E'+5-Math.min(9, x/10)))
  .forEach(System.out::println);

grades.stream()
  .filter(x -> x >= 50)
  .forEach(x -> System.out.printf("%d -> %c%n", x, x<60? 'F': 'E'+5-Math.min(9, x/10)));

grades.forEach(x -> {
   if(x >= 50) System.out.printf("%d -> %c%n", x, x<60? 'F': 'E'+5-Math.min(9, x/10));
});

答案 7 :(得分:-1)

for (int x : grades)
{
    if (x >= 90)
        System.out.println(x + " -> A");
    else if (x >= 80)
        System.out.println(x + " -> B");
    else if (x >= 70)
        System.out.println(x + " -> C");
    else if (x >= 60)
        System.out.println(x + " -> D");
    else if (x >= 50)
        System.out.println(x + " -> F");
}

更容易阅读和运行更快。 PS。那么“E”级呢?