Lambda表達(dá)式的使用

雖然從java8就開始支持lambda特性,如今java更是已經(jīng)升級(jí)了11等更高級(jí)的版本,但是本人一直使用的還是java8以下的語法,對(duì)于lambda語法更是甚少了解,剛開始接觸這個(gè)lambda還是十分不習(xí)慣的,感覺有點(diǎn)繞;怎么看怎么不順眼,在我看來,lambada特性單純只是為了簡(jiǎn)化java代碼的書寫的,所以實(shí)質(zhì)上并沒有什么用,甚至自己一度覺得很討厭,但作為Android開發(fā)者,為了更好的學(xué)習(xí)kotlin,也只能迎著頭皮接受這個(gè)自己討厭的東西;(獲取將來某一天會(huì)覺得他很香吧)開始學(xué)習(xí)java 8的lambda表達(dá)式。以下是針對(duì)一些教程以及本身對(duì)lambda的一些理解所作的作結(jié),寫的不好,還請(qǐng)大家指正;
學(xué)習(xí)的時(shí)候參考了很多資料,學(xué)著學(xué)著就越來越懵逼了,可能是看的資料太多反倒產(chǎn)生了影響。學(xué)完又忘記了。額,感覺自己在寫日記了。丟~~~

lambda表達(dá)式是什么?有什么作用?什么時(shí)候可以使用lambda表達(dá)式?

按照官方說法:lambda表達(dá)式就是一個(gè)匿名函數(shù),也就是沒有函數(shù)名的函數(shù)。什么叫沒有函數(shù)名?

//定義接口
public interface IUser {
    String getUserName(String name);
}
    public void test() {
        //正常情況下的接口實(shí)現(xiàn)方法
        IUser user = new IUser() {
            @Override
            public String getUserName(String name) {
                return name;
            }
        };
        system.out.print(user.getUserName("達(dá)文西"))
    }

上面的new IUser{...}就是一個(gè)匿名函數(shù),所謂匿名,就是我完全不需要知道你的名字,我也不關(guān)心,我真正關(guān)心的只是你的內(nèi)容,你的實(shí)現(xiàn)方法;故此,如果按照以往的寫法,如果每一個(gè)匿名函數(shù)我都需要new xxx那這些沒用的代碼就現(xiàn)的很多余了,而用lambda表達(dá)式我便可以省略這些沒有用的內(nèi)容,就一句IUser user=name->name,簡(jiǎn)單?。?!。所以簡(jiǎn)單來說lambda表示是為了簡(jiǎn)化java書寫冗余的。如果你覺得沒有必要,那是真的就沒有必要-_-!!。
那什么情況下才能使用lambda表達(dá)式呢?那就是只有當(dāng)你的接口符合函數(shù)式接口的時(shí)候?什么是函數(shù)式接口?只有一個(gè)抽象方法的接口就是函數(shù)式接口。
上面的 IUser接口便是函數(shù)式接口;
再舉個(gè)例子

@FunctionalInterface
public interface Comparator<T>{
  ...
  int compare(T o1,T o2);
}

其中的@FunctionalInterface是用來修飾函數(shù)式接口的,也就是說只要有這個(gè)修飾符,你的接口就必須有且僅有一個(gè)接口。如果你用了這個(gè)修飾符,還沒有來得及寫方法,那他會(huì)報(bào)No target method found的錯(cuò)誤,如果你的抽象方法不止一個(gè),那就會(huì)報(bào)Multiple non-overriding abstract methods found in interface xxx

lambda表達(dá)式語法

(參數(shù))->{實(shí)現(xiàn)方法}

小括號(hào)內(nèi)的語法與傳統(tǒng)方法參數(shù)列表一致:無參數(shù)則留空;多個(gè)參數(shù)則用逗號(hào)分隔。
-> 是新引入的語法格式,是運(yùn)算符,代表指向動(dòng)作。
大括號(hào)內(nèi)的語法與傳統(tǒng)方法體要求基本一致。

有時(shí)候()和{}甚至可以省略,比如上面提到的IUser user=name->name接口的實(shí)現(xiàn);

        //完整的lambda的語法
        IUser user0 = (String name) -> {
            return name;
        };
        //省略{}
        IUser user2 = (String uname) -> uname;
        //省略()和{}
        IUser user = name -> null;
        

總結(jié)表達(dá)式一些總結(jié):

  1. 當(dāng)方法體只有一行的時(shí)候,花括號(hào){}可以省略;
  2. 當(dāng)接口方法有返回值,且方法體只有一行的時(shí)候,省略花括號(hào){}的同時(shí)可省略return,直接寫返回的表達(dá)式/值即可,單行代碼的執(zhí)行結(jié)果會(huì)自動(dòng)返回;
  3. 當(dāng)無參數(shù)的時(shí)候,()不可以省略;
  4. 當(dāng)參數(shù)只有一個(gè)的時(shí)候,()可以省略;
  5. 當(dāng)參數(shù)的類型確定的時(shí)候,可以省略參數(shù)的類型;jvm在運(yùn)行時(shí),會(huì)自動(dòng)根據(jù)綁定的抽象方法中的參數(shù)進(jìn)行推導(dǎo);
  //通常使用
   Supplier<String> sup1 = new Supplier<String>() {
          @Override
          public String get() {
              return "達(dá)文西";
          }
      };
  //lambda表達(dá)式
  Supplier<String> sup2 = ()-> "達(dá)文西";

JAVA內(nèi)置的4大核心函數(shù)式接口

  • 消費(fèi)型接口 ConSumer void accept(T t)
    典型的代表就是list集合的使用
private static void test4() {
        String[] strs = new String[]{"1", "2", "3", "4", "5"};
        List<String> list = Arrays.asList(strs);
        //正常情況遍歷
        for (String str : list) {
            System.out.print(str + ";");
        }
        //使用lambda表達(dá)式進(jìn)行遍歷,其中forEach方法接收的就是一個(gè)消費(fèi)型接口;
        list.forEach(str -> System.out.println(str + ";"));
    }
  • 供給型接口 Supplier T get()
  • 函數(shù)型接口 Function<T,R> R apply(T t)
    public static void test5() {
        //傳統(tǒng)方式
        Runnable run1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("傳統(tǒng)方式的Runnable");
            }
        };
        Thread t1 = new Thread(run1);
        t1.start();

        //使用lambda表達(dá)式
        Thread t2 = new Thread(() -> System.out.println("----------lambda方式----------------"));
        t2.start();
    }
  • 斷定型接口 Predicate boolean test(T t)

下面展示的是Java類型系統(tǒng)內(nèi)部的函數(shù)式接口

    public static void test6() {
        /* Java8中的java.util.function包中提供了一些常用的函數(shù)式功能接口*/
        // 1.java.util.function.Predicate<T>
        //接受參數(shù)T,返回一個(gè)Boolean類型的結(jié)果
        Predicate<String> predicate = String::isEmpty;
        boolean flag = predicate.test("");
        System.out.println(flag);

        // 2.java.util.function.Consumer<T>
        // 接收一個(gè)參數(shù)T,不返回結(jié)果
        Consumer<String> consumer = System.out::println;
        consumer.accept("哈哈哈");

        // 3.java.util.function.Function<T,R>
        // 接收參數(shù)對(duì)象T,返回結(jié)果對(duì)象R
        Function<String, Integer> function = (String userName) -> {
            return userName.equals("admin") ? 1 : 0;
        };
        int funflag = function.apply("admin");

        // 4.java.util.function.Supplier<T>
        // 不接收參數(shù),返回T對(duì)象創(chuàng)建的工廠
        Supplier<String> supplier = () -> UUID.randomUUID().toString();
        String s = supplier.get();

        // 5.java.util.UnaryOperator<T>
        // 接收參數(shù)對(duì)象T,返回結(jié)果參數(shù)對(duì)象T
        UnaryOperator<String> operator = (String userName) -> "Hello " + userName;
    
        //6.java.util.BinaryOperator<T>
        // 接收兩個(gè)T對(duì)象,返回一個(gè)T對(duì)象結(jié)果
        BinaryOperator<String> binary = (String userName, String userId) -> {
            return userName + ":" + userId;
        };
    }

靜態(tài)方法的引用
這個(gè)怎么解釋呢?我的理解就是,使用一個(gè)靜態(tài)的方法去替代你需要實(shí)現(xiàn)的接口方法,當(dāng)然前提是這個(gè)靜態(tài)方法必須跟你的接口方法是一樣的(即請(qǐng)求參數(shù)以及返回值一致)
例如:

//工具類Utils中含有一個(gè)靜態(tài)類
  public class Utils {
    public static final int compar(Integer p1, Integer p2) {
        return p1 - p2;
    }
  }

//調(diào)用
 List<Integer> list = Arrays.asList(33, 44, 55, 33, 22);
 Collections.sort(list, Utils::compar2);

其實(shí)就相當(dāng)于你在你的實(shí)現(xiàn)類中,調(diào)用了外部的靜態(tài)方法類,且把參數(shù)傳遞過去之后獲取到返回值進(jìn)行返回(即沒有對(duì)參數(shù)進(jìn)行任何加工就給第三方去處理了。外包出去了....):

      
        Collections.sort(list, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Utils.compar(o1, o2);
            }
        });

上述的調(diào)用方法有一個(gè)專業(yè)的名字叫【方法應(yīng)用】,直接使用符號(hào) :: (兩個(gè)冒號(hào))。
除了上述的靜態(tài)方法的引用之外,還有以下常見的幾種引用方式:

    1. 對(duì)象引用成員方法
          對(duì)象名::方法名
    2. 類名引用靜態(tài)方法
          類名::靜態(tài)方法
    3. 類的構(gòu)造器引用
        類的構(gòu)造器(類似于構(gòu)造方法)引用要在創(chuàng)建對(duì)象的時(shí)候使用。
         類名::new
    4. 數(shù)組的構(gòu)造器引用
         數(shù)組的構(gòu)造器引用在創(chuàng)建數(shù)組的時(shí)候使用。
         數(shù)據(jù)類型[]::new

Stream概念的引入(要去做核酸了,今天先到這里)

在Java 8中,得益于Lambda所帶來的函數(shù)式編程,引入了一個(gè)全新的Stream概念,用于解決已有集合類庫既有的弊端。
讓我先來看兩個(gè)例子:

    public static void main(String[] args) {
        List<String> list1 = new ArrayList<>();
        list1.add("黃毛");list1.add("黃文名");list1.add("黃文雄");list1.add("黃文離");list1.add("黃文金");list1.add("葉小平");list1.add("黃狗");
        System.out.println("------------方式1-------------");
        List<String> list2 = new ArrayList<>();
        list1.forEach(name -> {
            if (name.startsWith("黃")) {
                list2.add(name);
            }
        });
        ArrayList<String> list3 = new ArrayList<>();
        list2.forEach(name -> {
            if (name.length() == 3) {
                list3.add(name);
            }
        });
        list3.forEach(name -> {
            System.out.println("符合條件的姓名:" + name);
        });
        System.out.println("------------方式2-------------");
        list1.stream().filter(name -> name.startsWith("黃")).filter(name -> name.length() == 3).forEach(name -> System.out.println("符合條件的姓名:" + name));
    }

乍一看方式1跟方式2的結(jié)果是一樣的,都是想要遍歷出帶有三個(gè)字的姓黃的名字。但是很顯然第2種方式更為簡(jiǎn)潔,就一句話到底。這就是流式思想.

獲取流的方式
java.util.stream.Stream 是Java 8新加入的最常用的流接口。(這并不是一個(gè)函數(shù)式接口。)在 Java 8 中, 集合接口有兩個(gè)方法來生成流: ① stream() ? 為集合創(chuàng)建串行流。 ② parallelStream() ? 為集合創(chuàng)建并行流。

  1. 通過Collection(單列)集合獲取流
    Stream stream(): 獲取一個(gè)流對(duì)象
  2. 通過Map(雙列)集合獲取流
    通過Map集合獲取流(了解),Map集合不能直接獲取流對(duì)象, 只能間接獲取, 有三種間接獲取方式。
    ①. 先獲取Map集合中的所有的key, 然后獲取所有key的stream流
    ②. 先獲取Map集合中的所有的value,然后獲取所有value的stream流
    ③. 先獲取Map集合中的所有的Entry(鍵值對(duì)), 獲取所有entry的stream流。
  3. 通過數(shù)組獲取流
    方式一:使用Stream的靜態(tài)方法of完成(推薦, 因?yàn)閰?shù)不僅可以傳遞數(shù)組, 也可以傳遞任意個(gè)數(shù)據(jù))
    static Stream of(T… values): 根據(jù)一個(gè)數(shù)組獲取一個(gè)stream流
    方式二: 使用Arrays的靜態(tài)方法stream完成
    static Stream stream(T[] array) : 根據(jù)一個(gè)數(shù)組獲取對(duì)應(yīng)的stream流

流式布局常見用法

方法名稱 方法作用 方法種類 是否支持鏈?zhǔn)秸{(diào)用
forEach 逐一處理 終結(jié)方法 NO
count 獲取個(gè)數(shù) 終結(jié)方法 NO
filter 過濾 非終結(jié)方法 YES
limit 獲取前幾個(gè) 非終結(jié)方法 YES
skip 跳過前幾個(gè) 非終結(jié)方法 YES
concat 合并 非終結(jié)方法 YES
map 映射 非終結(jié)方法 YES

Stream中用于收集的方法
1. 收集到集合中:
R collect(Collector collector):可以將流中的數(shù)據(jù)收集到集合。 參數(shù)Collector表示收集到哪種集合。Collector是一個(gè)接口,如果要傳遞需要傳遞Collector實(shí)現(xiàn)類對(duì)象, 這個(gè)實(shí)現(xiàn)類對(duì)象不能由我們自己去new,要通過Collectors工具類的靜態(tài)方法進(jìn)行獲取, 使用不同方法獲取的Collector,表示收集到了不同的集合中。
static Collector toList():如果使用toList獲取的Collector對(duì)象,表示將數(shù)據(jù)收集到List集合中。
static Collector toSet()::如果使用toSet獲取的Collector對(duì)象,表示將數(shù)據(jù)收集到Set集合中。

2. stream收集到數(shù)組
我們可以使用Stream中的toArray方法進(jìn)行收集
Object[] toArray(): 將流中的數(shù)據(jù)收集到數(shù)組, 返回Object[]

注意:
1. stream流不會(huì)改變?cè)磳?duì)象.Stream流中的非終結(jié)方法返回的都是Stream對(duì)象, 返回的Stream是一個(gè)新的Stream
2. Stream流只能一次性使用,不能多次操作,否則會(huì)報(bào)錯(cuò)。
3. stream流本身不保存任何元素.便于理解可以把流看成容器,但是它不是容器.流本身不保存任何元素,他保存的是一些的中間操作步驟.
4. stream流操作是延遲執(zhí)行的.當(dāng)調(diào)用終結(jié)方法時(shí),會(huì)按順序?qū)⒉僮鞑襟E執(zhí)行.

參考資料:

  1. https://www.runoob.com/java/java8-lambda-expressions.html
  2. https://blog.csdn.net/qq_29660549/article/details/106564964
  3. https://blog.csdn.net/weixin_45735355/article/details/119911204
  4. https://blog.csdn.net/fengkuagn123/article/details/106315317
  5. 關(guān)于stream的用法
  6. https://blog.csdn.net/weixin_45590174/article/details/103542176
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容