Java8流特性和Lambda表達式

jopen 9年前發布 | 27K 次閱讀 Java8 Java開發

Java8主要的改變是為集合框架增加了流的概念,提高了集合的抽象層次。相比于舊有框架直接操作數據的內部處理方式,流+高階函數的外部處理方式對數據封裝更好。同時流的概念使得對并發編程支持更強。

在語法上Java8提供了Lambda表達式來傳遞方法體,簡化了之前方法必須藏身在不必要的類中的繁瑣。Lambda表達式體現了函數式編程的思想,即一個函數亦可以作為另一個函數參數和返回值,使用了函數作參數/返回值的函數被稱為高階函數。

1. Lambda表達式

Java 被詬病為繁瑣的地方就在于不支持傳遞方法,Java中的方法必須依賴類存在,也不能將方法作為參數或返回值,這是與python等語言相比的弱勢。Java 8中使用新特性Lambda表達式來改善這一點。

1.1 使用示例

以Runnable接口為例,如果需要執行一個線程,實際只需要run()方法中的代碼塊,但形式上必須要先制造一個Runnable接口實現類(通常是匿名內部類)。

使用Lambda表達式僅僅需要一行代碼,達到傳遞run方法的效果,而不必定義匿名內部類。

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

1.2 類型參數推斷機制(Type Argument Inference)

Lambda表達式之所以能夠做如此簡化得益于Java的類型參數推斷機制。所有省略的內容都可以由編譯器通過上下文推斷出來。

類型推斷機制在Java中的應用廣泛,例如數組類型確定,Java7引入的菱形操作符等等。

類型參數推斷機制要推斷的是Lambda表達式的目標類型,往往需要與Java的重載解析機制配合。其解析規則是

  • 只有一個可能目標類型時,由響應函數接口里的參數類型推導得出

    </li>

  • 有多個可能目標類型,選擇最具體的類型

    </li>

  • 有多個可能目標類型但無法明確最具體類型,則編譯報錯

    </li> </ul>

    1.3 函數接口(Functional Interface)

    一個方法可以抽象成函數接口。函數接口類似于一個黑箱,只需要關注其參數和返回值類型,函數接口中只有單方法。Runnable的函數接口如下:

    可以看到這是一個空接口。可以用它代表所有參數和返回值都為空的方法。

    Java8中定義若干函數接口(位于包java.util.function)。

    </tr> </tbody>

    </tr>

    </tr>

    </tr>

    </tr>

    </tr>

    </tr> </tbody> </table>

    以Pridicate函數接口為例,這是一個泛型接口,參數可以是任意類型,返回值是boolean類型,代表根據數值作判斷的一類方法。

    1.4 并非語法糖

    從類型推斷的角度看很容易覺得Lambda表達式是和泛型,裝箱等機制一樣的語法糖,編譯器在背后補全了省略信息,但實際上并非如此。

    class Apple{
        public String toString() {return "apple";};
        Runnable r1 = ()->{System.out.println(this);};
        Runnable r2 = new Runnable() {
            public void run() {
                System.out.println(this);
            }
        };

    }

    //執行兩個線程得到的結果是 apple Day0917.Apple$1@22e90474</pre>

    正常的匿名內部類中 this關鍵字 指向內部類對象自身,同時將生成Apple$1.class文件。

    Lambda表達式中this所指向的則是外部類對象,并不會生成內部類class文件,這說明Lambda表達式并不是語法糖,它沒有產生一個內部類,也沒有引入一個新的作用域。

    Lambda與內部類相同之處在于其內部所定義的變量均為final或既成事實上的final.

    </div>

    1.5 默認方法

    Java8最重要的改變就是對類庫的改造,使得接口中方法可以擁有代碼體。這種定義在接口中的包含方法體的方法,需要用default修飾,稱之為默認方法。

    interface Apple{
        default void show(){
            System.out.println("interface");
        }
    }
    class MyApple implements Apple{
        @Override
        public void show() {
            Apple.super.show();
        }
    }

    如果實現類中重寫了默認方法,則接口中默認方法就被覆蓋了。如果兩個接口定義了相同的默認方法,則實現類中可以通過指定全稱來確定使用哪個父類的方法。

    1.6 方法引用

    如果將匿名內部類改造為Lambda表達式是偷懶的話,那方法引用則是懶到連Lambda表達式都不想寫了。

    在之前,我們知道Lambda表達式可以作為函數參數和返回值,表示傳遞一個方法。方法引用就是使用 ClassName::MethodName 的形式來指定方法。故而方法引用與Lambda表達式完全同源同種,可以相互替代。

    //1,建立一個字符串
    String::new 
    //2.建立一個字符串數組
    String[]::new

    注意 lambda表達式與方法引用表示的是方法本身,將要被用過高階函數的參數/返回值,并不能單獨使用。

    </div>

    2. 流stream

    任務:創建一個姓名集合,要求出所有初始字母為a的人的總數目。使用流處理的代碼如下:

    ArrayList<String> person = new ArrayList<>();
    ----init----
    //1.由集合獲得流對象
    Stream<String> steam = person.stream();
    //2.對流對象進行過濾和統計
    steam.filter((s)->s.startsWith("a")) //1.流過濾
            .count(); //2.計算流對象中元素數目

    使用函數接口(形式上表現為Lambda表達式)作為參數和返回值的函數就是所謂的高階函數,如此處的filter,其參數為函數接口Predicate,亦可以理解為一個接口為 T--->boolean 的方法。

    </div>

    上述示例中為流對象的高階函數傳入一個函數接口Predicate,避免了直接處理集合中的數據對象。示例展示了流使用的通用格式:

    • 獲得流對象Stream

      </li>

    • 對流對象Stream進行惰性求值,返回值仍然是一個Stream對象。

      </li>

    • 對流對象Stream進行及早求值,返回值不在是一個Stream對象。

      </li> </ul>

      2.1常見高階函數

      1.collect方法

      collect方法屬于一個及早求值方法,負責將流對象轉換成其他數據結構,如列表,集合,值等。這項工作由收集器Collector完成。java8為此提供了Collectors工具類。

      1.1 轉換成集合

      List<Person> list = stream.collect(Collectors.toList());
      List<Person> arraylist = stream.collect(Collectors.toCollection(ArrayList::new));

      Set<Person> set = stream.collect(Collectors.toSet()); Set<Person> treeSet = stream.collect(Collectors.toCollection(TreeSet::new));</pre>

      使用Collectors.toList()將流對象轉換成集合時并不需要指定具體類型,Java默認選擇了實現類型,如果要自己指定,可以使用Collectors.toCollection(ArrayList::new),其參數ArrayList::new就是上文中的方法引用,表示一個建立ArrayList對象的方法,ArrayList就是想要轉換成的數據類型;

      1.2 轉換成值

      //1.獲得最大最小值
      Function<Person, Integer> getLevel = p->p.age; 
      Comparator<Person> comparator = Comparator.comparing(getLevel);
      stream.collect(Collectors.maxBy(comparator));
      stream.collect(Collectors.minBy(comparator));
      //2.獲得平均值
      ToIntFunction<Person> getAverage = p->p.age;
      stream.collect(Collectors.averagingInt(getAverage));

      1.3 數據分塊

      將流對象按某種條件分成兩部分

      Predicate<Person> isTang = p->p.country.equals(Country.Tang);
      stream.collect(Collectors.partitioningBy(isTang));

      1.4 數據分組

      Function<Person, Integer> country= p -> p.country.ordinal();
      stream.collect(Collectors.groupingBy(country));

      分塊和分組看似相同,但意義不同,分塊使用判斷作為方法,只能將流分成兩塊;分組則靈活的多。

      </div>

      1.5 字符串

      stream.map(Person::getName).collect(Collectors.joining("/", "[", "]"));

      1.6 合并收集器

      stream.collect(Collectors.groupingBy(country,Collectors.counting()));

      2.map

      map是一個惰性求值方法。函數接口為Function<T, R>函數接口,負責將數據從一個類型轉換為另一個類型;高階函數map的作用就是將數據從一個流轉換為另一個流。

      3.filter

      filter 是一個惰性求值方法。函數接口為Pridicate<T>,此方法負責對數據進行判斷,filter高階函數負責根據判斷結果對流進行過濾。

      4.flatMap系列

      flatMap 是一個惰性求值方法。其參數亦為Function<T, R>,將多個流組合為一個流。

      //1.a1,a2是兩個列表,map處理后仍是兩個列表

      Stream.of(a1,a2).map(s->s)

      [1, 2, 3, 4] []

      //2.flatMap將二者合并為一個流 Stream.of(a1,a2).map(s->s)

      .flatMap(s->s.stream())

      1234</pre>

      看源碼可知,flatMap中函數接口Function的輸出類型為Stream<R>。

      5.max/min

      屬于一個及早求值方法。需要傳入一個Comparator函數接口,Java8提供了Comparator.comparing方法獲得該函數接口的實現,該靜態方法是接口的靜態方法,獲得一個函數返回一個Comparator對象。

      min(Comparator.comparing(s->s.toString()));

      max/min的返回值是 Optional,代表一個或有或無的值,主要是用來取代萬惡的null值;使用get方法可以獲取其值。

      </div>

      6.reduce

      屬于一個及早求值方法。意為流數據的累加,有兩個版本。

      //1.無初始值累加
      T t = person.stream().reduce((a,b)->a+b);
      //2.帶初始值累加
      Optional<T> t = person.stream().reduce("1",(a,b)->a+b);

      7. foreach

      屬于一個及早求值方法,用來遍歷流對象。

      總而言之,Java8中流對象的引入使得可以在更高的層次上對集合進行處理,使得抽象的方法和具體的行為邏輯分離開來,也加強了數據的封裝性,另一個好處是對并發的支持更強,以后再補充。

      </div> </div> 原文 http://segmentfault.com/a/1190000003981375

       本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
       轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
       本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!
    接口 參數 返回類型 示例
    Pridicate<T> T boolean 條件判斷
    Consumer<T> T void 消費者
    Function<T, R> T R 再加工
    Supplier<T> void T 供應者
    BinaryOperator<T> <T,T> T 求和
    UnaryOperator<T> T T 邏輯非
sesese色