java随笔之--Stream流

本文探讨Java8中的Stream流和Lambda表达式,解释Lambda简化了面向对象编程中的复杂操作,如排序。Stream流提供了一种新的集合操作方式,如filter、map和forEach,通过链式调用实现高效的数据处理,同时不对原始数据造成影响。
摘要由CSDN通过智能技术生成

在Java8(JDK8)中.得益于Lambda表达式的发明,让Stream流开始大放异彩.本文主要讨论stream流引入的背景,stream的语法,以及它的实际应用小案例.

Lambda是什么

即使是完全的java小白也应该知道,java是完全面向对象的语言.对于执行一个动作,java实际上更关注如何执行而不是执行的目标.简单点说,任何一个方法都需要借助一个对象去调用,至于方法本身的内容,反倒不是java所真正关心的.这是典型的面向对象的思想.面向对象的思想,好处是很多的,比如它可以对事物进行分类,寻找类别之间的相似性,封装类的方法,使得程序具有很高的适应性和耐用性.在实现一个动作时,我们需要知道谁可以执行这个动作,还有谁可以执行这个动作,通过封装,重写等操作.我们可以不断完善这个执行者而它特有的动作.根据执行者的差异还可以对动作进行分化.只要我们稍加记忆,我们就能够随时随地地调用这个方法.虽然可能会费点功夫,但为了今后更方便地调用打下了基础.
但是面向对象的思想在某些情况下也有弊端.有时候我们想完成一个很简单的操作,比如排序.但是在java中就必须得构造一个比较器对象,还要重写比较器对象的compare方法才能够做到.这中间虽然达到了排序的目的,但产生了大量的冗余代码,可读性不是很好.这就为lambda表达式的发明提供了一个契机.其实开发者大概也是注意到了这一点,要完成一个很简单的操作,可不可以不要创建一个对象,只为了调用它的一个方法,而直接现场写一个方法呢?我这样说肯定让大家很懵.接下来举个栗子.
比如我们考虑一个Person类,它的属性有名字,年龄.设计一个有参构造器和set,get方法.就基本完成了一个对象的封装.再重写一个toString方法,方便在输出的时候能够可视化(而不是输出地址这种没有实际价值的内容).

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

现在刘备,关羽,张飞三个人要结拜,哪个当老大呢?我们必然是考虑年龄嘛.首先,我们设计一个ArrayList类用于保存这三个对象.然后重写Comparator类的compare()方法.具体细节我就不说了,大家可以自己复习一下.最后我们遍历并输出这三个对象.

public class ComparatorDemo {
    public static void main(String[] args) {
        //这三个人按照年龄排序,决定哪个当老大.
        List<Person> array = new ArrayList<>();
        array.add(new Person("刘备", 29));
        array.add(new Person("关羽", 26));
        array.add(new Person("张飞", 25));

        // 匿名内部类
        Comparator<Person> comp = new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o2.getAge() - o1.getAge();
            }
        };
        Collections.sort(array, comp); // 第二个参数为排序规则,即Comparator接口实例

        for (Person person : array) {
            System.out.println(person);
        }
    }
}

我们这里用到了匿名内部类的写法.这是我们对一个引用类型的数组排序的常规写法.
下面我们来搞清楚上述代码真正要做什么事情。

  • 为了排序,Collections.sort方法需要排序规则,即Comparator接口的实例,抽象方法compare是关键;
  • 为了指定compare的方法体,不得不需要Comparator接口的实现类;
  • 为了省去定义一个Comparator实现类的麻烦,不得不使用匿名内部类;
  • 必须覆盖重写抽象compare方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错;
  • 实际上,只有参数和方法体才是关键

为了突出我们想要的,尽可能地摒弃那些繁琐的对象的创建和方法的重写,我们就需要用到lambda表达式.lambda表达式的语法是这样的

public class ComparatorDemo1 {
    public static void main(String[] args) {
        //这三个人按照年龄排序,决定哪个当老大.
        List<Person> array = new ArrayList<>();
        array.add(new Person("刘备", 29));
        array.add(new Person("关羽", 26));
        array.add(new Person("张飞", 25));
        
        //lambda表达式一行搞定
       Collections.sort(array, (o1, o2) -> o2.getAge() - o1.getAge());

        for (Person person : array) {
            System.out.println(person);
        }
    }
}

读者可以自己去运行,结果都是一样的:
在这里插入图片描述
至于lambda表达式的省略情况和语法等大家不熟悉地自己去查就好了,我举这个例子主要是想说明lambda表达式关注的是"做什么",至于怎么做,谁来做,都被省略了.所以它更像是一种面向函数的写法.
事实上,lambda表达式的应用场景正是用于这种函数式接口的方法的重写上.
这就基本解决了lambda表达式是什么和能干什么的问题.
那么这跟我们的stream又有什么关系呢?关系可大了.

Stream流介绍

Stream流是一种针对集合的一种模型,这个模型对集合类进行了包装,对集合进行遍历.stream流对集合有这样几种操作:过滤(filter),映射(map),统计(count),逐一处理(forEach)等.

同lambda的思想一样,stream的出现也是为了简化对数组的操作.因为在stream引入之前,要对数组的每个元素进行操作,就必须要执行for循环.然后根据我们的条件对循环到的每个元素进行对应操作,比如下面这个栗子,就是根据条件打印集合中的某些元素的值.
假如有这么几个人,都存放在一个数组中,我们想要根据条件筛选出我们需要的名字.比如把姓张的单独拿出来存放在张姓数组中,再从张姓数组中找到名字长度为3的拿出来放到长名字数组中,最后我们打印出所有这样的名字.如果是常规做法,下面是一个参考代码:

public class Demo02NormalFilter {
  	public static void main(String[] args) {
      	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> threeList = new ArrayList<>();
        for (String name : zhangList) {
            if (name.length() == 3) {
              	threeList.add(name);
            }
        }

        for (String name : threeList) {
          	System.out.println(name);
        }
    }
}

这段代码中含有三个循环,每一个作用不同:

  1. 首先筛选所有姓张的人;
  2. 然后筛选名字有三个字的人;
  3. 最后进行对结果进行打印输出。

每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。这是理所当然的么?不是.循环是做事情的方式,而不是目的。另一方面,使用线性循环就意味着只能遍历一次。如果希望再次遍历,只能再使用另一个循环从头开始。

那么,Stream能给我们带来怎样更加优雅的写法呢?

Stream流定义

java.util.stream.Stream< T > 是Java 8新加入的最常用的流接口。它有两种声明方式:
1.根据collection获取流.
首先,java.util.Collection接口中加入了default方法stream用来获取流,所以其所有实现类均可获取流。

import java.util.*;
import java.util.stream.Stream;

public class Demo04GetStream {
    public static void main(String[] args) {
        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();
    }
}

2.根据Map获取流.

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;

public class Demo05GetStream {
    public static void main(String[] args) {
        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();
    }
}

借助Stream流的filter()方法和forEach()方法,我们可以优雅地写出以下代码:

public class Demo03StreamFilter {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");

        list.stream()
          	.filter(s -> s.startsWith("张"))
            .filter(s -> s.length() == 3)
            .forEach(System.out::println);
    }
}

是不是简单多了?

Stream语法

Stream(流)是一种函数模型,就跟上面的lambda表达式一个意思.它主要用来实现对数组的遍历操作.而且这种遍历是单向的,不回头的,你可以考虑成原数组的包装,但这种包装做出的任何操作都不会对原数组产生任何的更改.并且这个函数模型也并不是数组.
上述方式也被称作链式调用,意思是可以不断地调用,直到返回类型不是Stream类型为止.
类似于lambda的语法,它也包含一些东西.比如说箭头.箭头后面是内容,至于内容是什么,lambda是重写方法的方法体,Stream中要根据它的api函数来定.通常来说,stream具有筛选,映射,组合,提取,跳过,统计等操作.这里就介绍两个,也就是上面的.filter()和.forEach().

  1. .filter()意为筛选,stream调用此方法表示要对操作数组中的每个元素按照筛选条件进行筛选,筛选条件就是filter()的参数.比如"长度为3", "字符串以"张"打头"这样的条件就在上面的代码中实现了.值得注意的是,经过.filter()的Stream类型并没有被改变,因此,它可以继续实现其他Stream类型的相关操作.
  2. .forEach()意为逐一处理.他表示对每一个流元素执行某种操作,最常见的就是输出操作.相当于在for循环下对每个流元素做点什么.但需要注意的是它的循环是无序的.
    此外还有skip(). limit(). map()等方法,就不一一赘述了.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值