日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

最简单的Lambda入门教程

發布時間:2024/3/12 编程问答 58 豆豆
生活随笔 收集整理的這篇文章主要介紹了 最简单的Lambda入门教程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Lambda簡介


Lambda作為函數式編程中的基礎部分,在其他編程語言(例如:Scala)中早就廣為使用,但在JAVA領域中發展較慢,直到java8,才開始支持Lambda。

拋開數學定義不看,直接來認識Lambda。Lambda表達式本質上是匿名方法,其底層還是通過invokedynamic指令來生成匿名類來實現。它提供了更為簡單的語法和寫作方式,允許你通過表達式來代替函數式接口。在一些人看來,Lambda就是可以讓你的代碼變得更簡潔,完全可以不使用——這種看法當然沒問題,但重要的是lambda為Java帶來了閉包。得益于Lamdba對集合的支持,通過Lambda在多核處理器條件下對集合遍歷時的性能提高極大,另外我們可以以數據流的方式處理集合——這是非常有吸引力的。

Lambda語法


Lambda的語法極為簡單,類似如下結構:

(parameters) -> expression

或者

(parameters) -> { statements; }

Lambda表達式由三部分組成:

  • paramaters:類似方法中的形參列表,這里的參數是函數式接口里的參數。這里的參數類型可以明確的聲明也可不聲明而由JVM隱含的推斷1。另外當只有一個推斷類型時可以省略掉圓括號。
  • ->:可理解為“被用于”的意思
  • 方法體:可以是表達式也可以代碼塊,是函數式接口里方法的實現。代碼塊可返回一個值或者什么都不反回,這里的代碼塊塊等同于方法的方法體。如果是表達式,也可以返回一個值或者什么都不反回。
  • 我們通過以下幾個示例來做說明:

    //示例1:不需要接受參數,直接返回10 ()->10//示例2:接受兩個int類型的參數,并返回這兩個參數相加的和 (int x,int y)->x+y;//示例2:接受x,y兩個參數,該參數的類型由JVM根據上下文推斷出來,并返回兩個參數的和 (x,y)->x+y;//示例3:接受一個字符串,并將該字符串打印到控制到,不反回結果 (String name)->System.out.println(name);//示例4:接受一個推斷類型的參數name,并將該字符串打印到控制臺 name->System.out.println(name);//示例5:接受兩個String類型參數,并分別輸出,不反回 (String name,String sex)->{System.out.println(name);System.out.println(sex)}//示例6:接受一個參數x,并返回該該參數的兩倍 x->2*x

    Lambda用在哪里


    在[函數式接口][1]中我們知道Lambda表達式的目標類型是函數性接口——每一個Lambda都能通過一個特定的函數式接口與一個給定的類型進行匹配。因此一個Lambda表達式能被應用在與其目標類型匹配的任何地方,lambda表達式必須和函數式接口的抽象函數描述一樣的參數類型,它的返回類型也必須和抽象函數的返回類型兼容,并且他能拋出的異常也僅限于在函數的描述范圍中。
    接下來,我們看一個自定義的函數式接口示例:

    @FunctionalInterfaceinterface Converter<F, T>{T convert(F from);}

    首先用傳統的方式來使用該接口:

    Converter<String ,Integer> converter=new Converter<String, Integer>() {@Overridepublic Integer convert(String from) {return Integer.valueOf(from);}};Integer result = converter.convert("200");System.out.println(result);

    很顯然這沒任何問題,那么接下里就是Lambda上場的時刻,用Lambda實現Converter接口:

    Converter<String ,Integer> converter=(param) -> Integer.valueOf(param);Integer result = converter.convert("101");System.out.println(result);

    通過上例,我想你已經對Lambda的使用有了個簡單的認識,下面,我們在用一個常用的Runnable做演示:
    在以前我們可能會寫下這種代碼:

    new Thread(new Runnable() {@Overridepublic void run() {System.out.println("hello lambda");}}).start();

    在某些情況下,大量的匿名類會讓代碼顯得雜亂無章。現在可以用Lambda來使它變得簡潔:

    new Thread(() -> System.out.println("hello lambda")).start();

    方法引用


    方法引用是Lambda表達式的一個簡化寫法。所引用的方法其實是Lambda表達式的方法體的實現,其語法結構為:

    ObjectRef::methodName

    左邊可以是類名或者實例名,中間是方法引用符號”::”,右邊是相應的方法名。方法引用被分為三類:

    1. 靜態方法引用

    在某些情況下,我們可能寫出這樣的代碼:

    public class ReferenceTest {public static void main(String[] args) {Converter<String ,Integer> converter=new Converter<String, Integer>() {@Overridepublic Integer convert(String from) {return ReferenceTest.String2Int(from);}};converter.convert("120");}@FunctionalInterfaceinterface Converter<F,T>{T convert(F from);}static int String2Int(String from) {return Integer.valueOf(from);} }

    這時候如果用靜態引用會使的代碼更加簡潔:

    Converter<String, Integer> converter = ReferenceTest::String2Int;converter.convert("120");

    2. 實例方法引用

    我們也可能會寫下這樣的代碼:

    public class ReferenceTest {public static void main(String[] args) {Converter<String, Integer> converter = new Converter<String, Integer>() {@Overridepublic Integer convert(String from) {return new Helper().String2Int(from);}};converter.convert("120");}@FunctionalInterfaceinterface Converter<F, T> {T convert(F from);}static class Helper {public int String2Int(String from) {return Integer.valueOf(from);}} }

    同樣用實例方法引用會顯得更加簡潔:

    Helper helper = new Helper();Converter<String, Integer> converter = helper::String2Int;converter.convert("120");

    3. 構造方法引用

    現在我們來演示構造方法的引用。首先我們定義一個父類Animal:

    class Animal{private String name;private int age;public Animal(String name, int age) {this.name = name;this.age = age;}public void behavior(){}}

    接下來,我們在定義兩個Animal的子類:Dog、Bird

    public class Bird extends Animal {public Bird(String name, int age) {super(name, age);}@Overridepublic void behavior() {System.out.println("fly");} }class Dog extends Animal {public Dog(String name, int age) {super(name, age);}@Overridepublic void behavior() {System.out.println("run");} }

    隨后我們定義工廠接口:

    interface Factory<T extends Animal> {T create(String name, int age);}

    接下來我們還是用傳統的方法來創建Dog類和Bird類的對象:

    Factory factory=new Factory() {@Overridepublic Animal create(String name, int age) {return new Dog(name,age);}};factory.create("alias", 3);factory=new Factory() {@Overridepublic Animal create(String name, int age) {return new Bird(name,age);}};factory.create("smook", 2);

    僅僅為了創建兩個對象就寫了十多號代碼,現在我們用構造函數引用試試:

    Factory<Animal> dogFactory =Dog::new;Animal dog = dogFactory.create("alias", 4);Factory<Bird> birdFactory = Bird::new;Bird bird = birdFactory.create("smook", 3);

    這樣代碼就顯得干凈利落了。通過Dog::new這種方式來穿件對象時,Factory.create函數的簽名選擇相應的造函數。

    Lambda的域以及訪問限制


    域即作用域,Lambda表達式中的參數列表中的參數在該Lambda表達式范圍內(域)有效。在作用Lambda表達式內,可以訪問外部的變量:局部變量、類變量和靜態變量,但操作受限程度不一。

    訪問局部變量

    在Lambda表達式外部的局部變量會被JVM隱式的編譯成final類型,因此只能訪問外而不能修改。

    public class ReferenceTest {public static void main(String[] args) {int n = 3;Calculate calculate = param -> {//n=10; 編譯錯誤return n + param;};calculate.calculate(10);}@FunctionalInterfaceinterface Calculate {int calculate(int value);}}

    訪問靜態變量和成員變量

    在Lambda表達式內部,對靜態變量和成員變量可讀可寫。

    public class ReferenceTest {public int count = 1;public static int num = 2;public void test() {Calculate calculate = param -> {num = 10;//修改靜態變量count = 3;//修改成員變量return n + param;};calculate.calculate(10);}public static void main(String[] args) {}@FunctionalInterfaceinterface Calculate {int calculate(int value);}}

    Lambda不能訪問函數接口的默認方法

    java8增強了接口,其中包括接口可添加default關鍵詞定義的默認方法,這里我們需要注意,Lambda表達式內部不支持訪問默認方法。

    Lambda實踐


    在[函數式接口][2]一節中,我們提到java.util.function包中內置許多函數式接口,現在將對常用的函數式接口做說明。

    Predicate接口

    輸入一個參數,并返回一個
    Boolean值,其中內置許多用于邏輯判斷的默認方法:

    @Testpublic void predicateTest() {Predicate<String> predicate = (s) -> s.length() > 0;boolean test = predicate.test("test");System.out.println("字符串長度大于0:" + test);test = predicate.test("");System.out.println("字符串長度大于0:" + test);test = predicate.negate().test("");System.out.println("字符串長度小于0:" + test);Predicate<Object> pre = Objects::nonNull;Object ob = null;test = pre.test(ob);System.out.println("對象不為空:" + test);ob = new Object();test = pre.test(ob);System.out.println("對象不為空:" + test);}

    Function接口

    接收一個參數,返回單一的結果,默認的方法(andThen)可將多個函數串在一起,形成復合Funtion(有輸入,有輸出)
    結果,

    @Testpublic void functionTest() {Function<String, Integer> toInteger = Integer::valueOf;//toInteger的執行結果作為第二個backToString的輸入Function<String, String> backToString = toInteger.andThen(String::valueOf);String result = backToString.apply("1234");System.out.println(result);Function<Integer, Integer> add = (i) -> {System.out.println("frist input:" + i);return i * 2;};Function<Integer, Integer> zero = add.andThen((i) -> {System.out.println("second input:" + i);return i * 0;});Integer res = zero.apply(8);System.out.println(res);}

    Supplier接口

    返回一個給定類型的結果,與Function不同的是,Supplier不需要接受參數(供應者,有輸出無輸入)

    @Testpublic void supplierTest() {Supplier<String> supplier = () -> "special type value";String s = supplier.get();System.out.println(s);}

    Consumer接口

    代表了在單一的輸入參數上需要進行的操作。和Function不同的是,Consumer沒有返回值(消費者,有輸入,無輸出)

    @Testpublic void consumerTest() {Consumer<Integer> add5 = (p) -> {System.out.println("old value:" + p);p = p + 5;System.out.println("new value:" + p);};add5.accept(10);}

    以上四個接口的用法代表了java.util.function包中四種類型,理解這四個函數式接口之后,其他的接口也就容易理解了,現在我們來做一下簡單的總結:
    Predicate用來邏輯判斷,Function用在有輸入有輸出的地方,Supplier用在無輸入,有輸出的地方,而Consumer用在有輸入,無輸出的地方。你大可通過其名稱的含義來獲知其使用場景。

    Stream


    Lambda為java8帶了閉包,這一特性在集合操作中尤為重要:java8中支持對集合對象的stream進行函數式操作,此外,stream api也被集成進了collection api,允許對集合對象進行批量操作。
    下面我們來認識Stream。
    Stream表示數據流,它沒有數據結構,本身也不存儲元素,其操作也不會改變源Stream,而是生成新Stream.作為一種操作數據的接口,它提供了過濾、排序、映射、規約等多種操作方法,這些方法按照返回類型被分為兩類:凡是返回Stream類型的方法,稱之為中間方法(中間操作),其余的都是完結方法(完結操作)。完結方法返回一個某種類型的值,而中間方法則返回新的Stream。中間方法的調用通常是鏈式的,該過程會形成一個管道,當完結方法被調用時會導致立即從管道中消費值,這里我們要記住:Stream的操作盡可能以“延遲”的方式運行,也就是我們常說的“懶操作”,這樣有助于減少資源占用,提高性能。對于所有的中間操作(除sorted外)都是運行在延遲模式下。

    Stream不但提供了強大的數據操作能力,更重要的是Stream既支持串行也支持并行,并行使得Stream在多核處理器上有著更好的性能。

    Stream的使用過程有著固定的模式:

  • 創建Stream
  • 通過中間操作,對原始Stream進行“變化”并生成新的Stream
  • 使用完結操作,生成最終結果
    也就是
  • 創建——>變化——>完結

    Stream的創建


    對于集合來說,可以通過調用集合的stream()或者parallelStream()來創建,另外這兩個方法也在Collection接口中實現了。對于數組來說,可以通過Stream的靜態方法of(T … values)來創建,另外,Arrays也提供了有關stream的支持。
    除了以上基于集合或者數組來創建Stream,也可以通過Steam.empty()創建空的Stream,或者利用Stream的generate()來創建無窮的Stream。
    下面我們以串行Stream為例,分別說明Stream幾種常用的中間方法和完結方法。首先創建一個List集合:

    List<String> lists=new ArrayList<String >();lists.add("a1");lists.add("a2");lists.add("b1");lists.add("b2");lists.add("b3");lists.add("o1");

    中間方法


    過濾器(Filter)

    結合Predicate接口,Filter對流對象中的所有元素進行過濾,該操作是一個中間操作,這意味著你可以在操作返回結果的基礎上進行其他操作。

    public static void streamFilterTest() {lists.stream().filter((s -> s.startsWith("a"))).forEach(System.out::println);//等價于以上操作Predicate<String> predicate = (s) -> s.startsWith("a");lists.stream().filter(predicate).forEach(System.out::println);//連續過濾Predicate<String> predicate1 = (s -> s.endsWith("1"));lists.stream().filter(predicate).filter(predicate1).forEach(System.out::println);}

    排序(Sorted)

    結合Comparator接口,該操作返回一個排序過后的流的視圖,原始流的順序不會改變。通過Comparator來指定排序規則,默認是按照自然順序排序。

    public static void streamSortedTest() {System.out.println("默認Comparator");lists.stream().sorted().filter((s -> s.startsWith("a"))).forEach(System.out::println);System.out.println("自定義Comparator");lists.stream().sorted((p1, p2) -> p2.compareTo(p1)).filter((s -> s.startsWith("a"))).forEach(System.out::println);}

    映射(Map)

    結合Function接口,該操作能將流對象中的每個元素映射為另一種元素,實現元素類型的轉換。

    public static void streamMapTest() {lists.stream().map(String::toUpperCase).sorted((a, b) -> b.compareTo(a)).forEach(System.out::println);System.out.println("自定義映射規則");Function<String, String> function = (p) -> {return p + ".txt";};lists.stream().map(String::toUpperCase).map(function).sorted((a, b) -> b.compareTo(a)).forEach(System.out::println);}

    在上面簡單介紹了三種常用的操作,這三種操作極大簡化了集合的處理。接下來,介紹幾種完結方法:

    完結方法


    “變換”過程之后,需要獲取結果,即完成操作。下面我們來看相關的操作:

    匹配(Match)

    用來判斷某個predicate是否和流對象相匹配,最終返回Boolean類型結果,例如:

    public static void streamMatchTest() {//流對象中只要有一個元素匹配就返回trueboolean anyStartWithA = lists.stream().anyMatch((s -> s.startsWith("a")));System.out.println(anyStartWithA);//流對象中每個元素都匹配就返回trueboolean allStartWithA= lists.stream().allMatch((s -> s.startsWith("a")));System.out.println(allStartWithA);}

    收集(Collect)

    在對經過變換之后,我們將變換的Stream的元素收集,比如將這些元素存至集合中,此時便可以使用Stream提供的collect方法,例如:

    public static void streamCollectTest() {List<String> list = lists.stream().filter((p) -> p.startsWith("a")).sorted().collect(Collectors.toList());System.out.println(list);}

    計數(Count)

    類似sql的count,用來統計流中元素的總數,例如:

    public static void streamCountTest() {long count = lists.stream().filter((s -> s.startsWith("a"))).count();System.out.println(count);}

    規約(Reduce)

    reduce方法允許我們用自己的方式去計算元素或者將一個Stream中的元素以某種規律關聯,例如:

    public static void streamReduceTest() {Optional<String> optional = lists.stream().sorted().reduce((s1, s2) -> {System.out.println(s1 + "|" + s2);return s1 + "|" + s2;});}

    執行結果如下:

    a1|a2 a1|a2|b1 a1|a2|b1|b2 a1|a2|b1|b2|b3 a1|a2|b1|b2|b3|o1

    并行Stream VS 串行Stream


    到目前我們已經將常用的中間操作和完結操作介紹完了。當然所有的的示例都是基于串行Stream。接下來介紹重點戲——并行Stream(parallel Stream)。并行Stream基于Fork-join并行分解框架實現,將大數據集合切分為多個小數據結合交給不同的線程去處理,這樣在多核處理情況下,性能會得到很大的提高。這和MapReduce的設計理念一致:大任務化小,小任務再分配到不同的機器執行。只不過這里的小任務是交給不同的處理器。
    通過parallelStream()創建并行Stream。為了驗證并行Stream是否真的能提高性能,我們執行以下測試代碼:
    首先創建一個較大的集合:

    List<String> bigLists = new ArrayList<>();for (int i = 0; i < 10000000; i++) {UUID uuid = UUID.randomUUID();bigLists.add(uuid.toString());}

    測試串行流下排序所用的時間:

    private static void notParallelStreamSortedTest(List<String> bigLists) {long startTime = System.nanoTime();long count = bigLists.stream().sorted().count();long endTime = System.nanoTime();long millis = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);System.out.println(System.out.printf("串行排序: %d ms", millis));}

    測試并行流下排序所用的時間:

    private static void parallelStreamSortedTest(List<String> bigLists) {long startTime = System.nanoTime();long count = bigLists.parallelStream().sorted().count();long endTime = System.nanoTime();long millis = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);System.out.println(System.out.printf("并行排序: %d ms", millis));}

    結果如下:

    串行排序: 13336 ms 并行排序: 6755 ms

    看到這里,我們確實發現性能提高了約么50%,你也可能會想以后都用parallel Stream不久行了么?實則不然,如果你現在還是單核處理器,而數據量又不算很大的情況下,串行流仍然是這種不錯的選擇。你也會發現在某些情況,串行流的性能反而更好,至于具體的使用,需要你根據實際場景先測試后再決定。

    懶操作


    上面我們談到Stream盡可能以延遲的方式運行,這里通過創建一個無窮大的Stream來說明:
    首先通過Stream的generate方法來一個自然數序列,然后通過map變換Stream:

    //遞增序列class NatureSeq implements Supplier<Long> {long value = 0;@Overridepublic Long get() {value++;return value;}}public void streamCreateTest() {Stream<Long> stream = Stream.generate(new NatureSeq());System.out.println("元素個數:"+stream.map((param) -> {return param;}).limit(1000).count());}

    執行結果為:

    元素個數:1000

    我們發現開始時對這個無窮大的Stream做任何中間操作(如:filter,map等,但sorted不行)都是可以的,也就是對Stream進行中間操作并生存一個新的Stream的過程并非立刻生效的(不然此例中的map操作會永遠的運行下去,被阻塞住),當遇到完結方法時stream才開始計算。通過limit()方法,把這個無窮的Stream轉為有窮的Stream。


  • java8中對類型推斷進行了增強
    [1]: http://blog.csdn.net/dd864140130/article/details/50521125
    [2]: http://blog.csdn.net/dd864140130/article/details/50521125 ?
  • 總結

    以上是生活随笔為你收集整理的最简单的Lambda入门教程的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。