前言
由于項目中用到了比較多有關于 Java8 中新的東西,一開始自己隻是會寫,但是寫起來不太順,然後就在網上找到了一個很好的關于Java8新特性的視訊,是以就進行了學習了一下,以下是自己對 lambda 表達式和 Stream API 的筆記和相應的了解。 視訊位址,有興趣的可以自行觀看。
Java8 新特性
- 速度更快 更換了資料結構,記憶體結構(JVM)
- 代碼更少了(Lambda表達式)
- 強大的Stream API
- 便于并行 fork join (串行切換并行)
- 最大化減少空指針異常 Optional
Lambda表達式
在說 Lambda 之前,首先要說的是函數式接口。這個可以說是為了 Lambda 表達式而存在的一個東西。那麼什麼是函數式接口?
函數式接口
定義: 接口中隻有一個抽象接口。
像 java.util.function 下的所有接口都是行數式接口。Java1.8提供@FunctionalInterface檢測一個接口是否是一個函數式接口。
eg: java.util.function 包下的 Consumer 接口代碼
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
// jdk 1.8 接口可以有預設實作
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
了解了什麼是函數式接口後,lambda 表達式就很好了解了。
"->" 是 lambda 表達式的符号 左側表示函數式接口中抽象方法的參數清單,右側表示你對這個方法的實作。
舉個例子eg:
public class Test{
public static void main(String[] args){
Consumer consumer = x-> System.out.println(x);
consumer.accept(1);
}
}
輸出 1;
四大函數式接口
我們一般對函數式接口的使用的時候,都會對其進行封裝。
消費性接口
Consumer 隻有一個抽象方法名為 accept,參數清單隻有一個泛型t,無傳回值。參數的資料類型有類決定
eg:
/**
* @ClassName ConsumerTest
* @Description 消費型接口, 消費字元串字段 列印輸出
* @Author ouyangkang
* @Date 2019-02-18 15:46
**/
public class ConsumerTest {
public static void main(String[] args) {
test("hello",x-> System.out.println(x));
}
public static <T> void test(T t, Consumer<T> consumer) {
consumer.accept(t);
}
}
輸出:hello
如果需要多個參數清單的話,也可以在 java.util.function 包下找到相應的函數式接口 比如 ObjLongConsumer。其他的可以自行檢視
供給型接口
Supplier 隻有一個抽象方法名為 get,參數清單為空,有傳回值,傳回值得資料類型為T。
/**
* @ClassName SupplerTest
* @Description 供給型接口 字元串拼接
* @Author ouyangkang
* @Date 2019-02-18 15:53
**/
public class SupplerTest {
public static void main(String[] args) {
String hello = test("hello ", () -> "word!");
System.out.println(hello);
}
public static String test(String str,Supplier<String> supplier){
return str + supplier.get();
}
}
輸出為:hello word!
如果需要傳回得資料為基本資料類型,可以在 java.util.function 包下找到相應的函數式接口 比如:getAsLong 其他的可以自行檢視
函數型接口
Function<T, R> 隻有一個抽象方法名為 apply,參數清單隻有一個參數為T,有傳回值,傳回值的資料類型為R。
/**
* @ClassName FunctionTest
* @Description 函數式接口 将字元串轉換成大寫的
* @Author ouyangkang
* @Date 2019-02-18 16:01
**/
public class FunctionTest {
public static void main(String[] args) {
String test = test("hello", x -> x.toUpperCase());
System.out.println(test);
}
public static String test(String str , Function<String,String> function){
return function.apply(str);
}
}
輸出為:HELLO
如果需要多個入參,然後又傳回值的話,可以在 java.util.function 包下找到相應的函數式接口 比如 BiFunction。其他的可以自行檢視
斷言型接口
斷言型又名判斷型。 Predicate 隻有一個抽象方法名為 test,參數清單隻有一個參數為 T,有傳回值,傳回值類型為 boolean。
/**
* @ClassName PredicateTest
* @Description 斷言型接口,判斷字元串大小是否大于6
* @Author ouyangkang
* @Date 2019-02-18 16:16
**/
public class PredicateTest {
public static void main(String[] args) {
boolean hello = test("hello", x -> x.length() > 6);
System.out.println(hello);
}
public static boolean test(String str, Predicate<String> predicate){
return predicate.test(str);
}
}
輸出為: false
Stream API
Stream 作為 Java 8 的一大亮點,它與 java.io 包裡的 InputStream 和 OutputStream 是完全不同的概念。Stream中間操作,多個中間操作可以連接配接起來形成一個流水線,除非流水線上觸發了終止操作,否則中間不會執行任何處理!而終止操作時會一次性全部處理,稱為惰性處理。要進行流操作首先要擷取流。有4中方法可以擷取流。
- 擷取流 通過集合系列提供的stream方法和 parallelStream()(并行流)方法擷取流
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
// 常用擷取流的方式
Stream<Integer> stream = list.stream();
}
- 通過Arrays.stream() 将數組轉換成流
public static void main(String[] args) {
int[] a = new int[]{1,2,3,4};
IntStream stream = Arrays.stream(a);
}
- 通過Stream.of今天方法建立流
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, 2, 3);
}
- 建立無限流
public static void main(String[] args) {
Stream<Integer> iterate = Stream.iterate(0, x -> x + 2);
}
所有的對流的操作可以分為4種,分别為篩選與分片,映射,排序,終結(歸約,收集)
篩選與分片
操作有filter,distant,limit,skip。
filter : 過濾操作,方法參數為斷言型接口
eg:
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, 2, 3);
stream.filter(x->x != 2).forEach(x-> System.out.println(x));
}
輸出:
text 1 3
distinct : 去重操作,方法無參數
limit : 擷取前幾條資料,方法參數為long
skip : 跳過前多少條資料,然後擷取後面所有的。 方法參數為long
映射
常用操作有 map ,flatMap。
map: 對原資料進行處理,并傳回處理後的資料。 方法參數為函數型接口。
eg:
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, 2, 3);
stream.map(x->x*2).forEach(System.out::println);
}
輸出:
2
4
6
flatMap : 使原來流種的原有資料一個一個整合在另一個流中。方法參數為函數型接口,但是傳回值為流。
eg:
public static void main(String[] args) {
List<String> list = Arrays.asList("a", "b", "c");
List<String> list2 = Arrays.asList("f","d");
list.stream().flatMap(x->list2.stream().map(y-> x + y)).forEach(System.out::println);
}
排序
常用操作有sort自然排序,合sort參數為排序器的定制排序
自然排序eg:
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, 2, 3);
stream.sorted().forEach(System.out::println);
}
輸出:
1
2
3
定制排序
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, 2, 3);
stream.sorted((x,y)->-Integer.compare(x,y)).forEach(System.out::println);
}
輸出:
3
2
1
終止操作
- allMatch 檢查是否比對所有元素 方法參數為斷言型接口
- anyMatch 檢查是否比對所有元素 方法參數為斷言型接口
- noneMatch 檢查是否沒有比對所有元素 方法參數為斷言型接口
- findFirst 傳回第一個元素 無方法參數
- findAny 傳回目前流的任意元素 無方法參數
- count 傳回流中的元素總個數 無方法參數
- max 傳回流的最大值 無方法參數
- min 傳回流中的最小值 無方法參數
歸約
reduce : 歸約 -- 可以将流中的元素反複結合起來,得到一個值。
eg:
public static void main(String[] args) {
List<Integer> list1 = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer reduce = list1.stream().reduce(11, (x, y) -> x + y);
System.out.println(reduce);
}
輸出 : 66
收集
這個是非常常用的一個操作。 将流裝換為其他形式。接收到一個Collector接口的實作,用于給Stream中的元素彙總的方法。用collect方法進行收集。方法參數為Collector。Collector可以由Collectors中的toList(),toSet(),toMap(Function(T,R) key,Function(T,R) value)等靜态方法實作。
- toList() 傳回一個 Collector ,它将輸入元素 List到一個新的 List 。
- toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper) 傳回一個 Collector ,它将元素累加到一個 Map ,其鍵和值是将所提供的映射函數應用于輸入元素的結果。
-
toSet() 傳回一個 Collector ,将輸入元素 Set到一個新的 Set 。
eg:
User類
@Data
@ToString
public class User {
private String name;
private Integer age;
private Integer salary;
}
public static void main(String[] args) {
List<User> users = Arrays.asList(new User("張三", 19, 1000),
new User("張三", 58, 2000),
new User("李四", 38, 3000),
new User("趙五", 48, 4000)
);
List<String> collect = users.stream().map(x -> x.getName()).collect(Collectors.toList());
Set<String> collect1 = users.stream().map(x -> x.getName()).collect(Collectors.toSet());
Map<Integer, String> collect2 = users.stream().collect(Collectors.toMap(x -> x.getAge(), x -> x.getName()));
System.out.println(collect);
System.out.println(collect1);
System.out.println(collect2);
}
輸出:
[張三, 張三, 李四, 趙五]
[李四, 張三, 趙五]
{48=趙五, 19=張三, 38=李四, 58=張三}
分組
Collectors.groupingBy()方法是 傳回 Collector “由基團”上的類型的輸入元件操作實作 T ,根據分類功能分組元素。這個是非常常用的操作。
比如你要對名字相同的進行分組。
groupingBy(Function<? super T,? extends K> classifier)
eg:
public static void main(String[] args) {
List<User> users = Arrays.asList(new User("張三", 19, 1000),
new User("張三", 58, 2000),
new User("李四", 38, 3000),
new User("趙五", 48, 4000)
);
Map<String, List<User>> collect3 = users.stream().collect(Collectors.groupingBy(x -> x.getName()));
System.out.println(collect3);
}
輸出:{李四=[User{name='李四', age=38, salary=3000}], 張三=[User{name='張三', age=19, salary=1000}, User{name='張三', age=58, salary=2000}], 趙五=[User{name='趙五', age=48, salary=4000}]}
當然還有其他的一些比較複雜的分組操作,實際代碼看業務來進行實作。
總結
java8中的lambda表達式可能一開始用的時候還不是很熟悉,但是隻要熟悉了,你會發現非常的好用,而且lambda表達式結合stream API可以進行編寫自己的工具類。在平常項目中可以非常的省時間,提高寫代碼的效率。我現在給出一個List轉Map的工具類。
public class CollectionStream {
public static void main(String[] args) {
List<User> users = Arrays.asList(new User("張三", 19, 1000),
new User("張三", 58, 2000),
new User("李四", 38, 3000),
new User("趙五", 48, 4000)
);
Map<Integer, Integer> map = listToMap(users, x -> x.getAge(), x -> x.getSalary());
System.out.println(map);
}
/**
* @Author ouyangkang
* @Description list 轉map key不能相同 ,如果相同會報錯。 方法對 源資料,key,value過濾null。
* @Date 9:27 2019/2/19
* @param source 源資料
* @param key
* @param value
* @return java.util.Map<K,V>
**/
public static <DTO, K, V> Map<K, V> listToMap(List<DTO> source, Function<? super DTO, ? extends K> key, Function<? super DTO, ? extends V> value) {
Objects.requireNonNull(source, "source not null");
Objects.requireNonNull(key, "key not null");
Objects.requireNonNull(value, "value not null");
Map<K, V> map = source.stream()
.filter(dto -> dto != null)
.filter(dto -> key.apply(dto) != null)
.filter(dto -> value.apply(dto) != null)
.collect(Collectors.toMap(key, value));
return map;
}
}