JAVA8 新特性~~~~

Lambda 表達(dá)式&函數(shù)式接口

Lambda表達(dá)式好像已經(jīng)出現(xiàn)很久了好像大部分人還是比較傾向于使用內(nèi)部類
(老程序員表示Lambda表達(dá)式影響可讀性,但是在很多組件中使用Lambda表達(dá)式還是很有必要的)

Lamdba表達(dá)式和接口是分不開的,JAVA8改動(dòng)Lambda表達(dá)式的同時(shí)也新增了函數(shù)式接口

函數(shù)式接口

Functional Interface的定義很簡單:任何包含唯一一個(gè)抽象方法的接口都可以稱之為函數(shù)式接口。

但是函數(shù)式接口中還可以存在簽名與Object的public方法相同的接口(畢竟最終父類時(shí)Object),并且可以存在靜態(tài)方法。

@FunctionalInterface
interface FunctionalInterfaceWithStaticMethod {
    static int sum(int[] array) {
        return Arrays.stream(array).reduce((a, b) -> a+b).getAsInt();
    }

    boolean equals(Object obj);
    
    void apply();
}
//這依舊是一個(gè)函數(shù)式接口

為了讓編譯器幫助我們確保一個(gè)接口滿足函數(shù)式接口的要求,Java8提供了@FunctionalInterface注解。

另外JDK中已有的一些接口本身就是函數(shù)式接口,如Runnable。 JDK 8中又增加了java.util.function包, 提供了常用的函數(shù)式接口。

舉個(gè)函數(shù)式接口的栗子

@FunctionalInterface  
public interface Runnable {  
    public abstract void run();  
}  
//Runnable接口提供了一個(gè)run()抽象方法

在java8之前,你可以通過匿名內(nèi)部類來實(shí)現(xiàn)對這個(gè)接口的調(diào)用,像下面這樣

Thread thread = new Thread(new Runnable() {
  public void run() {
    System.out.println("In another thread");
  }
}); 

但是如果我們想要我們的代碼更加優(yōu)雅,我們應(yīng)該使用Lambda表達(dá)式,當(dāng)我們使用Lambda表達(dá)式剛剛代碼就可以這樣寫:

Thread thread = new Thread(() -> 
System.out.println("In another thread"));

Lambda表達(dá)式

Lambda表達(dá)式在Java8中最大的改動(dòng)是
它允許把函數(shù)作為一個(gè)方法的參數(shù)(函數(shù)作為參數(shù)傳遞進(jìn)方法中)

以下是lambda表達(dá)式的重要特征:

  • 可選類型聲明:不需要聲明參數(shù)類型,編譯器可以統(tǒng)一識(shí)別參數(shù)值。
  • 可選的參數(shù)圓括號(hào):一個(gè)參數(shù)無需定義圓括號(hào),但多個(gè)參數(shù)需要定義圓括號(hào)。
  • 可選的大括號(hào):如果主體包含了一個(gè)語句,就不需要使用大括號(hào)。
  • 可選的返回關(guān)鍵字:如果主體只有一個(gè)表達(dá)式返回值則編譯器會(huì)自動(dòng)確定返回值

還是先上栗子:

public class Java8Tester {
   public static void main(String args[]){
      Java8Tester tester = new Java8Tester();
        
      // 類型聲明
      MathOperation addition = (int a, int b) -> a + b;
        
      // 不用類型聲明
      MathOperation subtraction = (a, b) -> a - b;
        
      // 大括號(hào)中的返回語句
      MathOperation multiplication = (int a, int b) -> { return a * b; };
        
      // 沒有大括號(hào)及返回語句
      MathOperation division = (int a, int b) -> a / b;
        
      System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
      System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
      System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
      System.out.println("10 / 5 = " + tester.operate(10, 5, division));
        
      // 不用括號(hào)
      GreetingService greetService1 = message ->
      System.out.println("Hello " + message);
        
      // 用括號(hào)
      GreetingService greetService2 = (message) ->
      System.out.println("Hello " + message);
        
      greetService1.sayMessage("Runoob");
      greetService2.sayMessage("Google");
   }
    
   interface MathOperation {
      int operation(int a, int b);
   }
    
   interface GreetingService {
      void sayMessage(String message);
   }
    
   private int operate(int a, int b, MathOperation mathOperation){
      return mathOperation.operation(a, b);
   }
}

變量作用域

  • lambda 表達(dá)式只能引用標(biāo)記了 final 的外層局部變量,這就是說不能在 lambda 內(nèi)部修改定義在域外的局部變量,否則會(huì)編譯錯(cuò)誤。(其實(shí)也可以不聲明final)
int num = 1;
Converter<Integer, String> s =
        (param) -> String.valueOf(param + num);
num = 5;
//編譯會(huì)出錯(cuò)
  • 在 Lambda 表達(dá)式當(dāng)中不允許聲明一個(gè)與局部變量同名的參數(shù)或者局部變量。
String first = "";  
Comparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length()); 
 //編譯會(huì)出錯(cuò) 

方法引用

方法引用通過方法的名字來指向一個(gè)方法。
方法引用使用一對冒號(hào) ::
內(nèi)容比較簡單并且Java核心已經(jīng)有講過
所以直接上栗子,我們在 Car 類中定義了 4 個(gè)方法作為例子來區(qū)分 Java 中 4 種不同方法的引用。

package com.runoob.main;
 
@FunctionalInterface
public interface Supplier<T> {
    T get();
}
 
class Car {

    public static Car create(final Supplier<Car> supplier) {
        return supplier.get();
    }
 
    public static void collide(final Car car) {
        System.out.println("Collided " + car.toString());
    }
 
    public void follow(final Car another) {
        System.out.println("Following the " + another.toString());
    }
 
    public void repair() {
        System.out.println("Repaired " + this.toString());
    }
}
  • 構(gòu)造器引用
final Car car = Car.create( Car::new );
final List< Car > cars = Arrays.asList( car );
  • 靜態(tài)方法引用
cars.forEach( Car::collide );
//Class< T >::new
  • 特定類的任意對象的方法引用
    //Class::static_method
cars.forEach( Car::repair );
//Class::method
  • 特定對象的方法引用
final Car police = Car.create( Car::new );
cars.forEach( police::follow );
//instance::method

Java Stream

Stream是 Java 8新增加的類,用來補(bǔ)充集合類。
既然是補(bǔ)充集合類,那么先來列舉一下它們的區(qū)別(主要是與迭代器的區(qū)別):
1.不儲(chǔ)存數(shù)據(jù) 流是基于數(shù)據(jù)源的對象,它本身不存儲(chǔ)數(shù)據(jù)元素,而是通過管道將數(shù)據(jù)源的元素傳遞給操作。
2.函數(shù)式編程 流的操作不會(huì)修改數(shù)據(jù)源,例如filter不會(huì)將數(shù)據(jù)源中的數(shù)據(jù)刪除。
3.延遲操作 流的很多操作如filter,map等中間操作是延遲執(zhí)行的,只有到終點(diǎn)操作才會(huì)將操作順序執(zhí)行。
4.可以解綁 對于無限數(shù)量的流,有些操作是可以在有限的時(shí)間完成的,比如limit(n) 或 findFirst(),這些操作可是實(shí)現(xiàn)"短路"(Short-circuiting),訪問到有限的元素后就可以返回。
5.純消費(fèi) 流的元素只能訪問一次,類似Iterator,操作沒有回頭路,如果你想從頭重新訪問流的元素,對不起,你得重新生成一個(gè)新的流。

在具體講解方法之前,還是先來看一個(gè)栗子:

List<Integer> a = {1,2,3};
List<Integer> b = a.stream()
             .filter(i ->  i >= 2 )
             .collect(Collectors.toList());

我們可以把以上代碼分為三部分來具體分析:
1.創(chuàng)建Stream

  • 通過集合的stream()方法或者parallelStream(),比如Arrays.asList(1,2,3).stream()。
  • 通過Arrays.stream(Object[])方法, 比如Arrays.stream(new int[]{1,2,3})。
  • 使用流的靜態(tài)方法,比如Stream.of(Object[]), IntStream.range(int, int) 或者 Stream.iterate(Object, UnaryOperator),如Stream.iterate(0, n -> n *
  • BufferedReader.lines()從文件中獲得行的流。
  • Files類的操作路徑的方法,如list、find、walk等。
  • 隨機(jī)數(shù)流Random.ints()。
  • 更底層的使用StreamSupport,它提供了將Spliterator轉(zhuǎn)換成流的方法。

2.中間操作

  • distinct
    distinct保證輸出的流中包含唯一的元素,它是通過Object.equals(Object)來檢查是否包含相同的元素。
List<String> l = Stream.of("a","b","c","b")
        .distinct()
        .collect(Collectors.toList());
System.out.println(l); //[a, b, c]
  • filter
    filter返回的流中只包含滿足斷言(predicate)的數(shù)據(jù)。
    下面的代碼返回流中的偶數(shù)集合。
List<Integer> l = IntStream.range(1,10)
        .filter( i -> i % 2 == 0)
        .boxed()
        .collect(Collectors.toList());
System.out.println(l); //[2, 4, 6, 8]
  • map
    map方法將流中的元素映射成另外的值,新的值類型可以和原來的元素的類型不同。
map( c -> c*2)
  • flatmap
    flatmap方法將映射后的流的元素全部放入到一個(gè)新的流中。
String poetry = "Where, before me, are the ages that have gone?\n" +
        "And where, behind me, are the coming generations?\n" +
        "I think of heaven and earth, without limit, without end,\n" +
        "And I am all alone and my tears fall down.";
Stream<String> lines = Arrays.stream(poetry.split("\n"));
Stream<String> words = lines.flatMap(line -> Arrays.stream(line.split(" ")));

  • limit
    limit方法指定數(shù)量的元素的流。對于串行流,這個(gè)方法是有效的,這是因?yàn)樗恍璺祷厍皀個(gè)元素即可,但是對于有序的并行流,它可能花費(fèi)相對較長的時(shí)間,如果你不在意有序,可以將有序并行流轉(zhuǎn)換為無序的,可以提高性能。
List<Integer> l = IntStream.range(1,100).limit(5)
        .boxed()
        .collect(Collectors.toList());
System.out.println(l);//[1, 2, 3, 4, 5]
  • peek
    peek方法方法會(huì)使用一個(gè)Consumer消費(fèi)流中的元素,但是返回的流還是包含原來的流中的元素。
String[] arr = new String[]{"a","b","c","d"};
Arrays.stream(arr)
        .peek(System.out::println) //a,b,c,d
        .count();
  • sorted
    sorted()將流中的元素按照自然排序方式進(jìn)行排序,如果元素沒有實(shí)現(xiàn)Comparable,則終點(diǎn)操作執(zhí)行時(shí)會(huì)拋出java.lang.ClassCastException異常。
    sorted(Comparator<? super T> comparator)可以指定排序的方式。

對于有序流,排序是穩(wěn)定的。對于非有序流,不保證排序穩(wěn)定。

String[] arr = new String[]{"b_123","c+342","b#632","d_123"};
List<String> l  = Arrays.stream(arr)
        .sorted((s1,s2) -> {
            if (s1.charAt(0) == s2.charAt(0))
                return s1.substring(2).compareTo(s2.substring(2));
            else
                return s1.charAt(0) - s2.charAt(0);
        })
        .collect(Collectors.toList());
System.out.println(l); //[b_123, b#632, c+342, d_123]

  • skip
    skip返回丟棄了前n個(gè)元素的流,如果流中的元素小于或者等于n,則返回空的流。

3.終點(diǎn)操作

  • Match
    分為三種具體方法,用來檢查流中的元素是否滿足段言。
public boolean  allMatch(Predicate<? super T> predicate)
public boolean  anyMatch(Predicate<? super T> predicate)
public boolean  noneMatch(Predicate<? super T> predicate)

  • count
    count方法返回流中的元素的數(shù)量。

  • collect
    這是一個(gè)比較重要的終點(diǎn)操作,實(shí)現(xiàn)最終對處理過的流的收集。輔助類Collectors提供了很多的collector,可以滿足我們?nèi)粘5男枨?,你也可以?chuàng)建新的collector實(shí)現(xiàn)特定的需求。

  • find
    findAny()返回任意一個(gè)元素,如果流為空,返回空的Optional,對于并行流來說,它只需要返回任意一個(gè)元素即可,所以性能可能要好于findFirst(),但是有可能多次執(zhí)行的時(shí)候返回的結(jié)果不一樣。
    findFirst()返回第一個(gè)元素,如果流為空,返回空的Optional。

  • forEach
    forEach遍歷流的每一個(gè)元素,執(zhí)行指定的action。

Stream.of(1,2,3,4,5).forEach(System.out::println);

  • max/min
    max返回流中的最大值,
    min返回流中的最小值。

  • toArray()
    將流中的元素放入到一個(gè)數(shù)組中。

多說一句~~~~~~~~~
流可以從非線程安全的集合中創(chuàng)建,當(dāng)流的管道執(zhí)行的時(shí)候,非concurrent數(shù)據(jù)源不應(yīng)該被改變。下面的代碼會(huì)拋出java.util.ConcurrentModificationException異常:

List<String> l = new ArrayList(Arrays.asList("one", "two"));
Stream<String> sl = l.stream();
sl.forEach(s -> l.add("three"));

但是使用CopyOnWriteArrayList可以解決這個(gè)問題

Optional——一個(gè)可以為 null 的容器

Java 8中的Optional<T>是一個(gè)可以包含或不可以包含非空值的容器對象,在 Stream API中很多地方也都使用到了Optional。(暫時(shí)沒有找到特別合適的用法...)

基本方法:

  • of()

  • ofNullable()

  • isPresent()
    如果值存在,返回 true,否則返回 false

  • map()

  • orElse()

  • orElseGet()

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • Nothing is imposible for a willing heart. Java 8 (又稱為 jdk...
    北緯26閱讀 1,093評論 1 6
  • 家中三個(gè)哥哥黑不溜秋的,吊兒郎當(dāng),而她,卻生就一副美人坯子的模樣,乖巧懂事,聰明伶俐。父親沒怎么讀過書,也沒啥...
    錢一青子閱讀 426評論 0 0
  • “ 要么不做,做就做好” 前幾天,我在微信上呼叫夏天同學(xué),讓他幫我設(shè)計(jì)一下微信公眾號(hào)的二維碼圖片。 夏天同學(xué)是跟我...
    斤斗云閱讀 405評論 0 1
  • 從心底里去愛每一個(gè)階段的你,學(xué)會(huì)自己陪伴自己,去寬容自己。如果你真的可以去欣賞每一個(gè)階段的你,我覺得你會(huì)是美的。 ...
    童希園閱讀 275評論 0 0
  • 不過是一場雨,只是略微大些,無人為此驚異,不似繁花惹人愛,還不時(shí)背負(fù)著被咒罵的命運(yùn)。 昨日的雨,一反常態(tài),...
    風(fēng)蕭蕭兮丶閱讀 241評論 0 1

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