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

‍作者简介:一位大四、研0学生,正在努力准备大四暑假的实习
上期文章:JAVASE进阶:源码精读——HashMap源码详细解析
订阅专栏:JAVASE进阶
希望文章对你们有所帮助

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

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

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

  • 引入
  • Stream流思想
  • 获取Stream流
  • Stream流的中间方法
  • Stream流终结方法详解
  • 收集方法collect
  • 练习
    • 数字过滤
    • 字符串过滤并收集
    • 自定义对象过滤并收集

引入

现在实现一个简单的需求:创建一个集合来存储名字,并且输出名字长度为3,且姓为“张”的所有名字。
这里直接用ArrayList,模拟起来还是很简单的,但是当相对还是要写很长的语句,而Stream流和Lambda表达式的集合能够极大地非常简化我们的代码编写,处理集合并输出的语句只需要一句话:

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、单列集合

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

2、双列集合

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、数组

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

4、零散数据

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)匿名内部类

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

(2)lambda表达式:

list.stream().filter(s->s.startWith("张")).forEach(s->System.out.println(s));

2、limit:

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

3、skip:

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

4、distinct:

list.stream().distinct().forEach(s->System.out.println(s));

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

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

Stream.concat(list1.stream(), list2.stream()).forEach(s->System.out.println(s));

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

Collections.addAll(list, "一二-12", "布布-14");

(1)匿名内部类:

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表达式:

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

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

Stream流终结方法详解

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

1、forEach:
(1)匿名内部类

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

(2)lambda表达式

list.stream().forEach(s->System.out.println(s));

2、count统计:

long count = list.stream().count();

3、toArray收集int类型数据到String类型数组中
(1)匿名内部类

//注意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表达式

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

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

收集方法collect

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

收集到List集合:

	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对象:
JAVASE进阶:一文精通Stream流+函数式编程_第1张图片
收集到Map集合里面的代码和细节如下所示,需要注意的是,要想收集Map必须保证键是不重复的。

(1)匿名内部类:

	//键:姓名 值:年龄
	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表达式

	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,过滤奇数只留下偶数,并将结果保存起来:

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集合中,姓名为键,年龄为值:

	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);
    }

自定义对象过滤并收集

JAVASE进阶:一文精通Stream流+函数式编程_第2张图片
自行去创建一个Actor对象,String类型的name、int类型的age,并且重写toString方法。

代码如下:

	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表达式:

s->new Actor(s.split(",")[0], Integer.parseInt(s.split(",")[1]))

你可能感兴趣的:(JAVASE进阶,java,lambda,函数式编程,面试,stream流)