只要Lambda表達式的聲明形式與接口相一致,在不少狀況下均可以替換接口。見以下代碼html
Thread t1 = new Thread(new Runnable() { public void run() { System.out.println("hi"); } }); t1.start(); Thread t2 = new Thread(() -> System.out.println("hi")); t2.start();
t1與t2完成相同的功能。t2中的Lambda表達式() -> System.out.println("hi")
與Runnable
接口中的方法public abstract void run();
的形式同樣:java
下面一個例子中express
String[] arr = {"111","22","3"}; Arrays.sort(arr,new Comparator<String>() { @Override public int compare(String o1, String o2) { return o1.length()-o2.length(); } }); Arrays.sort(arr, (o1,o2)->o1.length()-o2.length());
(o1,o2)->o1.length()-o2.length()
的形式與Compartor中public int compare(String o1, String o2)
形式也同樣:數組
Lambda表達式在用法上看起來很像匿名內部類,但其並非匿名內部類。好比,在如下代碼中,在Lambda表達式中不能得到this。oracle
Thread t1 = new Thread(new Runnable() { public void run() { System.out.println(this);//打印匿名內部類 } }); t1.start(); Thread t2 = new Thread(()->{ //System.out.println(this);//沒法編譯經過 }); t2.start();
觀察以下代碼,會發現Lambda表達式中的this與所處環境有關,在這裏this是對外部對象的引用。app
class Foo{ Runnable r1 = ()->{ System.out.println(this); }; Runnable r2 = ()->{ System.out.println(this); }; void test(){ r1.run(); r2.run(); } } //測試代碼以下 Foo foo = new Foo(); System.out.println(foo); foo.test();
從輸出能夠看出,輸出了三個對象其實是同一個對象。ide
Foo@87aac27 Foo@87aac27 Foo@87aac27
Java8中爲Iterable引入了默認實現方法default void forEach(Consumer<? super T> action)
。用法以下:函數
List<String> strs = Arrays.asList("1","222","33"); //List接口間接繼承了Iterable接口,因此strs也會有forEach方法。 strs.forEach(e->System.out.println(e)); //將strs中的每一個元素迭代輸出
爲何能夠將Lambda表達式e->System.out.println(e)
做爲Consumer<? super T> action
類型的參數。
先看一下Consumer的代碼post
@FunctionalInterface public interface Consumer<T> { void accept(T t); //其餘代碼 }
能夠看到void accept(T t)
與e->System.out.println(e)
形式上是一致的,因此能夠將該Lambda表達式做爲輸入參數。
注意:這裏使用了@FunctionalInterface
標註該結構爲函數式接口。也能夠本身建立函數式接口。但要注意函數接口只能有一個抽象方法。
以下代碼能夠經過:學習
@FunctionalInterface interface MyFuncInterface{ void test(); }
但以下代碼卻沒法編譯經過
@FunctionalInterface interface MyFuncInterface{ void test(); void test1(); }
JDK中大量使用了幾個經常使用的標準函數接口。以下所示:
public interface Consumer<T> {//無返回值,消費傳入的T。可接受e->System.out.println(e)或System.out::println void accept(T t); //其餘代碼 } public interface Function<T, R> {//將t轉化爲r。可接受e->Integer.parseInt或Integer::parseInt,將String類型轉化爲int R apply(T t); //其餘代碼 } public interface Predicate<T> {//根據傳入t判斷真假。可接受x->x>3或String::equals(與傳入String對象比較,返回True或False) boolean test(T t); //其餘代碼 } public interface Supplier<T> {//無輸入參數,直接獲取T。可接受()->Arrays.asList("1","2","3"}或 T get(); }
前面出現的System.out::println
就是方法引用。下面的代碼中,strs.forEach的入參類型爲Consumer<? super T> action
,
前面已經提到可使用e->System.out.println(e)
做爲入參,同時咱們知道System.out.println
方法簽名中返回值爲void、
無入參也符合要求,因此咱們可使用System.out::println
來替代e->System.out.println(e)
。注意:要使用::
來引用相關
的方法。
···
List
strs.forEach(e->System.out.println(e));
strs.forEach(System.out::println);
···
方法引用不只能夠引用jdk中已有類的方法,還能夠引用自定義類的相關方法。好比:
class Foo{ <T> void myPrintX(T t) { //必須建立Foo對象才能對非static進行方法引用 System.out.println("x="+t); } static <T> void myPrint(T t) { System.out.println("element="+t); } } //測試代碼 List<String> strs = Arrays.asList("1","222","33"); strs.forEach(Foo::myPrint); strs.forEach(new Foo()::myPrintX);
輸出結果爲
element=1 element=222 element=33 x=1 x=222 x=33
從Java 8起,能夠將集合中數據放入流並進行管道式操做。
管道式操做包含3部分:
中間操做產生的仍是流,那麼經過filter獲得的流還能夠繼續進行filter。
終端操做產生的就不是流了(多是一個List、Map或int等),對一個流進行終端操做後,就不能在進行任何其餘中間操做。
對一個流一旦進行完終端操做,就不能再進行中間操做,運行以下代碼
List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5); Stream<Integer> stream1 = intList.stream().filter(e->e>3); stream1.forEach(System.out::println); Stream<Integer> stream2 = stream1.filter(e->e>4); stream2.forEach(System.out::println);
會提示stream has already been operated upon or closed
。
Predicate接口(boolean test(T t))的做用是根據傳入參數t判斷真假。
Function接口(R apply(T t);)的做用是將T類型的t轉換成R類型。
觀察以下代碼:
List<String> strs = Arrays.asList("1","222", null, "33"); Stream<Integer> stream = strs.stream().filter(e -> e != null).map(Integer::parseInt); stream.forEach(e -> System.out.println(1 + e));
輸出
2 223 34
其中strs.stream().filter(e->e!=null)
的filter方法聲明以下Stream<T> filter(Predicate<? super T> predicate);
,即這裏須要一個
Predicate<? super T> predicate
類型的參數。前面能夠看到Predicate接口中的方法爲boolean test(T t);
,即接受一個t返回
boolean值。e->e!=null
符合這樣的要求。
而strs.stream().filter(e->e!=null).map(Integer::parseInt);
中的map方法聲明以下
Stream<R> map(Function<? super T, ? extends R> mapper)
即這裏須要一個Function<? super T, ? extends R> mapper)
類型的參數。前面能夠看到Function接口中的方法爲R apply(T t);
,
即接受一個類型爲T的元素,將其轉換爲元素R。在這裏實際上就是將String類型元素轉化成int類型元素。Integer::parseInt
恰好
符合這種要求。
從剛纔的例子中,咱們能夠看Function接口的做用能夠將一個類型的轉換成另一個類型。好比
Student s1 = new Student("zhang san"); String name = s1.getName(); //對應的方法引用是Student::getName()
中Student::getName()至關於Student類型轉換成String類型。
以下代碼中,一個Course有不少Student(stuList),每一個Student有均可以getName()。如今想要獲取該Course中某個學生的姓名。
以往的代碼若是使用course.getStuList().get(i).getName()
來獲取某個學生的姓名,看起來代碼風格當然流暢,然而卻沒有正確處理:
course1爲null,get(i)爲null,getName爲null的狀況。那麼必須在整個處理過程編寫大量的判斷null的代碼。
可使用Optional進行改進,即保持了流暢的編碼風格,又能夠正確處理null。
如下代碼中:Optional.ofNullable方法能夠將給定值轉化爲Optional類型(可包含表明給定值的Optional對象,也可包含表明null的Optional對象)
import java.util.ArrayList; import java.util.List; import java.util.Optional; class Student{ private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public Student(String name) { this.name = name; } } class Course{//課程 private String name; private List<Student> stuList; public String getName() { return name; } public void setName(String name) { this.name = name; } public void addStudent(Student... stus) { for (Student student : stus) { stuList.add(student); } } public Student getStu(int i) { return stuList.get(i); } public List<Student> getStuList() { return stuList; } public Course(String name) { this.name = name; stuList = new ArrayList<>(); } } public class TestOptional { public static void main(String[] args) { Course course = new Course("數學"); Student s0 = new Student("s1"); Student s1 = new Student(null); Student s2 = null; course.addStudent(s0, s1, s2); String result = findStudent(course, 0);// orElse,當處理過程當中過程當中有一個null時,即返回orElse中的值 System.out.println("均不爲空狀況下姓名爲:" + result); result = findStudent(course, 1); System.out.println("student的name爲null的狀況:" + result); result = findStudent(course, 2); System.out.println("student爲null的狀況:" + result); Course courseNull = null; result = findStudent(courseNull, 3); System.out.println("course爲null的狀況:" + result); } private static String findStudent(Course course, int i) { Optional<Course> courseNullable = Optional.ofNullable(course); String result = courseNullable.map(e -> e.getStu(i)).map(Student::getName).orElse("查詢不到"); return result; } }
注意:
map(e->e.getStu(0))
與map(Student::getName)
形式都可執行。map(e->e.getStu(2)).map(Student::getName)
流暢的編寫對應代碼。List<String> strs = Arrays.asList("1", "222", null, "33"); IntStream intStream = strs1.stream().filter(e -> e != null).mapToInt(e -> e.length()); intStream.forEach(System.out::println);
mapToInt(e -> e.length())
的mapToInt方法參數類型爲ToIntFunction<? super T> mapper
,查詢源代碼ToIntFunction
包含方法
int applyAsInt(T value);
,即須要一個方法接受T類型輸入參數,而後將其轉化爲int。在這裏,e -> e.length()
起到了這個做用。
代碼的做用就是要將求得流中每一個非null的字符串的長度,而後放入intStream中。
int[] arr = {1,2,3,4,5}; int x = 0; for (int i = 0; i < arr.length; i++) { x = x + arr[i]; } System.out.println(x);
這段代碼每回從數組中取出一個元素,而後與前一個元素相加。最後求的全部元素值的和。這類操做常用,可使用stream中的
reduce方法來簡化實現。
List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5); Stream<Integer> intStream1 = intList.stream(); Optional<Integer> result = intStream1.reduce((a, b) -> a + b); Integer integer = result.get(); System.out.println(integer);// 15
intStream1的reduce方法入參爲BinaryOperator
其繼承自接口BiFunction
,內有一方法R apply(T t, U u);
,將傳入參t與u進行運算,
而後返回一個結果。(a,b)->a+b就知足如此行事,其中a最開始爲流中第1個元素,b爲第2個元素,a+b之後再賦給a,而後b爲第3個元
素,依次類推。
reduce方法還有另一種形式,能夠指定初始值,以下述代碼指定迭代開始的初始值爲10,即a開始爲10,b爲流中第1個元素 。而後
將a+b放入a,b爲流中第2個元素,而後依次類推。
Integer x = intList.stream().reduce(10, (a, b) -> a + b); System.out.println(x);
輸出
25
其中的reduce(10, (a, b) -> a + b)
類型爲T reduce(T identity, BinaryOperator<T> accumulator);
,能夠看到該方法的返回值由identity的類型決定,
在這裏由10來決定,即返回值類型應爲Integer。
Supplier接口(提供者)的定義以下
@FunctionalInterface public interface Supplier<T> { T get(); }
能夠看到,其經過get方法返回一個對象。咱們能夠將Supplier看成一個生成某個對象的工廠。
爲了對流進行一些管道操做的實驗,且由於流不能反覆操做,咱們須要不斷生成內部元素徹底相同的流。
以下代碼中,經過Supplier<Stream<Integer>> factory = () -> Stream.of(1, 2, 3, 4, 5);
聲明Supplier類型變量
factory,經過該factory的get方法就能夠不斷生成流,實際上就是不斷調用() -> Stream.of(1, 2, 3, 4, 5);
。而
這段() -> Stream.of(1, 2, 3, 4, 5);
Lambda表達式形式上是與Supplier標準函數式接口是一致:無入參,有一個返回值。
Supplier<Stream<Integer>> factory = () -> Stream.of(1, 2, 3, 4, 5); Stream<Integer> stream1 = factory.get(); Stream<Integer> stream2 = factory.get(); System.out.println(stream1 == stream2); // false
如何抽取二維數組Integer[][] arr1 = {{1,2},{2,3}}
每一個元素(排除掉重複的元素),即將一、二、3
抽取出來?
可使用flatmap方法。
Integer[][] arr1 = {{1,2},{2,3}}; Stream<Integer[]> t1 = Arrays.stream(arr1);//流中每一個元素是一行(一維數組) Stream<Integer> flatMap = t1.flatMap(Arrays::stream);//扁平化處理後,流中的每個元素是一個Integer flatMap.distinct().forEach(System.out::println); //distinct()排除掉重複的元素
不過這種方法對基本類型數組,如int[][]就不起做用。不知道爲什麼?
更多參考資料見:
本文使用了幾個例子展現了Java 8中經常使用函數式接口在流的管道操做中的應用,Lambda表達式、方法引用與函數式接口之間的關係。但願你們之後在使用流的管道操做時,能夠知其然也知其因此然。
Java學習筆記(第8版) 林信良
Java Tutorial中的Lambda Expressions、Aggregate Operations
Java 8 Stream Tutorial