偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Java中的七種函數(shù)式編程技巧

開(kāi)發(fā) 前端
在Java中限制數(shù)據(jù)變異的方法并不多。然而,通過(guò)使用純函數(shù),并明確避免數(shù)據(jù)變異和重新賦值(使用我們之前討論過(guò)的其他概念),可以實(shí)現(xiàn)這一目標(biāo)。對(duì)于變量,我們可以使用final關(guān)鍵字,它是一個(gè)非訪問(wèn)修飾符,用于防止通過(guò)重新賦值來(lái)改變變量的值。

環(huán)境:Java21

1. 簡(jiǎn)介

函數(shù)式編程是一種編程范式,以函數(shù)為核心,避免改變狀態(tài)與可變數(shù)據(jù),強(qiáng)調(diào)函數(shù)的第一公民地位。它通過(guò)使用高階函數(shù)和純函數(shù),實(shí)現(xiàn)代碼的模塊化和重用性,提升可讀性和可維護(hù)性,常用于并發(fā)編程和數(shù)學(xué)計(jì)算等領(lǐng)域。

在函數(shù)式編程中,有兩條非常重要的規(guī)則:

  • 無(wú)數(shù)據(jù)變異
    這意味著一旦數(shù)據(jù)對(duì)象被創(chuàng)建后就不應(yīng)該再被更改。任何對(duì)該對(duì)象的操作都應(yīng)該返回一個(gè)新的對(duì)象,而不是修改原始對(duì)象。
  • 無(wú)隱式狀態(tài)
    應(yīng)避免隱藏或隱式狀態(tài)。這樣理解:在傳統(tǒng)編程中,一個(gè)函數(shù)可能依賴于一些外部或隱藏的狀態(tài),比如全局變量、靜態(tài)變量或者類成員變量等,這些狀態(tài)不是通過(guò)參數(shù)傳遞給函數(shù)的。在函數(shù)式編程中,提倡避免這種隱式的依賴關(guān)系,而是將所有需要的狀態(tài)都作為參數(shù)顯式地傳遞給函數(shù)。這樣做的結(jié)果是提高了代碼的透明度和可測(cè)試性,因?yàn)槟闱宄刂篮瘮?shù)依賴哪些輸入來(lái)產(chǎn)生輸出,同時(shí)也減少了副作用的發(fā)生,即函數(shù)執(zhí)行時(shí)除了返回值外不改變其他任何東西。

除了上述內(nèi)容外,還有以下可以在Java中應(yīng)用的函數(shù)式編程概念:

  • 高階函數(shù)(Higher-order functions)
  • 閉包(Closures)
  • 柯里化(Currying)
  • 遞歸(Recursion)
  • 惰性求值(Lazy evaluations)
  • 引用透明性(Referential transparency)

使用函數(shù)式編程并不意味著必須全盤采用,你可以始終使用函數(shù)式編程概念來(lái)補(bǔ)充面向?qū)ο蟮母拍?,尤其是在Java中。無(wú)論你使用的范式或語(yǔ)言是什么,都可以盡可能地利用函數(shù)式編程的優(yōu)點(diǎn)。

接下來(lái),我們將詳細(xì)介紹函數(shù)式編程在Java中的應(yīng)用

2. 實(shí)戰(zhàn)案例

2.1 一等函數(shù)和高階函數(shù)

在一等函數(shù)的上下文中,函數(shù)被視為頭等公民,意味著它們可以被賦值給變量、作為參數(shù)傳遞給其他函數(shù)、從函數(shù)中返回,以及包含在數(shù)據(jù)結(jié)構(gòu)中。遺憾的是,Java并不完全支持這一特性,因此像閉包、柯里化和高階函數(shù)這樣的概念在Java中實(shí)現(xiàn)起來(lái)不如在其他語(yǔ)言中那么方便。

在Java中最接近一等函數(shù)的概念是Lambda表達(dá)式。此外,在java.util.function包下還有一些內(nèi)置的函數(shù)式接口,如Function、Consumer、Predicate、Supplier等,可以用于函數(shù)式編程。

只有當(dāng)一個(gè)函數(shù)接受一個(gè)或多個(gè)函數(shù)作為參數(shù),或者返回另一個(gè)函數(shù)作為結(jié)果時(shí),它才能被視為高階函數(shù)。在Java中,我們最接近高階函數(shù)的方式是使用Lambda表達(dá)式和內(nèi)置的函數(shù)式接口。

public class Test {
  public static void main(String[] args) {
    var list = Arrays.asList("Orange", "Apple", "Banana", "Grape", "XPack", "AKF");


    var ret = calcLength(list, new FnFactory<String, Object>() {
      public Object execute(final String it) {
        return it.length();
      }
    });
    System.err.printf("Length: %s%n", ret);
  }


  static <T, S> ArrayList<S> calcLength(List<T> arr, FnFactory<T, S> fn) {
    var list = new ArrayList<S>();
    arr.forEach(t -> list.add(fn.execute(t)));
    return list;
  }


  @FunctionalInterface
  public interface FnFactory<T, S> {
    S execute(T it);
  }

輸出結(jié)果:

Length: [6, 5, 6, 5, 5, 3]

接下來(lái),我們使用內(nèi)置的Function接口和Lambda表達(dá)式語(yǔ)法來(lái)簡(jiǎn)化上面的示例:

public class Test1 {
  public static void main(String[] args) {
    var list = Arrays.asList("Orange", "Apple", "Banana", "Grape", "XPack", "AKF") ;
    var ret = calcLength(list, it -> it.length()) ;
    System.err.printf("Length: %s%n", ret) ;
  }


  static <T, S> ArrayList<S> calcLength(List<T> arr, Function<T, S> fn) {
    var list = new ArrayList<S>() ;
    arr.forEach(t -> list.add(fn.apply(t))) ;
    return list ;
  }
}

使用這些概念加上Lambda表達(dá)式,我們可以像下面這樣編寫閉包和柯里化。

public class ClosureTest {
  Function<Integer, Integer> add(final int x) {
    Function<Integer, Integer> add(final int x) {
    // 普通寫法
//    var partial = new Function<Integer, Integer>() {
//      public Integer apply(Integer y) {
//        return x + y;
//      }
//    };
    // 使用Lambda表達(dá)式語(yǔ)法;注意這里不能使用var
    Function<Integer, Integer> partial = y -> x + y ;
    return partial;
  }
    return partial;
  }


  public static void main(String[] args) {
    ClosureTest closure = new ClosureTest();


    var c1 = closure.add(100) ;
    var c2 = closure.add(200) ;


    System.out.println(c1.apply(66));
    System.out.println(c2.apply(66));
  }
}

運(yùn)行結(jié)果

166
266

以上是關(guān)于閉包的應(yīng)用。

Java中也有許多內(nèi)置的高階函數(shù),如java.util.Collections#sort方法:

public static void main(String[] args) {
  var list = Arrays.asList("Apple", "Orange", "Banana", "Grape");


  Collections.sort(list, (String a, String b) -> {
    return a.compareTo(b);
  });


  System.err.printf("%s%n", list) ; 
}

Java Stream相關(guān)API中也提供了許多高階函數(shù),比如forEach、map等。

2.2 純函數(shù)

函數(shù)式編程傾向于使用遞歸而不是循環(huán)。在Java中,這可以通過(guò)使用流API或編寫遞歸函數(shù)來(lái)實(shí)現(xiàn)。讓我們來(lái)看一個(gè)計(jì)算數(shù)字階乘的例子。還使用JMH對(duì)這些方法進(jìn)行了基準(zhǔn)測(cè)試,并在下方列出了每操作的納秒數(shù)。

在傳統(tǒng)的迭代方法中:

@State(Scope.Thread)
public class FactorialTest {
  // 我們要使用JMH進(jìn)行測(cè)試,所以通過(guò)@Param定義入?yún)?  @Param({"20"})
  private long num ;
  @Benchmark
  public long factorial() {
    long result = 1;
    for (; num > 0; num--) {
      result *= num;
    }
    return result;
  }


  public static void main(String[] args) throws Exception {
    Options options = new OptionsBuilder()
        .include(FactorialTest.class.getSimpleName())
        .forks(1)
        .build() ;
    new Runner(options).run() ;
  }
}

測(cè)試結(jié)果

Benchmark                (num)  Mode  Cnt  Score   Error  Units
FactorialTest.factorial     20  avgt    5  0.475 ± 0.013  ns/op

同樣的功能也可以使用遞歸來(lái)實(shí)現(xiàn),如下所示,這在函數(shù)式編程中更為青睞。

@State(Scope.Thread)
public class FactorialTest2 {
  @Param({ "20" })
  private long num;


  @Benchmark
  public long factorialRec() {
    return factorial(num);
  }
  private long factorial(long n) {
    return n == 1 ? 1 : n * factorial(n - 1);
  }
  public static void main(String[] args) throws Exception {
    Options options = new OptionsBuilder()
        .include(FactorialTest2.class.getSimpleName())
        .forks(1)
        .build();
    new Runner(options).run();
  }
}

測(cè)試結(jié)果

Benchmark                    (num)  Mode  Cnt   Score   Error  Units
FactorialTest2.factorialRec     20  avgt    5  17.316 ± 0.792  ns/op

遞歸方法的缺點(diǎn)是,它通常會(huì)比迭代方法更慢(我們追求的優(yōu)勢(shì)在于代碼的簡(jiǎn)潔性和可讀性),并且由于每次函數(shù)調(diào)用都需要作為棧幀保存到堆棧中,可能會(huì)導(dǎo)致棧溢出錯(cuò)誤。

我們還可以使用Stream進(jìn)行遞歸調(diào)用

@Param({ "20" })
private long num;


@Benchmark
public long factorialRec() {
  return LongStream.rangeClosed(1, num)
      .reduce(1, (n1, n2) -> n1 * n2);
}

運(yùn)行結(jié)果

Benchmark                    (num)  Mode  Cnt   Score   Error  Units
FactorialTest2.factorialRec     20  avgt    5  17.618 ± 1.414  ns/op

與遞歸算法差不多。

在編寫Java代碼時(shí),考慮到可讀性和不可變性,可以考慮使用流API或遞歸;但如果性能至關(guān)重要,或者迭代次數(shù)將非常大,則應(yīng)使用標(biāo)準(zhǔn)循環(huán)。

2.3 惰性求值(Lazy evaluations)

惰性求值(Lazy evaluation)或非嚴(yán)格求值是指推遲表達(dá)式的計(jì)算,直到其結(jié)果真正被需要時(shí)才進(jìn)行計(jì)算。一般來(lái)說(shuō),Java執(zhí)行的是嚴(yán)格求值,但對(duì)于像&&、||和?:這樣的運(yùn)算符,它會(huì)進(jìn)行惰性求值。我們可以利用這一點(diǎn)在編寫Java代碼時(shí)實(shí)現(xiàn)惰性求值。

考慮下面這個(gè)例子,在這個(gè)例子中Java會(huì)急切地(eagerly)計(jì)算所有內(nèi)容:

public static void main(String[] args) {
  System.out.println(addOrMultiply(true, add(4), multiply(4))); // 8
  System.out.println(addOrMultiply(false, add(4), multiply(4))); // 16
}


public static int add(int x) {
  System.out.println("executing add");
  return x + x;
}


public static int multiply(int x) {
  System.out.println("executing multiply");
  return x * x;
}


public static int addOrMultiply(boolean add, int onAdd, int onMultiply) {
  return (add) ? onAdd : onMultiply;
}

執(zhí)行結(jié)果

executing add
executing multiply
8
executing add
executing multiply
16

函數(shù)一早就被執(zhí)行了。

我們可以使用Lambda表達(dá)式和高階函數(shù)將此重寫為惰性求值的版本:

public static void main(String[] args) {
  UnaryOperator<Integer> add = t -> {
    System.out.println("executing add");
    return t + t;
  };
  UnaryOperator<Integer> multiply = t -> {
    System.out.println("executing multiply");
    return t * t;
  };
  System.out.println(addOrMultiply(true, add, multiply, 4));
  System.out.println(addOrMultiply(false, add, multiply, 4));
}


public static <T, R> R addOrMultiply(
    boolean add, Function<T, R> onAdd, 
    Function<T, R> onMultiply, T t) {
  return (add ? onAdd.apply(t) : onMultiply.apply(t));
}

執(zhí)行結(jié)果

executing add
8
executing multiply
16

我們可以看到只執(zhí)行了所需的功能。

2.4 引用透明性(Referential transparency)

表示在程序中,一個(gè)函數(shù)調(diào)用可以用它的返回值來(lái)替換,而不改變程序的行為。換句話說(shuō),對(duì)于相同的輸入,函數(shù)總是產(chǎn)生相同的結(jié)果,沒(méi)有副作用。

遺憾的是,在Java中限制數(shù)據(jù)變異的方法并不多。然而,通過(guò)使用純函數(shù),并明確避免數(shù)據(jù)變異和重新賦值(使用我們之前討論過(guò)的其他概念),可以實(shí)現(xiàn)這一目標(biāo)。對(duì)于變量,我們可以使用final關(guān)鍵字,它是一個(gè)非訪問(wèn)修飾符,用于防止通過(guò)重新賦值來(lái)改變變量的值。

例如,下面的代碼將在編譯時(shí)產(chǎn)生錯(cuò)誤:

final var list = Arrays.asList("Apple", "Orange") ;
// 你不能重新賦值
list = Arrays.asList("Pack", "XXXOOO") ;

但是,當(dāng)變量持有對(duì)其他對(duì)象的引用時(shí),這并不會(huì)起到作用。例如,即使使用了final關(guān)鍵字,下面的對(duì)象變異仍然會(huì)發(fā)生:

final var list = new ArrayList<>() ;
// 我們還是可以添加數(shù)據(jù)
list.add("XXX") ;
list.add("OOO") ;

final 關(guān)鍵字允許引用變量的內(nèi)部狀態(tài)被修改,因此從函數(shù)式編程的角度來(lái)看,final 關(guān)鍵字僅對(duì)常量和捕獲重新賦值有用。

責(zé)任編輯:武曉燕 來(lái)源: Spring全家桶實(shí)戰(zhàn)案例源碼
相關(guān)推薦

2011-02-22 16:09:53

Eclipse調(diào)試

2021-10-19 14:51:33

說(shuō)服力IT主管CIO

2022-07-01 08:00:44

異步編程FutureTask

2015-09-02 12:12:13

2019-11-11 16:44:20

機(jī)器學(xué)習(xí)Python算法

2025-05-13 08:20:58

2013-01-07 10:14:06

JavaJava枚舉

2025-01-21 08:00:00

限流微服務(wù)算法

2025-02-10 08:43:31

Java異步編程

2022-07-25 10:15:29

垃圾收集器Java虛擬機(jī)

2025-05-12 10:00:00

JavaScript代碼編碼

2022-05-10 08:08:01

find命令Linux

2020-01-14 08:00:00

.NET緩存編程語(yǔ)言

2023-02-14 08:32:41

Ribbon負(fù)載均衡

2022-03-14 07:40:14

RibbonSpringNacos

2017-06-14 16:44:15

JavaScript原型模式對(duì)象

2017-06-02 09:52:50

2010-08-31 10:57:36

2021-07-16 09:55:46

數(shù)據(jù)工具軟件

2017-08-31 14:57:53

數(shù)據(jù)庫(kù)MySQLJOIN
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)