深入學習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);
}
}