1. 程式人生 > >Java8新特性之lambda表示式

Java8新特性之lambda表示式

     在瞭解lambda表示式之前,必須先了解函數語言程式設計、函式式介面和default方法。在Java8出來之前,別的程式語言中已經有了函數語言程式設計這種概念,只不過後來沒落了,不過在最近幾年又火起來了。在Java8中大力提倡我們使用函數語言程式設計,並且更新了一些類(java.utl.function),讓我們根據這些類來程式設計。

一、有關函數語言程式設計、函式式介面和default方法的介紹

1.什麼是函數語言程式設計?

    函數語言程式設計是種程式設計正規化,與之相對應的是指令式程式設計,下面舉個例子來對比一下,比如計算(1+1)*2-3的結果:

在指令式程式設計中可以這樣寫: int a = (1+1)*2-3;為了體現命令式和函式式的區別,用以下的方式方便理解:

int a = 1 + 1 ;
int b = a * 2 ;
int c = b - 3 ;

函數語言程式設計:

subtract(multiply(add(1,1),2),3);

通過上面這個例子可以看出,函數語言程式設計大量使用的是函式+表示式,不使用語句,因此函數語言程式設計的程式碼會看起來很精簡。

2.什麼是函式式介面?

    函數語言程式設計是一個很早就提出來的概念,不過函式式介面是Java8中新增的,那什麼是函式式介面呢?在一個介面中只有一個抽象方法的介面被稱為函式式介面,比如說java.lang.Runnable、java.util.concurrent.Callable。對比看這兩個介面可以發現,介面定以前都有一個@FunctionalInterface

註解,這是Java8中新增的註解,用來標註一個函式式介面

    

自定義一個函式式介面:

/**
 * 函式式介面
 */
@FunctionalInterface
public interface MyInterface {
    void method01();   //修飾符預設為public abstract,省略不寫
}

3.default方法又是什麼?

      這個方法也是Java8中新增的方法,在jdk8之前,介面中的方法必須都是抽象的,在jdk8中允許介面中定義非抽象方法,在介面中的非抽象方法使用default修飾即可,比如在jdk8中新增一個函式式介面:java.util.function。Java8中打破了介面中的方法必須都是抽象這一規範,有一個好處就是可以提高程式的相容性,在java.lang.Iterable介面中新增了foreach方法,該方法就是使用default修飾的。

在介面中定義default方法可以變相的讓Java支援“多繼承”。

  下面程式碼是jdk8中java.lang.Iterable的原始碼(一共有3個方法, 除了iterator(),另外兩個方法都是被default修飾的):

package java.lang;

import java.util.Iterator;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;

/**
 * Implementing this interface allows an object to be the target of
 * the "for-each loop" statement. See
 * <strong>
 * <a href="{@docRoot}/../technotes/guides/language/foreach.html">For-each Loop</a>
 * </strong>
 *
 * @param <T> the type of elements returned by the iterator
 *
 * @since 1.5
 * @jls 14.14.2 The enhanced for statement
 */
public interface Iterable<T> {
    /**
     * Returns an iterator over elements of type {@code T}.
     *
     * @return an Iterator.
     */
    Iterator<T> iterator();

    /**
     * Performs the given action for each element of the {@code Iterable}
     * until all elements have been processed or the action throws an
     * exception.  Unless otherwise specified by the implementing class,
     * actions are performed in the order of iteration (if an iteration order
     * is specified).  Exceptions thrown by the action are relayed to the
     * caller.
     *
     * @implSpec
     * <p>The default implementation behaves as if:
     * <pre>{@code
     *     for (T t : this)
     *         action.accept(t);
     * }</pre>
     *
     * @param action The action to be performed for each element
     * @throws NullPointerException if the specified action is null
     * @since 1.8
     */
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

    /**
     * Creates a {@link Spliterator} over the elements described by this
     * {@code Iterable}.
     *
     * @implSpec
     * The default implementation creates an
     * <em><a href="Spliterator.html#binding">early-binding</a></em>
     * spliterator from the iterable's {@code Iterator}.  The spliterator
     * inherits the <em>fail-fast</em> properties of the iterable's iterator.
     *
     * @implNote
     * The default implementation should usually be overridden.  The
     * spliterator returned by the default implementation has poor splitting
     * capabilities, is unsized, and does not report any spliterator
     * characteristics. Implementing classes can nearly always provide a
     * better implementation.
     *
     * @return a {@code Spliterator} over the elements described by this
     * {@code Iterable}.
     * @since 1.8
     */
    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}

自定義一個由default方法的介面:

@FunctionalInterface
public interface MyInterface {
    double method01(double a, double b);  //抽象方法

    default String method02() {   //default方法
        return "hello";
    }
    default int method03(int a) {  //default方法
        return a*a;
    }
}

二、lambda表示式的介紹

1.lambda表示式是啥?

     可以將lambda表示式看作是一個匿名方法,lambda表示式只能用於函式式介面。主要由()->{}這些符號組成,()裡放的是引數(列表),{}裡放的是程式碼塊(和返回值)。使用lambda表示式可以編寫出比匿名內部類更簡潔的程式碼。

使用lambda表示式的例子(用到了上面的例子中函式式介面MyInterface):

public class TestLambda01 {
    public static void main(String[] args) {
        //不使用lambda表示式,使用匿名內部類建立MyInterface的物件
        MyInterface mi1 = new MyInterface() {
            @Override
            public double method01(double a, double b) {
                System.out.println("no lambda:");
                return a+b;
            }
        };

        //使用lambda表示式(介面一定要是函式式介面)
        MyInterface mi2 = (double a, double b) -> {
            System.out.println("lambda:");
            return a+b;
        };
        //使用lambda表示式,省略引數型別(編譯器都可以從上下文環境中推斷出lambda表示式的引數型別,因此可以省略引數型別)
        MyInterface mi3 = (a, b) -> {
            System.out.println("lambda:");
            return a+b;
        };
        //使用lambda表示式,省略return關鍵字(程式碼塊只有一行程式碼時,{}可以省略)
        MyInterface mi4 = (a, b) -> a+b;
    }
}

使用lambda表示式建立執行緒的例子:

public class TestLambda02 {
    public static void main(String[] args) {
        //不使用lambda表示式建立執行緒
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("no lambda");
            }
        }).start();

        //使用lambda表示式
        //因為Thread類中接收Runnable型別的物件,所以編譯器會識別出lambda表示式是Runnable物件
        new Thread(() -> System.out.println("lambda")).start();
    }
}

lambda表示式為什麼只能用於函式式介面中?

    lambda表示式實際上就是重寫了介面中的抽象方法,在函式式介面中只有一個抽象方法,此時編譯器會認定重寫的就是該唯一的抽象方法。倘若一個介面中有多個抽象方法,而lambda表示式是沒有匿名的,編譯器就無法判斷其重寫的是哪個抽象方法了。

2. forEach方法

    在jdk8中的java.lang.Iterable介面中新增了非抽象的forEach方法,可以使用該方法配合lambda表示式來遍歷集合。

// 使用forEach方法+lambda表示式遍歷集合

public class TestLambda03 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>(6);
        list.add(3);
        list.add(8);
        list.add(2);
        list.add(7);
        list.add(5);
        list.add(8);

        //以前遍歷集合的一種方式
        for (Integer i:list) {
            System.out.println(i);
        }

        //使用forEach方法+lambda表示式遍歷集合
        //list.forEach((i) -> System.out.println(i));
        //只有一個引數時,可以省略()
        list.forEach(i -> System.out.println(i));
    }
}

    以上使用lambda表示式之後已經夠簡潔,但是上述的最後一條語句還可以簡介,當lambda表示式只有一行程式碼時,可以使用方法的引用,上述可以寫成:

list.forEach(System.out :: println);

3.方法引用

    方法引用主要是用來簡寫lambda表示式,有些lambda表示式裡面僅僅是執行一個方法呼叫,這時使用方法引用可以簡寫lambda表示式。

四種類型的方法引用: ①靜態方法引用類名::方法名某個物件的引用物件變數名::方法名特定類的任意物件的方法引用類名::方法名構造方法類名::new

前三種比較好理解,就舉一個靜態方法引用的例子:

public class TestLambda04 {
    public static void main(String[] args) {
        Integer[] arr = {4,8,2,9,6,5,1}; //對該陣列排序
        //不使用lambda表示式
        Arrays.sort(arr, new Comparator<Integer>() {
            @Override
            public int compare(Integer x, Integer y) {
                return Integer.compare(x,y);
            }
        });

        //lambda表示式
        Arrays.sort(arr,(x,y) -> Integer.compare(x,y));
        //上述只有 Integer.compare(x,y)一條語句,所以可以使用方法的引用,改為:
        Arrays.sort(arr,Integer :: compare);
    }
}

構造方法引用例子:

import java.util.function.Supplier;

public class Car {
    public static Car buy(Supplier<Car> s) {
        return s.get(); //通過get方法獲取傳入的Car型別的物件
    }
}
// 構造方法引用
public class Testlambda05 {
    public static void main(String[] args) {
        Car car = Car.buy(Car::new);
    }
}