小序

第一次学java 就是用的jdk8,但在当时并不知道8的新特性。前半年学习过一次,但也就是过一遍,这次再来学习记录一下。

lambda 表达式

lambda 表达式可以省去不部分代码,并且让我们自己的代码看起来简洁。方便了开发。

具体例子:

  1. //未使用 lambda
  2. Comparator<Integer> c1 = new Comparator<Integer>() {
  3. @Override
  4. public int compare(Integer o1, Integer o2) {
  5. return Integer.compare(o1,o2);
  6. }
  7. };
  8. //使用 lambda 表达式
  9. //lambda 表达式可以省去部分代码,让我们的代码看起来更简洁
  10. Comparator<Integer> c2 = (o1, o2) -> Integer.compare(o1,o2);
  11. //方法引用
  12. Comparator<Integer> c3 = Integer::compare;

但需要注意的是,只有当接口有一个抽象类方法的时候,才可以使用lambda表达式
lambda 表达式会进行类型推断。如例子中 o1 ,o2因为 前边Comparator 已经定义了范型 Integer 所以这里的 o1,o2就是Integer 类型

当只有一个参数时,()可以省略
当只有一条语句时(可能是return语句)的时候{}和 return可以省略

函数式接口

在 java8里新加了函数式接口的概念。函数式接口是什么呢?
如果一个接口只有一个抽象方法,那么这个接口就可以称之为函数式接口
通常会用 @FunctionalInterface 来标识
我们同样可以自定义函数式接口

  1. @FunctionalInterface
  2. public interface MyFunctionInterface {
  3. void method();
  4. }

@FunctionalInterface 会帮我们检测我们的接口是否符合 函数式接口的定义

lambda 表达式依赖于函数式接口,其本质就是作为 函数式接口的实例

java 内置的函数式接口

Consumer 消费型接口

参数类型为 T :接收一个T类型的值,没有返回值
包含方法 void accept (T t)
🌰:

  1. public void testConsumer(){
  2. //java8 之前
  3. happyTime(500.0, new Consumer<Double>() {
  4. @Override
  5. public void accept(Double aDouble) {
  6. System.out.println("看电影和吃饭消费了" + aDouble);
  7. }
  8. });
  9. //lambda 表达式
  10. happyTime(500.0,money -> System.out.println("看电影和吃饭消费了" + money));
  11. }
  12. public void happyTime(Double money, Consumer<Double> con){
  13. con.accept(money);
  14. }

Supplier 供给型接口

没有参数,返回一个T类型的值
包含方法 T get()

  1. //lambda 表达式
  2. Supplier<String> sup = () -> employee.getName();
  3. System.out.println(sup.get());

Function 函数型接口

参数类型为T,返回类型为R:接收一个 T类型的值进行操作,但返回结果为R类型
包含方法 R apply(T t)

Predicate 断定型接口

进行判断的,接受参数为T的类型,返回一个boolean类型的值
包含方法 boolean test(T t)
举个🌰:

  1. public void testPredicate() {
  2. List<String> list = Arrays.asList("北京","南京","天津");
  3. //过滤出带有 京 的字符串
  4. List<String> filterString = filterString(list, string -> string.contains("京"));
  5. System.out.println(filterString);
  6. }
  7. public List<String> filterString(List<String> strings, Predicate<String> pre) {
  8. ArrayList<String> list = new ArrayList<>();
  9. for (String s : strings) {
  10. if(pre.test(s)) {
  11. list.add(s);
  12. }
  13. }
  14. return list;
  15. }

方法引用

当要传递给lambda 体的操作已经有实现的方法时,就可以使用方法引用

  1. //lambda 表达式
  2. Consumer<String> con = (s) -> System.out.println(s);
  3. con.accept("halo");
  4. //方法引用的方式
  5. Consumer<String> con1 = System.out::println;
  6. con1.accept("halo");

可以看到 Consumer的 accpet方法的接收参数是 字符串类型 s,而且System.out.println也可以接收 字符串 s,这个时候就可以使用方法引用 类名(对象名)::方法名
方法引用 是对 lambda 表达式的进一步简化操作。

我觉得可以简单这样理解: 你的参数和返回值 和你 要引用的参数和返回值一致,就可以使用方法引用。

  • 对象名::非静态方法
  1. //lambda 表达式
  2. Supplier<String> sup = () -> employee.getName();
  3. System.out.println(sup.get());
  4. //方法引用
  5. Supplier<String> sup1 = employee::getName;
  6. System.out.println(sup1.get());
  • 类名::静态方法
  1. Comparator<Integer> comparator1 = (o1, o2) -> Integer.compare(o1, o2);
  2. System.out.println(comparator1.compare(1,2));
  3. Comparator<Integer> comparator2 = Integer::compare;
  4. System.out.println(comparator2.compare(1,2));
  • 类名::非静态方法
    这种使用就比较特殊了,它并不符合 参数和返回值相同就可以使用 方法引用 的原则。
  1. Comparator<String> com = (s1, s2) -> s1.compareTo(s2);
  2. com.compare("abc","abd");
  3. Comparator<String> com2 = String::compareTo;
  4. com2.compare("abc","abd");

第一个参数作为了方法调用者出现, s1.compareTo(s2),这个时候也是可以 方法引用的。

Stream API

java8 另一个重要的改变就是 Stream API,它给我们提供了对集合数据的操作,有点类似于 sql 的查询操作。当 数据库使用 nosql 的时候,岂不是能出现神奇的效果。
相比于Collection在内存中的存储,stream 更多的是是对数据的操作。

Stream 有三个步骤
创建 -> 中间操作 -> 终止操作

  • 创建:通过 集合,数组,获取一个 stream对象
  • 中间操作:一个中间操作链,对数据执行完操作后返回当前这个 stream对象,是链式的。中间操作在没有执行 终止操作 前是不会 执行的。
  • 终止操作:一旦执行,就会产生结果,之后这个stream 不能再被使用

Stream 的创建

这个就比较简单了,java8在 Collection 接口中添加了 stream的默认方法

  1. List<String> strings = new ArrayList<>();
  2. strings.add("hello");
  3. strings.add("java8");
  4. strings.add("nihao");
  5. strings.add("a");
  6. //返回一个顺序流
  7. Stream<String> stream = strings.stream();
  8. //返回一个并行流
  9. Stream<String> stringStream = strings.parallelStream();

当然你也可以通过数组来获取,而且 Stream中也有一个of()的静态方法来获取 stream 对象

  1. Stream<String> stringStream1 = Stream.of("123", "456", "789");

Stream 的中间操作

筛选与切片

  • filter(Predicate p) 从流中排出某些元素
  • limit(n) 截断流,使流中元素个数不超过n
  • skip(n) 跳过元素,返回一个扔掉前n个的流,若流中元素个数不足n个,则返回一个空流
  • distinct() 筛选去除重复元素
  1. List<String> strings = new ArrayList<>();
  2. strings.add("hello");
  3. strings.add("java8");
  4. strings.add("nihao");
  5. strings.add("a");
  6. strings.add("nihao");
  7. //去除a这个元素
  8. strings.stream()
  9. .filter((s -> !s.equals("a")))
  10. .forEach(System.out::println);
  11. System.out.println();
  12. //只要前两个元素
  13. strings.stream()
  14. .limit(2)
  15. .forEach(System.out::println);
  16. System.out.println();
  17. //去除前两个元素
  18. strings.stream()
  19. .skip(2)
  20. .forEach(System.out::println);
  21. System.out.println();
  22. //去除重复元素 nihao
  23. strings.stream()
  24. .distinct()
  25. .forEach(System.out::println);

映射

map

map(Function f) 接收一个一个函数作为参数(lambda表达式),该函数会被作用到每一个元素上,并将其映射成一个新的元素.(也就是 函数型接口)
将集合中所有的字符串变为大写,并过滤出长度大于2的字符串

  1. List<String> stringStream = Arrays.asList("aa", "bb", "cc", "ddd", "eeee");
  2. stringStream.stream()
  3. .map((s) -> s.toUpperCase())
  4. .filter(s -> s.length() > 2)
  5. .forEach(System.out::println);

flatMap

flatMap(Function f)接收一个函数作为参数,将流中的每一个元素都变成另一种类型单独的流,再将所有的流合成一个流
这个方法有点难度。
可以举个例子看一下,还是上边的 stringStream

  1. public static void main(String[] args) {
  2. //可以看一下返回类型是 Stream<Stream<Character>> 这种套娃型的
  3. Stream<Stream<Character>> streamStream = stringStream.map(Test::fromStringToStream);
  4. //如果这时要进行元素便利的话,需要进行双层循环
  5. streamStream.forEach(characterStream -> {
  6. characterStream.forEach(System.out::println);
  7. });
  8. }
  9. public static Stream<Character> fromStringToStream(String str) {
  10. List<Character> list = new ArrayList<>();
  11. for (Character c : str.toCharArray()) {
  12. list.add(c);
  13. }
  14. return list.stream();
  15. }

Stream< Stream< Character >>这种套娃型的stream 最好使用 flatMap,它会自动帮你把这些里边的全部的Stream< Character >打开变成一个 Stream< Character >
使用flatMap后就变成了:

        stringStream.stream()
                .flatMap(Test::fromStringToStream)
                .forEach(System.out::println);

看起来简洁的许多。

stream 的终止操作

匹配和分类

  • allMatch(Predicate p) 接收一个断定型接口,判断是否所有元素符合条件,符合则返回true
  • anyMatch(Predicate p) 判断是否有一个元素符合条件,如果符合,返回true
  • noneMatch(Predicate p) 检查是否没有匹配的元素,如果有匹配上的,返回false
  • findFirst() 获取第一个元素
  • findAny() 返回流中任意一个元素
  • count() 获取流中元素的个数
  • max(Comparator com)
  • min(Comparator com)
  • forEach(Consumer con)

归约

  • reduce(T identity, BinaryOperator) 将流中的元素反复结合起来,得到一个值, identity 是初始值。
        //reduce(T identity, BinaryOperator) 将流中的元素反复结合起来,得到一个值, identity 是初始值。
        //1 + 2 的结果再作为第一个值,再将第三个元素3作为第二值,循环往复
        List<Integer> integers  = Arrays.asList(1, 2, 3, 4, 5);
        Integer sum = integers.stream().reduce(0, Integer::sum);
        System.out.println(sum);

1 + 2 的结果再作为第一个值,再将第三个元素3作为第二值,两次再次相加的结果作为第一值,循环往复。
当然如果你不是设置identity的值的话,reduce(BinaryOperator)还是有这个只有一个形参的方法,只不过返回的是一个optoinal<>的值

收集

collet(Collector c) 将流中的结果转换成一个Collector(集合)出来

        //获取元素长度大于2的集合
       List<String> collect = stringStream.stream()
               .filter(s -> s.length() > 2)
               .collect(Collectors.toList());