<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          深入理解 Java 中的 Lambda

          共 7267字,需瀏覽 15分鐘

           ·

          2022-07-23 16:43

          作者:李三石
          來(lái)源:my.oschina.net/leili

          我花了相當(dāng)多的閱讀和編碼時(shí)間才最終理解Java Lambdas如何在概念上正常工作的。我閱讀的大多數(shù)教程和介紹都遵循自頂向下的方法,從用例開(kāi)始,最后以概念性問(wèn)題結(jié)束。在這篇文章中,我想提供一個(gè)自下而上的解釋?zhuān)瑥钠渌呀⒌腏ava概念中推導(dǎo)出Lambdas的概念。

          首先介紹下方法的類(lèi)型化,這是支持方法作為一流公民的先決條件。基于此,Lambdas的概念是被以匿名類(lèi)用法的進(jìn)化和特例提出的。所有這一切都通過(guò)實(shí)現(xiàn)和使用高階函數(shù)映射來(lái)說(shuō)明。

          這篇文章的主要受眾是那些已掌握函數(shù)式編程基礎(chǔ)的人,以及那些想從概念上理解Lambdas如何嵌入Java語(yǔ)言的人。

          方法類(lèi)型

          從Java 8起方法就是一等公民了。按照標(biāo)準(zhǔn)的定義,編程語(yǔ)言中的一等公民是一個(gè)具有下列功能的實(shí)體,

          • 可以作為參數(shù)進(jìn)行傳遞,

          • 可以作為方法的返回值

          • 可以賦值給一個(gè)變量.

          在Java中,每一個(gè)參數(shù)、返回值或變量都是有類(lèi)型的,因此每個(gè)一等公民都必須是有類(lèi)型的。Java中的一種類(lèi)型可以是以下內(nèi)容之一:

          • 一種內(nèi)建類(lèi)型 (比如 int 或者 double)

          • 一個(gè)類(lèi) (比如ArrayList)

          • 一個(gè)接口 (比如 Iterable)

          方法是通過(guò)接口進(jìn)行定義類(lèi)型的。它們不隱式的實(shí)現(xiàn)特定接口,但是在必要的時(shí)候,如果一個(gè)方法符合一個(gè)接口,那么在編譯期間,Java編譯器會(huì)對(duì)其進(jìn)行隱式的檢查。舉個(gè)例子說(shuō)明:

          class LambdaMap {
              static void oneStringArgumentMethod(String arg{
                  System.out.println(arg);
              }
          }

          關(guān)于oneStringArgumentMethod函數(shù)的類(lèi)型,與之相關(guān)的有:它的的函數(shù)是靜態(tài)的,返回類(lèi)型是void,它接受一個(gè)String類(lèi)型的參數(shù)。一個(gè)靜態(tài)函數(shù)符合包含一個(gè)apply函數(shù)的接口,apply函數(shù)的簽名相應(yīng)地符合這個(gè)靜態(tài)函數(shù)的簽名。oneStringArgumentMethod函數(shù)對(duì)應(yīng)的接口因此必須符合下列標(biāo)準(zhǔn)。

          • 它必須包含一個(gè)名為apply的函數(shù)。

          • 函數(shù)返回類(lèi)型必須是void。

          • 函數(shù)必須接受一個(gè)String類(lèi)型可以轉(zhuǎn)換到的對(duì)象的參數(shù)。

          在符合這個(gè)標(biāo)準(zhǔn)的接口之中,下面的這個(gè)是最明確的:

          interface OneStringArgumentInterface {
              void apply(String arg);
          }

          利用這個(gè)接口,函數(shù)可以分配給一個(gè)變量:

          OneStringArgumentInterface meth = LambdaMap::oneStringArgumentMethod;

          用這種方法使用接口作為類(lèi)型,函數(shù)可以借此被分配給變量,傳遞參數(shù)并且從函數(shù)返回:

          static OneStringArgumentInterface getWriter() {
              return LambdaMap::oneStringArgumentMethod;
          }

          static void write(OneStringArgumentInterface writer, String msg) {
              writer.apply(msg);
          }

          最終函數(shù)是一等公民。

          泛型函數(shù)類(lèi)型

          就像使用集合一樣,泛型為函數(shù)類(lèi)型增加了大量的功能和靈活性。實(shí)現(xiàn)功能上的算法而不考慮類(lèi)型相關(guān)信息,泛型函數(shù)類(lèi)型使其變?yōu)榭赡堋T趯?duì)map函數(shù)的實(shí)現(xiàn)中,會(huì)在下面用到這種功能。

          在這提供的OneStringArgumentInterface一個(gè)泛型版本:

          interface OneArgumentInterface<T{
              void apply(T arg);
          }

          OneStringArgumentInterface函數(shù)可以被分配給它:

          OneArgumentInterface<String> meth = LambdaMap::oneStringArgumentMethod;

          通過(guò)使用泛型函數(shù)類(lèi)型,它現(xiàn)在可以以一種通用的方法實(shí)現(xiàn)算法,就像它在集合中使用的一樣:

          static <T> void applyArgument(OneArgumentInterface<T> meth, T arg) {
              meth.apply(arg);
          }

          上面的函數(shù)并沒(méi)有什么用,然而它至少可以提出一個(gè)想法:對(duì)函數(shù)作為第一個(gè)類(lèi)成員的支持怎樣可以形成非常簡(jiǎn)潔且靈活的代碼:

          applyArgument(Lambda::oneStringArgumentMethod, "X ");

          實(shí)現(xiàn)map

          在諸多高階函數(shù)中,map是最經(jīng)典的. map的第一個(gè)參數(shù)是函數(shù),該函數(shù)可以接收一個(gè)參數(shù)并返回一個(gè)值;第二個(gè)參數(shù)是值列表. map使用傳入的函數(shù)處理值列表的每一項(xiàng),然后返回一個(gè)新的值列表。下面Python的代碼片段,可以很好的說(shuō)明map的用法:

          >>> map(math.sqrt[1, 4, 9, 16])
          [1.0, 2.0, 3.0, 4.0]

          在本節(jié)的后續(xù)內(nèi)容中,將給出該函數(shù)的Java實(shí)現(xiàn)。Java 8已經(jīng)通過(guò)Stream提供了該函數(shù)。因?yàn)橹饕鲇诮虒W(xué)目的,所以,本節(jié)中給出的實(shí)現(xiàn)特意保持簡(jiǎn)單,僅限于List對(duì)象使用。

          與Python不同,在Java中必須首先考慮map第一個(gè)參數(shù)的類(lèi)型:一個(gè)可以接收一個(gè)參數(shù)并返回一個(gè)值的方法。參數(shù)的類(lèi)型和返回值的類(lèi)型可以不同。下面接口符合這個(gè)預(yù)期,顯然,I表示參數(shù)(入?yún)ⅲ琌表示返回值(出參):

          interface MapFunction<IO{
              apply(I in);
          }

          泛型map方法的實(shí)現(xiàn),變得驚人的簡(jiǎn)單明了:

          static <I, O> List<O> map(MapFunction<I, O> func, List<I> input{
              List<O> out = new ArrayList<>();

              for (I in : input) {
                  out.add(func.apply(in));
              }

              return out;
          }
          1. 創(chuàng)建新的返回值列表out(用于保存O類(lèi)型的對(duì)象).

          2. 通過(guò)遍歷input,func處理列表的每一項(xiàng),并將返回值添加到out中。

          3. 返回out.

          下面是實(shí)際使用map方法的實(shí)例:

          MapFunction<Integer, Double> func = Math::sqrt;

          List<Doubleoutput = map(func, Arrays.asList(1., 4., 9., 16.));
          System.out.println(output);

          在Python one-liner的推動(dòng)下,可以用更簡(jiǎn)潔的方法表達(dá):

          System.out.println(map(Math::sqrtArrays.asList(1., 4., 9., 16.)));

          Java畢竟不是Python…

          Lambdas來(lái)了!

          讀者可能會(huì)注意到,還沒(méi)有提到Lambdas。這是由于采用了“自下而上”的方式描述,現(xiàn)在基礎(chǔ)已基本建立,Lambdas將在后續(xù)的章節(jié)中介紹。

          下面的用例作為基礎(chǔ):一個(gè)double類(lèi)型的list,表示半徑,然后得到一個(gè)列表,表示圓面積。map方法就是為此任務(wù)預(yù)先準(zhǔn)備的。計(jì)算圓面積的公式是眾所周知的:

          A?=?r2π

          應(yīng)用這個(gè)公式的方法很容易實(shí)現(xiàn):

          static Double circleArea(Double radius) {
              return Math.pow(radius, 2) * Math.PI;
          }

          這個(gè)方法現(xiàn)在可以用作map方法的第一個(gè)參數(shù):

          System.out.println(
                  map(LambdaMap::circleArea,
                      Arrays.asList(1., 4., 9., 16.)));

          如果circleArea方法只需要這一次, 沒(méi)有道理把類(lèi)接口被他弄得亂七八糟,也沒(méi)有道理將實(shí)現(xiàn)和真正使用它的地方分離。最佳實(shí)踐是使用用匿名內(nèi)部類(lèi)。可以看到,實(shí)例化一個(gè)實(shí)現(xiàn)MapFunction接口的匿名內(nèi)部類(lèi)可以很好的完成這個(gè)任務(wù):

          System.out.println(
                  map(new MapFunction<DoubleDouble>() {
                          public Double apply(Double radius) {
                              return Math.sqrt(radius) * Math.PI;
                          }
                      },
                      Arrays.asList(1.2.3.4.)));

          這看起來(lái)很漂亮,但是很多人會(huì)認(rèn)為函數(shù)式的解決方案更清晰,更具可讀性:

          List<Double> out = new ArrayList<>();
          for (Double radius : Arrays.asList(1.2.3.4.)) {
              out.add(Math.sqrt(radius) * Math.PI);
          }
          System.out.println(out);

          到目前為止,最后是使用Lambda表達(dá)式。讀者應(yīng)該注意Lambda如何取代上面提到的匿名類(lèi):

          System.out.println(
                  map(radius -> { return Math.sqrt(radius) * Math.PI; },
                      Arrays.asList(1.2.3.4.)));

          這看起來(lái)簡(jiǎn)潔明了 - 請(qǐng)注意 Lambda 表達(dá)式如何缺省任何明確的類(lèi)型信息。沒(méi)有顯式模板實(shí)例化,沒(méi)有方法簽名。

          Lambda表達(dá)式由兩部分組成,這兩部分被->分隔。第一部分是參數(shù)列表,第二部分是實(shí)際實(shí)現(xiàn)。

          Lambda表達(dá)式和匿名內(nèi)部類(lèi)作用完全相同,然而它摒棄了許多編譯器可以自動(dòng)推斷的樣板代碼。讓我們?cè)俅伪容^這兩種方式,然后分析編譯器為開(kāi)發(fā)人員節(jié)省了哪些工作。

          MapFunction<DoubleDouble> functionLambda =
                  radius -> Math.sqrt(radius) * Math.PI;

          MapFunction<DoubleDouble> functionClass =
                  new MapFunction<DoubleDouble>() {
                      public Double apply(Double radius) {
                          return Math.sqrt(radius) * Math.PI;
                      }
                  };
          • 對(duì)于Lambda實(shí)現(xiàn)來(lái)說(shuō),只有一個(gè)表達(dá)式,返回語(yǔ)句和花括號(hào)可以省略。這使得代碼更簡(jiǎn)短。

          • Lambda表達(dá)式的返回值類(lèi)型是從Lambda實(shí)現(xiàn)推斷出來(lái)的。

          • 對(duì)于參數(shù)類(lèi)型,我不完全確定,但我認(rèn)為必須從Lambda表達(dá)式所處的上下文中推斷出參數(shù)類(lèi)型。

          • 最后編譯器必須檢查返回值類(lèi)型是否與Lambda的上下文匹配,以及參數(shù)類(lèi)型是否與Lambda實(shí)現(xiàn)匹配。

          這一切都可以在編譯期間完成,根本沒(méi)有運(yùn)行時(shí)開(kāi)銷(xiāo)。

          結(jié)語(yǔ)

          總而言之,Java中的Lambdas的概念是整潔的。我支持編寫(xiě)更簡(jiǎn)潔、更清晰的代碼,并讓程序員免于編寫(xiě)可由編譯器自動(dòng)推斷的架手架代碼。它是語(yǔ)法糖,如上所述,它只不過(guò)是使用匿名類(lèi)也能實(shí)現(xiàn)的功能。然而,我會(huì)說(shuō)它是非常甜的語(yǔ)法糖。

          另一方面,Lambdas還支持更加混淆以及難以調(diào)試的代碼。Python社區(qū)很早就意識(shí)到了這一點(diǎn) - 雖然Python也有Lambda,但它若被廣泛使用則通常被認(rèn)為是不好的風(fēng)格(當(dāng)嵌套函數(shù)可以被使用時(shí),它并不難于規(guī)避)。對(duì)于Java來(lái)說(shuō),我會(huì)給出類(lèi)似的建議。毫無(wú)疑問(wèn),在某些情況下,使用Lambdas會(huì)導(dǎo)致代碼大大縮減并更易讀,尤其在與流有關(guān)時(shí)。在其他情況下,如果采取更保守的做法和最佳實(shí)踐,另外一種方法可能會(huì)是更好的替代。

          如有文章對(duì)你有幫助,

          在看”和轉(zhuǎn)發(fā)是對(duì)我最大的支持!

          暫時(shí)開(kāi)放微信大號(hào),好友位不多,需要的小伙伴們可以加,朋友圈和交流群里會(huì)發(fā)一些學(xué)習(xí)資料、個(gè)人見(jiàn)解、白嫖課程等等。

          掃描下方二維碼即可加我微信啦,2022,抱團(tuán)取暖,一起牛逼。

          瀏覽 50
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产自产精品一区精品 | 亚洲日韩黄色片 | 激情另类视频 | 人人草人人爽 | 婷婷五月天91 |