一、函数式接口
1.1、函数式接口概述
-
概念
有且仅有一个抽象方法的接口
-
如何检测一个接口是不是函数式接口
@FunctionalInterface
放在接口定义的上方:如果接口是函数式接口,编译通过;如果不是,编译失败
-
注意事项
我们自己定义函数式接口的时候,@FunctionalInterface是可选的,就算我不写这个注解,只要保证满足函数式接口定义的条件,也照样是函数式接口。但是,建议加上该注解
1.2、函数式接口作为方法的参数
package com.jdk8.functionalinterface;public class RunnableDemo {public static void main(String[] args) {// 在主方法中调用startThread方法// 匿名内部类的方式startThread(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "线程启动了");}});// Lambda方式startThread(() -> System.out.println(Thread.currentThread().getName() + "线程启动了"));}private static void startThread(Runnable r) {new Thread(r).start();}
}
1.3、函数式接口作为方法的返回值
需求描述
定义一个类(ComparatorDemo),在类中提供两个方法
一个方法是:Comparator getComparator() 方法返回值Comparator是一个函数式接口
一个方法是主方法,在主方法中调用getComparator方法
package com.jdk8.functionalinterface;import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;public class ComparatorDemo {public static void main(String[] args) {// 定义集合,存储字符串元素ArrayList<String> array = new ArrayList<String>();array.add("cccc");array.add("aa");array.add("b");array.add("ddd");System.out.println("排序前:" + array);Collections.sort(array, getComparator());System.out.println("排序后:" + array);}private static Comparator<String> getComparator() {// 匿名内部类的方式实现
// return new Comparator<String>() {
// @Override
// public int compare(String s1, String s2) {
// return s1.length()-s2.length();
// }
// };// Lambda方式实现return (s1, s2) -> s1.length() - s2.length();}
}
1.4、常用函数式接口之Supplier
-
Supplier接口
Supplier接口也被称为生产型接口,如果我们指定了接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据供我们使用。
-
常用方法
只有一个无参的方法
方法名 说明 T get() 按照某种实现逻辑(由Lambda表达式实现)返回一个数据
package com.jdk8.functionalinterface;import java.util.function.Supplier;public class SupplierDemo {public static void main(String[] args) {String s = getString(() -> "林青霞");System.out.println(s);Integer i = getInteger(() -> 30);System.out.println(i);}// 定义一个方法,返回一个整数数据private static Integer getInteger(Supplier<Integer> sup) {return sup.get();}// 定义一个方法,返回一个字符串数据private static String getString(Supplier<String> sup) {return sup.get();}}
案例需求
定义一个类(SupplierTest),在类中提供两个方法
一个方法是:int getMax(Supplier sup) 用于返回一个int数组中的最大值
一个方法是主方法,在主方法中调用getMax方法
package com.jdk8.functionalinterface;import java.util.function.Supplier;public class SupplierTest {public static void main(String[] args) {// 定义一个int数组int[] arr = {19, 50, 28, 37, 46};int maxValue = getMax(() -> {int max = arr[0];for (int i = 1; i < arr.length; i++) {if (arr[i] > max) {max = arr[i];}}return max;});System.out.println(maxValue);}// 返回一个int数组中的最大值private static int getMax(Supplier<Integer> sup) {return sup.get();}
}
1.5、常用函数式接口之Consumer
-
Consumer接口
Consumer接口也被称为消费型接口,它消费的数据的数据类型由泛型指定
-
常用方法
Consumer:包含两个方法
方法名 说明 void accept(T t) 对给定的参数执行此操作 default Consumer andThen(Consumer after) 返回一个组合的Consumer,依次执行此操作,然后执行 after操作
package com.jdk8.functionalinterface;import java.util.function.Consumer;public class ConsumerDemo {public static void main(String[] args) {// 操作一operatorString("林青霞", s -> System.out.println(s));// 操作二operatorString("林青霞", s -> System.out.println(new StringBuilder(s).reverse().toString()));System.out.println("--------");// 传入两个操作使用andThen完成operatorString("林青霞", s -> System.out.println(s), s -> System.out.println(new StringBuilder(s).reverse().toString()));}// 定义一个方法,用不同的方式消费同一个字符串数据两次private static void operatorString(String name, Consumer<String> con1, Consumer<String> con2) {
// con1.accept(name);
// con2.accept(name);con1.andThen(con2).accept(name);}// 定义一个方法,消费一个字符串数据private static void operatorString(String name, Consumer<String> con) {con.accept(name);}
}
案例需求
String[] strArray = {“林青霞,30”, “张曼玉,35”, “王祖贤,33”};
字符串数组中有多条信息,请按照格式:“姓名:XX,年龄:XX"的格式将信息打印出来
要求:
把打印姓名的动作作为第一个Consumer接口的Lambda实例
把打印年龄的动作作为第二个Consumer接口的Lambda实例
将两个Consumer接口按照顺序组合到一起使用
package com.jdk8.functionalinterface;import java.util.function.Consumer;public class ConsumerTest {public static void main(String[] args) {String[] strArray = {"林青霞,30", "张曼玉,35", "王祖贤,33"};printInfo(strArray, str -> System.out.print("姓名:" + str.split(",")[0]),str -> System.out.println(",年龄:" + Integer.parseInt(str.split(",")[1])));}private static void printInfo(String[] strArray, Consumer<String> con1, Consumer<String> con2) {for (String str : strArray) {con1.andThen(con2).accept(str);}}
}
1.6、常用函数式接口之Predicate
Predicate接口
Predicate接口通常用于判断参数是否满足指定的条件
常用方法
方法名 | 说明 |
---|---|
boolean test(T t) | 对给定的参数进行判断(判断逻辑由Lambda表达式实现),返回一个布尔值 |
default Predicate negate() | 返回一个逻辑的否定,对应逻辑非 |
default Predicate and(Predicate other) | 返回一个组合判断,对应短路与 |
default Predicate or(Predicate other) |
package com.jdk8.functionalinterface;import java.util.function.Predicate;/*** @Author: 史小创* @Time: 2024/8/11 下午8:14* @Description:*/public class PredicateDemo {public static void main(String[] args) {m1();System.out.println("---------------");m2();}public static void m2() {boolean b1 = checkString("hello", s -> s.length() > 8);System.out.println(b1);boolean b2 = checkString("helloworld", s -> s.length() > 8);System.out.println(b2);boolean b3 = checkString("hello", s -> s.length() > 8, s -> s.length() < 15);System.out.println(b3);boolean b4 = checkString("helloworld", s -> s.length() > 8, s -> s.length() < 15);System.out.println(b4);}// 同一个字符串给出两个不同的判断条件,最后把这两个判断的结果做逻辑与运算的结果作为最终的结果private static boolean checkString(String s, Predicate<String> pre1, Predicate<String> pre2) {return pre1.or(pre2).test(s);}// 判断给定的字符串是否满足要求private static boolean checkString(String s, Predicate<String> pre) {return pre.test(s);}public static void m1() {boolean b1 = checkString_m1("hello", s -> s.length() > 8);System.out.println(b1);boolean b2 = checkString_m1("helloworld", s -> s.length() > 8);System.out.println(b2);}// 判断给定的字符串是否满足要求private static boolean checkString_m1(String s, Predicate<String> pre) {
// return !pre.test(s);return pre.negate().test(s);}
}
练习描述
-
String[] strArray = {“林青霞,30”, “柳岩,34”, “张曼玉,35”, “貂蝉,31”, “王祖贤,33”};
-
字符串数组中有多条信息,请通过Predicate接口的拼装将符合要求的字符串筛选到集合ArrayList中,并遍历ArrayList集合
-
同时满足如下要求:姓名长度大于2;年龄大于33
-
分析
-
有两个判断条件,所以需要使用两个Predicate接口,对条件进行判断
-
必须同时满足两个条件,所以可以使用and方法连接两个判断条件
-
package com.jdk8.functionalinterface;import java.util.ArrayList;
import java.util.function.Predicate;public class PredicateTest {public static void main(String[] args) {String[] strArray = {"林青霞,30", "柳岩,34", "张曼玉,35", "貂蝉,31", "王祖贤,33"};ArrayList<String> array = myFilter(strArray, s -> s.split(",")[0].length() > 2,s -> Integer.parseInt(s.split(",")[1]) > 33);for (String str : array) {System.out.println(str);}}// 通过Predicate接口的拼装将符合要求的字符串筛选到集合ArrayList中private static ArrayList<String> myFilter(String[] strArray, Predicate<String> pre1, Predicate<String> pre2) {// 定义一个集合ArrayList<String> array = new ArrayList<String>();// 遍历数组for (String str : strArray) {if (pre1.and(pre2).test(str)) {array.add(str);}}return array;}
}
1.7、常用函数式接口之Function
-
Function接口
Function<T,R>接口通常用于对参数进行处理,转换(处理逻辑由Lambda表达式实现),然后返回一个新的值
-
常用方法
方法名 说明 R apply(T t) 将此函数应用于给定的参数 default Function andThen(Function after) 返回一个组合函数,首先将该函数应用于输入,然后将after函数应用于结果
package com.jdk8.functionalinterface;import java.util.function.Function;public class FunctionDemo {public static void main(String[] args) {// 操作一convert("100", s -> Integer.parseInt(s));// 操作二convert(100, i -> String.valueOf(i + 566));// 使用andThen的方式连续执行两个操作convert("100", s -> Integer.parseInt(s), i -> String.valueOf(i + 566));}// 定义一个方法,把一个字符串转换int类型,在控制台输出private static void convert(String s, Function<String, Integer> fun) {
// Integer i = fun.apply(s);int i = fun.apply(s);System.out.println(i);}// 定义一个方法,把一个int类型的数据加上一个整数之后,转为字符串在控制台输出private static void convert(int i, Function<Integer, String> fun) {String s = fun.apply(i);System.out.println(s);}// 定义一个方法,把一个字符串转换int类型,把int类型的数据加上一个整数之后,转为字符串在控制台输出private static void convert(String s, Function<String, Integer> fun1, Function<Integer, String> fun2) {String ss = fun1.andThen(fun2).apply(s);System.out.println(ss);}}
练习描述
-
String s = “林青霞,30”;
-
请按照我指定的要求进行操作:
1:将字符串截取得到数字年龄部分
2:将上一步的年龄字符串转换成为int类型的数据
3:将上一步的int数据加70,得到一个int结果,在控制台输出
-
请通过Function接口来实现函数拼接
package com.jdk8.functionalinterface;import java.util.function.Function;public class FunctionTest {public static void main(String[] args) {String s = "林青霞,30";convert(s, ss -> ss.split(",")[1], Integer::parseInt, i -> i + 70);}private static void convert(String s, Function<String, String> fun1, Function<String, Integer> fun2, Function<Integer, Integer> fun3) {int i = fun1.andThen(fun2).andThen(fun3).apply(s);System.out.println(i);}
}
二、Stream流
在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。
2.1、引言
传统集合的多步遍历代码
几乎所有的集合(如Collection
接口或Map
接口等)都支持直接或间接的遍历操作。而当我们需要对集合中的元素进行操作的时候,除了必需的添加、删除、获取外,最典型的就是集合遍历。例如:
package com.jdk8.stream;import java.util.ArrayList;
import java.util.List;/*** @Author: 史小创* @Time: 2024/8/11 下午4:54* @Description:*/public class StreamDemo01 {public static void main(String[] args) {m1();}public static void m() {}/*** 传统的方式遍历一个集合*/public static void m1() {List<String> list = new ArrayList<>();list.add("张无忌");list.add("周芷若");list.add("赵敏");list.add("张强");list.add("张三丰");for (String name : list) {System.out.println(name);}}
}
这是一段非常简单的集合遍历操作:对集合中的每一个字符串都进行打印输出操作。
循环遍历的弊端
Java 8的Lambda让我们可以更加专注于做什么(What),而不是怎么做(How),这点此前已经结合内部类进行了对比说明。现在,我们仔细体会一下上例代码,可以发现:
- for循环的语法就是“怎么做”
- for循环的循环体才是“做什么”
为什么使用循环?因为要进行遍历。但循环是遍历的唯一方式吗?遍历是指每一个元素逐一进行处理,而并不是从第一个到最后一个顺次处理的循环。前者是目的,后者是方式。
试想一下,如果希望对集合中的元素进行筛选过滤:
- 将集合A根据条件一过滤为子集B;
- 然后再根据条件二过滤为子集C。
那怎么办?在Java 8之前的做法可能为:
/*** 对集合中的元素进行筛选过滤*/public static void m2() {List<String> list = new ArrayList<>();list.add("张无忌");list.add("周芷若");list.add("赵敏");list.add("张强");list.add("张三丰");List<String> zhangList = new ArrayList<>();for (String name : list) {if (name.startsWith("张")) {zhangList.add(name);}}List<String> shortList = new ArrayList<>();for (String name : zhangList) {if (name.length() == 3) {shortList.add(name);}}for (String name : shortList) {System.out.println(name);}}
这段代码中含有三个循环,每一个作用不同:
- 首先筛选所有姓张的人;
- 然后筛选名字有三个字的人;
- 最后进行对结果进行打印输出。
每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。这是理所当然的么?**不是。**循环是做事情的方式,而不是目的。另一方面,使用线性循环就意味着只能遍历一次。如果希望再次遍历,只能再使用另一个循环从头开始。
那,Lambda的衍生物Stream能给我们带来怎样更加优雅的写法呢?
Stream的更优写法
下面来看一下借助Java 8的Stream API,什么才叫优雅:
/*** 我们使用Stream流*/public static void m3() {List<String> list = new ArrayList<>();list.add("张无忌");list.add("周芷若");list.add("赵敏");list.add("张强");list.add("张三丰");list.stream().filter(name -> name.startsWith("张")).filter(name -> name.length() == 3).forEach(System.out::println);}
直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印。代码中并没有体现使用线性循环或是其他任何算法进行遍历,我们真正要做的事情内容被更好地体现在代码中。
代码合集:
package com.jdk8.stream;import java.util.ArrayList;
import java.util.List;/*** @Author: 史小创* @Time: 2024/8/11 下午4:54* @Description:*/public class StreamDemo01 {public static void main(String[] args) {// m1();// m2();m3();}/*** 我们使用Stream流*/public static void m3() {List<String> list = new ArrayList<>();list.add("张无忌");list.add("周芷若");list.add("赵敏");list.add("张强");list.add("张三丰");list.stream().filter(name -> name.startsWith("张")).filter(name -> name.length() == 3).forEach(System.out::println);}/*** 对集合中的元素进行筛选过滤*/public static void m2() {List<String> list = new ArrayList<>();list.add("张无忌");list.add("周芷若");list.add("赵敏");list.add("张强");list.add("张三丰");List<String> zhangList = new ArrayList<>();for (String name : list) {if (name.startsWith("张")) {zhangList.add(name);}}List<String> shortList = new ArrayList<>();for (String name : zhangList) {if (name.length() == 3) {shortList.add(name);}}for (String name : shortList) {System.out.println(name);}}/*** 传统的方式遍历一个集合*/public static void m1() {List<String> list = new ArrayList<>();list.add("张无忌");list.add("周芷若");list.add("赵敏");list.add("张强");list.add("张三丰");for (String name : list) {System.out.println(name);}}
}
Stream流的好处
-
直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印
-
Stream流把真正的函数式编程风格引入到Java中
2.2、流式思想概述
注意:请暂时忘记对传统IO流的固有印象!
整体来看,流式思想类似于工厂车间的“生产流水线”。
当需要对多个元素进行操作(特别是多步操作)的时候,考虑到性能及便利性,我们应该首先拼好一个“模型”步骤方案,然后再按照方案去执行它。
这张图中展示了过滤、映射、跳过、计数等多步操作,这是一种集合元素的处理方案,而方案就是一种“函数模型”。图中的每一个方框都是一个“流”,调用指定的方法,可以从一个流模型转换为另一个流模型。而最右侧的数字3是最终结果。
这里的filter
、map
、skip
都是在对函数模型进行操作,集合元素并没有真正被处理。只有当终结方法count
执行的时候,整个模型才会按照指定策略执行操作。而这得益于Lambda的延迟执行特性。
备注:“Stream流”其实是一个集合元素的函数模型,它并不是集合,也不是数据结构,其本身并不存储任何元素(或其地址值)。
2.3、Stream流的常见生成方式
生成Stream流的方式
-
Collection体系集合
使用默认方法stream()生成流, default Stream stream()
-
Map体系集合
把Map转成Set集合,间接的生成流
-
数组
通过Stream接口的静态方法of(T… values)生成流
说明:
方式1 : 根据Collection获取流
首先,java.util.Collection
接口中加入了default方法stream
用来获取流,所以其所有实现类均可获取流。
方式2 : 根据Map获取流
java.util.Map
接口不是Collection
的子接口,且其K-V数据结构不符合流元素的单一特征,所以获取对应的流需要分key、value或entry等情况.
方式3 : 根据数组获取流
如果使用的不是集合或映射而是数组,由于数组对象不可能添加默认方法,所以Stream
接口中提供了静态方法of
,使用很简单:
package com.jdk8.stream;import java.util.*;
import java.util.stream.Stream;/*** @Author: 史小创* @Time: 2024/8/11 下午5:28* @Description:*/public class StreamDemo02 {public static void main(String[] args) {}/*** 数组:通过Stream接口的静态方法of(T... values)生成流*/public static void m3() {String[] strArray = {"hello", "world", "java"};Stream<String> strArrayStream = Stream.of(strArray);Stream<String> strArrayStream2 = Stream.of("hello", "world", "java");Stream<Integer> intStream = Stream.of(10, 20, 30);}/*** Map体系集合:把Map转成Set集合,间接的生成流*/public static void m2() {Map<String, String> map = new HashMap<>();Stream<String> keyStream = map.keySet().stream();Stream<String> valueStream = map.values().stream();Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();}/*** Collection体系集合* 使用默认方法stream()生成流, default Stream<E> stream()*/public static void m1() {List<String> list = new ArrayList<>();Stream<String> stream1 = list.stream();Set<String> set = new HashSet<>();Stream<String> stream2 = set.stream();Vector<String> vector = new Vector<>();Stream<String> stream3 = vector.stream();}
}
备注:
of
方法的参数其实是一个可变参数,所以支持数组。
2.4、常用方法
流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:
- 终结方法:返回值类型不再是
Stream
接口自身类型的方法,因此不再支持类似StringBuilder
那样的链式调用。本小节中,终结方法包括count
和forEach
方法。 - 非终结方法:返回值类型仍然是
Stream
接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为非终结方法。)
函数拼接与终结方法
在上述介绍的各种方法中,凡是返回值仍然为Stream
接口的为函数拼接方法,它们支持链式调用;而返回值不再为Stream
接口的为终结方法,不再支持链式调用。如下表所示:
方法名 | 方法作用 | 方法种类 | 是否支持链式调用 |
---|---|---|---|
count | 统计个数 | 终结 | 否 |
forEach | 逐一处理 | 终结 | 否 |
filter | 过滤 | 函数拼接 | 是 |
limit | 取用前几个 | 函数拼接 | 是 |
skip | 跳过前几个 | 函数拼接 | 是 |
map | 映射 | 函数拼接 | 是 |
concat | 组合 | 函数拼接 | 是 |
备注:本小节之外的更多方法,请自行参考API文档。
2.4.1、非终结操作方法的使用
常见方法
方法名 | 说明 |
---|---|
Stream filter(Predicate predicate) | 用于对流中的数据进行过滤 |
Stream limit(long maxSize) | 返回此流中的元素组成的流,截取前指定参数个数的数据 |
Stream skip(long n) | 跳过指定参数个数的数据,返回由该流的剩余元素组成的流 |
static Stream concat(Stream a, Stream b) | 合并a和b两个流为一个流 |
Stream distinct() | 返回由该流的不同元素(根据Object.equals(Object) )组成的流 |
Stream sorted() | 返回由此流的元素组成的流,根据自然顺序排序 |
Stream sorted(Comparator comparator) | 返回由该流的元素组成的流,根据提供的Comparator进行排序 |
Stream map(Function mapper) | 返回由给定函数应用于此流的元素的结果组成的流 |
IntStream mapToInt(ToIntFunction mapper) | 返回一个IntStream其中包含将给定函数应用于此流的元素的结果 |
2.4.1.1、forEach : 逐一处理
虽然方法名字叫forEach
,但是与for循环中的“for-each”昵称不同,该方法并不保证元素的逐一消费动作在流中是被有序执行的。
void forEach(Consumer<? super T> action);
该方法接收一个Consumer
接口函数,会将每一个流元素交给该函数进行处理。例如:
/*** foreach逐一处理*/public static void m1() {Stream<String> stream = Stream.of("张无忌", "张三丰", "周芷若");stream.forEach(s -> System.out.println(s));}
2.4.1.2、filter:过滤
可以通过filter
方法将一个流转换成另一个子集流。方法声明:
Stream<T> filter(Predicate<? super T> predicate);
该接口接收一个Predicate
函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件。
基本使用
/*** filter过滤方法的使用*/public static void m2() {// 创建一个集合,存储多个字符串元素ArrayList<String> list = new ArrayList<String>();list.add("林青霞");list.add("张曼玉");list.add("王祖贤");list.add("柳岩");list.add("张敏");list.add("张无忌");// 需求1:把list集合中以张开头的元素在控制台输出list.stream().filter(s -> s.startsWith("张")).forEach(System.out::println);System.out.println("------------------------------------");// 需求2:把list集合中长度为3的元素在控制台输出list.stream().filter(s -> s.length() == 3).forEach(System.out::println);System.out.println("------------------------------------");// 需求3:把list集合中以张开头的,长度为3的元素在控制台输出list.stream().filter(s -> s.startsWith("张") && s.length() == 3).forEach(System.out::println);}
2.4.1.3、limit & skip
limit:取用前几个
limit
方法可以对流进行截取,只取用前n个。方法签名:
Stream<T> limit(long maxSize);
参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作。
skip:跳过前几个
如果希望跳过前几个元素,可以使用skip
方法获取一个截取之后的新流:
Stream<T> skip(long n);
如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。
/*** limit&skip代码演示*/public static void m3() {//创建一个集合,存储多个字符串元素ArrayList<String> list = new ArrayList<String>();list.add("林青霞");list.add("张曼玉");list.add("王祖贤");list.add("柳岩");list.add("张敏");list.add("张无忌");//需求1:取前3个数据在控制台输出list.stream().limit(3).forEach(System.out::println);System.out.println("------------------------------------");//需求2:跳过3个元素,把剩下的元素在控制台输出list.stream().skip(3).forEach(System.out::println);System.out.println("------------------------------------");//需求3:跳过2个元素,把剩下的元素中前2个在控制台输出list.stream().skip(2).limit(2).forEach(System.out::println);}
2.4.1.4、concat & distinct
/*** static <T> Stream<T> concat(Stream a, Stream b):合并a和b两个流为一个流* Stream<T> distinct():返回由该流的不同元素(根据Object.equals(Object) )组成的流*/public static void m4() {// 创建一个集合,存储多个字符串元素ArrayList<String> list = new ArrayList<String>();list.add("林青霞");list.add("张曼玉");list.add("王祖贤");list.add("柳岩");list.add("张敏");list.add("张无忌");// 需求1:取前4个数据组成一个流Stream<String> s1 = list.stream().limit(4);// 需求2:跳过2个数据组成一个流Stream<String> s2 = list.stream().skip(2);// 需求3:合并需求1和需求2得到的流,并把结果在控制台输出// Stream.concat(s1, s2).forEach(System.out::println);//会报错// 需求4:合并需求1和需求2得到的流,并把结果在控制台输出,要求字符串元素不能重复Stream.concat(s1, s2).distinct().forEach(System.out::println);}
2.4.1.5、sorted
/*** Stream<T> sorted()返回由此流的元素组成的流,根据自然顺序排序*/public static void m5() {// 创建一个集合,存储多个字符串元素ArrayList<String> list = new ArrayList<String>();list.add("linqingxia");list.add("zhangmanyu");list.add("wangzuxian");list.add("liuyan");list.add("zhangmin");list.add("zhangwuji");// 需求1:按照字母顺序把数据在控制台输出list.stream().sorted().forEach(System.out::println);System.out.println("------------------------------------");// 需求2:按照字符串长度把数据在控制台输出list.stream().sorted((s1, s2) -> {int num = s1.length() - s2.length();int num2 = num == 0 ? s1.compareTo(s2) : num;return num2;}).forEach(System.out::println);}
2.4.1.6、map & mapToInt
/*** <R> Stream<R> map(Function mapper)返回由给定函数应用于此流的元素的结果组成的流* IntStream mapToInt(ToIntFunction mapper)返回一个IntStream其中包含将给定函数应用于此流的元素的结果*/public static void m6() {// 创建一个集合,存储多个字符串元素ArrayList<String> list = new ArrayList<String>();list.add("10");list.add("20");list.add("30");list.add("40");list.add("50");// 需求:将集合中的字符串数据转换为整数之后在控制台输出// list.stream().map(s -> Integer.parseInt(s)).forEach(System.out::println);// list.stream().map(Integer::parseInt).forEach(System.out::println);// list.stream().mapToInt(Integer::parseInt).forEach(System.out::println);System.out.println("------------------------------------");// int sum() 返回此流中元素的总和int result = list.stream().mapToInt(Integer::parseInt).sum();System.out.println(result);}
代码合集:
package com.jdk8.stream;import java.util.ArrayList;
import java.util.stream.Stream;/*** @Author: 史小创* @Time: 2024/8/11 下午6:35* @Description:*/public class StreamDemo03 {public static void main(String[] args) {// m1();// m2();// m3();// m4();// m5();m6();}/*** <R> Stream<R> map(Function mapper)返回由给定函数应用于此流的元素的结果组成的流* IntStream mapToInt(ToIntFunction mapper)返回一个IntStream其中包含将给定函数应用于此流的元素的结果*/public static void m6() {// 创建一个集合,存储多个字符串元素ArrayList<String> list = new ArrayList<String>();list.add("10");list.add("20");list.add("30");list.add("40");list.add("50");// 需求:将集合中的字符串数据转换为整数之后在控制台输出// list.stream().map(s -> Integer.parseInt(s)).forEach(System.out::println);// list.stream().map(Integer::parseInt).forEach(System.out::println);// list.stream().mapToInt(Integer::parseInt).forEach(System.out::println);System.out.println("------------------------------------");// int sum() 返回此流中元素的总和int result = list.stream().mapToInt(Integer::parseInt).sum();System.out.println(result);}/*** Stream<T> sorted()返回由此流的元素组成的流,根据自然顺序排序*/public static void m5() {// 创建一个集合,存储多个字符串元素ArrayList<String> list = new ArrayList<String>();list.add("linqingxia");list.add("zhangmanyu");list.add("wangzuxian");list.add("liuyan");list.add("zhangmin");list.add("zhangwuji");// 需求1:按照字母顺序把数据在控制台输出list.stream().sorted().forEach(System.out::println);System.out.println("------------------------------------");// 需求2:按照字符串长度把数据在控制台输出list.stream().sorted((s1, s2) -> {int num = s1.length() - s2.length();int num2 = num == 0 ? s1.compareTo(s2) : num;return num2;}).forEach(System.out::println);}/*** static <T> Stream<T> concat(Stream a, Stream b):合并a和b两个流为一个流* Stream<T> distinct():返回由该流的不同元素(根据Object.equals(Object) )组成的流*/public static void m4() {// 创建一个集合,存储多个字符串元素ArrayList<String> list = new ArrayList<String>();list.add("林青霞");list.add("张曼玉");list.add("王祖贤");list.add("柳岩");list.add("张敏");list.add("张无忌");// 需求1:取前4个数据组成一个流Stream<String> s1 = list.stream().limit(4);// 需求2:跳过2个数据组成一个流Stream<String> s2 = list.stream().skip(2);// 需求3:合并需求1和需求2得到的流,并把结果在控制台输出// Stream.concat(s1, s2).forEach(System.out::println);// 会报错// 需求4:合并需求1和需求2得到的流,并把结果在控制台输出,要求字符串元素不能重复Stream.concat(s1, s2).distinct().forEach(System.out::println);}/*** limit&skip代码演示*/public static void m3() {// 创建一个集合,存储多个字符串元素ArrayList<String> list = new ArrayList<String>();list.add("林青霞");list.add("张曼玉");list.add("王祖贤");list.add("柳岩");list.add("张敏");list.add("张无忌");// 需求1:取前3个数据在控制台输出list.stream().limit(3).forEach(System.out::println);System.out.println("------------------------------------");// 需求2:跳过3个元素,把剩下的元素在控制台输出list.stream().skip(3).forEach(System.out::println);System.out.println("------------------------------------");// 需求3:跳过2个元素,把剩下的元素中前2个在控制台输出list.stream().skip(2).limit(2).forEach(System.out::println);}/*** filter过滤方法的使用*/public static void m2() {// 创建一个集合,存储多个字符串元素ArrayList<String> list = new ArrayList<String>();list.add("林青霞");list.add("张曼玉");list.add("王祖贤");list.add("柳岩");list.add("张敏");list.add("张无忌");// 需求1:把list集合中以张开头的元素在控制台输出list.stream().filter(s -> s.startsWith("张")).forEach(System.out::println);System.out.println("------------------------------------");// 需求2:把list集合中长度为3的元素在控制台输出list.stream().filter(s -> s.length() == 3).forEach(System.out::println);System.out.println("------------------------------------");// 需求3:把list集合中以张开头的,长度为3的元素在控制台输出list.stream().filter(s -> s.startsWith("张") && s.length() == 3).forEach(System.out::println);}/*** foreach逐一处理*/public static void m1() {Stream<String> stream = Stream.of("张无忌", "张三丰", "周芷若");stream.forEach(s -> System.out.println(s));}}
2.4.2、Stream流终结操作方法
概念
终结操作的意思是,执行完此方法之后,Stream流将不能再执行其他操作。
常见方法
方法名 | 说明 |
---|---|
void forEach(Consumer action) | 对此流的每个元素执行操作 |
long count() | 返回此流中的元素数 |
package com.jdk8.stream;import java.util.ArrayList;/*** @Author: 史小创* @Time: 2024/8/11 下午7:06* @Description:*/public class StreamDemo04 {public static void main(String[] args) {// 创建一个集合,存储多个字符串元素ArrayList<String> list = new ArrayList<String>();list.add("林青霞");list.add("张曼玉");list.add("王祖贤");list.add("柳岩");list.add("张敏");list.add("张无忌");// 需求1:把集合中的元素在控制台输出list.stream().forEach(System.out::println);System.out.println("------------------------------------");// 需求2:统计集合中有几个以张开头的元素,并把统计结果在控制台输出long count = list.stream().filter(s -> s.startsWith("张")).count();System.out.println(count);}
}
2.5、综合案例
2.5.1、综合案例1
现在有两个ArrayList
集合存储队伍当中的多个成员姓名,要求使用传统的for循环(或增强for循环)依次进行以下若干操作步骤:
- 第一个队伍只要名字为3个字的成员姓名;
- 第一个队伍筛选之后只要前3个人;
- 第二个队伍只要姓张的成员姓名;
- 第二个队伍筛选之后不要前2个人;
- 将两个队伍合并为一个队伍;
- 根据姓名创建
Person
对象; - 打印整个队伍的Person对象信息。
package com.jdk8.stream;public class Person {private String name;public Person() {}public Person(String name) {this.name = name;}@Overridepublic String toString() {return "Person{name='" + name + "'}";}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
package com.jdk8.stream;import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;/*** @Author: 史小创* @Time: 2024/8/11 下午7:10* @Description:*/public class StreamDemo05 {public static void main(String[] args) {m1();System.out.println("------------------");m2();}/*** 等效的Stream流式处理代码*/public static void m2() {List<String> one = new ArrayList<>();one.add("迪丽热巴");one.add("宋远桥");one.add("苏星河");one.add("老子");one.add("庄子");one.add("孙子");one.add("洪七公");List<String> two = new ArrayList<>();two.add("古力娜扎");two.add("张无忌");two.add("张三丰");two.add("赵丽颖");two.add("张二狗");two.add("张天爱");two.add("张三");// 第一个队伍只要名字为3个字的成员姓名;// 第一个队伍筛选之后只要前3个人;Stream<String> streamOne = one.stream().filter(s -> s.length() == 3).limit(3);// 第二个队伍只要姓张的成员姓名;// 第二个队伍筛选之后不要前2个人;Stream<String> streamTwo = two.stream().filter(s -> s.startsWith("张")).skip(2);// 将两个队伍合并为一个队伍;// 根据姓名创建Person对象;// 打印整个队伍的Person对象信息。System.out.println("使用stream流的方式:");Stream.concat(streamOne, streamTwo).map(s -> new Person(s)).forEach(s -> System.out.println(s));//也可替换为方法引用的方式// Stream.concat(streamOne, streamTwo).map(Person::new).forEach(System.out::println);}/*** 传统的方式*/public static void m1() {List<String> one = new ArrayList<>();one.add("迪丽热巴");one.add("宋远桥");one.add("苏星河");one.add("老子");one.add("庄子");one.add("孙子");one.add("洪七公");List<String> two = new ArrayList<>();two.add("古力娜扎");two.add("张无忌");two.add("张三丰");two.add("赵丽颖");two.add("张二狗");two.add("张天爱");two.add("张三");// 第一个队伍只要名字为3个字的成员姓名;List<String> oneA = new ArrayList<>();for (String name : one) {if (name.length() == 3) {oneA.add(name);}}// 第一个队伍筛选之后只要前3个人;List<String> oneB = new ArrayList<>();for (int i = 0; i < 3; i++) {oneB.add(oneA.get(i));}// 第二个队伍只要姓张的成员姓名;List<String> twoA = new ArrayList<>();for (String name : two) {if (name.startsWith("张")) {twoA.add(name);}}// 第二个队伍筛选之后不要前2个人;List<String> twoB = new ArrayList<>();for (int i = 2; i < twoA.size(); i++) {twoB.add(twoA.get(i));}// 将两个队伍合并为一个队伍;List<String> totalNames = new ArrayList<>();totalNames.addAll(oneB);totalNames.addAll(twoB);// 根据姓名创建Person对象;List<Person> totalPersonList = new ArrayList<>();for (String name : totalNames) {totalPersonList.add(new Person(name));}System.out.println("传统的方式:");// 打印整个队伍的Person对象信息。for (Person person : totalPersonList) {System.out.println(person);}}
}
2.5.1、综合案例2
案例需求
现在有两个ArrayList集合,分别存储6名男演员名称和6名女演员名称,要求完成如下的操作
-
男演员只要名字为3个字的前三人
-
女演员只要姓林的,并且不要第一个
-
把过滤后的男演员姓名和女演员姓名合并到一起
-
把上一步操作后的元素作为构造方法的参数创建演员对象,遍历数据
演员类Actor已经提供,里面有一个成员变量,一个带参构造方法,以及成员变量对应的get/set方法
package com.jdk8.stream;public class Actor {private String name;public Actor(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
package com.jdk8.stream;import java.util.ArrayList;
import java.util.stream.Stream;/*** @Author: 史小创* @Time: 2024/8/11 下午7:40* @Description:*/public class StreamDemo06 {public static void main(String[] args) {// 创建集合ArrayList<String> manList = new ArrayList<String>();manList.add("周润发");manList.add("成龙");manList.add("刘德华");manList.add("吴京");manList.add("周星驰");manList.add("李连杰");ArrayList<String> womanList = new ArrayList<String>();womanList.add("林心如");womanList.add("张曼玉");womanList.add("林青霞");womanList.add("柳岩");womanList.add("林志玲");womanList.add("王祖贤");/*//男演员只要名字为3个字的前三人Stream<String> manStream = manList.stream().filter(s -> s.length() == 3).limit(3);//女演员只要姓林的,并且不要第一个Stream<String> womanStream = womanList.stream().filter(s -> s.startsWith("林")).skip(1);//把过滤后的男演员姓名和女演员姓名合并到一起Stream<String> stream = Stream.concat(manStream, womanStream);//把上一步操作后的元素作为构造方法的参数创建演员对象,遍历数据
// stream.map(Actor::new).forEach(System.out::println);stream.map(Actor::new).forEach(p -> System.out.println(p.getName()));*///链式编程Stream.concat(manList.stream().filter(s -> s.length() == 3).limit(3),womanList.stream().filter(s -> s.startsWith("林")).skip(1)).map(Actor::new).forEach(p -> System.out.println(p.getName()));}
}
2.6、Stream流的收集操作
概念:对数据使用Stream流的方式操作完毕后,可以把流中的数据收集到集合中。
-
常用方法
方法名 说明 R collect(Collector collector) 把结果收集到集合中 -
工具类Collectors提供了具体的收集方式
方法名 说明 public static Collector toList() 把元素收集到List集合中 public static Collector toSet() 把元素收集到Set集合中 public static Collector toMap(Function keyMapper,Function valueMapper) 把元素收集到Map集合中
package com.jdk8.stream;import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;/*** @Author: 史小创* @Time: 2024/8/11 下午7:44* @Description:*/public class StreamDemo07 {public static void main(String[] args) {// 创建List集合对象List<String> list = new ArrayList<String>();list.add("林青霞");list.add("张曼玉");list.add("王祖贤");list.add("柳岩");/*//需求1:得到名字为3个字的流Stream<String> listStream = list.stream().filter(s -> s.length() == 3);//需求2:把使用Stream流操作完毕的数据收集到List集合中并遍历List<String> names = listStream.collect(Collectors.toList());for(String name : names) {System.out.println(name);}*/// 创建Set集合对象Set<Integer> set = new HashSet<Integer>();set.add(10);set.add(20);set.add(30);set.add(33);set.add(35);/*//需求3:得到年龄大于25的流Stream<Integer> setStream = set.stream().filter(age -> age > 25);//需求4:把使用Stream流操作完毕的数据收集到Set集合中并遍历Set<Integer> ages = setStream.collect(Collectors.toSet());for(Integer age : ages) {System.out.println(age);}*/// 定义一个字符串数组,每一个字符串数据由姓名数据和年龄数据组合而成String[] strArray = {"林青霞,30", "张曼玉,35", "王祖贤,33", "柳岩,25"};// 需求5:得到字符串中年龄数据大于28的流Stream<String> arrayStream = Stream.of(strArray).filter(s -> Integer.parseInt(s.split(",")[1]) > 28);// 需求6:把使用Stream流操作完毕的数据收集到Map集合中并遍历,字符串中的姓名作键,年龄作值Map<String, Integer> map = arrayStream.collect(Collectors.toMap(s -> s.split(",")[0], s -> Integer.parseInt(s.split(",")[1])));Set<String> keySet = map.keySet();for (String key : keySet) {Integer value = map.get(key);System.out.println(key + "," + value);}}
}
三、接口组成更新
3.1、接口组成更新概述
-
常量
public static final
-
抽象方法
public abstract
-
默认方法(Java 8)
-
静态方法(Java 8)
-
私有方法(Java 9)
3.2、接口中默认方法
-
格式
public default 返回值类型 方法名(参数列表) { }
-
范例
public default void show3() { }
-
注意事项
-
默认方法不是抽象方法,所以不强制被重写。但是可以被重写,重写的时候去掉default关键字
-
public可以省略,default不能省略
-
3.3、接口中静态方法
-
格式
public static 返回值类型 方法名(参数列表) { }
-
范例
public static void show() { }
-
注意事项
-
静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
-
public可以省略,static不能省略
-
3.4、接口中私有方法
-
私有方法产生原因
Java 9中新增了带方法体的私有方法,这其实在Java 8中就埋下了伏笔:Java 8允许在接口中定义带方法体的默认方法和静态方法。这样可能就会引发一个问题:当两个默认方法或者静态方法中包含一段相同的代码实现时,程序必然考虑将这段实现代码抽取成一个共性方法,而这个共性方法是不需要让别人使用的,因此用私有给隐藏起来,这就是Java 9增加私有方法的必然性
-
定义格式
-
格式1
private 返回值类型 方法名(参数列表) { }
-
范例1
private void show() { }
-
格式2
private static 返回值类型 方法名(参数列表) { }
-
范例2
private static void method() { }
-
-
注意事项
- 默认方法可以调用私有的静态方法和非静态方法
- 静态方法只能调用私有的静态方法