一、新特性
Java8带来了很多的新特性,本篇就以下几个方面,从实际用法的角度进行介绍。
- Lambda 表达式
- 函数式接口
- Stream
- 默认方法
- Optional 类
二、Lambda表达式
2.1 引例
@Data@Builder@NoArgsConstructor@AllArgsConstructorpublic class Product { private String id; private Long num; private Double price;}
为了以后排序,我们定义一种比较器,按价格排序:
ComparatorbyPrice = new Comparator () { @Override public int compare(Product o1, Product o2) { return o1.getPrice().compareTo(o2.getPrice()); }};
byPrice的作用是按价格比较2种产品,它是一种行为,可以用Lambda表达:
ComparatorbyPrice = (Product o1, Product o2) -> o1.getPrice().compareTo(o2.getPrice();
这里只有一种类型Product,可根据Comparator判断,因此进一步简化:
ComparatorbyPrice = (o1, o2) -> o1.getPrice().compareTo(o2.getPrice();
2.2 概念
Lambda表示一种行为,通过Lambda表达式将行为参数化,这样,行为可以和对象一样传递;从第三章可以了解,Lambda表达式可以用函数式接口表示,Comparator就是一种函数式接口;
2.3 表达式
Lambda表达式有三部分,参数列表、"->"、Lambda主体,实际中有以下2种形式:
(parameters) -> expression(parameters) ->{ statements; }
(Listlist) -> list.isEmpty; // 判断队列为空() -> new Product(); // 新建一个对象(String s) -> s.length; // 求字符串长度(Product p) -> System.out.println(p); // 输出对象
三、函数式接口
3.1 相关概念
函数式接口:只定义一个抽象方法的接口;它可能还会有很多默认方法,但有且仅有一个抽象方法;常见的函数式接口如Comparator, Runnable;函数式接口可以用来
表达Lamdba表达式;如将一个Lamdba表达式传递给一个函数式接口,即Lamdba表达式以内联的方式实现了函数式接口;
函数描述符:函数式接口的抽象方法;
3.2 引例
如果我们想写一个用于2个数计算的计算器,可能需要实现如下几个函数,根据运算符调用对应函数计算;
publicT add(T a, T b);public T add(T a, T b);public T multiply(T a, T b);public T divide(T a, T b);
换一种思路,如果有这样一个函数 public double func(double a, double b, Function f); f是一个函数式接口,它表示具体运算,具体代码实现如下:
@Log4j2public 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 staticT myFunction(T a, T b, MyBiFunctionInterface f) { return f.apply(a, b); }}@FunctionalInterfacepublic interface MyBiFunctionInterface { 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.02018-09-01 19:39:11 - test.other.T19 INFO test.other.T19.main(T19.java:22) : 0Java8提供了很多函数式接口,一般情况下不用去定义函数式接口,比如例子中MyBiFunctionInterface,可用BinaryOperator代替,BinaryOperator这个函数式接口,接收2个类型为T的参数,返回一个类型为T的结果,即(T, T) -> T,修改后如下:
public staticT myFunction(T a, T b, BinaryOperator f) { return f.apply(a, b); }
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;
例:第二章引例中还可以如下表达:
Comparatorc = 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表达式;
Functionf = x -> x + 1; Function g = x -> x * 2; Function h1 = f.andThen(g); // (1 + 1) * 2 = 4 Function h2 = f.compose(g); // (1 * 2) + 1 = 3
(2)Predicate的复合,有negate, and, or,分别表示非、且、或,按从左到右的顺序
Predicatep1 = a -> a.getPrice() > 100; // 大于100 Predicate p2 = p1.negate(); // 小于等于100 Predicate p3 = p1.negate().and(a -> a.getNum() > 10); // 价格小于等于100,且数量大于10
(3)比较器复合,如
Comparatorc = 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@AllArgsConstructorpublic class Product { private String id; private Long num; private Double price; private Boolean isUse;}
Listlist = 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的产品:
Listlist1 = 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
Stream map(Function mapper);
list.stream().map(Product::getPrice).distinct().forEach(System.out::println);输出:100.0120.0110.0
(6)flatMap,扁平化,将每个元素产生的中间集合合并成一个大集合;接收的Function将T->Stream<R>
Stream flatMap(Function > 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 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)Optionalreduce(BinaryOperator accumulator);
(2)T reduce(T identity, BinaryOperatoraccumulator);
(3) U reduce(U identity, BiFunction accumulator, BinaryOperator combiner);
(1) 给定归约算法,最终归约成一个值,考虑到流可能为空,所以返回类型为Option,例:
Optionalop1 = 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为包装类型,没有sum,但Java8为流的原始类型提供了一些方法,如下Arrays.asList(1, 2, 3, 4, 1).stream().mapToInt(a -> a).sum();list.stream().mapToLong(Product::getNum).sum();
(3)第三个参数表示合并方式,当是并行流时,各线程独立计算结果,最后将各线程的结果合并;
BiFunctionf1 = (Double a, Product b) -> a + b.getNum(); BinaryOperator 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();//数值流->对象流Streamsd = doubleStream.boxed();
// 生成n以内的勾股数Streamstream = 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.05.0, 12.0, 13.06.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> 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归约
Optionalopp = 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>> map = list.stream().collect(groupingBy(Product::getPrice, groupingBy(Product::getNum)));Map >> 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 } ] }}
在一次分组的子集合中处理数据
Mapmap = list.stream().collect(groupingBy(Product::getPrice, counting()));Map > map2 = list.stream().collect(groupingBy(Product::getPrice, maxBy(comparingLong(Product::getNum))));Comparator c = ((p1, p2) -> p1.getNum().compareTo(p2.getNum()));Map > map3 = list.stream().collect(groupingBy(Product::getPrice, maxBy(c)));Map map4 = list.stream().collect(groupingBy(Product::getPrice, collectingAndThen(maxBy(comparing(Product::getNum)), Optional::get)));
5.4 分区
由一个谓词作为分类,分为2类,true与false,用法与groupingBy完全一样
Map> map = list.stream().collect(partitioningBy(Product::getIsUse));Map >> map2 = list.stream().collect(partitioningBy(Product::getIsUse, groupingBy(Product::getPrice)));Map map3 = list.stream().collect(partitioningBy(Product::getIsUse, summarizingLong(Product::getNum)));Map map4 = list.stream().collect(partitioningBy(Product::getIsUse, averagingLong(Product::getNum)));
六、optional
6.1 使用
(1)单级包装用法:我们会见到如下代码,
String name = null;if (product != null) { name = product.getId();}
利用optional可转化为
OptionaloptProduct = Optional.ofNullable(product);Optional 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) { OptionaloptPerson = Optional.ofNullable(person); return optPerson.flatMap(Person::getCar).flatMap(Car::getInsurance).map(Insurance::getName).orElse("Unknown");}