JDK8新特性详解Lambda、StreamAPI、Optional等(二)

简介: JDK8新特性详解Lambda、StreamAPI、Optional等(二)

2.4 Predicate

有参且返回值为Boolean的接口

@FunctionalInterface
public interface Predicate<T> {
    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
}

使用

public class PrediacateTest {
    public static void main(String[] args) {
        Boolean result = func(msg -> msg.length()>3);
        System.out.println(result);
    }
    public static Boolean func(Predicate<String> p){
        return p.test("Hellow");
    }
}

默认方法

and、or、negate、isEquals

五、 方法引用

1. 为什么要用方法引用

1.1 Lambda表达式冗余

在使用Lambda表达式的时候也会出现代码冗余的情况

public class FunRefTest01 {
    public static void main(String[] args) {
        fun(msg ->{
            int sum = 0;
            for (int i : msg) {
                sum+=i;
            }
            System.out.println("求和为:"+sum);
        });
    }
    public static void getTotal (int[] arr){
        int sum = 0;
        for (int i : arr) {
            sum+=i;
        }
        System.out.println("外部求和为:"+sum);
    }
    public static void fun(Consumer<int[]> c1){
        int[] arr = {1,1,3,23,4,52,3};
        c1.accept(arr);
    }
}

1.2 解决方案

因为在Lambda表达式中要执行的代码和我们另一个方法中的代码是一样的,这是就没有必要重写一份逻辑了,这是我们可以“引用”重复的代码

public class FunRefTest01 {
    public static void main(String[] args) {
        fun(FunRefTest01::getTotal);
    }
    public static void getTotal (int[] arr){
        int sum = 0;
        for (int i : arr) {
            sum+=i;
        }
        System.out.println("外部求和为:"+sum);
    }
    public static void fun(Consumer<int[]> c1){
        int[] arr = {1,1,3,23,4,52,3};
        c1.accept(arr);
    }
}

方法引用是JDK8的新语法

2. 方法引用的格式

符号表示::

符号说明:双冒号为方法引用运算符,而它所在的表达式被称为方法引用

应用场景:如果Lambda表达式所要实现的方案,已经有其他方法存在相同的方案,那么则可以使用方法引用。

常见的引用方式:

方法引用在JDK8中使用是相当灵活的,有以下几种形式:

  1. instanceName::methodName 对象名::方法名
  2. ClassName::staticMethodName 类名::静态方法
  3. ClassName::methodName 类名::普通方法
  4. ClassName::new 类名::new 调用的构造器
  5. TypeName[]::new String[]::new 调用数组的构造器

2.1对象名字::方法名

这是最常见的一种用法。如果一个类中的已经存在了一个成员方法,则可以通过对象名引用成员方法

public class FunRefTest02 {
    public static void main(String[] args) {
        Date date = new Date();
        Supplier<Long> supplier = () ->{
            return date.getTime();
        };
        System.out.println("时间输出:"+supplier.get());
        Supplier<Long> supplier1 = date::getTime;
        System.out.println("引用时间数据:"+ supplier1.get());
    }
}

2.2 类名::静态方法

也是比较常用的方式:

public class FunRefTest03 {
    public static void main(String[] args) {
        Supplier<Long> supplier = () ->{
           return System.currentTimeMillis();
        };
        System.out.println(supplier.get());
        Supplier<Long> supplier1 = System::currentTimeMillis;
        System.out.println(supplier1.get());
    }
}

2.3 类名::引用实例方法

java面向对象中,类名只能调用静态方法,类名引用实例方法是有前提的,实际上是用第一个参数作为方法的调用者

public class FunRefTest04 {
    public static void main(String[] args) {
        Function<String,Integer> function = (str) ->{
          return str.length();
        };
        System.out.println("简化写法");
        Function<String,Integer> function1 = str -> str.length();
        System.out.println(function.apply("Hello"));
        System.out.println(function1.apply("Hello"));
        System.out.println("引用");
        Function<String,Integer> function2 = String::length;
        System.out.println(function2.apply("Hello"));
    }
}

2.4 类名::new构造器

由于构造器的名称和类名完全一致,所以构造器引用使用::new的格式使用

public class FunRefTest05 {
    public static void main(String[] args) {
        Supplier<Person> supplier = ()-> new Person();
        System.out.println(supplier.get().toString());
        Supplier<Person> supplier1 = Person::new;
        System.out.println(supplier1.get().toString());
    }
}

2.5 数组::构造器

public class FunRefTest06 {
    public static void main(String[] args) {
        Function<Integer,String[]> function = len -> new String[len];
        System.out.println(function.apply(3).length);
        Function<Integer,String[]> function1 = String[]::new;
        System.out.println(function1.apply(4).length);
    }
}

小结:方法引用是对Lambda表达式符合特定情况下的一种缩写方式,它使得我们的Lambda表达式更加的精简,可以理解为Lambda表达式的缩写形式,不过要注意的是方法引用只能引用已经存在的方法。

六、Stream API

1. 集合处理数据的弊端

当我们在需要对集合中的元素进行操作的时候,除了必需的添加、删除、获取外最典型的操作就是遍历集合

public class StreamTest01 {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("张三","张三丰","刘德华","周星驰");
        //获取所有姓张的
        List<String> list1 = new ArrayList<>();
        for (String s : list) {
            if(s.contains("张"))
                list1.add(s);
        }
        //获取字符小于三的
        List<String> list2 = new ArrayList<>();
        for (String s : list1) {
            if(s.length()<3)
                list2.add(s);
        }
        //打印出最终结果集
        for (String s : list2) {
            System.out.println(s);
        }
    }
}

stream的解决方案

public class StreamTest02 {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("张三","张三丰","刘德华","周星驰");
        //获取所有姓张的
        //获取字符小于三的
        //打印出最终结果集
        list.stream()
                .filter(s -> s.contains("张"))
                .filter(s -> s.length()<3)
                .forEach(System.out::println);
    }
}

2. Stream流式思想概述

Stream流式思想类似于工厂车间的“生产流水线”,Stream流不是一种数据结构,不保存数据,而是对数据进行加工处理。Stream可以看作是流水线上的多个工序,让一个原材料加工成一个商品。

StreamAPI可以让我们快速完成许多复杂的操作,如筛选、切片、映射、查找、去重、统计、匹配和归约。

3. Stream流的获取方式

3.1 根据Collection获取

首先java.util.Collection接口中加入了default方法stream,也就是说Collection接口下的所有实现都可以通过stream方法获取Stream流

public class StreamTest03 {
    public static void main(String[] args) {
        List<String> list1 = new ArrayList<>();
        list1.stream();
        List<String> list2 = new LinkedList<>();
        list2.stream();
        Set<String> set = new HashSet<>();
        set.stream();
        Vector vector = new Vector();
        vector.stream();
    }
}

Map接口没有实现Collection接口,可以通过Map获取对应的key和value的集合

public class StreamTest04 {
    public static void main(String[] args) {
        Map<String,Object> map = new HashMap<>();
        map.keySet().stream();
        map.values().stream();
        map.entrySet().stream();
    }
}

3.2 通过Stream的of方法

在实际开发中我们不可避免的还是会操作到数据中的数据,由于数组对象不可能添加默认方法,所以Stream接口中提供了静态方法of

public class StreamTest05 {
    public static void main(String[] args) {
        Stream<String> stringStream = Stream.of("1","2","3","4");
        String[] arr1 = {"aa","bb","cc","dd"};
        Stream<String> stream = Stream.of(arr1);
        stream.forEach(System.out::println);
        Integer[] arr2 = {1,2,3,4};
        Stream<Integer> stream1 = Stream.of(arr2);
        stream1.forEach(System.out::println);
        //注意: 基本数据类型的数组是不行的
        int[] arr3 = {1,2,3,4};
        Stream.of(arr3).forEach(System.out::println);
    }
}

4. Stream常用方法介绍

Stream流模型的操作很丰富,这里介绍

一些常用的API,这些方法可以被分成两种:

方法名 方法作用 返回值类型 方法种类
count 统计个数 long 终结
forEach 注意处理 void 终结
filter 过滤 Stream 函数拼接
limit 取用前几个 Stream 函数拼接
skip 跳过前几个 Stream 函数拼接
map 映射 Stream 函数拼接
concat 组合 Stream 函数拼接
match 匹配 boolean 终结

终结方法: 返回值类型不再是Stream类型,不再支持链式调用

非中介方法: 返回值类型仍然是Stream类型的方法,支持链式调用

Stream注意事项(重要)

  1. Stream只能操作一次
  2. Stream方法返回的是最新的流
  3. Stream不调用中介方法,中间的操作是不会执行的

4.1 forEach

forEach用来遍历流中的数据的

void forEach(Consumer<? super T> action);

该方法接受一个Consumer接口,会将每一个流元素交给瀚书处理

public class StreamTest06ForEach {
    public static void main(String[] args) {
        Stream.of("1","2","3","4")
                .forEach(System.out::println);
    }
}

4.2 count

Stream流中的count方法用来统计其中元素个数的

long count();

该方法会返回一个long值,代表元素的个数

public class StreamTest07Count {
    public static void main(String[] args) {
        long count = Stream.of("1", "2", "3", "4")
                .count();
        System.out.println(count);
    }
}

4.3 filter

filter方法的做哦那个是用来过滤数据的。返回符合条件的数据

可以通过filter方法将一个流转换成另一个子集流

Stream<T> filter(Predicate<? super T> predicate);

该接口接收一个Predicate函数接口作为筛选条件

public class StreamTest08Filter {
    public static void main(String[] args) {
        Stream.of("a1","a2","a3","b2","b3","c2")
                .filter(e->e.contains("a"))
                .forEach(System.out::println);
    }
}

输出

a1
a2
a3

4.4 limit

limit方法可以对流进行截取处理,截取前n个数据

Stream<T> limit(long maxSize);

参数是一个long类型的值,如果集合当前长度大于参数就进行截取,否则不操作

public class StreamTest09Limit {
    public static void main(String[] args) {
        Stream.of("a1","a2","a3","b2","b3","c2")
                .limit(111)
                .forEach(System.out::println);
    }
}

4.5 skip

如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的流

Stream<T> skip(long n);
public class StreamTest09Skip {
    public static void main(String[] args) {
        Stream.of("a1","a2","a3","b2","b3","c2")
                .skip(2)
                .forEach(System.out::println);
    }
}

4.6 map

如果我们需要将流中的元素映射到另一个流中,可以使用map方法:

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换成另一种R类型的数据

4.7 sorted

如果需要将数据排序,可以使用sorted方法

Stream<T> sorted();
    Stream<T> sorted(Comparator<? super T> comparator);

默认是增序排序

public class StreamTest12Sorted {
    public static void main(String[] args) {
        Stream.of("6","2","7","5","7","8")
//                .map(e-> Integer.parseInt(e))
                .map(Integer::parseInt)
//                .sorted()//默认增序
                .sorted(((o1, o2) -> o2-o1))
                .forEach(System.out::println);
    }
}

4.8 distinct

如果需要去掉重复的数,可以使用distinct方法:

Stream<T> distinct();

使用

public class StreamTest13Distinct {
    public static void main(String[] args) {
        Stream.of("a1","a2","a1","a3","a4","a2")
                .distinct()
                .forEach(System.out::println);
        Stream.of(
                new Person("张三",18,12),
                new Person("李四",23,11),
                new Person("张三",18,12),
                new Person("王五",12,13)
        ).distinct().forEach(System.out::println);
    }
}

Stream流中的distinct方法对于基本数据类型是可以直接去重的,但是对于自定义类型,我们是需要重写hashCode和equals方法来移除重复的元素。

4.9 match

如果需要判断数据是否匹配指定的条件,可以使用match相关的方法

boolean anyMatch(Predicate<? super T> predicate);//元素是否有任意一个满足条件
    boolean allMatch(Predicate<? super T> predicate);//元素是否都满足条件
    boolean noneMatch(Predicate<? super T> predicate);//元素是否都不满足条件

使用

public class StreamTest14Match {
    public static void main(String[] args) {
        Boolean result = Stream.of("1", "2", "3", "4", "5")
                .map(Integer::parseInt)
//                .allMatch(s -> s > 0);
//                .allMatch(s -> s > 3);
//                .anyMatch(s -> s > 3);
                .noneMatch(s -> s > 10);
        System.out.println(result);
    }
}

注意match是一个终结方法

4.10 find

如果我们需要找到某些数据,可以使用find方法来实现

Optional<T> findFirst();//就是找第一个元素
    Optional<T> findAny();

使用

public class StreamTest15Find {
    public static void main(String[] args) {
        Optional<String> first = Stream.of("2", "21", "1", "3", "4", "3", "9", "22")
                .findFirst();
        System.out.println(first.get());
        Optional<String> any = Stream.of("2", "21", "1", "3", "4", "3", "9", "22")
                .findAny();
        System.out.println(any.get());
    }
}

输出结果

2
2

可以看到findFirst和findAny结果都一样,大家有没有注意到对“names”这个集合做流化处理使用的是“stream”,这是串行流。如果我们的“names”是有序的,那findAny的任意一个都是第一个了

使用并行流

public class StreamTest15Find {
    public static void main(String[] args) {
        Optional<String> first = Stream.of("2", "21", "1", "3", "4", "3", "9", "22")
                .findFirst();
        System.out.println(first.get());
        Optional<String> any = Stream.of("2", "21", "1", "3", "4", "3", "9", "22")
                .findAny();
        System.out.println(any.get());
        System.out.println("并行流测试");
        for (int i=0;i<10;i++){
            List<String> list = Arrays.asList("2", "21", "1", "82", "4", "3", "9", "22");
            Optional<String> nio = list.parallelStream().findAny();
            System.out.println(nio.get());
        }
    }
}

输出结果

2
2
并行流测试
3
3
4
3
1
3
3
3
3
3

并行流效率更快

目录
相关文章
|
5天前
|
安全 JavaScript 前端开发
JDK1.8的重要的新特性与功能
Java Development Kit (JDK) 1.8,也称为Java 8,是Java平台的一个重大更新,于2014年3月发布。它引入了多项新特性、新的API和性能提升
102 3
|
5天前
|
Java
【JAVA进阶篇教学】第二篇:JDK8中Lambda表达式
【JAVA进阶篇教学】第二篇:JDK8中Lambda表达式
|
5天前
|
算法 Java 编译器
升级指南之JDK 11+ 新特性和AJDK
本文详细介绍了JDK个版本之间的特性、区别以及JDK版本更迭时优化了哪些地方,对JDK的版本选择给出了建议,以及升级教程。
|
5天前
|
存储 安全 Java
JDK22发布了!来看看有哪些新特性
以上是介绍 JDK22新特性的全部内容了,突然V哥想要感慨一下,技术之路,学无止境,选择 IT 技术,作个纯粹的人,享受研究技术的过程,这种带来的快感,也许只有真正热爱编程的人才能有体会。
|
5天前
|
Java
Java jdk1.8 lambda 遍历集合的时候到底需不需判空
Java jdk1.8 lambda 遍历集合的时候到底需不需判空
|
5天前
|
编解码 Java API
集合在JDK9中的新特性
集合在JDK9中的新特性
|
5天前
|
Java
JDK8新特性--lambda表达式
面向对象思想强调:必须通过对象的形式来做事情
JDK8新特性--lambda表达式
|
5天前
|
SQL Java API
浅析jdk8所包含的主要特性
浅析jdk8所包含的主要特性
|
5天前
|
IDE Java Shell
02|手把手教你安装JDK与配置主流IDE
02|手把手教你安装JDK与配置主流IDE
27 0
|
5天前
|
Java Shell 开发者
都2024年了!你还不知道在Docker中安装jdk?
都2024年了!你还不知道在Docker中安装jdk?