参考了Java8新特性之三:Stream API、Java 8系列之Stream的基本语法详解、Java 8 Stream API 实用指南
我们先上一个简单的案例,比如说我们要输出学生中,性别为G学生的信息,
public class Student {
int no;
String name;
String sex;
float height;
//省略getter setter toString
}
public class run {
public static void main(String[] args) {
Student stuA = new Student(1, "A", "M", 184);
Student stuB = new Student(2, "B", "G", 163);
Student stuC = new Student(3, "C", "M", 175);
Student stuD = new Student(4, "D", "G", 158);
Student stuE = new Student(5, "E", "M", 170);
List<Student> list = new ArrayList<>();
list.add(stuA);
list.add(stuB);
list.add(stuC);
list.add(stuD);
list.add(stuE);
// 老方法,用for遍历
for (Student student: list) {
if (student.getSex().equals("G")) {
System.out.println(student);
}
}
// 用stream
list.stream()
.filter(student -> student.getSex().equals("G"))
.forEach(System.out::println);
}
}
可能这还不是很明显,但如果我们提升筛选的要求
假设我们有一批商品, 我们需要将:价格 > 500 & 销量< 200 的商品, 价格最高的 10 件商品, 价格减半
class Good {
String name; // 商品名称
long price; // 价格
long sales; // 销量
List<String> categories; // 类别
// ... 省略 constructor、getter / setter , 省略 toString
}
void process(List<Good> goods) {
//
// 筛选 price > 500 & sales < 200 的商品, 价格最高的 10 件商品, 价格减半(双十一来啦!)
//
goods.stream()
.filter(good -> good .getPrice() > 500 && good .getSales() < 200)
.sorted(Comparator.comparing(Good::getPrice).reversed())
.limit(10)
.forEach(good -> { good.setPrice(good .getPrice() / 2); });
}
没有用stream,我们可以用老方法,如 for 遍历, if 判断等,但大家知道,for、if的嵌套会使得代码无法像stream一样,简洁清晰。
一、创建Stream
从一个数据源,如集合、数组中获取流。
二、中间操作
一个操作的中间链,对数据源的数据进行操作。如filter、sorted、limit 这种只描述Stream,最终不产生新集合的方法,均为中间操作,其中filter又称为惰性求值
三、终止操作
一个终止操作,执行中间操作链,并产生结果。像count这种最终会从Stream产生值的方法,为终止操作,其中count 也称为及早求值。
我们已刚才商品为例,上文的代码有此示意图
图中所示,整个过程就是将 goods 元素集合作为一个 “序列”,进行一组 “流水线” 操作,其中:
需要说明,filter / sorted / limit 的返回值均为 Stream(类似于 Builder 模式),但它们并不立即执行,而是构成了 “流水线”,直到 forEach:最终执行,并且关闭 Stream。因此:
特别地,需要记住:Stream 的中间操作并不是立即执行,而是 “延迟的”、“按需计算”;并且,完成 “终止操作” 后,Stream 将被关闭。
Java 8 的 Collection 接口添加了 Stream
方法,由集合生成 Stream,List和set均继承了collection接口,所以List和set可直接调用stream方法。
//list、set、vector相同
void print(List<Good> goods) {
goods.stream().forEach(System.out::println);
}
// 数组生成stream方法
void print(Good[] goods) {
Arrays.stream(goods).forEach(System.out::println);
}
上面的代码分为两个步骤,一个stream(),生成stream,forEach()用是stream的终止操作,上面的代码还可以写为以下形式:
void print(List<Good> goods) {
Stream<goods> stream = goods.stream();
stream..forEach(System.out::println);
}
map不是collection的子接口,所以获取对应的流需要分为key、value、entry三种情况
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 API 提供了静态方法 Stream.generate(Supplier
、Stream.iterator(final T, final UnaryOperator
,直接创建 Stream。
//生成number个的商品,并逐一输出
Stream.generate(Good::new).limit(number).forEach().forEach(System.out::println)
//
// 生成指定数量的序列 1, 2, 4, 8, 16 ... 并输出,个数为number个
//
Stream.iterate(0, n -> n * 2).limit(number).forEach(System.out::println);
接下来让我们使用person类进行举例
class Person {
private String name;
private Integer age;
private String country;
private char sex;
// 省略 getter\setter\tostring\constructor\hashCode
}
List<Person> personList = new ArrayList<>();
personList.add(new Person("欧阳雪",18,"中国",'F'));
personList.add(new Person("Tom",24,"美国",'M'));
personList.add(new Person("Harley",22,"英国",'F'));
personList.add(new Person("向天笑",20,"中国",'M'));
personList.add(new Person("李康",22,"中国",'M'));
personList.add(new Person("小梅",20,"中国",'F'));
personList.add(new Person("何雪",21,"中国",'F'));
personList.add(new Person("李康",22,"中国",'M'));
需求:获取person列表中的女性,生成一个新list
List<Person> womanList = personList.stream().filter((person) -> person.getSex() == 'F').collect(Collectors.toList());
从pserson列表中,输出两个人的信息
personList.stream().limit(2).forEach(System.out::println);
输出为,可见,是按照添加顺序的先后,取前两个的信息
Person{name='欧阳雪', age=18, country='中国', sex=F}
Person{name='Tom', age=24, country='美国', sex=M}
从person列表中第二个人开始,输出所有人的信息
personList.stream().skip(1).forEach(System.out::println);
我们获取所有age 为 22的人并且输出,distinct的去重是根据hashCode进行去重,所以需要重写hashCode()方法,或者使用lombok,给类加上@Data注解,类似于如下修改
class Person {
// ......
@Override
public int hashCode() {
return Objects.hash(name, age, country, sex);
}
}
执行代码如下
personList.stream().filter((p) -> p.getSex() == 'M').distinct().forEach(System.out::println);
输出如下所示
Person{name='Tom', age=24, country='美国', sex=M}
Person{name='向天笑', age=20, country='中国', sex=M}
Person{name='李康', age=22, country='中国', sex=M}
可发现,男性中有两个李康,但此处仅输出了一个,去掉了一个重复的。
map方法将对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素。为了提高处理效率,官方已封装好了,三种变形:mapToDouble,mapToInt,mapToLong。
其实很好理解,如果想将原Stream中的数据类型,转换为double,int或者是long是可以调用相对应的方法。
Stream.of("a", "b", "hello")
.map(item-> item.toUpperCase())
.forEach(System.out::println);
// 打印结果
// A, B, HELLO
flatMap方法与map方法类似,都是将原Stream中的每一个元素通过转换函数转换,不同的是,该换转函数的对象是一个Stream,也不会再创建一个新的Stream,而是将原Stream的元素取代为转换的Stream。
如果转换函数生产的Stream为null,应由空Stream取代。flatMap有三个对于原始类型的变种方法,分别是:flatMapToInt,flatMapToLong和flatMapToDouble。
Stream.of(1, 2, 3)
.flatMap(integer -> Stream.of(integer * 10))
.forEach(System.out::println);
// 打印结果
// 10,20,30
自然排序比较好理解,这里只讲一下定制排序,对前面的personList按年龄从小到大排序,年龄相同,则再按姓名排序:
final Stream<Person> sorted = personList.stream().sorted((p1, p2) -> {
if (p1.getAge().equals(p2.getAge())) {
return p1.getName().compareTo(p2.getName());
} else {
return p1.getAge().compareTo(p2.getAge());
}
});
sorted.forEach(System.out::println);
运行结果
Person(name=欧阳雪, age=18, country=中国, sex=F)
Person(name=向天笑, age=20, country=中国, sex=M)
Person(name=小梅, age=20, country=中国, sex=F)
Person(name=何雪, age=21, country=中国, sex=F)
Person(name=Harley, age=22, country=英国, sex=F)
Person(name=李康, age=22, country=中国, sex=M)
Person(name=李康, age=22, country=中国, sex=M)
Person(name=Tom, age=24, country=美国, sex=M)
大多都很简单移动,此处仅对allMatch、max各举一例说明
final boolean adult = personList.stream().allMatch(p -> p.getAge() >= 18);
System.out.println("是否都是成年人:" + adult);//是否都是成年人:true
final Optional<Person> maxAge = personList.stream().max((p1, p2) -> p1.getAge().compareTo(p2.getAge()));
System.out.println("年龄最大的人信息:" + maxAge.get());
Stream API的归约操作可以将流中元素反复结合起来,得到一个值。
我们举一个例子,我们需要计算 1 至 100 的和。
List<Integer> integerList = new ArrayList<>(100);
for(int i = 1;i <= 100;i++) {
integerList.add(i);
}
Optional<Integer> reduce = integerList.stream().reduce((total, item) -> total + item);
System.out.println(reduce.get());
求所有人的年龄之和
final Optional<Integer> reduce = personList.stream().map(Person::getAge).reduce(Integer::sum);
System.out.println("年龄总和:" + reduce);
刚加详细的解析请看下面的这个链接
Java 1.8 新特性——Stream 流中 Reduce 操作
collect:将流转换为其他形式
List<Person> womanList = personList.stream().filter((person) -> person.getSex() == 'F').collect(Collectors.toList());
final Double collect1 = personList.stream().collect(Collectors.averagingInt(p -> p.getAge()));
2 System.out.println("平均年龄为:" + collect1);