1. 程式人生 > >深入學習java原始碼之lambda表示式與函式式介面

深入學習java原始碼之lambda表示式與函式式介面

深入學習java原始碼之lambda表示式與函式式介面

@FunctionalInterface
JDK中的函式式介面舉例
java.lang.Runnable,
java.awt.event.ActionListener, 
java.util.Comparator,
java.util.concurrent.Callable
java.util.function包下的介面,如Consumer、Predicate、Supplier等
所謂的函式式介面,當然首先是一個介面,然後就是在這個接口裡面只能有一個抽象方法。
加不加@FunctionalInterface對於介面是不是函式式介面沒有影響,該註解知識提醒編譯器去檢查該介面是否僅包含一個抽象方法
主要用在Lambda表示式和方法引用(實際上也可認為是Lambda表示式)上。
如定義了一個函式式介面如下:

   @FunctionalInterface
    interface GreetingService 
    {
        void sayMessage(String message);
    }

那麼就可以使用Lambda表示式來表示該介面的一個實現(注:JAVA 8 之前一般是用匿名類實現的):

GreetingService greetService1 = message -> System.out.println("Hello " + message);	

進一步的,還可以這樣使用:

class MyStream<T>{
    private List<T> list;
    ...
    public void myForEach(GreetingService <T> consumer){// 1
        for(T t : list){
            consumer.accept(t);
        }
    }
}
MyStream<String> stream = new MyStream<String>();
stream.myForEach(str -> System.out.println(str));// 使用自定義函式介面書寫Lambda表示式

Consumer< T>介面接受一個T型別引數,沒有返回值。

package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Consumer<T> {


    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}
package java.util;

import java.util.function.Consumer;

public interface Iterator<E> {
    boolean hasNext();

    E next();

    default void remove() {
        throw new UnsupportedOperationException("remove");
    }

    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}
    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

Consumer測試程式碼:

@Test
public void test(){
    UserT userT = new UserT("zm");
    //接受一個引數
    Consumer<UserT> userTConsumer = userT1 -> userT1.setName("zmChange");
    userTConsumer.accept(userT);
    logger.info(userT.getName());//輸出zmChange
}

java8以前的實現如下:

@Test
public void test1(){
    UserT userT = new UserT("zm");
    this.change(userT);
    logger.info(userT.getName());//輸出zmChange
}

private void change(UserT userT){
    userT.setName("zmChange");
}

Predicate和Consumer綜合應用

package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}
    @Override
    public boolean removeIf(Predicate<? super E> filter) {
        Objects.requireNonNull(filter);
        // figure out which elements are to be removed
        // any exception thrown from the filter predicate at this stage
        // will leave the collection unmodified
        int removeCount = 0;
        final BitSet removeSet = new BitSet(size);
        final int expectedModCount = modCount;
        final int size = this.size;
        for (int i=0; modCount == expectedModCount && i < size; i++) {
            @SuppressWarnings("unchecked")
            final E element = (E) elementData[i];
            if (filter.test(element)) {
                removeSet.set(i);
                removeCount++;
            }
        }
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }

        // shift surviving elements left over the spaces left by removed elements
        final boolean anyToRemove = removeCount > 0;
        if (anyToRemove) {
            final int newSize = size - removeCount;
            for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
                i = removeSet.nextClearBit(i);
                elementData[j] = elementData[i];
            }
            for (int k=newSize; k < size; k++) {
                elementData[k] = null;  // Let gc do its work
            }
            this.size = newSize;
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
            modCount++;
        }

        return anyToRemove;
    }

為了詳細說明Predicate和Consumer介面,通過一個學生例子:
Student類包含姓名、分數以及待付費用,每個學生可根據分數獲得不同程度的費用折扣。

Student類程式碼:

public class Student {

    String firstName;
    String lastName;
    Double grade;
    Double feeDiscount = 0.0;
    Double baseFee = 2000.0;

    public Student(String firstName, String lastName, Double grade) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.grade = grade;
    }

    public void printFee(){
        Double newFee = baseFee - ((baseFee * feeDiscount)/100);
        System.out.println("The fee after discount: " + newFee);
    }
}

然後分別宣告一個接受Student物件的Predicate介面以及Consumer介面的實現類。
本例子使用Predicate介面實現類的test()方法判斷輸入的Student物件是否擁有費用打折的資格,然後使用Consumer介面的實現類更新輸入的Student物件的折扣。

public class PredicateConsumerDemo {
    public static Student updateStudentFee(Student student, Predicate<Student> predicate, Consumer<Student> consumer){
        if (predicate.test(student)){
            consumer.accept(student);
        }
        return student;
    }

}

Predicate和Consumer介面的test()和accept()方法都接受一個泛型引數。不同的是test()方法進行某些邏輯判斷並返回一個boolean值,而accept()接受並改變某個物件的內部值。updateStudentFee方法的呼叫如下所示:

public class Test {
    public static void main(String[] args) {
        Student student1 = new Student("Ashok","Kumar", 9.5);

        student1 = updateStudentFee(student1,
                //Lambda expression for Predicate interface
                student -> student.grade > 8.5,
                //Lambda expression for Consumer inerface
                student -> student.feeDiscount = 30.0);
        student1.printFee(); //The fee after discount: 1400.0

        Student student2 = new Student("Rajat","Verma", 8.0);
        student2 = updateStudentFee(student2,
                //Lambda expression for Predicate interface
                student -> student.grade >= 8,
                //Lambda expression for Consumer inerface
                student -> student.feeDiscount = 20.0);
        student2.printFee();//The fee after discount: 1600.0

    }
}

UnaryOperator

package java.util.function;

@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {

    static <T> UnaryOperator<T> identity() {
        return t -> t;
    }
}

這個介面繼承Function介面,Funtion介面,定義了一個apply的抽象類,接收一個泛型T物件,並且返回泛型R物件

關於Funtcion的意思以及用法;


package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Function<T, R> {

    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

這個介面,只接收一個泛型引數T,整合Function介面,也就是說,傳入泛型T型別的引數,呼叫apply後,返回也T型別的引數;這個介面定義了一個靜態方法,返回泛型物件的本身;

具體用法

UnaryOperator<Integer> dda = x -> x + 1;
		System.out.println(dda.apply(10));// 11
		UnaryOperator<String> ddb = x -> x + 1;
		System.out.println(ddb.apply("aa"));// aa1

 

匿名內部類

匿名內部類也就是沒有名字的內部類正因為沒有名字,所以匿名內部類只能使用一次,它通常用來簡化程式碼編寫,但使用匿名內部類還有個前提條件:必須繼承一個父類或實現一個介面,但最多隻能繼承一個父類,或實現一個介面。
關於匿名內部類還有如下兩條規則:
    1)匿名內部類不能是抽象類,因為系統在建立匿名內部類的時候,會立即建立內部類的物件。因此不允許將匿名內部類定義成抽象類。
    2)匿名內部類不等定義構造器(構造方法),因為匿名內部類沒有類名,所以無法定義構造器,但匿名內部類可以定義例項初始化塊,
    怎樣判斷一個匿名類的存在啊?看不見名字,感覺只是父類new出一個物件而已,沒有匿名類的名字。

abstract class Father(){
....
}
public class Test{
   Father f1 = new Father(){ .... }  //這裡就是有個匿名內部類
}

一般來說,new 一個物件時小括號後應該是分號,也就是new出物件該語句就結束了。但是出現匿名內部類就不一樣,小括號後跟的是大括號,大括號中是該new 出物件的具體的實現方法。因為我們知道,一個抽象類是不能直接new 的,必須先有實現類了我們才能new出它的實現類。上面的虛擬碼就是表示new 的是Father的實現類,這個實現類是個匿名內部類。
 其實拆分上面的匿名內部類可為:

class SonOne extends Father{
  ...       //這裡的程式碼和上面匿名內部類,大括號中的程式碼是一樣的
}
public class Test{
   Father f1 = new SonOne() ;
}

執行結果:eat something
可以看到,我們直接將抽象類Person中的方法在大括號中實現了,這樣便可以省略一個類的書寫。並且,匿名內部類還能用於介面上

public class JavaTest2 {
	public static void main(String[] args) {
		Person per = new Person() {
			public void say() {// 匿名內部類自定義的方法say
				System.out.println("say方法呼叫");
			}
			@Override
			public void speak() {// 實現介面的的方法speak
				System.out.println("speak方法呼叫");
			}
		};
		per.speak();// 可呼叫
		per.say();// 出錯,不能呼叫
	}
}
 
interface Person {
	public void speak();
}

這裡per.speak()是可以正常呼叫的,但per.say()不能呼叫,為什麼呢?注意Person per = new Person()建立的是Person的物件,而非匿名內部類的物件。其實匿名內部類連名字都沒有,你咋例項物件去呼叫它的方法呢?但繼承父類的方法和實現的方法是可以正常呼叫的,本例子中,匿名內部類實現了介面Person的speak方法,因此可以藉助Person的物件去呼叫。
若你確實想呼叫匿名內部類的自定義的方法say(),當然也有方法:
 (1)類似於speak方法的使用,先在Person介面中宣告say()方法,再在匿名內部類中覆寫此方法。
 (2)其實匿名內部類中隱含一個匿名物件,通過該方法可以直接呼叫say()和speak()方法;程式碼修改如下:

public class JavaTest2 {
	public static void main(String[] args) {
		new Person() {
			public void say() {// 匿名內部類自定義的方法say
				System.out.println("say方法呼叫");
			}
 
			@Override
			public void speak() {// 實現介面的的方法speak
				System.out.println("speak方法呼叫");
			}
		}.say();// 直接呼叫匿名內部類的方法
	}
}
interface Person {
	public void speak();
}

 

λ表示式本質上是一個匿名方法

λ表示式主要用於替換以前廣泛使用的內部匿名類,各種回撥,比如事件響應器、傳入Thread類的Runnable等。

Lambda表示式的語法
基本語法:
(parameters) -> expression

(parameters) ->{ statements; }

    public int add(int x, int y) {
        return x + y;
    }

轉成λ表示式後是這個樣子:
    
    (int x, int y) -> x + y;

引數型別也可以省略,Java編譯器會根據上下文推斷出來:

    (x, y) -> x + y; //返回兩數之和
 
或者

    (x, y) -> { return x + y; } //顯式指明返回值

下面這個例子裡的λ表示式沒有引數,也沒有返回值(相當於一個方法接受0個引數,返回void,其實就是Runnable裡run方法的一個實現):

    () -> { System.out.println("Hello Lambda!"); }

如果只有一個引數且可以被Java推斷出型別,那麼引數列表的括號也可以省略:

  c -> { return c.size(); }

 Lambda表示式的一個重要用法是簡化某些匿名內部類(Anonymous Classes)的寫法
 實際上Lambda表示式並不僅僅是匿名內部類的語法糖,JVM內部是通過invokedynamic指令來實現Lambda表示式的。
 Lambda表示式並不能取代所有的匿名內部類,只能用來取代函式介面(Functional Interface)的簡寫
 如果需要新建一個執行緒,一種常見的寫法是這樣:

用匿名內部類的方式來建立一個執行緒

  JDK7 匿名內部類寫法

new Thread(new Runnable(){// 介面名
    @Override
    public void run(){// 方法名
        System.out.println("Thread run()");
    }
}).start();

上述程式碼給Tread類傳遞了一個匿名的Runnable物件,過載Runnable介面的run()方法來實現相應邏輯。這是JDK7以及之前的常見寫法。匿名內部類省去了為類起名字的煩惱,但還是不夠簡化,在Java 8中可以簡化為如下形式:
 JDK8 Lambda表示式寫法

new Thread(
        () -> System.out.println("Thread run()")// 省略介面名和方法名
).start();

上述程式碼跟匿名內部類的作用是一樣的,但比匿名內部類更進一步。這裡連線口名和函式名都一同省掉了,寫起來更加神清氣爽。如果函式體有多行,可以用大括號括起來,就像這樣:

new Thread(
        () -> {
            System.out.print("Hello");
            System.out.println(" Hoolee");
        }
).start();
// 使用匿名內部類  
Runnable race1 = new Runnable() {  
    @Override  
    public void run() {  
        System.out.println("Hello world !");  
    }  
};  
  
// 使用 lambda expression  
Runnable race2 = () -> System.out.println("Hello world !");  
   
// 直接呼叫 run 方法(沒開新執行緒哦!)  
race1.run();  
race2.run(); 

介面式的匿名內部類是實現了一個介面的匿名類。而且只能實現一個介面。

interface People {  
    public void eat();  
}  
   
public class Demo {  
    public static void main(String[] args) {  
        People p = new People() {             
            public void eat() {  
                System.out.println("I can eat ");  
            }  
        };  
        p.eat();  
    }  
}  

 

Lambda表示式帶參函式的簡寫
如果要給一個字串列表通過自定義比較器,按照字串長度進行排序,Java 7的書寫形式如下:

 JDK7 匿名內部類寫法

List<String> list = Arrays.asList("I", "love", "you", "too");
Collections.sort(list, new Comparator<String>(){// 介面名
    @Override
    public int compare(String s1, String s2){// 方法名
        if(s1 == null)
            return -1;
        if(s2 == null)
            return 1;
        return s1.length()-s2.length();
    }
});

上述程式碼通過內部類過載了Comparator介面的compare()方法,實現比較邏輯。採用Lambda表示式可簡寫如下:

 JDK8 Lambda表示式寫法

List<String> list = Arrays.asList("I", "love", "you", "too");
Collections.sort(list, (s1, s2) ->{// 省略引數表的型別
    if(s1 == null)
        return -1;
    if(s2 == null)
        return 1;
    return s1.length()-s2.length();
});

上述程式碼跟匿名內部類的作用是一樣的。除了省略了介面名和方法名,程式碼中把引數表的型別也省略了。這得益於javac的型別推斷機制,編譯器能夠根據上下文資訊推斷出引數的型別,當然也有推斷失敗的時候,這時就需要手動指明引數型別了。注意,Java是強型別語言,每個變數和物件都必需有明確的型別。

能夠使用Lambda的依據是必須有相應的函式介面(函式介面,是指內部只有一個抽象方法的介面)。這一點跟Java是強型別語言吻合,也就是說你並不能在程式碼的任何地方任性的寫Lambda表示式。實際上Lambda的型別就是對應函式介面的型別。Lambda表示式另一個依據是型別推斷機制,在上下文資訊足夠的情況下,編譯器可以推斷出引數表的型別,而不需要顯式指名。Lambda表達更多合法的書寫形式如下:

Lambda表示式的書寫形式

Runnable run = () -> System.out.println("Hello World");// 1
ActionListener listener = event -> System.out.println("button clicked");// 2
Runnable multiLine = () -> {// 3 程式碼塊
    System.out.print("Hello");
    System.out.println(" Hoolee");
};
BinaryOperator<Long> add = (Long x, Long y) -> x + y;// 4
BinaryOperator<Long> addImplicit = (x, y) -> x + y;// 5 型別推斷

1展示了無參函式的簡寫;2處展示了有參函式的簡寫,以及型別推斷機制;3是程式碼塊的寫法;4和5再次展示了型別推斷機制。

 

集合類(包括List)現在都有一個forEach方法,對元素進行迭代(遍歷),所以我們不需要再寫for迴圈了。forEach方法接受一個函式介面Consumer做引數,所以可以使用λ表示式。

Java8之前集合類的迭代(Iteration)都是外部的,即客戶程式碼。而內部迭代意味著改由Java類庫來進行迭代,而不是客戶程式碼。

    for(Object o: list) { // 外部迭代
        System.out.println(o);
    }

可以寫成:

   list.forEach(o -> {System.out.println(o);}); //forEach函式實現內部迭代

Iterable介面新增了一個forEach(Consumer action)預設方法,該方法所需引數的型別是一個函式式介面,而Iterable介面是Collection介面的父介面,因此Collection集合也可以直接呼叫該方法。

  當程式呼叫Iterable的forEach(Consumer action)遍歷集合元素時,程式會依次將集合元素傳給Consumer的accept(T t)方法(該介面中唯一的抽象方法)。正因為Consumer是函式式介面,因此可以使用Lambda表示式來遍歷集合元素。

        //建立一個集合
        Collection<String> c=new HashSet<String>();
        //需要泛型,否則提示警告:使用了未經檢查或不安全的操作,可以直接執行
        c.add("ni");
        c.add("hao");
        c.add("java");
        c.forEach(new Consumer<String>(){
            public void accept(String t){
                System.out.println("集合元素是:"+t);
            }
        });
                //不使用lambda表示式和兩種使用lambda表示式方式
        c.forEach(t->System.out.println("集合元素是:"+t));
        c.forEach(System.out::println);

Iterator新增了一個forEachRemaining(Consumer action)方法,該方法所需的Consumer引數同樣也是函式式介面。當程式呼叫Iterator的forEachRemaining(Consumer action)遍歷集合元素時,程式會一次將集合元素傳遞給Consumer的accept(T t)方法(該介面中唯一的抽象方法)。

        Collection<String> c=new HashSet<String>();
        c.add("ni");
        c.add("hao");
        c.add("java");
        Iterator<String> it=c.iterator();//使用泛型
        it.forEachRemaining(System.out::println);

遍歷陣列

String[] atp = {"Rafael Nadal", "Novak Djokovic",  
       "Stanislas Wawrinka",  
       "David Ferrer","Roger Federer",  
       "Andy Murray","Tomas Berdych",  
       "Juan Martin Del Potro"};  
List<String> players =  Arrays.asList(atp);  
  
// 以前的迴圈方式  
for (String player : players) {  
     System.out.print(player + "; ");  
}  
  
// 使用 lambda 表示式以及函式操作(functional operation)  
players.forEach((player) -> System.out.print(player + "; "));  
   
// 在 Java 8 中使用雙冒號操作符(double colon operator)  
players.forEach(System.out::println);  

Java8的流

	    public default IntStream chars() {
        class CharIterator implements PrimitiveIterator.OfInt {
            int cur = 0;

            public boolean hasNext() {
                return cur < length();
            }

            public int nextInt() {
                if (hasNext()) {
                    return charAt(cur++);
                } else {
                    throw new NoSuchElementException();
                }
            }

            @Override
            public void forEachRemaining(IntConsumer block) {
                for (; cur < length(); cur++) {
                    block.accept(charAt(cur));
                }
            }
        }

        return StreamSupport.intStream(() ->
                Spliterators.spliterator(
                        new CharIterator(),
                        length(),
                        Spliterator.ORDERED),
                Spliterator.SUBSIZED | Spliterator.SIZED | Spliterator.ORDERED,
                false);
    }
	
    public default IntStream codePoints() {
        class CodePointIterator implements PrimitiveIterator.OfInt {
            int cur = 0;

            @Override
            public void forEachRemaining(IntConsumer block) {
                final int length = length();
                int i = cur;
                try {
                    while (i < length) {
                        char c1 = charAt(i++);
                        if (!Character.isHighSurrogate(c1) || i >= length) {
                            block.accept(c1);
                        } else {
                            char c2 = charAt(i);
                            if (Character.isLowSurrogate(c2)) {
                                i++;
                                block.accept(Character.toCodePoint(c1, c2));
                            } else {
                                block.accept(c1);
                            }
                        }
                    }
                } finally {
                    cur = i;
                }
            }

            public boolean hasNext() {
                return cur < length();
            }

            public int nextInt() {
                final int length = length();

                if (cur >= length) {
                    throw new NoSuchElementException();
                }
                char c1 = charAt(cur++);
                if (Character.isHighSurrogate(c1) && cur < length) {
                    char c2 = charAt(cur);
                    if (Character.isLowSurrogate(c2)) {
                        cur++;
                        return Character.toCodePoint(c1, c2);
                    }
                }
                return c1;
            }
        }

        return StreamSupport.intStream(() ->
                Spliterators.spliteratorUnknownSize(
                        new CodePointIterator(),
                        Spliterator.ORDERED),
                Spliterator.ORDERED,
                false);
    }
public final class StreamSupport {

    public static IntStream intStream(Spliterator.OfInt spliterator, boolean parallel) {
        return new IntPipeline.Head<>(spliterator,
                                      StreamOpFlag.fromCharacteristics(spliterator),
                                      parallel);
	}
    public static IntStream intStream(Supplier<? extends Spliterator.OfInt> supplier,
                                      int characteristics,
                                      boolean parallel) {
        return new IntPipeline.Head<>(supplier,
                                      StreamOpFlag.fromCharacteristics(characteristics),
                                      parallel);
    }
}

public final class Spliterators {

        private Spliterators() {}
	    public static <T> Spliterator<T> spliterator(Object[] array,
                                                 int additionalCharacteristics) {
        return new ArraySpliterator<>(Objects.requireNonNull(array),
                                      additionalCharacteristics);
		}						
		public static <T> Spliterator<T> spliterator(Object[] array, int fromIndex, int toIndex,
                                                 int additionalCharacteristics) {
        checkFromToBounds(Objects.requireNonNull(array).length, fromIndex, toIndex);
        return new ArraySpliterator<>(array, fromIndex, toIndex, additionalCharacteristics);
       }						
        public static <T> Spliterator<T> spliterator(Iterator<? extends T> iterator,
                                                 long size,
                                                 int characteristics) {
        return new IteratorSpliterator<>(Objects.requireNonNull(iterator), size,
                                         characteristics);
    }
	    public static <T> Spliterator<T> spliteratorUnknownSize(Iterator<? extends T> iterator,
                                                            int characteristics) {
        return new IteratorSpliterator<>(Objects.requireNonNull(iterator), characteristics);
    }
}