1 函數(shù)式接口和lambda表達(dá)式
函數(shù)式接口
函數(shù)式接口就是有且只有一個(gè)抽象方法的接口狐蜕,需要注意的是辐董,如果方法覆蓋了Object的方法伐坏,那么不會(huì)被認(rèn)為是一個(gè)抽象方法。如果需要指定接口就是函數(shù)式接口击胜,可以使用@FunctionalInterface注解亏狰,使用該注解,編譯器會(huì)按照函數(shù)式接口的定義來檢查接口偶摔,如果不滿足定義暇唾,會(huì)拋出異常。也可以不使用該注解辰斋,但是只要該接口只有一個(gè)抽象方法策州,編譯器也會(huì)認(rèn)為該接口是函數(shù)式接口。
java8已經(jīng)提供了很多的函數(shù)式接口了宫仗,基本不需要我們自己定義够挂。
如下是部分函數(shù)式接口:
Function<T,R> 抽象方法為 R apply(T t);
Predicate<T> 抽象方法為 boolean test(T t);
Consumer<T> 抽象方法為 void accept(T t);
Supplier<T> 抽象方法為 T get();
UnaryOperator<T> 抽象方法為 T apply(T t);
lambda表達(dá)式
lambda表達(dá)式就是基于函數(shù)式接口實(shí)現(xiàn)的,lambd語(yǔ)法接口如下:
(參數(shù))-> {執(zhí)行體}
(參數(shù))-> {執(zhí)行體} 可以看作傳統(tǒng)的方法格式 方法名(參數(shù)){執(zhí)行體} 藕夫,只是省略了方法名而已 孽糖。
在lambda表達(dá)式出現(xiàn)之前,都是使用匿名類來實(shí)現(xiàn)接口并重寫其的方法毅贮,代碼比較臃腫办悟,而有了lambda表達(dá)式之后,使用起來就比較簡(jiǎn)短了嫩码。例如重寫java8提供的函數(shù)式接口Function<T,R>的apply方法誉尖,實(shí)現(xiàn)傳入Integer類型的參數(shù),乘以3后铸题,返回Integer類型的值铡恕,對(duì)比如下:
匿名類的方式:
Function<Integer,Integer> fun2 = new Function<Integer, Integer>() {
@Override
public Integer apply(Integer from) {
return from*3;
}
};
System.out.println(fun2.apply(3));
lambda的方式:
Function<Integer,Integer> fun = (Integer from) -> from * 3;
System.out.println(fun.apply(3));
以上兩種方式的輸出結(jié)果都是 9琢感;
lambda的一些特性:
1)編譯器自動(dòng)推導(dǎo)參數(shù)類型與返回類型。這個(gè)特性需要結(jié)合函數(shù)式接口來解釋探熔,例如這里的Function<Integer,Integer> fun = (Integer from) -> from * 3驹针,由于前面
Function<Integer,Integer> fun進(jìn)行了聲明,編譯器就知道返回的是一個(gè)Function<Integer,Integer> 诀艰。并且用過lambda的都知道柬甥,他就是在創(chuàng)建接口的子類對(duì)象并實(shí)現(xiàn)接口的抽象方法,像匿名類一樣其垄,但是編譯器是如何判斷到底實(shí)現(xiàn)的那個(gè)方法呢苛蒲,lambda又沒有指定方法名,而與函數(shù)式接口聯(lián)合使用绿满,就解決了這個(gè)問題臂外,因?yàn)楹瘮?shù)式接口只有一個(gè)抽象方法,只能實(shí)現(xiàn)該抽象方法喇颁。而作為唯一的抽象方法漏健,它的參數(shù)類型與返回值類型不用指定,編譯器也能推導(dǎo)出來橘霎。
2)所以根據(jù)編譯器自動(dòng)推導(dǎo)參數(shù)類型的特性蔫浆,lambda的特性2就是可以不用指定參數(shù)類型,因?yàn)樗茏詣?dòng)推導(dǎo)姐叁。所以以上lambda的方式還可以寫成如下方式瓦盛,
Function<Integer,Integer> fun = (from) -> from * 3;
System.out.println(fun.apply(3));
并且,java8為了更簡(jiǎn)潔一點(diǎn)七蜘,當(dāng)該函數(shù)式接口的抽象方法只有一個(gè)參數(shù)時(shí)谭溉,可以去掉括號(hào),寫成如下方式:
Function<Integer,Integer> fun = from -> from * 3;
System.out.println(fun.apply(3));
3)還有一個(gè)特性就是當(dāng)方法體只有一句時(shí)可以不用使用return返回值橡卤,也可以不用{}括起來扮念。
個(gè)人理解lambda最大的作用就是替換掉了使用匿名類來創(chuàng)建接口的子類對(duì)象并實(shí)現(xiàn)方法這樣臃腫的方式,使得代碼變得更加的簡(jiǎn)潔碧库。
2 Stream
java8的引進(jìn)的一種新的特性柜与,流,它主要對(duì)數(shù)據(jù)進(jìn)行篩選嵌灰、排序等操作弄匕。使用得最多的就是集合和數(shù)組。
Stream的使用主要是三個(gè)步驟分別是獲取流對(duì)象沽瞭、流的中間操作(對(duì)數(shù)據(jù)的操作迁匠,可以調(diào)用多種操作,形成中間操作鏈)、終止操作(終止操作是執(zhí)行中間操作鏈城丧,產(chǎn)生結(jié)果)延曙。只有執(zhí)行終止操作才會(huì)具體執(zhí)行中間操作,這個(gè)就是所謂的惰性求值亡哄。
并且枝缔,還需要了解的是,stream不會(huì)存儲(chǔ)數(shù)據(jù)蚊惯,一般都會(huì)執(zhí)行中間操作后返回結(jié)果愿卸。
2.1 獲取流對(duì)象(常用的)
獲取數(shù)組的流
Integer[] array = new Integer[]{3,4,8,16,19,27,23,99};
Stream<Integer> arrayStream = Arrays.stream(array);
獲取集合的流
ArrayList<String> list = new ArrayList<>();
final Stream<String> stream = list.stream();
由于集合的父接口Collection實(shí)現(xiàn)了stream()方法,所以任何集合都可以通過stream()獲取到流截型。
2.2 流的中間操作
Stream<T> filter(Predicate<? super T> predicate);
過濾方法趴荸,該方法會(huì)將流里面滿足條件的對(duì)象過濾出來并返回一個(gè)流對(duì)象(沒調(diào)用流的終止操作,filter是不會(huì)執(zhí)行的菠劝,這里的終止操作是count(),執(zhí)行中間操作赊舶,返回流中剩下元素個(gè)數(shù))睁搭。
list.add(new User("張三",22));
list.add(new User("李四",25));
list.add(new User("王麻子",27));
list.add(new User("小明",33));
//輸出年齡大于等于25的元素個(gè)數(shù)
long count = list.stream().filter(u -> u.getAge() >= 25).count();
System.out.println(count);
2.2.1 <R> Stream<R> map(Function<? super T, ? extends R> mapper);
該方法的作用是對(duì)元素進(jìn)行類型轉(zhuǎn)換赶诊,返回我們需要的類型的集合。如下代碼是獲取用戶名集合园骆。
ArrayList<User> list = new ArrayList<>();
list.add(new User("張三",22));
list.add(new User("李四",25));
list.add(new User("王麻子",27));
list.add(new User("小明",33));
//獲取姓名集合
List<String> names = list.stream().map(u -> u.getName()).collect(Collectors.toList());
names.forEach(System.out::println);
需要注意的是map(Function<? super T, ? extends R>)這個(gè)方法舔痪,這里傳入的是lambda表達(dá)式 u -> u.getName() ,里面的參數(shù)u也就是Function<? super T, ? extends R>的泛型T由list對(duì)象的泛型決定锌唾,這里就是User類型锄码,而Function<? super T, ? extends R>的R由u.getName(),執(zhí)行后返回的類型是什么就是什么類型的集合晌涕∽檀罚可以加深對(duì)編譯器自動(dòng)推導(dǎo)lambda表達(dá)式的參數(shù)類型與返回值類型的特性的理解。
2.2.2 Stream<T> sorted(Comparator<? super T> comparator);
按年齡從小到大排序(默認(rèn))
ArrayList<User> list = new ArrayList<>();
list.add(new User("張三",22));
list.add(new User("李四",18));
list.add(new User("王麻子",14));
list.add(new User("小明",33));
//獲取升序集合
List<User> collect = list.stream()
.sorted(Comparator.comparing(u -> u.getAge()))
.collect(Collectors.toList());
collect.forEach(System.out::println);
排序需要用到Comparator的一些方法余黎,Comparator常用方法如下(例子都是以上面代碼為例子):
public static <T extends Comparable<? super T>> Comparator<T> reverseOrder():指定為降序排列重窟,例如通過年齡降序排列
List<User> collect = list.stream()
.sorted(Comparator.comparing(u -> u.getAge(),Comparator.reverseOrder()))
.collect(Collectors.toList());
public static <T extends Comparable<? super T>> Comparator<T> naturalOrder():按自然順序排列,即按照升序排列惧财,默認(rèn)巡扇,不用特別指定,使用方式與降序完全一樣垮衷。
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(Function<? super T, ? extends U> keyExtractor):
不知道具體比較類型時(shí)使用該方法進(jìn)行比較厅翔,虛擬機(jī)能自動(dòng)推導(dǎo)出具體類型。需要注意的是最終比較的類型必須實(shí)現(xiàn)了Comparator接口搀突,常見的Integer與String都實(shí)現(xiàn)了Comparator該接口,可以進(jìn)行比較刀闷。
如果知道使用int或者long類型進(jìn)行比較,可以調(diào)用對(duì)應(yīng)的比較方法,例如
public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor)
public static <T> Comparator<T> comparingLong(ToLongFunction<? super T> keyExtractor)
public static <T, U> Comparator<T> comparing(Function<? super T, ? extends U> keyExtractor,Comparator<? super U> keyComparator):
指定排序方式進(jìn)行排序比較甸昏,第二個(gè)參數(shù)就是排序方式戈次,為reverseOrder的返回值。
default <U> Comparator<T> thenComparing(Function<? super T, ? extends U> keyExtractor,Comparator<? super U> keyComparator):
default <U extends Comparable<? super U>> Comparator<T> thenComparing(Function<? super T, ? extends U> keyExtractor):
thenComparing類比comparing筒扒,該方法只能在調(diào)用了comparing后鏈?zhǔn)秸{(diào)用怯邪,可以實(shí)現(xiàn)多字段排序。類似于數(shù)據(jù)庫(kù) order by 字段1 字段2花墩。需要注意的是悬秉,鏈?zhǔn)秸{(diào)用編譯器不能自動(dòng)推導(dǎo)出參數(shù)類型,需要自己指定(不知道為啥鏈?zhǔn)秸{(diào)用不能自動(dòng)推導(dǎo)冰蘑,而單獨(dú)使用卻能自動(dòng)推導(dǎo)和泌,和我理解的泛型有點(diǎn)不同,個(gè)人理解是能夠?qū)蛹?jí)遞推的)祠肥,如下武氓,實(shí)現(xiàn)先按照年齡降序排列,再按照名字升序排列仇箱。
2.2.3 Stream<T> sorted();
該方法是使用自然排序县恕,流中的元素必須實(shí)現(xiàn)Comparable接口
2.2.4 Stream<T> distinct();
該方法是去掉重復(fù)的元素,核心是通過元素類型的equals與hashcode方法判斷元素是否是一個(gè)元素的剂桥,當(dāng)hashcode相等并且equals返回true即為重復(fù)元素忠烛。如果比較Integer和String類型的話,就是通過具體的值判斷是否相等权逗。
2.2.5 Stream<T> peek(Consumer<? super T> action);
可以對(duì)流中的元素進(jìn)行修改美尸,Consumer的accept方法沒有返回值。
2.3 流的終止操作
當(dāng)我們定義好中間需要執(zhí)行的操作過后斟薇,就可以調(diào)用流的終止操作開始執(zhí)行所有中間操作了师坎。
2.3.1 <R, A> R collect(Collector<? super T, A, R> collector)
需要使用的一個(gè)Collector的工具Collectors,常用的方法如下:
toSet():將stream里面的元素轉(zhuǎn)化成Set
toList():將stream里面的元素轉(zhuǎn)化成List
toMap(Function<? super T, ? extends K> keyMapper,Function<? super T, ? extends U> valueMapper):將里面的元素生成一個(gè)Map,由于Map與List堪滨、Set不同胯陋,需要指定key與value,所以需要指定key與value的值。例如將用戶名作為key,用戶對(duì)象作為value椿猎,實(shí)例代碼如下惶岭。
ArrayList<User> list = new ArrayList<>();
list.add(new User("王麻子",24));
list.add(new User("張三",18));
list.add(new User("李四",15));
list.add(new User("小明",25));
Map<String, User> collect = list.stream()
.collect(Collectors.toMap(u -> u.getName(), u -> u));
toCollection(Supplier<C> collectionFactory):想toList與toSet都是生成的默認(rèn)的是ArrayList與HashSet,如果你想要指定生成的類型犯眠,就可以使用該方法按灶,需要注意的是,指定的生成類型必須是實(shí)現(xiàn)Collection接口筐咧。如下鸯旁,指定生成LinkedList
inked
ArrayList<User> list = new ArrayList<>();
list.add(new User("王麻子",24));
list.add(new User("張三",18));
list.add(new User("李四",15));
list.add(new User("小明",25));
LinkedList<User> collect = list.stream()
.collect(Collectors.toCollection(() -> new LinkedList<>()));
maxBy(Comparator<? super T> comparator):按照指定規(guī)則最值元素噪矛,例如獲取年齡最大的用戶(如果有多個(gè),取第一個(gè))
ArrayList<User> list = new ArrayList<>();
list.add(new User("王麻子",24));
list.add(new User("張三",18));
list.add(new User("李四",15));
list.add(new User("小明",25));
User user = list.stream()
.collect(Collectors.maxBy(Comparator.comparing(u -> u.getAge()))).get();
minBy(Comparator<? super T> comparator):
類比maxBy
public static <T> Collector<T, ?, Integer>summingInt(ToIntFunction<? super T> mapper):
將元素的某個(gè)Integer字段求和铺罢。例如將用戶年齡求和
ArrayList<User> list = new ArrayList<>();
list.add(new User("王麻子",25));
list.add(new User("張三",18));
list.add(new User("李四",15));
list.add(new User("小明",25));
Integer sum = list.stream().collect(Collectors.summingInt(u -> u.getAge()));
System.out.println(sum);
public static <T> Collector<T, ?, Long>summingLong(ToLongFunction<? super T> mapper):
public static <T> Collector<T, ?, Double>summingDouble(ToDoubleFunction<? super T> mapper):
這兩個(gè)方法求和類型不同以外艇挨,其他基本類比summingInt。
public static <T> Collector<T, ?, Double>averagingInt(ToIntFunction<? super T> mapper):
public static <T> Collector<T, ?, Double>averagingLong(ToLongFunction<? super T> mapper):
public static <T> Collector<T, ?, Double>averagingDouble(ToDoubleFunction<? super T> mapper):
這三個(gè)方法都是用于求平均值韭赘,和求和用法基本相同缩滨。
public static <T, K> Collector<T, ?, Map<K, List<T>>>groupingBy(Function<? super T, ? extends K> classifier):
該方法主要用于給元素分組,如下按照用戶年齡給用戶分組泉瞻,返回的是分組關(guān)鍵字與該組元素集合的map映射脉漏,由于分組后的也是List集合,可以繼續(xù)對(duì)集合做分組后的平均值袖牙,最大值侧巨,求和等操作。
ArrayList<User> list = new ArrayList<>();
list.add(new User("王麻子",25));
list.add(new User("張三",18));
list.add(new User("李四",15));
list.add(new User("小明",25));
Map<Integer, List<User>> collect = list.stream()
.collect(Collectors.groupingBy(u -> u.getAge()));
System.out.println(collect);
public static <T, K, A, D>Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,Collector<? super T, A, D> downstream):
默認(rèn)情況下鞭达,使用的是List來接收分組后的元素司忱,也可以指定類型,例如用set接收分組后的元素畴蹭。
ArrayList<User> list = new ArrayList<>();
list.add(new User("王麻子",25));
list.add(new User("張三",18));
list.add(new User("李四",15));
list.add(new User("小明",25));
Map<Integer, Set<User>> collect = list.stream()
.collect(Collectors.groupingBy(u -> u.getAge(), Collectors.toSet()));
System.out.println(collect);
public static <T, K, D, A, M extends Map<K, D>>Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,Supplier<M> mapFactory,Collector<? super T, A, D> downstream):
一般默認(rèn)返回的是HashMap實(shí)例坦仍,如果需要指定返回Map類型,可以通過該方法指定撮胧,例如指定返回LinkedHashMap桨踪。
ArrayList<User> list = new ArrayList<>();
list.add(new User("王麻子",25));
list.add(new User("張三",18));
list.add(new User("李四",15));
list.add(new User("小明",25));
Map<Integer, Set<User>> collect = list.stream()
.collect(
Collectors
.groupingBy(
u -> u.getAge(),
()-> new LinkedHashMap<>(),
Collectors.toSet()
)
);
System.out.println(collect);