简体   繁体   English

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

[英]Simplifying Lambda expressions on stream in Java

I'm trying to teach myself how to use lambda expressions in Java instead of regular external iteration type solutions. 我正在尝试自学如何在Java中使用lambda表达式而不是常规的外部迭代类型解决方案。 I've made a short program where I use a stream of integers and assign them grade letters. 我做了一个简短的程序,我使用整数流并为它们分配等级字母。 Instead of using the typical "switch" statement, I've used these lambda expressions. 我没有使用典型的“switch”语句,而是使用了这些lambda表达式。 Is there any way I can simplify it? 有什么办法可以简化吗? It seems like there's a better way to do it. 似乎有更好的方法来做到这一点。 It works, but doesn't look as tidy. 它有效,但看起来并不整洁。

    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 is an ArrayList. grades是一个ArrayList。

Is there any way I can combine the logic all in one stream statement? 有什么办法可以将逻辑全部合并到一个流语句中吗? I tried just having them follow eachother but kept getting syntax errors. 我试过让他们跟随彼此,但不断得到语法错误。 What am I missing? 我错过了什么? I tried looking into the documentation for IntStream and Stream and couldn't really find what I was looking for. 我试着查看IntStream和Stream的文档,但是找不到我想要的东西。

Just create a separate function to map grade letters and call that in a single stream: 只需创建一个单独的函数来映射成绩字母并在单个流中调用它:

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

If you're particular about ordering the results by grade, you can add a sort to the beginning of your stream: 如果您特别想按年级排序结果,可以在流的开头添加排序:

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

If you're open to using a third-party library, this solution will work with Java Streams and Eclipse Collections . 如果您愿意使用第三方库,此解决方案将适用于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);

This outputs the following: 这输出如下:

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

I used the Java 9 factory method for the List and IntCaseFunction from Eclipse Collections. 我使用Java 9工厂方法来处理 Eclipse Collections中的List和IntCaseFunction The lambda I passed to the constructor will be the default case if none of the other cases match. 如果没有其他情况匹配,我传递给构造函数的lambda将是默认情况。

The number ranges could also be represented by instances of IntInterval , which can be tested using contains as a method reference. 数字范围也可以由IntInterval的实例表示,可以使用contains作为方法引用来测试。 The following solution will work using Local-variable type inference in Java 10. 以下解决方案将在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);

If I drop all of the Predicates and Functions and remove the IntCaseFunction and replace with a method reference call to an instance method using a ternary operator, the following will work. 如果我删除所有PredicatesFunctions并删除IntCaseFunction并使用三元运算符替换为方法引用调用实例方法,则以下内容将起作用。

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

I am using the toString() implementation of the pair here, so the following will be the output. 我在这里使用了对的toString()实现,因此以下将是输出。

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

The output could be customized in the forEach using the number grade and letter grade contained in the pair. 可以使用对中包含的数字等级和字母等级在forEach中自定义输出。

Note : I am a committer for Eclipse Collections. 注意 :我是Eclipse Collections的提交者。

Here's a solution without switch/if,else. 这是一个没有开关/ if的解决方案,否则。

It has mappings between the various conditions and the action (printing logic) 它具有各种条件和动作之间的映射(打印逻辑)

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

Note: 注意:

  1. It is not elegant when compared to the conventional switch/if,else. 与传统的开关/ if相比,它不优雅。
  2. It is not that efficient as for each grade it can potentially go through each entry in the map (not a big problem since the map has only a few mappings). 它不是那么有效,因为它可以潜在地遍历地图中的每个条目(这不是一个大问题,因为地图只有几个映射)。

Summary: I would prefer to stick with your original code using switch/if,else. 简介:我更喜欢使用switch / if,else来坚持原始代码。

You would want separate the logic of translating the marks to a grade (int -> String) and looping through your arraylist. 您可能希望将标记转换为等级(int - > String)并循环遍历arraylist。

Use streams as a mechanism to loop through your data and a method for translation logic 使用流作为循环数据的机制和翻译逻辑的方法

Here is a quick example 这是一个简单的例子

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

}

You can add these conditions inside forEach statement something like 您可以在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
});

There's no way to a "fork" stream into multiple streams, if that's what you're after. 没有办法将“分叉”流分成多个流,如果这就是你所追求的。 The way you've currently written it is the best you can do if you want all of the logic written in the stream manipulation. 如果您想要在流操作中编写所有逻辑,那么您当前编写它的方式是最好的。

I think the simplest thing to do here would be to just write the logic for producing the string normally. 我认为这里最简单的方法就是编写正常生成字符串的逻辑。 If you wanted to be fancy about it you could use mapToObj , like so: 如果您想要了解它,可以使用mapToObj ,如下所示:

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

The following code produces exactly the same output as you original code, ie in the same order. 以下代码生成与原始代码完全相同的输出,即以相同的顺序。

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

If you don't need that order, you can omit the grouping and sorting step: 如果您不需要该订单,则可以省略分组和排序步骤:

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

or 要么

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

or 要么

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

Easier to read and runs faster. 更容易阅读和运行更快。 ps. PS。 What about the "E" grade? 那么“E”级呢?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM