1.Java8新特性
1.Lambda表达式
1.1 概述
Lambda表达式是一种函数式编程(函数编程思想)方式,用于替代匿名内部类。它使得代码更具有可读性和简洁性,并提供更好的代码复用性和可维护性。
面向对象编程思想:
强调的是对象,必须通过对象来完成操作,情况较复杂。例如:多线程执行任务,需要创建对象。首先定义一个实现类实现接口Runnable,然后重写run方法中的代码传递给线程对象,这么麻烦?直接执行不就好了吗?
函数编程思想:
函数需要得有输入量、输出量,使用输入量计算得到输出量。为了尽量忽略对象的复杂用法---强调做什么,而不是以什么去做。
同样执行线程任务,使用函数编程思想,可以直接通过传递一段代码给线程对象执行,不需要创建任务对象。
小结:函数编程思想可以通过一段代码完成面向对象想要做的代码量。
1.2 Lambda表达式格式
1.2.1 标准格式
(参数列表)->{代码}
1.2.2 格式说明
- 小括号内的语法与接口中的方法括号里面的参数列表一致,空参数则空,多个参数逗号(",")隔开;
- ->新引入的语法格式,表示指向动作;
- 大括号内的内容与接口实现类中方法体的内容一致
1.2.3 代码实例
public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println("第一种创建线程方式"); } }).start(); new Thread(()->{ System.out.println("更简洁地方法创建多线程"); }).start(); }
运行结果
在上述代码中,我们可以看到使用lambda表达式,省去了创建接口的流程(new Runnable),连里面的方法(run()方法)都省去,只保留方法中的内容。
List<Integer> list = new ArrayList<>(); //为列表一次性添加多个元素 Collections.addAll(list, 11,22,33,44,55); System.out.println(list); //比较器的正常书写格式 Collections.sort(list, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o2 - o1;//后面数减前面数是降序排列 } }); System.out.println(list); //lambda表达式 Collections.sort(list,(Integer o1, Integer o2)->{ return o1 -o2;});//前面数减后面数是升序排列 System.out.println(list);
运行结果
1.3 Lambda表达式使用的条件
首先,都是接口,且接口中只有一个抽象方法,才可以使用Lambda表达式。
- 接口中只有一个抽象方法,叫函数式接口
- 如果是函数式接口,可以用@FunctionInterface注解标识
在上图中,Runnable是一个接口,其只有一个抽象方法,且被@FunctionInterface注解标识,只需满足其中一个就可以使用Lambda表达式。
1.4 Lambda 表达式的简写格式
- 小括号中的形参类型可以省略;
- 如果小括号中只有一个参数的话,那么小括号可以省略
- 如果大括号中只有一条语句,那么大括号、分号、return可以一起省略
new Thread(()->System.out.println("更简洁的线程创建")).start(); 运行结果 更简洁的线程创建
//继续简化lambda表达式 Collections.sort(list, (o1, o2)->o2-o1); 运行结果 [55, 44, 33, 22, 11]
1.5 Lambda表达式的表现形式
1.变量的形式:变量为函数式接口,就么可以复制一个Lambda表达式【不常用】
// 变量的形式 Runnable r = ()->{ System.out.println("任务代码"); }; // 函数式接口类型的变量 Thread t = new Thread(r);
2.参数的形式:方法的形参为函数式接口,就可以传入一个Lambda表达式【常用】
// 变量的形式-比较器 Comparator<Integer> comparable = (o1, o2)->{return o2 - o1;}; // 创建集合 ArrayList<Integer> list = new ArrayList<>(); // 存入数据 Collections.addAll(list,11,22,33,44,55); // 将函数式接口类型 的 形参类型,传给Collections Collections.sort(list,comparable);
3.返回值的形式:方法的返回值类型为函数式接口,就可以返回一个Lambda表达式【常用】
// 定义一个方法 public static Comparator<Integer> getComparator(){ return (Integer o1,Integer o2)->{return o2-o1;}; } public static void main (String[] args) { // 返回值形式 Collections.sort(list,getComparator()); }
2.Stream流
Stream流是一种用于处理集合数据和进行数据操作的API。它提供了一种简单而强大的方法,可以以声明式的方式对集合进行过滤、映射、排序、分组等操作。
2.1 Stream流引入
问题:
- 将list集合中姓张的元素过滤到一个新的集合中
- 然后将过滤出来的姓张的元素中,再过滤出来长度为3的元素,存储到一个新的集合中
代码实例
//①将list集合中姓张的元素过滤到一个新的集合中 //②然后将过滤出来的姓张的元素中,再过滤出来长度为3的元素,存储到一个新的集合中 List<String> list = new ArrayList<>(); Collections.addAll(list, "张老三", "张小三", "李四", "王五", "张六", "赵八"); List<String> list1 = new ArrayList<>(); for (String name:list) { if (name.startsWith("张")){ list1.add(name); } } System.out.println(list1); List<String> list2 = new ArrayList<>(); for (String name:list1) { if (name.length() == 3){ list2.add(name); } } System.out.println(list2);
运行结果
[张老三, 张小三, 张六]
[张老三, 张小三]
用Stream流操作集合,获取流,过滤操作,打印输出
list.stream().filter((String name)->name.startsWith("张")) //方法体中只有一个形参时可省略小括号,方法体中只有一个语句时可省略大括号 .filter(name -> name.length() == 3) //遍历 .forEach(name -> System.out.println(name));
运行结果
张老三
张小三
2.2 Stream的格式
Stream<T> filter(Predicate<? super T> predicate);
参数:public interface Preicate<T>
在filter()方法中的Predicate接口是一个函数式接口。
2.3 获取流
根据集合(Collection)获取流,Collection中有一个stream()方法可获取流。
default Stream<E> stream() { return StreamSupport.stream(spliterator(), false); }
2.3.1 根据list集合获取流
List<String> list = new ArrayList<>(); Collections.addAll(list, "张老三", "张小三", "李四", "王五", "张六", "赵八"); Stream<String>listStream = list.stream();
2.3.2 根据Set集合获取流
Set<String>set = new HashSet<>(); Collections.addAll(set, "张老三", "张小三", "李四", "王五", "张六", "赵八"); Stream<String> setStream = set.stream();
2.3.3 根据Map集合获取流
Map<Integer, String>map = new HashMap<>(); map.put(1,"张老三"); map.put(2,"张小三"); map.put(3,"李四"); map.put(4,"王五"); map.put(5,"张六"); map.put(6,"赵八"); //获取键的流 Set<Integer> keySet = map.keySet(); Stream<Integer> keySetStream = keySet.stream(); //获取值的流 Collection<String> values = map.values(); Stream<String> valuesStream = values.stream(); //获取键值对对象的流 Set<Map.Entry<Integer, String>> entries = map.entrySet(); Stream<Map.Entry<Integer, String>> entryStream = entries.stream();
2.3.4 根据数组获取流
// 根据数组获取流 String[] arr = {"张三","李四","赵五","刘六","王七"}; Stream<String> arrStream = Stream.of(arr);
2.4 Stream常用方法
- 终结方法:返回值类型不再是Stream接口本身类型的方法,例如:forEach方法和count方法
- 非终结方法/延迟方法:返回值类型仍然是Stream接口自身类型的方法,除了终结方法都是延迟方法。例如:filter,limit,skip,map,conat
下面方法公用代码
List<String> list = new ArrayList<>(); Collections.addAll(list, "张老三", "张小三", "李四", "王五", "张六", "赵八"); Stream<String> arrStream = Stream.of("1","2","3","4","5");
2.4.1 count方法
概述
long count (); 统计流中的元素,返回long类型数据
long count = list.stream().count();//终结方法后续无法继续链接 System.out.println(count); 运行结果:
6
2.4.2 filter()方法
Stream<T> filter(Predicate<? super ?> predicate); 过滤出满足条件的元素 参数Predicate:函数式接口,抽象方法:boolean test (T t) Predicate接口:是一个判断接口
//filter方法加foreach方法 list.stream().filter(name ->name.startsWith("张")) .filter(name -> name.length() == 3) .forEach(name -> System.out.println(name)); 运行结果:
张老三
张小三
2.4.3 foreach()方法
void forEach(Consumer<? super T> action):逐一处理流中的元素 参数 Consumer<? super T> action:函数式接口,只有一个抽象方法:void accept(T t);
注意:
1.此方法并不保证元素的逐一消费动作在流中是有序进行的(元素可能丢失)
2.Consumer是一个消费接口(可以获取流中的元素进行遍历操作,输出出去),可以使用Lambda表达式
2.4.4 limit()方法
Stream<T> limit(long maxSize); 取用前几个元素 注意: 参数是一个long 类型,如果流的长度大于参数,则进行截取;否则只截取已有流长度的所有元素
//limit方法(取前面几个) list.stream().limit(3).forEach(name -> System.out.print(name)+" ");
运行结果
张老三 张小三 李四
list.stream().limit(7).forEach(name -> System.out.print(name)+" ");//比已有流的长度6大
运行结果:
张老三 张小三 李四 王五 张六 赵八
2.4.5 skip()方法
Stream<T> skip(long n); 跳过前几个元素 注意: 如果流的当前长度大于n,则跳过前n个,否则将会得到一个长度为0的空流
//skip()方法(跳过前面几个) list.stream().skip(3).forEach(name -> System.out.print(name)+" ");
运行结果
王五 张六 赵八
list.stream().skip(7).forEach(name -> System.out.println(name)); 运行结果:
无值
2.4.6 map()方法
<r> Stream <R> map(Function<? super T,? exception R> mapper; 参数Function<T,R>:函数式接口,抽象方法:R apply(T t); Function<T,R>:其实就是一个类型转换接口(T和R的类型可以一致,也可以不一致)
//6.map()类型转换接口 arrStream.map(num -> Integer.parseInt(num)) .forEach(i -> System.out.print(i)+" "); 运行结果 1 2 3 4 5
2.4.7 concat()方法
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) --> 合并两个流
//concat()方法:合并两个流
Stream<String>listStream = list.stream(); Stream<String>arrStream = Stream.of(arr);
Stream.concat(listStream, arrStream).forEach(name -> System.out.println(name));
运行结果:
报错
原因:Stream API只能被消费一次,后续重复使用已建立的流会报异常!所以stream流是线程安全的!前面代码我使用过arrStream的流,因此这里报错。
解决办法:创建一个新的流进行操作
String[] arr1 = {"1", "2", "3", "4"}; Stream<String>arr1Stream = Stream.of(arr1); //7.concat()合并两个流 System.out.println("-----"); Stream.concat(listStream, arr1Stream).forEach(name -> System.out.print(name+" "));
运行结果
张老三 张小三 李四 王五 张六 赵八 1 2 3 4
2.5 收集Stream流
Stream流中提供了一个方法,可以将流中的元素收集到一个单例流中
<R, A> R collect(Collector<? super T, A, R> collector); 把流中的数据手机到单列集合中 返回值类型是R。R指定为什么类型,那么就收集什么类型的集合 参数Collector<? super T, A, R>中的R类型,决定把流中的元素收集到哪个集合中 参数Collector如何得到 ?,可以使用 java.util.stream.Collectors工具类中的静态方法: - public static <T> Collector<T, ?, List<T>> toList():转换为List集合 - public static <T> Collector<T, ?, Set<T>> toSet() :转换为Set集合
ArrayList<Character> charList = new ArrayList<>(); Collections.addAll(charList, 'a', 'b', 'c', 'd', 'e'); Stream<Character> cStream = charList.stream();//获取一个流 List<Character> newList = cStream.collect(Collectors.toList()); System.out.println(newList); Stream<Character> c1Stream = charList.stream();//不能重复消费,这里重新获取一个流对象 Set<Character> newSet = c1Stream.collect(Collectors.toSet()); System.out.println(newSet);
运行结果
[a, b, c, d, e]
[a, b, c, d, e]
参考链接
https://blog.csdn.net/m0_60489526/article/details/119959355
https://blog.csdn.net/m0_60489526/article/details/119984236