< 返回新闻公共列表
JDK8新特性详解Lambda、StreamAPI、Optional等(二)
发布时间:2023-06-27 14:00:47
2.4 Predicate
有参且返回值为Boolean的接口
@FunctionalInterface public interface Predicate
{ /** * 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 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 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 c1){ int[] arr = {1,1,3,23,4,52,3}; c1.accept(arr); } }
方法引用是JDK8的新语法
2. 方法引用的格式
符号表示::
符号说明:双冒号为方法引用运算符,而它所在的表达式被称为方法引用
应用场景:如果Lambda表达式所要实现的方案,已经有其他方法存在相同的方案,那么则可以使用方法引用。
常见的引用方式:
方法引用在JDK8中使用是相当灵活的,有以下几种形式:
instanceName::methodName 对象名::方法名
ClassName::staticMethodName 类名::静态方法
ClassName::methodName 类名::普通方法
ClassName::new 类名::new 调用的构造器
TypeName[]::new String[]::new 调用数组的构造器
2.1对象名字::方法名
这是最常见的一种用法。如果一个类中的已经存在了一个成员方法,则可以通过对象名引用成员方法
public class FunRefTest02 { public static void main(String[] args) { Date date = new Date(); Supplier supplier = () ->{ return date.getTime(); }; System.out.println("时间输出:"+supplier.get()); Supplier supplier1 = date::getTime; System.out.println("引用时间数据:"+ supplier1.get()); } }
2.2 类名::静态方法
也是比较常用的方式:
public class FunRefTest03 { public static void main(String[] args) { Supplier supplier = () ->{ return System.currentTimeMillis(); }; System.out.println(supplier.get()); Supplier supplier1 = System::currentTimeMillis; System.out.println(supplier1.get()); } }
2.3 类名::引用实例方法
java面向对象中,类名只能调用静态方法,类名引用实例方法是有前提的,实际上是用第一个参数作为方法的调用者
public class FunRefTest04 { public static void main(String[] args) { Function function = (str) ->{ return str.length(); }; System.out.println("简化写法"); Function function1 = str -> str.length(); System.out.println(function.apply("Hello")); System.out.println(function1.apply("Hello")); System.out.println("引用"); Function function2 = String::length; System.out.println(function2.apply("Hello")); } }
2.4 类名::new构造器
由于构造器的名称和类名完全一致,所以构造器引用使用::new的格式使用
public class FunRefTest05 { public static void main(String[] args) { Supplier supplier = ()-> new Person(); System.out.println(supplier.get().toString()); Supplier supplier1 = Person::new; System.out.println(supplier1.get().toString()); } }
2.5 数组::构造器
public class FunRefTest06 { public static void main(String[] args) { Function function = len -> new String[len]; System.out.println(function.apply(3).length); Function 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 list = Arrays.asList("张三","张三丰","刘德华","周星驰"); //获取所有姓张的 List list1 = new ArrayList<>(); for (String s : list) { if(s.contains("张")) list1.add(s); } //获取字符小于三的 List 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 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 list1 = new ArrayList<>(); list1.stream(); List list2 = new LinkedList<>(); list2.stream(); Set 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 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 stringStream = Stream.of("1","2","3","4"); String[] arr1 = {"aa","bb","cc","dd"}; Stream stream = Stream.of(arr1); stream.forEach(System.out::println); Integer[] arr2 = {1,2,3,4}; Stream 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注意事项(重要)
Stream只能操作一次
Stream方法返回的是最新的流
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 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 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 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方法:
Stream map(Function super T, ? extends R> mapper);
该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换成另一种R类型的数据
4.7 sorted
如果需要将数据排序,可以使用sorted方法
Stream sorted(); Stream 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 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 findFirst();//就是找第一个元素 Optional findAny();
使用
public class StreamTest15Find { public static void main(String[] args) { Optional first = Stream.of("2", "21", "1", "3", "4", "3", "9", "22") .findFirst(); System.out.println(first.get()); Optional 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 first = Stream.of("2", "21", "1", "3", "4", "3", "9", "22") .findFirst(); System.out.println(first.get()); Optional 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 list = Arrays.asList("2", "21", "1", "82", "4", "3", "9", "22"); Optional nio = list.parallelStream().findAny(); System.out.println(nio.get()); } } }
输出结果
2 2 并行流测试 3 3 4 3 1 3 3 3 3 3
并行流效率更快