Java8用法总结

一、新特性

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 

猜你喜欢

转载自www.cnblogs.com/whuxz/p/9570613.html