JAVASE进阶:一文精通Stream流+函数式编程

👨‍🎓作者简介:一位大四、研0学生,正在努力准备大四暑假的实习

🌌上期文章:JAVASE进阶:源码精读------HashMap源码详细解析

📚订阅专栏:JAVASE进阶

希望文章对你们有所帮助

Stream流的使用是一种高级的写法,配合函数式编程(lambda表达式),能够极大简化我们程序的编写,有些二十行的代码也可以一行代码就实现,代码看起来也高雅了很多,这也是成为高级程序员的必会技能。

先讲原理,再实现一些例子。

一文精通Stream流+函数式编程

引入

现在实现一个简单的需求:创建一个集合来存储名字,并且输出名字长度为3,且姓为"张"的所有名字。

这里直接用ArrayList,模拟起来还是很简单的,但是当相对还是要写很长的语句,而Stream流和Lambda表达式的集合能够极大地非常简化我们的代码编写,处理集合并输出的语句只需要一句话:

java 复制代码
list.stream().filter(name->name.startWith("张")).filter(name->name.length()==3).forEach(name->System.out.println(name));

这代码对一个程序员的诱惑程度别提有多大。

Stream流思想

流:可以视为流水线,学过计算机系统都知道流水线的相关原理,它可以大大提升执行的性能。

如上例子可以分为流水线中的3个子任务:过滤留下"张"姓开头的;过滤留下长度为3的;输出。

对于每一个子任务,Stream流一般都会结合lambda表达式来简化集合、数组的操作。

Stream流的使用步骤:

1、先得到一条Stream流(流水线),并把数据放上去

2、利用Stream流的API进行各种操作:

(1)中间方法:过滤、转换

(2)终结方法:统计、打印

获取Stream流

获取方式 方法名 说明
单列集合 stream() Collection中的默认方法
双列集合 无法直接使用是Stream流,需要先通过keySet或entrySet转换成单列集合
数组 stream(T[] array) Arrays工具类中的静态方法
一堆零散数据 of(T...values) Stream接口中的静态方法

1、单列集合

java 复制代码
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "a", "b", "c", "d");
//Stream<String> stream = list.stream()可以获取流,但一般不这么用,而是直接一路链式编程
list.stream().forEach(s->System.out.println(s));//forEach是终结方法

2、双列集合

java 复制代码
HashMap<String, Integer> map = new HashMap<>();
map.put("aaa", 111);map.put("bbb", 222);
//方法一,把key都拿出来单独做处理
map.keySet().stream().forEach(s->System.out.println(s));
//方法二,获取每个键值对对象
map.entrySet().stream().forEach(s->System,out.println(s));

3、数组

java 复制代码
int[] arr = {1,2,3,4,5};
Arrays.stream(arr).forEach(s->System,out.println(s));

4、零散数据

java 复制代码
Stream.of(1,2,3,4,5).forEach(s->System,out.println(s));
Stream.of("a","b","c").forEach(s->System,out.println(s));

在很多时候,都是推荐使用of方法,这是因为of方法形参写法为T...values,这是一种可变参数的书写形式,所以无论传递零散的数据还是一个数组,都是可以成功处理的。

但是!有一个很关键的点,of方法传入的数组里面的数据必须是引用数据类型的,如果是基本数据类型,那么只会把这一整个数组当作一个元素,输出的会是地址!

Stream流的中间方法

名称 说明
filter 过滤
limit 获取前几个元素
skip 跳过前几个元素
distinct 元素去重,依赖hashCode和equals方法
concat 合并a和b两个流为一个流
map 转换流中的数据类型

注意:

1、中间方法会返回新的流,原来的Stream流只能使用一次,建议使用链式编程

2、修改Stream流中的数据,不会影响原来集合或数组中的数据

1、filter:

(1)匿名内部类

java 复制代码
list.stream().filter(new Predicate<String>() {
	@Override
	public boolean test(String s){
		//返回值为true则表示当前数据留下
		return s.startWith("张");
	}
}).forEach(s->System.out.println(s));

(2)lambda表达式:

java 复制代码
list.stream().filter(s->s.startWith("张")).forEach(s->System.out.println(s));

2、limit:

java 复制代码
//输出前3个
list.stream().limit(3).forEach(s->System.out.println(s));

3、skip:

java 复制代码
//跳过前4个
list.stream().skip(4).forEach(s->System.out.println(s));

4、distinct:

java 复制代码
list.stream().distinct().forEach(s->System.out.println(s));

distinct的底层是非常复杂的,其核心是使用HashSet实现去重的

5、concat尽可能让两个stream流中的数据保持一致,不然会将数据类型变成它们的共同父类,导致缺失一些子类的特有功能:

java 复制代码
Stream.concat(list1.stream(), list2.stream()).forEach(s->System.out.println(s));

6、map实现流中数据的类型转换,将String类型字符串中的年龄转换成int并输出:

java 复制代码
Collections.addAll(list, "一二-12", "布布-14");

(1)匿名内部类:

java 复制代码
list.stream().map(new Function<String, Integer>(){
	//String表示stream中的数据类型,Integer表示要转换的数据类型,不能写int,必须写包装类,默认为Object
	@Override
	public Integer apply(String s){
		//s表示stream流里的每一个数据,返回值为转换后的数据
		String[] arr = s.split("-");
		String ageString = arr[1];
		int age = Integer.parseInt(ageString);
		return age;
	}
}).forEach(s->System.out.println(s));

(2)lambda表达式:

java 复制代码
list.stream().map(s->Integer.parseInt(s.split("-")[1]))
			 .forEach(s->System.out.println(s));

这个lambda表达式写起来剪枝不要太爽!

Stream流终结方法详解

名称 说明
forEach 遍历
count 统计
toArray 收集流中数据,放到数组中
collect 收集流中数据,放到集合中

1、forEach:

(1)匿名内部类

java 复制代码
list.stream().forEach(new Consumer<String>(){
	@Override
	public void accept(String s){
		System.out.println(s)
	}
});

(2)lambda表达式

java 复制代码
list.stream().forEach(s->System.out.println(s));

2、count统计:

java 复制代码
long count = list.stream().count();

3、toArray收集int类型数据到String类型数组中

(1)匿名内部类

java 复制代码
//注意IntFunction中要传递数组泛型,所以需要传入"String[]"
String[] arr = list.stream().toArray(new new IntFunction<String[]>() {
	@Override
	public void apply(int value){
		//value表示数组的长度,方法体只需要创建一个数组即可,返回的类型要和泛型一致
		return new String[value];
	}
});
System.out.println(Arrays.toString(arr));

(2)lambda表达式

java 复制代码
String[] arr = list.stream().toArray(value->new String[value]);
System.out.println(Arrays.toString(arr));

collect方法算是比较重要的方法了,在后面详细讲解。

收集方法collect

collect可以将流里面的数据收集到单列集合或双列集合中去的。

收集到List集合:

java 复制代码
	List<String> list = new ArrayList<>();
    Collections.addAll(list, "张无忌-男-15", "小龙女-女-12", "一二-女-4", "布布-男-5");
    //把所有的男性收集起来
    List<String> newList = list.stream().filter(s -> "男".equals(s.split("-")[1])).collect(Collectors.toList());
    System.out.println(newList);

同样的,也可以收集到Set中去,单列集合还是比较方便的。

如果要收集到双列集合Map中,就需要指定键和值是哪个字段:

可以打开ToMap的底层源码,可以清楚的看到,调用toMap方法需要指定键和值的规则,其底层会自动的创建一个HashMap对象:

收集到Map集合里面的代码和细节如下所示,需要注意的是,要想收集Map必须保证键是不重复的。

(1)匿名内部类:

java 复制代码
	//键:姓名 值:年龄
	Map<String, Integer> map = list.stream().filter(s -> "男".equals(s.split("-")[1]))
            /**
             * toMap:参数一表示键的生成规则
             *       参数二表示值的生成规则
             *
             *  参数一:
             *      Function泛型一:流中每一个数据的类型
             *              泛型二:Map集合中键的数据类型
             *      方法apply形参:依次表示流里面的每一个数据
             *              方法体:生成键的代码
             *              返回值:已经生成的键
             *  也就是说,Function中第一个泛型与apply的形参一致,第二个泛型与apply的返回类型一致
             *
             *  参数二同理
             */
            .collect(Collectors.toMap(
                    new Function<String, String>() {
                        @Override
                        public String apply(String s) {
                            return s.split("-")[0];
                        }
                    },
                    new Function<String, Integer>() {
                        @Override
                        public Integer apply(String s) {
                            return Integer.parseInt(s.split("-")[2]);
                        }
                    }
            ));
    System.out.println(map);

(2)lambda表达式

java 复制代码
	Map<String, Integer> map = list.stream().filter(s -> "男".equals(s.split("-")[1]))
                .collect(Collectors.toMap(
                        s->s.split("-")[0],
                        s->Integer.parseInt(s.split("-")[2])));

练习

数字过滤

定义一个集合并添加整数1-10,过滤奇数只留下偶数,并将结果保存起来:

java 复制代码
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> list1 = list.stream().filter(s -> (s & 1) == 0).collect(Collectors.toList());
System.out.println(list1);

字符串过滤并收集

创建一个ArrayList集合,并添加以下字符串:

"zhangsan,23"

"lisi,24"

"wangwu,25"

保留年龄大于等于24岁的人,并将结果收集到Map集合中,姓名为键,年龄为值:

java 复制代码
	public static void main(String[] args) {
        String s1 = "a", s2 = "b";
        //System.out.println(s1.compareTo(s2));
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "zhangsan,23", "lisi,24", "wangwu,25");
        //filter也可以转换成Integer类型再去做比较
        Map<String, Integer> map = list.stream().filter(s -> "24".compareTo(s.split(",")[1]) <= 0)
                .collect(Collectors.toMap(
                        s -> s.split(",")[0],
                        s -> Integer.parseInt(s.split(",")[1])
                ));
        System.out.println(map);
    }

自定义对象过滤并收集

自行去创建一个Actor对象,String类型的name、int类型的age,并且重写toString方法。

代码如下:

java 复制代码
	public static void main(String[] args) {
        ArrayList<String> manList = new ArrayList<>();
        ArrayList<String> womenList = new ArrayList<>();
        Collections.addAll(manList, "坤坤,24", "再多,23", "看一眼,20", "就快,19", "要爆炸,22", "铁山靠,26");
        Collections.addAll(womenList, "朵拉,21", "杨幂,30", "杨超越,22", "露娜,25", "妲己,24", "安琪拉,23");
        Stream<String> stream1 = manList.stream().filter(s -> s.split(",")[0].length() == 3).limit(2);
        Stream<String> stream2 = womenList.stream().filter(s->s.startsWith("杨")).skip(1);
        List<Actor> list = Stream.concat(stream1, stream2).map(new Function<String, Actor>() {
            @Override
            public Actor apply(String s) {
                return new Actor(s.split(",")[0], Integer.parseInt(s.split(",")[1]));
            }
        }).collect(Collectors.toList());
    }
    System.out.println(list);

其中,map里面的匿名内部类还可以转化为lambda表达式:

java 复制代码
s->new Actor(s.split(",")[0], Integer.parseInt(s.split(",")[1]))
相关推荐
奋进的芋圆1 天前
Java 延时任务实现方案详解(适用于 Spring Boot 3)
java·spring boot·redis·rabbitmq
sxlishaobin1 天前
设计模式之桥接模式
java·设计模式·桥接模式
model20051 天前
alibaba linux3 系统盘网站迁移数据盘
java·服务器·前端
荒诞硬汉1 天前
JavaBean相关补充
java·开发语言
提笔忘字的帝国1 天前
【教程】macOS 如何完全卸载 Java 开发环境
java·开发语言·macos
2501_941882481 天前
从灰度发布到流量切分的互联网工程语法控制与多语言实现实践思路随笔分享
java·开发语言
han_1 天前
从一道前端面试题,谈 JS 对象存储特点和运算符执行顺序
前端·javascript·面试
華勳全栈1 天前
两天开发完成智能体平台
java·spring·go
alonewolf_991 天前
Spring MVC重点功能底层源码深度解析
java·spring·mvc
沛沛老爹1 天前
Java泛型擦除:原理、实践与应对策略
java·开发语言·人工智能·企业开发·发展趋势·技术原理