JAVA函數式編程

来源:https://www.cnblogs.com/Courage129/archive/2023/08/04/17606013.html
-Advertisement-
Play Games

## JAVA函數式編程 ### 函數式編程的背景和概念 維基百科:**函數式編程**,或稱**函數程式設計**、**泛函編程**(英語:Functional programming),是一種[編程範型](https://zh.wikipedia.org/wiki/編程範型),它將[電腦運算](ht ...


JAVA函數式編程

函數式編程的背景和概念

維基百科:函數式編程,或稱函數程式設計泛函編程(英語:Functional programming),是一種編程範型,它將電腦運算視為函數運算,並且避免使用程式狀態以及可變物件

背景

函數式編程的發展可以追溯到數學家阿隆佐·邱奇(Alonzo Church)在1930年代提出的λ演算。λ演算是一種形式化的計算模型,用於研究計算過程和可計算性。在20世紀後期,函數式編程開始在電腦科學領域嶄露頭角,成為一種重要的編程範式。

在傳統的命令式編程中,程式被視為一系列命令和狀態的改變。這種編程範式易於理解和實現,但隨著程式規模的增加,代碼的複雜性和維護成本也會增加。函數式編程通過使用純函數和不可變數據來避免副作用,使得代碼更容易理解、測試和並行化,進而提高了代碼質量和開發效率。

概念

函數式編程有以下主要概念:

  1. 純函數(Pure Function):純函數是指具有相同輸入始終產生相同輸出的函數,且沒有副作用。副作用是指函數對除了返回值之外的其他數據進行了修改,例如修改全局變數、寫入文件等。純函數不依賴於外部狀態,且對相同輸入總是產生相同輸出,這使得純函數易於測試和理解。
  2. 不可變性(Immutability):在函數式編程中,數據是不可變的,一旦創建就不能被修改。這意味著每次對數據的操作都會返回一個新的數據,而不是直接修改原有的數據。不可變性有助於減少競態條件和併發問題,提高代碼的穩定性和並行性。
  3. 高階函數(Higher-order Function):函數式編程語言允許將函數作為參數傳遞給其他函數,或者返回一個函數作為結果。這種能力被稱為高階函數。高階函數是函數式編程的重要特性,它可以使代碼更具靈活性和抽象性。
  4. 遞歸(Recursion):函數式編程中常常使用遞歸來解決問題。遞歸是一種通過自身調用來解決問題的方法,它在函數式編程中可以替代迴圈,並且可以非常優雅地解決一些複雜問題。
  5. Lambda 表達式:Lambda 表達式是函數式編程語言中的一種匿名函數。它可以用於定義簡短的函數體,方便地將函數作為參數傳遞給其他函數或者直接返回一個函數。

函數式編程語言(例如 Haskell、Clojure、Scala)以及支持函數式編程特性的現代編程語言(例如 Java、Python、JavaScript)都受益於這些概念,通過引入函數式編程範式,開發者可以編寫更加簡潔、清晰和可維護的代碼。

函數式編程在不同編程語言中的應用有一些相似之處,但也存在一些細微的差異。以下是對比JAVA、Python、JavaScript和Scala中函數式編程的應用以及函數式編程和傳統命令式編程的不同之處:

函數式編程在不同語言中的應用

  1. JAVA:
    • JAVA 8 引入了函數式編程特性,包括 Lambda 表達式和 Stream API。
    • Lambda 表達式可以用於傳遞匿名函數,使代碼更加簡潔和靈活。
    • Stream API 提供了一套豐富的函數式操作,用於對集合進行過濾、映射、排序、歸約等處理。
  2. Python:
    • Python 本身就支持函數式編程,函數是一等公民,可以作為參數傳遞和返回。
    • Python 提供了 map、filter、reduce 等函數式編程工具,用於對集合進行處理。
    • Python 支持匿名函數(使用 lambda 表達式)和列表推導式,進一步提高了函數式編程的便利性。
  3. JavaScript:
    • JavaScript 是一門多範式語言,支持函數式編程。
    • JavaScript 提供了高階函數、閉包、箭頭函數等特性,使函數式編程更加便捷。
    • 在現代 JavaScript 中,特別是使用 ES6/ES7 特性,函數式編程在前端開發中得到廣泛應用。
  4. Scala:
    • Scala 是一門結合了面向對象編程和函數式編程的多範式語言。
    • Scala 提供了強大的函數式編程特性,包括高階函數、閉包、模式匹配等。
    • 在 Scala 中,函數式編程和麵向對象編程可以自由地混用,使得代碼更加靈活和簡潔。

與傳統命令式編程的不同

  1. 副作用:
    • 函數式編程強調使用純函數,避免副作用。副作用指的是函數對除了返回值之外的其他數據進行了修改,如修改全局變數或修改傳入的參數。
    • 傳統命令式編程往往會使用副作用,例如修改全局狀態、執行 I/O 操作等。
  2. 可變性:
    • 函數式編程鼓勵使用不可變數據結構,即一旦創建數據就不能修改。這有助於避免競態條件和併發問題。
    • 傳統命令式編程通常使用可變數據結構,允許對數據進行直接修改。
  3. 控制流:
    • 函數式編程常常使用遞歸和高階函數來實現控制流,避免了顯式的迴圈結構。
    • 傳統命令式編程則使用顯式的迴圈結構(例如 for、while 迴圈)來控制流程。
  4. 編程風格:
    • 函數式編程更加傾向於使用表達式式的編程風格,強調將問題拆分成更小的函數,減少副作用,使代碼更具可讀性和可維護性。
    • 傳統命令式編程則更加強調使用語句式的編程風格,強調計算的過程和狀態的改變。

JAVA函數式編程特性

AVA 函數式編程的特性主要是在 Java 8 引入的新特性,主要包括Lambda 表達式、函數式介面以及Stream API。

Lambda 表達式是函數式編程的核心特性之一。它是一種匿名函數,允許將函數作為參數傳遞給其他函數或直接返回一個函數。Lambda 表達式可以使代碼更加簡潔、靈活,減少冗餘代碼,並且在集合操作中有著廣泛的應用。

// 傳統的匿名內部類寫法
Runnable runnable1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello, World!");
    }
};

// 使用 Lambda 表達式
Runnable runnable2 = () -> System.out.println("Hello, World!");

函數式介面: 函數式介面是只包含一個抽象方法的介面,通過 @FunctionalInterface 註解標識。函數式介面用於支持 Lambda 表達式,讓開發者可以在不聲明新介面的情況下,直接使用已有的介面。

@FunctionalInterface
public interface C300_Function {
    int apply(int x, int y);
}

Stream API 提供了一套豐富的函數式操作,用於對集合數據進行過濾、映射、排序、歸約等處理。Stream API 避免了顯式使用迭代器或迴圈,使得代碼更加簡潔和易讀。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().filter(n -> n % 2 == 0).mapToInt(Integer::intValue).sum();

這兒主要先研究下@FunctionallInterface註解

在Spring中很多註解(特別是)是通過代理實現的,特別是與 AOP(Aspect-Oriented Programming,面向切麵編程)相關的註解。 Spring 使用代理來實現 AOP 的橫切關註點,比如事務管理、日誌記錄等。,但上述這個介面只是用來起標識作用,當介面被這個註解標識的話那麼就只能有一個抽象方法介面,目的是為了保證介面的設計人員在介面中添加多個抽象方法時能夠在編譯時得到錯誤提示。例如一個介面標記了 @FunctionalInterface 註解若它包含多個抽象方法,編譯器會報錯。一般函數式介面只能有一個抽象方法,並且排除介面預設(default)方法及聲明中覆蓋Object的公開方法,同時,@FunctionalInterface不能標註在註解、類、枚舉上。如果違背以上的原則那麼這個介面就不能視為函數式介面,當標註這個註解後編譯會報錯, 不過任何一個介面滿足上述函數式介面的要求後,無論介面上是否添加@FunctionallInterface註解都能被編譯器視為函數式介面。無論是哪一種註解其實都是數據流轉對象。

理解@FunctionallInterface

An informative annotation type used to indicate that an interface type declaration is intended to be a functional interface as defined by the Java Language Specification. Conceptually, a functional interface has exactly one abstract method. Since default methods have an implementation, they are not abstract. If an interface declares an abstract method overriding one of the public methods of java.lang.Object, that also does not count toward the interface's abstract method count since any implementation of the interface will have an implementation from java.lang.Object or elsewhere.

一種信息類的註解類型,用於介面類型聲明,旨在成為 Java 語言規範定義的功能性介面。從概念上講,函數介面只能有一個抽象方法。但由於預設方法已經被實現,所以它們不是抽象的。如果介面聲明瞭一個覆蓋java.lang.Object公共方法之一的抽象方法,則該方法也不計入介面的抽象方法計數,因為該介面的任何實現都將具有來自java.lang.Object或其他地方的實現。

例如:

@FunctionalInterface
public interface C301_FunctionalInterface {
    /**
     * 重寫的方法不算,因為會預設調用其繼承父類的這個方法
     */
    @Override
    String toString();

    /**
     * default方法由於會在定義時實現,所以不作為函數式介面唯一的方法的要求
     *
     * @return String
     */
    default String toUpperCase() {
        return "toUpperCase";
    }

    void test();

    public static void main(String[] args) {
        /**調用之前需要先實現其定義的方法  test(),這個地方也就體現出為啥只能定義一個抽象方法,否則該如何表示實現哪個預設方法呢?*/
        C301_FunctionalInterface functionalInterface = () -> {
            System.out.println("test");
        };
        /**調用了其定位的方法*/
        functionalInterface.test();
        System.out.println(functionalInterface.toUpperCase());
        System.out.println(functionalInterface.toString());
    }
}

Note that instances of functional interfaces can be created with lambda expressions, method references, or constructor references.
If a type is annotated with this annotation type, compilers are required to generate an error message unless:
The type is an interface type and not an annotation type, enum, or class.
The annotated type satisfies the requirements of a functional interface.
However, the compiler will treat any interface meeting the definition of a functional interface as a functional interface regardless of whether or not a FunctionalInterface annotation is present on the interface declaration.

請註意,可以使用 lambda 表達式、方法引用或構造函數引用創建函數介面的實例。此類型可以作用的對象是該類型是介面,而不能註解、枚舉,否則編譯器會生成錯誤消息 ,帶註解的類型滿足功能介面的要求。但是,無論是否使用這個註解,只需要介面滿足函數式介面的要求都可以被編譯器視為函數式介面。

理解Supplier<T>

位於java.util.function.Supplier<T>,它在 Java 8 中引入。該介面代表一個供給型介面,它不接受任何參數,返回一個類型為 T 的結果。該介面有一個抽象方法 T get(),用於獲取結果,數據提供類型, 特點是只出不進,一般作為方法、構造參數,方法返回值。

/**
 * Represents a supplier of results.
 *
 * <p>There is no requirement that a new or distinct result be returned each
 * time the supplier is invoked.
 *
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #get()}.
 *
 * @param <T> the type of results supplied by this supplier
 *
 * @since 1.8
 */
@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

示例使用 Supplier 介面:

import java.util.function.Supplier;

public class SupplierExample {
    public static void main(String[] args) {
        Supplier<String> messageSupplier = () -> "Hello, World!";
        String message = messageSupplier.get();
        System.out.println(message); // 輸出:Hello, World!
    }
}

在上面的示例中,我們使用了 Supplier<String> 函數式介面創建了一個供給型對象 messageSupplier,它返回一個固定的字元串 "Hello, World!"。然後通過調用 messageSupplier.get() 方法獲取供給型對象的結果。

理解Consumer<T>

它代表了一個消費型介面。Consumer 介面接受一個參數,但沒有返回值。該介面有一個抽象方法 void accept(T t),用於執行接受到的參數的操作。Consumer 介面通常用於對某個對象或值進行處理,例如列印輸出、寫入文件、發送消息等操作。由於沒有返回值,Consumer 介面更多地用於執行一些操作,而不是產生一個結果。數據是只進不出,一般是作為方法參數或者構造器

@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);

    /**
     * Returns a composed {@code Consumer} that performs, in sequence, this
     * operation followed by the {@code after} operation. If performing either
     * operation throws an exception, it is relayed to the caller of the
     * composed operation.  If performing this operation throws an exception,
     * the {@code after} operation will not be performed.
     *
     * @param after the operation to perform after this operation
     * @return a composed {@code Consumer} that performs in sequence this
     * operation followed by the {@code after} operation
     * @throws NullPointerException if {@code after} is null
     */
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

案例

public class C303_Consumer {
    public static void main(String[] args) {
        Consumer consumer = System.out::println;
        Consumer<String> consumer2 = C303_Consumer::println;
        // 輪流消費
        consumer2.andThen(consumer).andThen(consumer2).andThen(consumer2).accept("hello world2");
        consumer.accept("hello world1");
    }

    public static void println(String message){
        System.out.println("println:"+message);
    }
}

列印結果:

println:hello world2
hello world2
println:hello world2
println:hello world2
hello world1

理解Function<T,R>

數據轉換類型,是一個函數式介面,它代表了一個函數,該函數接受一個參數類型為 T 的輸入,並返回一個參數類型為 R 的結果。Function 介面有一個抽象方法 R apply(T t),用於執行函數的邏輯,將輸入類型 T 轉換為輸出類型 R。

Function 介面常用於對一個對象或值進行轉換、映射或計算,並返回一個結果。它將一個類型的數據映射到另一個類型,常用於對集合的處理、數據轉換等場景。

示例使用 Function 介面:

public class C304_Function {
   public static void main(String[] args) {
      /**示例一:將入參為String轉為Integer類型返回*/
      Function <String,Integer> function1 = new Function<String, Integer>() {
         @Override
         public Integer apply(String s) {
             System.out.println("function1");
            return Integer.parseInt(s);
         }

      };
      /**通過Function裡面的apply函數調用*/
      // System.out.println(function1.apply("123"));
       /**示例二:將入參為Integer轉為String類型返回*/
       Function<Integer,String> function2 = new Function<Integer, String>() {
           @Override
           public String apply(Integer integer) {
               System.out.println("function2");
               return String.valueOf(integer);
           }
       };
       /**示例三:將入參為String轉為String類型返回*/
       Function<String,String> function3 = new Function<String, String>() {
           @Override
           public String apply(String string) {
               System.out.println("function3");
               return String.valueOf(string);
           }
       };
       // 執行循序:fun2 -> fun3 -> fun1
       Integer apply = function1.compose(function3).compose(function2).apply(123456);
   }
}

列印結果:

function2
function3
function1

Function 介面在 Java 中廣泛用於集合框架的操作,例如 List 的 map() 方法接受一個 Function 對象作為參數,用於對集合中的每個元素進行映射轉換。另外,在 Stream API 中也經常使用 Function 介面進行數據轉換和處理。

示例使用 Function 介面處理集合元素:

import java.util.Arrays;
import java.util.List;
import java.util.function.Function;

public class FunctionExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
        
        // 使用 Function 介面處理集合元素
        Function<String, Integer> nameLengthMapper = name -> name.length();
        names.stream()
             .map(nameLengthMapper)
             .forEach(System.out::println);
    }
}

理解Predicate<T>

斷言類型,Predicate 介面表示一個接收一個參數並返回布爾值的函數。它常用於對集合元素進行過濾或條件判斷。Predicate 介面只有一個抽象方法 test(T t),其中 test 方法接收一個泛型參數 T,表示待檢查的對象,然後返回一個布爾值,表示是否滿足指定條件。

以下是 Predicate 介面的簡單示例:

public class C305_Predicate {
    /**
     * Predicate<T>:這一介面定了一個預設方法:boolean test(T t),這一方法用檢查給定的參數是否符合查詢檢查。
     * Predicate<T>介面中有兩中重要的方法:and(),or(),negate()。
     * Predicate<T>介面中的方法如下:
     * and(Predicate<? super T> other):對當前 Predicate 和另一個 Predicate 進行邏輯與操作。
     * or(Predicate<? super T> other):對當前 Predicate 和另一個 Predicate 進行邏輯或操作。
     * negate():返回當前 Predicate 的邏輯非。
     * 下麵是使用 Predicate 進行條件組合的示例:
     */
    public static void main(String[] args) {
        //能夠被2整除
        Predicate<Integer> dividedTwo = new Predicate<Integer>() {
            @Override
            public boolean test(Integer o) {
                return o % 2 == 0;
            }
        };
        //能夠被3整除
        Predicate<Integer> dividedThree = o -> o % 3 == 0;
        //能夠被5整除
        Predicate<Integer> dividedFive = o -> o % 5 == 0;
        //Predicate 支持多種邏輯操作,可以通過 and、or、negate 等方法將多個 Predicate 進行組合,形成更複雜的條件。
        // and使用:true
        System.out.println(dividedTwo.and(dividedThree).and(dividedFive).test(30));
        // or使用:false
        System.out.println(dividedTwo.and(dividedThree).or(dividedFive).test(3));
        // negate使用:true
        System.out.println((dividedTwo.and(dividedThree).or(dividedFive)).negate().test(3));
    }
}

FunctionPredicate應用

Function作過濾函數

 /**
     * 過濾函數,將集合內滿足一定條件的元素返回
     *
     * @param sourece   源數據集合
     * @param predicate 處理函數Function
     * @param <T>
     * @return 滿足斷言的所有數據源集合
     * TODO: 我這兒用一個List接收,我理解這不夠抽象
     */
    public static <T> Collection<T> filter_1(Collection<? extends T> sourece, Function<T, Boolean> predicate) {
        Collection<T> temp = new ArrayList<T>();
        for (T t : sourece) {
            if (predicate.apply(t)) {
                temp.add(t);
            }
        }
        return Collections.unmodifiableCollection(temp);
    }

斷言作過濾函數

    /**
     * @param sourece   輸入數據集合
     * @param predicate 斷言
     * @param <E>       泛型
     * @return
     */
    public static <E> Collection<E> filter_2(Collection<? extends E> sourece, Predicate<E> predicate) {
        Collection<E> temp = new ArrayList<E>();
        for (E t : sourece) {
            if (predicate.test(t)) {
                temp.add(t);
            }
        }
        return Collections.unmodifiableCollection(temp);
    }
	// 調用
    public static void testFilter_2() {
        Collection<Integer> collection = filter_2(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), (Integer i) -> {
            return i % 2 == 0;
        });
        System.out.println(collection);
    }

隱藏類型-Action

JAVA中並沒有具體的Action類型,但是有一些操作有Action的作用,例如:

public class C3_06_Action {
    public static void main(String[] args) {
        // 匿名內置類實現
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("定時任務執行");
            }
        };
        Thread thread = new Thread(runnable);
        // invokeDynamic指令實現的(反彙編)
        // MethodHandle
        Runnable r = () -> System.out.println("定時任務執行");
        Thread t = new Thread(r);
    }
}

理解Stream

Stream是用於對集合(Collection)進行處理和操作的工具,讓我們能夠以聲明式的方式進行數據的轉換、過濾、映射等操作。Stream 可以看作是對集合進行高級迭代和函數式處理的工具,它提供了一種更現代、更簡潔的方式來操作數據,使得代碼更加清晰、易讀、易維護。

Stream API 提供了一系列操作,這些操作分為兩類:

  1. Intermediate(中間操作):這些操作返回一個新的 Stream,用於定義一系列的數據轉換和處理操作,但並不立即執行。中間操作包括 filtermapdistinctsortedlimitskip 等。
  2. Terminal(終端操作):這些操作觸發 Stream 的處理,執行中間操作定義的一系列數據轉換和處理操作,生成最終的結果。終端操作包括 forEachcollectcountreduceminmaxanyMatchallMatchnoneMatch 等。

!!一般不會將Stream作為參數進行傳遞,但是如果有Stream作為參數進行傳遞的話,一定要進行串列與並行的判斷,並且根據實際情況進行切換

    /**
     * Returns an equivalent stream that is sequential.  May return
     * itself, either because the stream was already sequential, or because
     * the underlying stream state was modified to be sequential.
     *
     * <p>This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * @return a sequential stream
     */
    S sequential();

    /**
     * Returns an equivalent stream that is parallel.  May return
     * itself, either because the stream was already parallel, or because
     * the underlying stream state was modified to be parallel.
     *
     * <p>This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * @return a parallel stream
     */
    S parallel();

示例使用 Stream API:

import java.util.Arrays;
import java.util.List;

public class StreamExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eva");
        
        // 使用 Stream API 對集合進行操作
        long count = names.stream()
                         .filter(name -> name.length() <= 4)
                         .map(String::toUpperCase)
                         .sorted()
                         .count();
        System.out.println(count); // 輸出:4
    }
}

在上面的示例中,我們使用 Stream API 對集合 names 進行一系列的操作:首先用 filter 過濾掉長度大於 4 的字元串,然後用 map 將字元串轉換為大寫,接著用 sorted 進行排序,最後用 count 終端操作統計符合條件的元素個數。

Stream API 的優勢在於它具有懶載入的特性,即只有當終端操作觸發時,中間操作才會執行。這使得 Stream 在處理大量數據時,能夠有效地提高性能和資源利用率。

sorted的應用

public class C03_08_Stream {
    public static void main(String[] args) {
        sorted(Integer::compareTo, Arrays.asList(5, 1, 5, 4, 8, 9, 6, 5, 4, 8, 5, 4, 2, 3, 4)).stream().distinct().forEach(System.out::println);
    }
    /**
     * sorted<Comparator<? super T> comparator>的使用
     * @param comparator 比較器
     * @param collection 待排序的集合
     * @param <T>
     * @return 排序後的集合
     */
    private static <T> Collection<T> sorted(Comparator<T> comparator, Collection<T> collection) {
        return collection.stream().sorted(comparator).collect(Collectors.toList());
    }
}

map的應用

map主要是做一個映射,例如這就是將Integer類型轉換成Long類型

reduce主要是合併操作,例如將這將多個元素求和

/**
     * Stream 的 map<Function>操作
     *
     * @param integers
     */
    private static void count(Integer... integers) {
        Stream.of(integers)
                .map(Long::valueOf)
                .reduce(Long::sum)
                .ifPresent(System.out::println);
    }

並行parallel

/**
     * 並行parallel案例
     * @param integers
     */
private static void parallelSort(Integer... integers){
    Stream.of(integers).sorted(Integer::compareTo).parallel().forEach(C03_08_Stream::println);
}

public static void println(Object obj){
    System.out.printf("[%s]:%s \n",Thread.currentThread().getName(),obj);
}

列印結果

[main]:7 
[ForkJoinPool.commonPool-worker-2]:1 
[ForkJoinPool.commonPool-worker-2]:4 
[ForkJoinPool.commonPool-worker-9]:8 
[ForkJoinPool.commonPool-worker-6]:3 
[ForkJoinPool.commonPool-worker-13]:6 
[ForkJoinPool.commonPool-worker-15]:5 
[ForkJoinPool.commonPool-worker-4]:9 
[ForkJoinPool.commonPool-worker-11]:2 
[main]:1

這個預設創建系統設置的線程數(NUMBER_OF_PROCESSORS)

理解Collect

    /**collect的使用案例
     * @param integers
     * @return
     */
    public static List<Integer> collect(Integer... integers){
        List<Integer> collect = Stream.of(integers).collect(Collectors.toList());
        List<Integer> collect2 = Stream.of(integers).collect(LinkedList::new,LinkedList::add,LinkedList::addAll);
        return collect;
    }

理解flatMap

一般是處理一些降維的需求,例如List<List<Klass>> -- > List<Klass>

    static class Klass {
        private String name;

        public Klass(String name) {
            this.name = name;
        }
        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "Klass{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }

    static class KlassGroup {
        private List<Klass> group = new ArrayList<>();

        public KlassGroup(Klass... klass) {
            group.addAll(Arrays.asList(klass));
        }

        public List<Klass> getGroup() {
            return group;
        }

    }
    // 現在要做的一件事是如果有多個List<Klass>想合併成一個List<Klass>

    /**
     * 關於flatMap的使用
     * @param klassLoist
     * @return
     */
    public static List<Klass> mergeKlass(List<List<Klass>> klassLoist){
        return klassLoist.stream().flatMap(Collection::stream).collect(Collectors.toList());
    }

函數式編程的局限性

儘管函數式編程在很多場景下提供了很多優勢,但它也有一些局限性:

  1. 學習曲線:對於習慣了命令式編程範式的開發者來說,切換到函數式編程需要適應新的思維方式和編程習慣。函數式編程的概念和特性可能對初學者來說較為陌生,需要一定的學習和實踐才能熟練掌握。

  2. 性能問題:雖然函數式編程提倡不可變數據和純函數,但有時候為了實現函數式編程的特性,可能需要引入額外的對象拷貝或數據結構轉換,導致一定的性能損失。對於一些性能敏感的場景,可能需要權衡是否使用函數式編程。

  3. 可讀性問題:雖然函數式編程鼓勵使用更簡潔、聲明式的代碼,但對於複雜的業務邏輯,過度使用函數式編程可能會導致代碼的可讀性下降,增加理解的難度。

  4. 嵌套問題:函數式編程中的函數組合、嵌套等特性,可能導致代碼的層級嵌套過深,降低代碼的可讀性和可維護性。

  5. 現有項目相容性:對於現有的項目,特別是老舊項目,可能由於歷史原因和架構設計,不太容易完全引入函數式編程的特性,可能需要進行較大的重構。

  6. 併發處理複雜性:函數式編程鼓勵不可變數據和純函數,這在併發處理時可以避免一些線程安全問題。但對於複雜的併發場景,函數式編程的特性可能會增加代碼的複雜性,需要謹慎處理。

站在巨人的肩膀上

  1. 函數式編程思維(Functional Thinking)(書籍) - Neal Ford 這本書向讀者介紹了函數式編程的思維方式和概念,它從函數式編程的基礎開始,逐步深入,涵蓋了函數式編程在 Java、JavaScript、Scala 等編程語言中的應用。
  2. Java 8 in Action(書籍) - Raoul-Gabriel Urma, Mario Fusco, Alan Mycroft 這本書全面講解了 Java 8 的新特性,包括函數式編程、Lambda 表達式、Stream API 等。它提供了大量實例和案例,幫助讀者快速上手函數式編程在 Java 中的應用。
  3. Functional Programming Principles in Scala(線上課程) - Martin Odersky 這是由 Scala 語言的創始人 Martin Odersky 主講的線上課程。課程涵蓋函數式編程的基本概念和原則,並通過 Scala 語言進行實踐。
  4. Functional Programming for the Object-Oriented Programmer(線上文章) - Brian Marick 這篇文章向面向對象編程開發者介紹函數式編程的概念和思維方式,幫助他們理解函數式編程並適應新的編程範式。
  5. Functional Programming Concepts in Python(線上教程) - Real Python 這個教程將 Python 中的函數式編程概念進行了詳細介紹,涵蓋了函數、Lambda 函數、高階函數、Generator 等內容。

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ## 5.1、bean的作用域 ### 5.1.1、單例(預設且常用) #### 5.1.1.1、配置bean ![image](https://img2023.cnblogs.com/blog/2052479/202308/2052479-20230803010539572-840709484.p ...
  • ## 1. 問題復現 話不多說,先貼出問題代碼:這裡的`GetUserInfoByAccessToken`是我自定義的一個實體類。 ``` GetUserInfoByAccessToken getUserInfoByAccessTokenString = restTemplate.getForObj ...
  • 想要搭建一個強大的後臺管理系統?本文提供了詳細的 Webman-Admin 安裝指南,幫助您快速部署和配置這個功能豐富的 Web 開發工具。瞭解如何安裝 Webman-Admin,並利用其強大的功能來管理和監控您的應用程式。立即開始搭建您的後臺管理系統,提升工作效率和用戶體驗! ...
  • 選擇哪種分配方式由Java堆是否規整決定,而Java堆是否規整又由所採用的垃圾收集器是否帶有壓縮整理功能決定。因此,在使用Serial、ParNew等帶Compact過程的收集器時,系統採用的分配演算法是指針碰撞,而使用CMS這種基於Mark-Sweep演算法的收集器時,通常採用空閑列表。這兩種對象訪問... ...
  • 環境: centos7.9 tomcat9 jdk1.8 # 一.阿裡雲申請 [免費SSL](https://yundunnext.console.aliyun.com/?spm=5176.21213303.782131.4.304053c9wUb2BP&p=cas#/certExtend/free ...
  • python中的多線程其實並不是真正的多線程,如果想要充分地使用多核CPU資源,在python中大部分情況需要使用多進程。 python提供了非常好用的多進程包Multiprocessing,只需要定義一個函數,python會完成其它所有事情。藉助這個包,可以輕鬆完成從單進程到併發執行的轉換。 mu ...
  • Lucas定理: 主要是求$C_{n}^{m}$在模$p$情況下($mod \, p$)(一般$p$較小,而$n,m$較大的情況) 公式: $ C_{n}^{m} ≡ C_{n \, mod \, p}^{m \, mod \, p} \times C_{n/p}^{m/p} (mod \, p) ...
  • ## 測試 Spring提供了一組測試工具,可以輕鬆地測試Spring應用程式的各個組件,包括控制器、服務、存儲庫和其他組件。它具有豐富的測試註釋、實用程式類和其他功能,以幫助進行單元測試、集成測試等。 ### JPA測試 Spring JPA(Java Persistence API)是一個庫,它 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...