java lambda表達式學習筆記


lambda是函數式編程(FP,functional program),在java8中引入,而C#很早之前就有了。在java中lambda表達式是'->',在C#中是‘=>’。

杜甫說:射人先射馬,擒賊先擒王。學習一個庫要學習它的入口類。lambda的入口類是Stream,一看Stream中的函數就會發現Function,Predicate等lambda元素。

一.幾個概念

    函數式接口 Functional Interface,除了static和default類型的方法外,只有一個函數的接口。以前,接口中的一切方法都是public的,現在接口中可以包含default類型的實現方法了。java中沒有函數指針的概念,C#中有delegate委托相當於函數指針,但java也是有辦法的,用一個類,類里面有一個函數,這個類就相當於函數指針。這么整實現簡單,理解簡單,但是代碼比較冗長。

  謂詞 Predicate, 簡單來說,謂詞就是條件。正規來說,謂詞就是一個函數boolean f(x1,x2...),表示變量x1,x2...是否滿足條件f。在java中謂詞的定義就是一個函數式接口。

    函數(映射) Function,將一種類型的對象映射為另一種或同種類型的對象,它就是一個函數ObjectA f(ObjectB)。在java中映射的定義也是一個函數式接口。

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    } 
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    } 
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

可見,除了apply()函數以外,其余default方法在外部都是不可見的。所以定義函數式接口的時候需要把其他函數聲明稱static或者default類型的。

Optional這個值表示一個對象,這個對象可能為空也可能不為空,對於它可以產生許多行為:

如果它是null,該怎么做orElse()和orElseGet()

如果不為null,該怎么做ifPresent()

判斷是否為null,isPresent()

這個類看上去十分雞肋,但用處十分廣泛

package aaa;

import java.util.NoSuchElementException;
import java.util.Optional;

public class OptionalDemo {
     
      public static void main(String[] args) {
        //創建Optional實例,也可以通過方法返回值得到。
        Optional<String> name = Optional.of("Sanaulla");
     
        //創建沒有值的Optional實例,例如值為'null'
        Optional<Object> empty = Optional.ofNullable(null);
     
        //isPresent方法用來檢查Optional實例是否有值。
        if (name.isPresent()) {
          //調用get()返回Optional值。
          System.out.println(name.get());
        }
     
        try {
          //在Optional實例上調用get()拋出NoSuchElementException。
          System.out.println(empty.get());
        } catch (NoSuchElementException ex) {
          System.out.println(ex.getMessage());
        }
     
        //ifPresent方法接受lambda表達式參數。
        //如果Optional值不為空,lambda表達式會處理並在其上執行操作。
        name.ifPresent((value) -> {
          System.out.println("The length of the value is: " + value.length());
        });
     
        //如果有值orElse方法會返回Optional實例,否則返回傳入的錯誤信息。
        System.out.println(empty.orElse("There is no value present!"));
        System.out.println(name.orElse("There is some value!"));
     
        //orElseGet與orElse類似,區別在於傳入的默認值。
        //orElseGet接受lambda表達式生成默認值。
        System.out.println(empty.orElseGet(() -> "Default Value"));
        System.out.println(name.orElseGet(() -> "Default Value"));
     
        try {
          //orElseThrow與orElse方法類似,區別在於返回值。
          //orElseThrow拋出由傳入的lambda表達式/方法生成異常。
          empty.orElseThrow(Exception::new);
        } catch (Throwable ex) {
          System.out.println(ex.getMessage());
        }
     
        //map方法通過傳入的lambda表達式修改Optonal實例默認值。 
        //lambda表達式返回值會包裝為Optional實例。
        Optional<String> upperName = name.map((value) -> value.toUpperCase());
        System.out.println(upperName.orElse("No value found"));
     
        //flatMap與map(Funtion)非常相似,區別在於lambda表達式的返回值。
        //map方法的lambda表達式返回值可以是任何類型,但是返回值會包裝成Optional實例。
        //但是flatMap方法的lambda返回值總是Optional類型。
        upperName = name.flatMap((value) -> Optional.of(value.toUpperCase()));
        System.out.println(upperName.orElse("No value found"));
     
        //filter方法檢查Optiona值是否滿足給定條件。
        //如果滿足返回Optional實例值,否則返回空Optional。
        Optional<String> longName = name.filter((value) -> value.length() > 6);
        System.out.println(longName.orElse("The name is less than 6 characters"));
     
        //另一個示例,Optional值不滿足給定條件。
        Optional<String> anotherName = Optional.of("Sana");
        Optional<String> shortName = anotherName.filter((value) -> value.length() > 6);
        System.out.println(shortName.orElse("The name is less than 6 characters"));
     
      }
     
    }
View Code

 

二.lambda表達式

有兩個作用

* 作為函數指針

* 替代匿名內部類,替代函數式接口(FunctionalInterface)

lambda不是語法糖,它在內部實現上也跟匿名內部類不同,匿名內部類需要進行類文件加載,而lambda表達式不用,所以lambda表達式效率比匿名內部類高。

三種用法

用'(x1,x2,x3)'表示傳入參數

如果無參寫作’()‘

參數類型可以指明,也可以不指明,java會根據后半部分函數形參自動推斷出來。

        List<String> a = Arrays.asList("we i di ao is great".split(" "));
        a.forEach((s) -> System.out.println(s));// 表達式
        a.forEach((String s) -> {
            System.out.println(s);
        });// 語句塊
        a.forEach(System.out::println);// 函數

方法引用

object::fun()

className::fun()靜態方法引用

className::new  構造函數引用

 

public class LambdaIntro {
    // functional interface 函數式接口
    public static interface ItemWithIndexVisitor<E> {
        public void visit(E item, int index);
    }

    public static <E> void eachWithIndex(List<E> list,
            ItemWithIndexVisitor<E> visitor) {
        for (int i = 0; i < list.size(); i++) {
            visitor.visit(list.get(i), i);
        }
    }
    // 一個普通函數,用作函數指針
    public static <E> void printItem(E value, int index) {
        String output = String.format("%d -> %s", index, value.toString());
        System.out.println(output);
    }
    public static void main(String[] args) {
        List<String> list = Arrays.asList("A", "B", "C");
        // 第一種方式
        eachWithIndex(list, (value, index) -> {
            String output = String.format("%d -> %s", index, value);
            System.out.println(output);
        });
        // 第二種方式
        eachWithIndex(list, LambdaIntro::printItem);
    }
}

 

三.使用Stream

創建Stream的兩種方式

* Stream接口的工廠方法

* 集合框架的stream()函數

首先來了解使用Stream接口來創建Stream,可以創建三種流:普通枚舉流,產生器,迭代器。

        //of:通過枚舉方式創建流
        Stream<Integer> one = Stream.of(1, 2, 3);
        //流是可以拼接的,從而產生新流
        Stream<Integer> two = Stream.concat(one, Stream.of(4, 5, 6));
        two.forEach(System.out::println);
        //逐個加入,那就用Builder構建器來實現
        Builder<Integer> builder = Stream.builder();
        Stream<Integer> three = builder.add(3).add(4).build();
        three.forEach((s) -> System.out.println(s));
        //產生器流generator
        Random random = new Random();
        Stream<Integer> four = Stream.generate(() -> random.nextInt());
        four.limit(10).forEach(System.out::println);
        //迭代器iterator,UnaryOperator一元運算符可以通過lambda表達式來創建
        Stream<Integer>five=Stream.iterate(2, new UnaryOperator<Integer>() {
            @Override
            public Integer apply(Integer t) {
                return t = (t * 5 + 7) % 13;
            }
        });
        five.limit(10).forEach(System.out::println);

注意產生器generator和迭代器iterator是無限輸出的,可以用limit來約束之。

集合框架都繼承了Collection接口,而Collection接口就有一個stream()函數。所以剩下的任務就是如何利用流的強大特性來寫出優雅的代碼來。

要想深刻的了解Stream的一些函數,那就先不要使用lambda表達式,一旦了解它的普通實現,很容易改寫成lambda的形式。

流Stream中的函數明顯分為兩類,一類返回值還是Stream,可以繼續用流來處理,另一類返回值不是Stream,不能再用流中函數處理了。

Stream filter(Predicate)刪除掉流中不滿足條件的元素並返回新的Stream

Stream map(Function)映射,把流中的元素映射一下變成一個新流,還有mapToInt(),mapToLong(),mapToDouble()等函數,它們終究還是映射,只是映射結果更單一。map是一對一映射,flatMap是一對多映射。把一個元素映射成多個元素

        Arrays.asList(1, 2, 3).stream()
                .flatMap(new Function<Integer, Stream<Integer>>() {
                    @Override
                    public Stream<Integer> apply(Integer t) {
                        return Arrays.asList(t, t + 10, t + 100).stream();
                    }
                }).forEach(System.out::println);

輸出為1 11 101 2 12 102 3 13 103   中間我省略了換行符 

distinct()去除流中重復元素

sorted()和sorted(Comparator cmp)對流中元素排序

peek()彈出一個元素

        Stream.of("one", "two", "three", "four").filter(e -> e.length() > 3)
                .peek(e -> System.out.println("Filtered value: " + e))
                .map(String::toUpperCase)
                .peek(e -> System.out.println("Mapped value: " + e))
                .collect(Collectors.toList());

執行結果

Filtered value: three
Mapped value: THREE
Filtered value: four
Mapped value: FOUR

limit(int cnt)只返回cnt個元素,skip(int cnt)跳過cnt個元素。

forEach(Consumer consumer)對於每一個元素都執行某種操作

reduce()將多個值映射為一個值,實現多對一映射

         Stream.of("one", "two", "three", "four")
                .reduce(new BinaryOperator<String>() {
                    @Override
                    public String apply(String t, String u) {
                        System.out.println(t+":"+u);
                        return t + "," + u;
                    }
                }).ifPresent(System.out::println);

輸出為

one:two
one,two:three
one,two,three:four
one,two,three,four

可見,apply(t,u)函數中的t表示當前總量,u表示當前元素。

reduce(T identity,BinaryOperator<T>f)表示帶初始值的reduce,比如求和函數,如果identity=9,表示一開始sum=9,此函數返回具體的對象。

        String s = Stream.of("one", "two", "three", "four").reduce("baga",
                new BinaryOperator<String>() {

                    @Override
                    public String apply(String t, String u) {
                        return t +","+ u;
                    }
                });
        System.out.println(s);

輸出:

baga,one,two,three,four

collect()

Map<String, Map<String, List<Person>>> peopleByStateAndCity
         = personStream.collect(Collectors.groupingBy(Person::getState,
                                                      Collectors.groupingBy(Person::getCity)));
        String s=Stream.of("one", "two", "three", "four")
                .collect(Collectors.joining(","));
        System.out.println(s);

輸出one,two,three,four

Collectors包含許多有用的靜態方法

聚集函數min(),max(),count()很像sql中的聚集函數

匹配函數allMatch(Predicate p),anyMatch(Predicate p),noneMath(Predicate p)流中全部匹配,部分匹配,完全不匹配,返回布爾值 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM