夯實Java基礎(二十二)——Java8新特性之Lambda表達式

一、前言

Java 8於14年發佈到如今已經有5年時間了,通過時間的磨練,毫無疑問,Java 8是繼Java 5(發佈於2004年)以後的又一個很是最重要的版本。由於Java 8裏面出現了很是多新的特徵,這些特徵主要包含語言、編譯器、庫、工具和JVM等方面,具體以下:html

  1. Lambda表達式         Left hug傳送門Right hug
  2. 方法引用/構造器引用  Left hug傳送門Right hug
  3. Stream API             Left hug傳送門Right hug
  4. 新的日期處理類         Left hug傳送門Right hug
  5. 函數式接口               Left hug傳送門Right hug
  6. 接口中容許定義默認方法
  7. Optional類              Left hug傳送門Right hug
  8. 重複註解、類型註解、通用類型推斷
  9. 新的編譯工具:jjs、jdeps
  10. JVM中的PermGen被Metaspace取代
  11. 新的Nashron引擎,容許在JVM上容許JS代碼
  12. ……

以上最值得咱們學習的應該就是Lambda表達式、Stream API和新的日期處理類。並非說其餘的就不用去學了,仍是要去了解一下的,而這三個對咱們來講很重要因此必須學習。java

二、Lambda表達式簡介

Lambda表達式本質上是一個匿名函數(方法),它沒有方法名,沒有權限修飾符,沒有返回值聲明。看起來就是一個箭頭(->)從左邊指向右邊。咱們能夠把Lambda表達式理解爲一段能夠傳遞的代碼(將代碼像數據同樣進行傳遞),它的核心思想是將面向對象中的傳遞數據變成傳遞行爲。Lambda表達式的出現就是爲了簡化匿名內部類,讓匿名內部類在方法中做爲參數的使用更加方便(這裏我的理解,可能有誤!Hot smile)。因此使用Lambda表達式可讓咱們的代碼更少,看上去更簡潔,代碼更加靈活。而Lambda表達式做爲一種更緊湊的代碼風格,使得Java的語言表達能力獲得了提高。但也有它的缺點所在,若是Lambda表達式用的很差的話,調試運行和後期維護很是的麻煩。express

三、Lambda表達式語法

Lambda表達式在Java語言中引入了一個新的語法元素和操做符。這個操做符爲"->",該操做符被稱爲Lambda操做符或箭頭操做符,它將Lambda分爲兩個部分:數組

  • 左側:指定了Lambda表達式所須要的全部參數。
  • 右側:指定了Lambda體,即Lambda表達式所要執行的功能。

Java8中的Lambda表達式的基本語法爲:app

(params) -> expression
(params) -> statement
(params) -> { statements }dom

上面只是基本的語法而已,因此看起來比較的簡單,其中的具體使用方法有不少,以下:編輯器

①、無參數,無返回值 void。
() -> System.out.print(「Lambda…」) ;ide

②、有一個參數,但無返回值 void函數

(String s) -> System.out.print(「Lambda…」) ;工具

③、有參數,可是參數數據類型省略,由編譯器推斷,稱爲‘類型推斷’。

(s) –> System.out.print(「Lambda…」) ;

④、若只有一個參數,方法的括號能夠省略,若是多個參數則必須寫上

s–> System.out.print(「Lambda…」) ;

⑤、有參數,且有返回值,若是顯式返回語句時就必須使用花括號「{}」。

(s,t) –> s+t ;

(s,t) –> {return s+t;};

⑥、若是有兩個或兩個以上的參數,而且有多條語句則須要加上「{}」,一條執行語句能夠省略。

(s,t) –> {

        System.out.print(s) ;

        System.out.print(t) ;

        return s+t;

    };

因此到目前爲止,咱們對Lambda表達式有了基本的認識,而前面講了那麼多的理論,就是爲了接下來的快樂時光(寫代碼Be right backBe right back),用幾個簡單的例子來讓咱們好好理解一下Lambda表達式的使用:

public class LambdaTest {
    public static void main(String[] args) {
        //一、建立線程舉例
        //普通寫法
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Ordinary Writing");
            }
        });
        thread.start();

        //Lambda寫法。無參無返回void
        Thread thread1 = new Thread(() -> System.out.println("Lambda Writing"));
        thread1.start();


        //二、排序舉例
        //普通寫法,默認升序
        List<Integer> list = Arrays.asList(26, 65, 13, 79, 6, 123);
        Collections.sort(list,new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1, o2);
            }
        });
        System.out.println(list.toString());

        //Lambda表達式寫法。有參有返回,「類型推斷」
        Collections.sort(list,(o1,o2)-> Integer.compare(o1,o2));
        System.out.println(list.toString());


        //三、遍歷list集合
        //普通寫法
        list.forEach(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println(integer);
            }
        });

        //Lambda表達式寫法。遍歷List集合,forEach方法中的參數是Consumer<? super T> action
        //其中Consumer是Java8新的新出現的函數式接口,下面會講到函數式接口
        list.forEach(alist-> System.out.println(alist));//只有一個參數可省略括號
    }
}

注意:要使用Lambda表達式的前提是函數式接口,因此接下來學習一下函數式接口。

三、函數式接口

函數式接口(Functional Interface)也是Java8中的新特徵。

函數式接口就是隻能有一個抽象方法,同時能夠有多個非抽象方法的接口(Java8中接口能夠定義普通方法)。

這樣接口就能夠被隱式轉換爲Lambda表達式。

若是咱們須要自定義一個函數式接口,就須要用到Java 8提供的一個特殊的註解@FunctionalInterface,該註解會檢查它是不是一個函數式接口,簡單的舉個定義函數式接口的例子,代碼以下:

//定義函數式接口註解
@FunctionalInterface
public interface MyInterface {
    //抽象方法
    void method();
    //void method1();不能再定義

    //默認方法,必須用default修飾
    default void defaultMethod(){
        System.out.println("默認方法...");
    }

    //靜態方法方法
    static void staticMethod(){
        System.out.println("靜態方法...");
    }
}

上面的例子能夠很容易的轉換成以下Lambda表達式:

MyInterface myInterface = () -> System.out.println("MyInterface...");

我須要注意的一點是,接口中的默認方法和靜態方法並不會破壞函數式接口的定義,既不會影響到Lambda表達式。同時也正由於Lambda表達式的引入,因此函數式接口也變得流行起來。

其實早在Java8以前就有不少接口是函數式接口,只是在Java8才正式提出之一特性,例如:

  • java.lang.Runnable
  • java.util.concurrent.Callable
  • java.util.Comparator
  • java.io.FileFilter
  • java.awt.event.ActionListener
  • ……

除了以上這些,在Java 8中還增長了一個新的包:java.util.function。它們裏面包含了經常使用的函數式接口,該包下定義的函數式接口很是多,這裏只列舉比較重要的四個,以下:

(博客園的表格編輯器真的無力吐槽,實在太垃圾了,而後從其餘編輯器編輯好了在截圖過來!Steaming madSteaming madSteaming mad)

image

如下是這四個函數式接口的簡單舉例,理解Lambda表達式以後很容易寫出來:

public class FunctionInterfaceTest {
    public static void main(String[] args) {
        //Consumer<T> : void accept(T t);
        Consumer<Integer> consumer = (a) -> System.out.println("消費型接口..."+a);
        consumer.accept(1000);

        //Function<T, R> : R apply(T t);
        Function<String,String> function = (b) -> b;
        Object apply = function.apply("函數型接口...");
        System.out.println(apply);

        //Predicate<T> : boolean test(T t);
        Predicate predicate = (c) -> c.equals(10);
        boolean test = predicate.test(10);
        System.out.println("判定型接口..."+test);

        //Supplier<T> : T get();
        Supplier supplier=()->(int)(Math.random() * 50);
        Object o = supplier.get();
        System.out.println("供給型接口..."+o);
    }
}

除了上面的這些基本的函數式接口,java.util.function包下還提供了一些針對多個參數的函數式接口,例如BiFunction<T,U,R>,它接收類型爲T和U的對象,而後返回R對象。後面還有BiConsumer<T,U>、BiPredicate<T,U>等。一樣還提供一些針對基本數據類型的特化函數式接口,例如XXXFunction:表示只接收XXX數據類型、XXXToXXXFunction:接收前一個XXX類型,而後返回後一個XXX類型、ToXXXFunction:表示返回值爲XXX類型等等不少這樣的類。(其中XXX只能是Int、Double和Long這三個基本數據類型

若是有須要學習這些API的,能夠自行去java.util.function包下查看學習,這裏很少作描述。

四、方法引用和構造器引用

經過上面Lambda表達式的學習,若是你認爲Lambda表達式已經讓代碼夠簡潔了,那麼這裏還有一個更加簡潔的方法——方法引用。

簡單來講,方法引用就是進一步的簡化Lambda表達式聲明的一種語法糖。也正是由於方法引用實在太簡潔了,因此學習方法引用前必需要對Lambda表達式很是的熟悉,不然學習方法引用會有點吃力Open-mouthed smile

方法引用使用操做符 「::」 將對象或類的名字和方法名分隔開來。方法引用有不少種,它們的語法以下(注意後面是不要寫括號的):

  • 靜態方法引用:ClassName::staticMethodName
  • 實例上的實例方法引用:instanceName::methodName
  • 類上的實例方法引用:ClassName::methodName
  • 父類上的實例方法引用:super::methodName
  • 構造方法引用:ClassName::new
  • 數組構造方法引用:TypeName[]::new

在使用方法引用時要注意一點:引用的函數一定與定義的接口形參和返回值類型一致

先用System.out.println()簡單舉例:

    //System.out.println()簡單舉例
    @Test
    public void test() {
        Consumer<String> consumer = (str) -> System.out.println(str);
        consumer.accept("Lambda表達式");

        PrintStream out = System.out;
        Consumer consumer1 = out::println;
        consumer1.accept("方法引用");
    }
其中Consumer中的accept(T t)方法中是一個參數,返回值是void,PrintStream中的println(Object x)也是一個參數,返回值也是void 。

①、靜態方法引用。語法格式:ClassName::staticMethodName。

    //一、靜態方法用——ClassName::staticMethodName
    @Test
    public void test1() {
        //Lambda表達式
        BiFunction<Double, Double, Double> biFunction = (x, y) -> Math.max(x, y);
        System.out.println(biFunction.apply(11.1, 22.2));
        System.out.println("-------------");
        //方法引用
        BiFunction<Double, Double, Double> biFunction1 = Math::max;
        System.out.println(biFunction1.apply(33.3, 44.4));

        //另一組例子,其中c1與c2是同樣的
        Comparator<Integer> c1 = (x, y) -> Integer.compare(x, y);
        Comparator<Integer> c2 = Integer::compare;
    }

②、實例上的實例方法引用。語法格式:instanceName::methodName。

    //二、實例上的實例方法引用——instanceName::methodName
    @Test
    public void test2(){
        Consumer<String> consumer = (str) -> System.out.println(str);
        Consumer consumer1 = System.out::println;

        Person person = new Person("唐浩榮", 20, "China");
        Supplier supplier = () -> person.getName();
        Supplier supplier1  = person::getName;
    }

③、類上的實例方法引用。語法格式:ClassName::methodName 。

    //三、類上的實例方法引用——ClassName::methodName
    public void test3(){

        BiPredicate<String, String> biPredicate = (x, y) -> x.equals(y);
        BiPredicate<String, String> biPredicate1 = String::equals;

        Function<Person, String> fun = (p) -> p.getName();
        Function<Person, String> fun2 = Person::getName;
    }

④、父類上的實例方法引用。語法格式:super::methodName。

    //四、父類上的實例方法引用——super::methodName
    @Test
    public void test4(){
        Person person=new Person();
        Supplier supplier = () -> super.toString();
        Supplier supplier1 =super::toString;
    }

⑤、構造方法引用。語法格式:ClassName::new。

    //五、構造方法引用——ClassName::new
    @Test
    public void test5() {
        Function<String, String> function = (n) -> new String(n);
        String apply = function.apply("Lambda構造方法");
        System.out.println(apply);

        Function<String, String> function1 = String::new;
        String apply1 = function.apply("構造方法引用");
        System.out.println(apply1);
    }

⑥、數組構造方法引用:TypeName[]::new。

    //六、數組構造方法引用——TypeName[]::new
    @Test
    public void test6() {
        Function<Integer, Integer[]> function = (n) -> new Integer[n];
        //Integer integer[]=new Integer[20];
        Integer[] apply = function.apply(3);
        apply[0] = 1;
        for (Integer integer : apply) {
            System.out.println(integer);
        }
        System.out.println("-----------------");

        Function<Integer, Integer[]> function1 = Integer[]::new;
        Integer[] apply1 = function1.apply(5);
        apply1[0] = 11;
        apply1[1] = 22;
        for (Integer integer : apply1) {
            System.out.println(integer);
        }
    }
相關文章
相關標籤/搜索