Lambda 表達(dá)式&函數(shù)式接口
Lambda表達(dá)式好像已經(jīng)出現(xiàn)很久了好像大部分人還是比較傾向于使用內(nèi)部類
(老程序員表示Lambda表達(dá)式影響可讀性,但是在很多組件中使用Lambda表達(dá)式還是很有必要的)
Lamdba表達(dá)式和接口是分不開的,JAVA8改動(dòng)Lambda表達(dá)式的同時(shí)也新增了函數(shù)式接口
函數(shù)式接口
Functional Interface的定義很簡單:任何包含唯一一個(gè)抽象方法的接口都可以稱之為函數(shù)式接口。
但是函數(shù)式接口中還可以存在簽名與Object的public方法相同的接口(畢竟最終父類時(shí)Object),并且可以存在靜態(tài)方法。
@FunctionalInterface
interface FunctionalInterfaceWithStaticMethod {
static int sum(int[] array) {
return Arrays.stream(array).reduce((a, b) -> a+b).getAsInt();
}
boolean equals(Object obj);
void apply();
}
//這依舊是一個(gè)函數(shù)式接口
為了讓編譯器幫助我們確保一個(gè)接口滿足函數(shù)式接口的要求,Java8提供了@FunctionalInterface注解。
另外JDK中已有的一些接口本身就是函數(shù)式接口,如Runnable。 JDK 8中又增加了java.util.function包, 提供了常用的函數(shù)式接口。
舉個(gè)函數(shù)式接口的栗子
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
//Runnable接口提供了一個(gè)run()抽象方法
在java8之前,你可以通過匿名內(nèi)部類來實(shí)現(xiàn)對這個(gè)接口的調(diào)用,像下面這樣
Thread thread = new Thread(new Runnable() {
public void run() {
System.out.println("In another thread");
}
});
但是如果我們想要我們的代碼更加優(yōu)雅,我們應(yīng)該使用Lambda表達(dá)式,當(dāng)我們使用Lambda表達(dá)式剛剛代碼就可以這樣寫:
Thread thread = new Thread(() ->
System.out.println("In another thread"));
Lambda表達(dá)式
Lambda表達(dá)式在Java8中最大的改動(dòng)是
它允許把函數(shù)作為一個(gè)方法的參數(shù)(函數(shù)作為參數(shù)傳遞進(jìn)方法中)
以下是lambda表達(dá)式的重要特征:
- 可選類型聲明:不需要聲明參數(shù)類型,編譯器可以統(tǒng)一識(shí)別參數(shù)值。
- 可選的參數(shù)圓括號(hào):一個(gè)參數(shù)無需定義圓括號(hào),但多個(gè)參數(shù)需要定義圓括號(hào)。
- 可選的大括號(hào):如果主體包含了一個(gè)語句,就不需要使用大括號(hào)。
- 可選的返回關(guān)鍵字:如果主體只有一個(gè)表達(dá)式返回值則編譯器會(huì)自動(dòng)確定返回值
還是先上栗子:
public class Java8Tester {
public static void main(String args[]){
Java8Tester tester = new Java8Tester();
// 類型聲明
MathOperation addition = (int a, int b) -> a + b;
// 不用類型聲明
MathOperation subtraction = (a, b) -> a - b;
// 大括號(hào)中的返回語句
MathOperation multiplication = (int a, int b) -> { return a * b; };
// 沒有大括號(hào)及返回語句
MathOperation division = (int a, int b) -> a / b;
System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
System.out.println("10 / 5 = " + tester.operate(10, 5, division));
// 不用括號(hào)
GreetingService greetService1 = message ->
System.out.println("Hello " + message);
// 用括號(hào)
GreetingService greetService2 = (message) ->
System.out.println("Hello " + message);
greetService1.sayMessage("Runoob");
greetService2.sayMessage("Google");
}
interface MathOperation {
int operation(int a, int b);
}
interface GreetingService {
void sayMessage(String message);
}
private int operate(int a, int b, MathOperation mathOperation){
return mathOperation.operation(a, b);
}
}
變量作用域
- lambda 表達(dá)式只能引用標(biāo)記了 final 的外層局部變量,這就是說不能在 lambda 內(nèi)部修改定義在域外的局部變量,否則會(huì)編譯錯(cuò)誤。(其實(shí)也可以不聲明final)
int num = 1;
Converter<Integer, String> s =
(param) -> String.valueOf(param + num);
num = 5;
//編譯會(huì)出錯(cuò)
- 在 Lambda 表達(dá)式當(dāng)中不允許聲明一個(gè)與局部變量同名的參數(shù)或者局部變量。
String first = "";
Comparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length());
//編譯會(huì)出錯(cuò)
方法引用
方法引用通過方法的名字來指向一個(gè)方法。
方法引用使用一對冒號(hào) ::
內(nèi)容比較簡單并且Java核心已經(jīng)有講過
所以直接上栗子,我們在 Car 類中定義了 4 個(gè)方法作為例子來區(qū)分 Java 中 4 種不同方法的引用。
package com.runoob.main;
@FunctionalInterface
public interface Supplier<T> {
T get();
}
class Car {
public static Car create(final Supplier<Car> supplier) {
return supplier.get();
}
public static void collide(final Car car) {
System.out.println("Collided " + car.toString());
}
public void follow(final Car another) {
System.out.println("Following the " + another.toString());
}
public void repair() {
System.out.println("Repaired " + this.toString());
}
}
- 構(gòu)造器引用
final Car car = Car.create( Car::new );
final List< Car > cars = Arrays.asList( car );
- 靜態(tài)方法引用
cars.forEach( Car::collide );
//Class< T >::new
- 特定類的任意對象的方法引用
//Class::static_method
cars.forEach( Car::repair );
//Class::method
- 特定對象的方法引用
final Car police = Car.create( Car::new );
cars.forEach( police::follow );
//instance::method
Java Stream
Stream是 Java 8新增加的類,用來補(bǔ)充集合類。
既然是補(bǔ)充集合類,那么先來列舉一下它們的區(qū)別(主要是與迭代器的區(qū)別):
1.不儲(chǔ)存數(shù)據(jù) 流是基于數(shù)據(jù)源的對象,它本身不存儲(chǔ)數(shù)據(jù)元素,而是通過管道將數(shù)據(jù)源的元素傳遞給操作。
2.函數(shù)式編程 流的操作不會(huì)修改數(shù)據(jù)源,例如filter不會(huì)將數(shù)據(jù)源中的數(shù)據(jù)刪除。
3.延遲操作 流的很多操作如filter,map等中間操作是延遲執(zhí)行的,只有到終點(diǎn)操作才會(huì)將操作順序執(zhí)行。
4.可以解綁 對于無限數(shù)量的流,有些操作是可以在有限的時(shí)間完成的,比如limit(n) 或 findFirst(),這些操作可是實(shí)現(xiàn)"短路"(Short-circuiting),訪問到有限的元素后就可以返回。
5.純消費(fèi) 流的元素只能訪問一次,類似Iterator,操作沒有回頭路,如果你想從頭重新訪問流的元素,對不起,你得重新生成一個(gè)新的流。
在具體講解方法之前,還是先來看一個(gè)栗子:
List<Integer> a = {1,2,3};
List<Integer> b = a.stream()
.filter(i -> i >= 2 )
.collect(Collectors.toList());
我們可以把以上代碼分為三部分來具體分析:
1.創(chuàng)建Stream
- 通過集合的stream()方法或者parallelStream(),比如Arrays.asList(1,2,3).stream()。
- 通過Arrays.stream(Object[])方法, 比如Arrays.stream(new int[]{1,2,3})。
- 使用流的靜態(tài)方法,比如Stream.of(Object[]), IntStream.range(int, int) 或者 Stream.iterate(Object, UnaryOperator),如Stream.iterate(0, n -> n *
- BufferedReader.lines()從文件中獲得行的流。
- Files類的操作路徑的方法,如list、find、walk等。
- 隨機(jī)數(shù)流Random.ints()。
- 更底層的使用StreamSupport,它提供了將Spliterator轉(zhuǎn)換成流的方法。
2.中間操作
- distinct
distinct保證輸出的流中包含唯一的元素,它是通過Object.equals(Object)來檢查是否包含相同的元素。
List<String> l = Stream.of("a","b","c","b")
.distinct()
.collect(Collectors.toList());
System.out.println(l); //[a, b, c]
- filter
filter返回的流中只包含滿足斷言(predicate)的數(shù)據(jù)。
下面的代碼返回流中的偶數(shù)集合。
List<Integer> l = IntStream.range(1,10)
.filter( i -> i % 2 == 0)
.boxed()
.collect(Collectors.toList());
System.out.println(l); //[2, 4, 6, 8]
- map
map方法將流中的元素映射成另外的值,新的值類型可以和原來的元素的類型不同。
map( c -> c*2)
- flatmap
flatmap方法將映射后的流的元素全部放入到一個(gè)新的流中。
String poetry = "Where, before me, are the ages that have gone?\n" +
"And where, behind me, are the coming generations?\n" +
"I think of heaven and earth, without limit, without end,\n" +
"And I am all alone and my tears fall down.";
Stream<String> lines = Arrays.stream(poetry.split("\n"));
Stream<String> words = lines.flatMap(line -> Arrays.stream(line.split(" ")));
- limit
limit方法指定數(shù)量的元素的流。對于串行流,這個(gè)方法是有效的,這是因?yàn)樗恍璺祷厍皀個(gè)元素即可,但是對于有序的并行流,它可能花費(fèi)相對較長的時(shí)間,如果你不在意有序,可以將有序并行流轉(zhuǎn)換為無序的,可以提高性能。
List<Integer> l = IntStream.range(1,100).limit(5)
.boxed()
.collect(Collectors.toList());
System.out.println(l);//[1, 2, 3, 4, 5]
- peek
peek方法方法會(huì)使用一個(gè)Consumer消費(fèi)流中的元素,但是返回的流還是包含原來的流中的元素。
String[] arr = new String[]{"a","b","c","d"};
Arrays.stream(arr)
.peek(System.out::println) //a,b,c,d
.count();
- sorted
sorted()將流中的元素按照自然排序方式進(jìn)行排序,如果元素沒有實(shí)現(xiàn)Comparable,則終點(diǎn)操作執(zhí)行時(shí)會(huì)拋出java.lang.ClassCastException異常。
sorted(Comparator<? super T> comparator)可以指定排序的方式。
對于有序流,排序是穩(wěn)定的。對于非有序流,不保證排序穩(wěn)定。
String[] arr = new String[]{"b_123","c+342","b#632","d_123"};
List<String> l = Arrays.stream(arr)
.sorted((s1,s2) -> {
if (s1.charAt(0) == s2.charAt(0))
return s1.substring(2).compareTo(s2.substring(2));
else
return s1.charAt(0) - s2.charAt(0);
})
.collect(Collectors.toList());
System.out.println(l); //[b_123, b#632, c+342, d_123]
- skip
skip返回丟棄了前n個(gè)元素的流,如果流中的元素小于或者等于n,則返回空的流。
3.終點(diǎn)操作
- Match
分為三種具體方法,用來檢查流中的元素是否滿足段言。
public boolean allMatch(Predicate<? super T> predicate)
public boolean anyMatch(Predicate<? super T> predicate)
public boolean noneMatch(Predicate<? super T> predicate)
count
count方法返回流中的元素的數(shù)量。collect
這是一個(gè)比較重要的終點(diǎn)操作,實(shí)現(xiàn)最終對處理過的流的收集。輔助類Collectors提供了很多的collector,可以滿足我們?nèi)粘5男枨?,你也可以?chuàng)建新的collector實(shí)現(xiàn)特定的需求。find
findAny()返回任意一個(gè)元素,如果流為空,返回空的Optional,對于并行流來說,它只需要返回任意一個(gè)元素即可,所以性能可能要好于findFirst(),但是有可能多次執(zhí)行的時(shí)候返回的結(jié)果不一樣。
findFirst()返回第一個(gè)元素,如果流為空,返回空的Optional。forEach
forEach遍歷流的每一個(gè)元素,執(zhí)行指定的action。
Stream.of(1,2,3,4,5).forEach(System.out::println);
max/min
max返回流中的最大值,
min返回流中的最小值。toArray()
將流中的元素放入到一個(gè)數(shù)組中。
多說一句~~~~~~~~~
流可以從非線程安全的集合中創(chuàng)建,當(dāng)流的管道執(zhí)行的時(shí)候,非concurrent數(shù)據(jù)源不應(yīng)該被改變。下面的代碼會(huì)拋出java.util.ConcurrentModificationException異常:
List<String> l = new ArrayList(Arrays.asList("one", "two"));
Stream<String> sl = l.stream();
sl.forEach(s -> l.add("three"));
但是使用CopyOnWriteArrayList可以解決這個(gè)問題
Optional——一個(gè)可以為 null 的容器
Java 8中的Optional<T>是一個(gè)可以包含或不可以包含非空值的容器對象,在 Stream API中很多地方也都使用到了Optional。(暫時(shí)沒有找到特別合適的用法...)
基本方法:
of()
ofNullable()
isPresent()
如果值存在,返回 true,否則返回 falsemap()
orElse()
orElseGet()