Stream流
一 为什么要用Stream流#
1.1 传统的方式,遍历,过滤集合中的数据#
//创建一个List集合,存储姓名
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
//对list集合中的元素进行过滤,只要以张开头的元素,存储到一个新的集合中
List<String> listA = new ArrayList<>();
for(String s : list){
if(s.startsWith("张")){
listA.add(s);
}
}
//对listA集合进行过滤,只要姓名长度为3的人,存储到一个新集合中
List<String> listB = new ArrayList<>();
for (String s : listA) {
if(s.length()==3){
listB.add(s);
}
}
//遍历listB集合
for (String s : listB) {
System.out.println(s);
}
}
这段代码中含有三个循环,每一个作用不同:
-
- 首先筛选所有姓张的人;
-
- 然后筛选名字有三个字的人;
-
- 最后进行对结果进行打印输出。
每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。这是理所当然的么?不是。循环是做事情的方式,而不是目的。另一方面,使用线性循环就意味着只能遍历一次。如果希望再次遍历,只能再使用另一个循环从头开始。
1.2 Stream流的方式,遍历,过滤集合中的数据#
//创建一个List集合,存储姓名
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
//对list集合中的元素进行过滤,只要以张开头的元素,存储到一个新的集合中
//对listA集合进行过滤,只要姓名长度为3的人,存储到一个新集合中
//遍历listB集合
list.stream()
.filter(name->name.startsWith("张"))
.filter(name->name.length()==3)
.forEach(name-> System.out.println(name));
1.3 流式思想概述#
- 流式思想类似于工厂车间的“生产流水线
元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。 数据源流的来源。 可以是集合,数组等当使用一个流的时候,通常包括三个基本步骤:
获取一个数据源(source)→ 数据转换→执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。
二 Stream流的基础使用#
2.1 两种获取Stream流的方式#
所有的 Collection 集合都可以通过 stream 默认方法获取流;
Stream 接口的静态方法 of 可以获取数组对应的流。
/*
java.util.stream.Stream<T>是Java 8新加入的最常用的流接口。(这并不是一个函数式接口。)
获取一个流非常简单,有以下几种常用的方式:
- 所有的Collection集合都可以通过stream默认方法获取流;
default Stream<E> stream()
- Stream接口的静态方法of可以获取数组对应的流。
static <T> Stream<T> of(T... values)
参数是一个可变参数,那么我们就可以传递一个数组
*/
public class Demo01GetStream {
public static void main(String[] args) {
//把集合转换为Stream流
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
Set<String> set = new HashSet<>();
Stream<String> stream2 = set.stream();
Map<String,String> map = new HashMap<>();
//获取键,存储到一个Set集合中
Set<String> keySet = map.keySet();
Stream<String> stream3 = keySet.stream();
//获取值,存储到一个Collection集合中
Collection<String> values = map.values();
Stream<String> stream4 = values.stream();
//获取键值对(键与值的映射关系 entrySet)
Set<Map.Entry<String, String>> entries = map.entrySet();
Stream<Map.Entry<String, String>> stream5 = entries.stream();
//把数组转换为Stream流
Stream<Integer> stream6 = Stream.of(1, 2, 3, 4, 5);
//可变参数可以传递数组
Integer[] arr = {1,2,3,4,5};
Stream<Integer> stream7 = Stream.of(arr);
String[] arr2 = {"a","bb","ccc"};
Stream<String> stream8 = Stream.of(arr2);
}
}
2.2 Stream流中的常用方法_forEach#
forEach()#
虽然名字叫forEach()
,但和for循环中的for-each 不同
void forEach(Consumer<? super T> action);
Consumer接口#
java.util.function.Consumer 接口是一个函数型接口, 消费一个数据, 其数据类型由泛型决定。
Consumer 接口中包含抽象方法 void accept(T t) ,意为消费一个指定泛型的数据。
2.3 Stream流中的常用方法_filter#
可以通过 filter
方法将一个流转换成另一个子集流
Stream<T> filter(Predicate<? super T> predicate);
该接口接受一个Predicate
函数式接口参数作为筛选条件
Predicate接口:
java.util.function.Predicate接口
作用:对某种数据类型的数据进行判断,结果返回一个boolean值抽象方法:test:
Predicate接口中包含了一个抽象方法:
boolean test(T t):用来对指定数据类型数据进行判断的方法
结果;
符合条件,返回true
不符合条件,返回false
eg:
Integer[] arr = {1,2,3,4,5,6,7,8,9,10};
Stream<Integer> stream1 = Stream.of(arr);
// stream1.forEach(x -> System.out.println(x)); // stream 流使用过一次后即废弃
stream1.filter(x -> x > 5).forEach(x -> System.out.println(x));
2.4 Stream流的特点_只能使用一次#
-
stream流的注意事项, 只能使用一次
若使用使用 已使用过的stream流,会在抛出如下错误
IllegalStateException: stream has already been operated upon or closed
三 Stream流中的常用方法#
3.1 map#
- map: 将一个流的数据映射到另一个流
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
//eg
Integer[] arr = {1,2,3,4,5,6,7,8,9,10};
Stream<Integer> stream1 = Stream.of(arr);
Stream<String> stream2 = stream1.map(x -> String.valueOf(x));
3.2 count#
- count : 统计流中元素的个数
3.3 limit#
- limit: 方法可以对流进行截取,只取用前n个
Stream<T> limit(long maxSize);
3.4 skip#
- skip: 返回跳过前n个元素的流
Stream<T> skip(long n);
3.5 concat#
- concat: 静态方法 合并两个流
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {
Objects.requireNonNull(a);
Objects.requireNonNull(b);
@SuppressWarnings("unchecked")
Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>(
(Spliterator<T>) a.spliterator(), (Spliterator<T>) b.spliterator());
Stream<T> stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());
return stream.onClose(Streams.composedClose(a, b));
}
四 集合元素处理#
/*
练习:集合元素处理(传统方式)
现在有两个ArrayList集合存储队伍当中的多个成员姓名,要求使用传统的for循环(或增强for循环)依次进行以下若干操作步骤:
1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中。
3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。
5. 将两个队伍合并为一个队伍;存储到一个新集合中。
6. 根据姓名创建Person对象;存储到一个新集合中。
7. 打印整个队伍的Person对象信息。
*/
//第一支队伍
ArrayList<String> one = new ArrayList<>();
one.add("迪丽热巴");
one.add("宋远桥");
one.add("苏星河");
one.add("石破天");
one.add("石中玉");
one.add("老子");
one.add("庄子");
one.add("洪七公");
//1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
//2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中。
Stream<String> oneStream = one.stream().filter(name -> name.length() == 3).limit(3);
//第二支队伍
ArrayList<String> two = new ArrayList<>();
two.add("古力娜扎");
two.add("张无忌");
two.add("赵丽颖");
two.add("张三丰");
two.add("尼古拉斯赵四");
two.add("张天爱");
two.add("张二狗");
//3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
//4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。
Stream<String> twoStream = two.stream().filter(name -> name.startsWith("张")).skip(2);
//5. 将两个队伍合并为一个队伍;存储到一个新集合中。
//6. 根据姓名创建Person对象;存储到一个新集合中。
//7. 打印整个队伍的Person对象信息。
Stream.concat(oneStream,twoStream).map(name->new Person(name)).forEach(p-> System.out.println(p));
五 方法引用#
5.1 方法引用简介#
在使用 Lambda 表达式的时候,我们实际上传递进去的代码就是一种解决方案:拿参数做操作。
如果我们在 Lambda 中所指定的操作方案,已经有地方存在相同方案,则没有必要再写重复逻辑。
那又是如何使用已经存在的方案的呢?通过方法引用来使用已经存在的方案。
5.2 方法引用符号#
::
该符号为引用运算符,而它所在的表达式被称为方法引用。
使用 Lambda,那么根据 “ 可推导就是可省略 ” 的原则,无需指定参数类型,也无需指定的重载形式,它们都将被自动推导。
使用方法引用,也是同样可以根据上下文进行推导,它是 Lambda 的孪生兄弟。
5.3 引用类的静态方法#
格式:类名::方法名
注意:Lambda 表达式被类方法替代时,它的形参全部传递给静态方法作为参数。
示例: 将 String 类型数字转为 int 类型数字。
public interface StringConvert {
int convertInt(String s);
}
测试:
public class Test {
public static void main(String[] args) {
// lambda方式
useStringConvert(s -> Integer.parseInt(s));
// 方法引用:引用类的静态方法,形参全部传递给静态方法作为参数
useStringConvert(Integer::parseInt);
}
public static void useStringConvert(StringConvert sc) {
int number = sc.convertInt("156");
System.out.println("number = " + number);
}
}
运行:
num = 156
num = 156
5.4 引用类的构造方法#
格式:类名::new
注意:Lambda 表达式被构造器替代时,它的形参全部传递给构造器作为参数。
示例:创建学生。
public class Student {
String name;
int age;
// 省略无参|有参构造方法、get|set方法、toString方法
}
public interface StudentBuilder {
Student builder(String name, int age);
}
测试:
public class Test {
public static void main(String[] args) {
// lambda方式
useStudentBuilder((name, age) -> new Student(name, age));
// 方法引用:引用类的构造方法,形参全部传递给构造器作为参数
useStudentBuilder(Student::new);
}
public static void useStudentBuilder(StudentBuilder sb) {
Student student = sb.builder("张三", 23);
System.out.println("student = " + student);
}
}
运行:
student = Student{name='张三', age=23}
student = Student{name='张三', age=23}
5.5 引用类的实例方法#
格式:类名::成员方法
注意:Lambda 表达式被类的实例方法替代时,第一个参数作为方法调用者,后面其余参数全部传递给该方法作为参数
示例:截取字符串,返回一个子串。
public interface StringMethod {
String mySubString(String string, int begin, int end);
}
测试:
public class Test {
public static void main(String[] args) {
// lambda方式
useStringMethod((string, begin, end) -> string.substring(begin, end));
// 方法引用:引用类的实例方法(成员方法),第一个参数作为方法调用者,后面其余参数全部传递给该方法作为参数
useStringMethod(String::substring);
}
public static void useStringMethod(StringMethod sm) {
String string = sm.mySubString("HelloWord!", 3, 6);
System.out.println("string = " + string);
}
}
运行:
string = loW
string = loW
5.6 引用对象的实例方法#
格式:对象::成员方法
说明:引用对象的实例方法就是引用类中的成员方法
注意:Lambda 表达式被对象的实例方法替代时,它的形参全部传递给该方法作为参数
示例:
public interface Printer {
void printUpper(String s);
}
public class PrintString {
void printUpperCase(String s) {
String string = s.toUpperCase();
System.out.println("string = " + string);
}
}
测试:
public class Test {
public static void main(String[] args) {
// lambda方式
usePrinter(s -> System.out.println(s.toUpperCase()));
// 方法引用:引用类对象的实例方法(成员方法),形参全部传递给该方法作为参数
PrintString ps = new PrintString();
usePrinter(ps::printUpperCase);
}
public static void usePrinter(Printer p) {
p.printUpper("HelloWord!");
}
}
运行:
HELLOWORD!
string = HELLOWORD!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?