一、新特性
Java8帶來了很多的新特性,本篇就以下幾個方面,從實際用法的角度進行介紹。
- Lambda 表達式
- 函數式接口
- Stream
- 默認方法
- Optional 類
二、Lambda表達式
2.1 引例
@Data @Builder @NoArgsConstructor @AllArgsConstructor public class Product { private String id; private Long num; private Double price; }
為了以后排序,我們定義一種比較器,按價格排序:
Comparator<Product> byPrice = new Comparator<Product>() { @Override public int compare(Product o1, Product o2) { return o1.getPrice().compareTo(o2.getPrice()); } };
byPrice的作用是按價格比較2種產品,它是一種行為,可以用Lambda表達:
Comparator<Product> byPrice = (Product o1, Product o2) -> o1.getPrice().compareTo(o2.getPrice();
這里只有一種類型Product,可根據Comparator<Product>判斷,因此進一步簡化:
Comparator<Product> byPrice = (o1, o2) -> o1.getPrice().compareTo(o2.getPrice();
2.2 概念
Lambda表示一種行為,通過Lambda表達式將行為參數化,這樣,行為可以和對象一樣傳遞;從第三章可以了解,Lambda表達式可以用函數式接口表示,Comparator就是一種函數式接口;
2.3 表達式
Lambda表達式有三部分,參數列表、"->"、Lambda主體,實際中有以下2種形式:
(parameters) -> expression
(parameters) ->{ statements; }
(List<Product> list) -> list.isEmpty; // 判斷隊列為空 () -> new Product(); // 新建一個對象 (String s) -> s.length; // 求字符串長度 (Product p) -> System.out.println(p); // 輸出對象
三、函數式接口
3.1 相關概念
函數式接口:只定義一個抽象方法的接口;它可能還會有很多默認方法,但有且僅有一個抽象方法;常見的函數式接口如Comparator, Runnable;函數式接口可以用來
表達Lamdba表達式;如將一個Lamdba表達式傳遞給一個函數式接口,即Lamdba表達式以內聯的方式實現了函數式接口;
函數描述符:函數式接口的抽象方法;
3.2 引例
如果我們想寫一個用於2個數計算的計算器,可能需要實現如下幾個函數,根據運算符調用對應函數計算;
public <T> T add(T a, T b); public <T> T add(T a, T b); public <T> T multiply(T a, T b); public <T> T divide(T a, T b);
換一種思路,如果有這樣一個函數 public double func(double a, double b, Function f); f是一個函數式接口,它表示具體運算,具體代碼實現如下:
@Log4j2 public class T19 { public static void main(String[] args) { log.info(myFunction(1, 2, (a, b) -> a + b)); log.info(myFunction(1.0, 2.0, (a, b) -> a - b)); log.info(myFunction(BigDecimal.ZERO, BigDecimal.valueOf(2), (a, b) -> a.multiply(b))); } public static <T> T myFunction(T a, T b, MyBiFunctionInterface<T> f) { return f.apply(a, b); } } @FunctionalInterface public interface MyBiFunctionInterface<T> { T apply(T a, T b); }
輸出如下:
2018-09-01 19:39:11 - test.other.T19 INFO test.other.T19.main(T19.java:20) : 3
2018-09-01 19:39:11 - test.other.T19 INFO test.other.T19.main(T19.java:21) : -1.0
2018-09-01 19:39:11 - test.other.T19 INFO test.other.T19.main(T19.java:22) : 0
Java8提供了很多函數式接口,一般情況下不用去定義函數式接口,比如例子中MyBiFunctionInterface,可用BinaryOperator代替,BinaryOperator這個函數式接口,接收2個類型為T的參數,返回一個類型為T的結果,即(T, T) -> T,修改后如下:
public static <T> T myFunction(T a, T b, BinaryOperator<T> f) { return f.apply(a, b); }
3.3 常見函數式接口
| Function<T, R> | T-> R |
| Predict<T> | T -> boolean |
| Consumer<T> | T -> void |
| Supplier<T> | () -> T |
| UnaryOperator<T> | T -> T |
| BinaryOperator<T> | (T, T) -> T |
| BiFunction<T, U> | (T, U) -> R |
| BiPredicate<L, R> | (L, R) -> boolean |
| BiConsumer<T, U> | (T, U) -> void |
3.4 方法引用
Lamdba表達式的快捷寫法,它更直觀,可讀性更好,比如:(Product p) -> p.getPrice == Product::getPrice
方法引用主要有二類:
(1)指向靜態方法;如 Integer::parseInt;
(2)指向實例方法:如 String::length;
(3)構造函數的方法引用:如Supplier<Product> p = Product::new;
例:第二章引例中還可以如下表達:
Comparator<Product> c = Comparator.comparing(Product::getPrice);
3.5 復合
復合,就是將多個Lamdba表達式連接起來,表示更加復雜的功能;主要有以下三種
(1)函數復合:將Function代表的Lamdba復合起來,有andThen, compose;其中
f.andThen(g) = g(f(x)),先計算f表達式,將結果再計算g表達式;
f.compose(g) = f(g(x)),先計算g表達式,將結果再計算f表達式;
Function<Integer, Integer> f = x -> x + 1; Function<Integer, Integer> g = x -> x * 2; Function<Integer, Integer> h1 = f.andThen(g); // (1 + 1) * 2 = 4 Function<Integer, Integer> h2 = f.compose(g); // (1 * 2) + 1 = 3
(2)Predicate的復合,有negate, and, or,分別表示非、且、或,按從左到右的順序
Predicate<Product> p1 = a -> a.getPrice() > 100; // 大於100 Predicate<Product> p2 = p1.negate(); // 小於等於100 Predicate<Product> p3 = p1.negate().and(a -> a.getNum() > 10); // 價格小於等於100,且數量大於10
(3)比較器復合,如
Comparator<Product> c = Comparator.comparing(Product::getPrice).reversed().thenComparing(Product::getNum);
四、流
4.1 概念
流用來處理數據集合,它具有如下特點:
(1)流強調的是計算,它是 源+數據處理,流將外部迭代(如for/while)轉化為對我們透明的內部迭代;
(2)只能遍歷一次,遍歷完就關閉;
流具有如下優點:
(1)內置了很多常用方法(如排序、分類、統計);
(2)能透明的並行處理;
(3)聲明式的,只需關注我要怎么樣,不用關注我該如何實現,通過內置的方法與復合很容易實現;
4.2 流的操作
流的操作分為:
(1)中間操作:filter(Predicate<T>), map(Function(T, R), limit, sorted(Comparator<T>), distinct,flatMap;
(2)終端操作:只有終端操作才能產生輸出,包括:allMatch, anyMatch, noneMatch, findAny, findFirst, forEach, collect, reduce, count
4.3 流的用法
@Data @Builder @NoArgsConstructor @AllArgsConstructor public class Product { private String id; private Long num; private Double price; private Boolean isUse; }
List<Product> list = Lists.newArrayList(
Product.builder().id("11").num(20l).price(100d).isUse(true).build(),
Product.builder().id("12").num(25L).price(120d).isUse(true).build(),
Product.builder().id("13").num(25L).price(100d).isUse(true).build(),
Product.builder().id("14").num(20L).price(110d).isUse(false).build()
);
(1)filter, 找出價格大於100的產品:
List<Product> list1 = list.stream().filter(p -> p.getPrice() > 100).collect(Collectors.toList());
(2)distinct,去重
Arrays.asList(1, 2, 3, 1).stream().distinct().forEach(System.out::print); // 輸出123
(3)limit,輸出前n個
Arrays.asList(1, 2, 3, 1).stream().limit(2).forEach(System.out::print); //輸出12
(4)skip,跳過前n個
Arrays.asList(1, 2, 3, 1).stream().skip(2).forEach(System.out::print); // 輸出31
(5)map, 映射,T -> R
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
list.stream().map(Product::getPrice).distinct().forEach(System.out::println);
輸出:
100.0
120.0
110.0
(6)flatMap,扁平化,將每個元素產生的中間集合合並成一個大集合;接收的Function將T->Stream<R>
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
Arrays.asList(new String[]{"hello", "world"}).stream().map(p -> p.split("")) .flatMap(Arrays::stream) //.flatMap(p -> Arrays.stream(p)) .distinct().forEach(System.out::print); // 輸出:helowrd
(7)匹配
boolean anyMatch(Predicate<? super T> predicate);
- allMatch: 都滿足條件才返回true;
- anyMatch: 有一個滿足就返回true;
- noneMatch: 都不滿足才返回true;
boolean b = Arrays.asList(1, 2, 3, 1).stream().anyMatch(p -> p > 2); //返回true
(8)查找,與其它操作結合使用
findAny: Optional<T> findAny()
findFirst: Optional<T> findFirst()
Arrays.asList(1, 2, 3, 4, 1).stream().filter(p -> p > 2).findAny() //輸出Optional[3] Arrays.asList(1, 2, 3, 4, 1).stream().filter(p -> p > 2).findFirst() //輸出Optional[3]
4.4 reduce歸約
歸約操作是很常用的操作,它將流中的值反復的結合起來,最終得到一個值,它是一種終端操作;
(1)Optional<T> reduce(BinaryOperator<T> accumulator);
(2)T reduce(T identity, BinaryOperator<T> accumulator);
(3)<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
(1) 給定歸約算法,最終歸約成一個值,考慮到流可能為空,所以返回類型為Option,例:
Optional<Integer> op1 = Arrays.asList(1, 2, 3, 4, 1).stream().reduce(Integer::sum); //輸出Optional[11]
(2)給定了初值,歸約算法,返回結果;
Arrays.asList(1, 2, 3, 4, 1).stream().reduce(0, Integer::sum); //輸出11
// Steam<T>中T為包裝類型,沒有sum,但Java8為流的原始類型提供了一些方法,如下 Arrays.asList(1, 2, 3, 4, 1).stream().mapToInt(a -> a).sum(); list.stream().mapToLong(Product::getNum).sum();
(3)第三個參數表示合並方式,當是並行流時,各線程獨立計算結果,最后將各線程的結果合並;
BiFunction<Double, Product, Double> f1 = (Double a, Product b) -> a + b.getNum(); BinaryOperator<Double> f2 = (a, b) -> a + b; double b2 = list.parallelStream().reduce(0d, f1, f2); log.info(b2); //輸出90
4.5 數值流
數值流除了具有流的方法外,還有一些特殊的統計方法,例
DoubleStream doubleStream = list.stream().mapToDouble(Product::getPrice); double average = doubleStream.average().getAsDouble(); //數值流->對象流 Stream<Double> sd = doubleStream.boxed();
// 生成n以內的勾股數 Stream<double[]> stream = IntStream.rangeClosed(1, 30).boxed().flatMap(a -> IntStream.rangeClosed(a, 30).mapToObj( b -> new double[]{a, b, Math.sqrt(a * a + b * b)}).filter(t -> t[2] % 1 == 0)); stream.limit(3).forEach(t -> System.out.println(t[0] + ", " + t[1] + ", " + t[2])); 輸出: 3.0, 4.0, 5.0 5.0, 12.0, 13.0 6.0, 8.0, 10.0
4.6 構建流
Stream.iterate(0, n -> n + 2).limit(10);
Stream.generate(Math::random).limit(10);
五、收集器(collect歸約)
5.1 常見用法
Map<Double, List<Product>> map = list.stream().collect(groupingBy(Product::getPrice)); Long allNum = list.stream().collect(summingLong(Product::getNum)); double average = list.stream().collect(averagingDouble(Product::getPrice)); LongSummaryStatistics statistics = list.stream().collect(summarizingLong(Product::getNum)); String ids = list.stream().map(Product::getId).collect(joining(", "));
5.2 reducing歸約
Optional<Product> opp = list.stream().collect(reducing((a, b) -> a.getPrice() > b.getPrice() ? a : b)); long allNum2 = list.stream().collect(reducing(0L, Product::getNum, Long::sum)); long allNum3 = list.stream().collect(reducing(0L, Product::getNum, (i, j) -> i + j));
collect中reducing歸約三要素,初值,提取值,歸約方法,若無初值返回Optional,若提取值即是對象本身,可省略;
5.3 多重分組
Map<Double, Map<Long, List<Product>>> map = list.stream().collect(groupingBy(Product::getPrice, groupingBy(Product::getNum))); Map<Double, Map<String, List<Product>>> map2 = list.stream().collect(groupingBy(Product::getPrice, groupingBy(p -> { if (p.getNum() <= 80L) return "little"; else if (p.getNum() >= 120L) return "many"; else return "normal"; }))); System.out.println(JacksonUtil.toJson(map)); System.out.println(JacksonUtil.toJson(map2));
輸出如下:
{ "100.0" : { "20" : [ { "id" : "11", "num" : 20, "price" : 100.0, "isUse" : true } ], "25" : [ { "id" : "13", "num" : 25, "price" : 100.0, "isUse" : true } ] }, "110.0" : { "20" : [ { "id" : "14", "num" : 20, "price" : 110.0, "isUse" : false } ] }, "120.0" : { "25" : [ { "id" : "12", "num" : 25, "price" : 120.0, "isUse" : true } ] } } { "100.0" : { "little" : [ { "id" : "11", "num" : 20, "price" : 100.0, "isUse" : true }, { "id" : "13", "num" : 25, "price" : 100.0, "isUse" : true } ] }, "110.0" : { "little" : [ { "id" : "14", "num" : 20, "price" : 110.0, "isUse" : false } ] }, "120.0" : { "little" : [ { "id" : "12", "num" : 25, "price" : 120.0, "isUse" : true } ] } }
在一次分組的子集合中處理數據
Map<Double, Long> map = list.stream().collect(groupingBy(Product::getPrice, counting())); Map<Double, Optional<Product>> map2 = list.stream().collect(groupingBy(Product::getPrice, maxBy(comparingLong(Product::getNum)))); Comparator<Product> c = ((p1, p2) -> p1.getNum().compareTo(p2.getNum())); Map<Double, Optional<Product>> map3 = list.stream().collect(groupingBy(Product::getPrice, maxBy(c))); Map<Double, Product> map4 = list.stream().collect(groupingBy(Product::getPrice,
collectingAndThen(maxBy(comparing(Product::getNum)), Optional::get)));
5.4 分區
由一個謂詞作為分類,分為2類,true與false,用法與groupingBy完全一樣
Map<Boolean, List<Product>> map = list.stream().collect(partitioningBy(Product::getIsUse)); Map<Boolean, Map<Double, List<Product>>> map2 = list.stream().collect(partitioningBy(Product::getIsUse,
groupingBy(Product::getPrice))); Map<Boolean, LongSummaryStatistics> map3 = list.stream().collect(partitioningBy(Product::getIsUse,
summarizingLong(Product::getNum))); Map<Boolean, Double> map4 = list.stream().collect(partitioningBy(Product::getIsUse, averagingLong(Product::getNum)));
六、optional
6.1 使用
(1)單級包裝用法:我們會見到如下代碼,
String name = null; if (product != null) { name = product.getId(); }
利用optional可轉化為
Optional<Product> optProduct = Optional.ofNullable(product);
Optional<String> optName = optProduct.map(Product::getId);
(2)多級包裝用法
public String getName(Person person) { return person.getCar().getInsurance().getName(); }
經過包裝如下,注意為防止Optional<Optional<T>>這種中間結果造成編譯不通過,需要使用flatMap
public String getName(Person person) { Optional<Person> optPerson = Optional.ofNullable(person); return optPerson.flatMap(Person::getCar).flatMap(Car::getInsurance).map(Insurance::getName).orElse("Unknown"); }
