簡體   English   中英

在Java中簡化流上的Lambda表達式

[英]Simplifying Lambda expressions on stream in Java

我正在嘗試自學如何在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的文檔,但是找不到我想要的東西。

只需創建一個單獨的函數來映射成績字母並在單個流中調用它:

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

如果您願意使用第三方庫,此解決方案將適用於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工廠方法來處理 Eclipse Collections中的List和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並使用三元運算符替換為方法引用調用實例方法,則以下內容將起作用。

@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的提交者。

這是一個沒有開關/ 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. 它不是那么有效,因為它可以潛在地遍歷地圖中的每個條目(這不是一個大問題,因為地圖只有幾個映射)。

簡介:我更喜歡使用switch / if,else來堅持原始代碼。

您可能希望將標記轉換為等級(int - > 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);

}

您可以在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
});

沒有辦法將“分叉”流分成多個流,如果這就是你所追求的。 如果您想要在流操作中編寫所有邏輯,那么您當前編寫它的方式是最好的。

我認為這里最簡單的方法就是編寫正常生成字符串的邏輯。 如果您想要了解它,可以使用mapToObj ,如下所示:

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

以下代碼生成與原始代碼完全相同的輸出,即以相同的順序。

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));
});
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”級呢?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM