Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
public class Person { private String name; private Integer age; private Integer score; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Integer getScore() { return score; } public void setScore(Integer score) { this.score = score; } public Person() { } public Person(String name, Integer age, Integer score) { this.name = name; this.age = age; this.score = score; } @Override public String toString() { return "Person{" + "name='" + name + '/'' + ", age=" + age + ", score=" + score + '}'; } } 复制代码
public class Program { public static void main(String[] args) { //使用构造器设置对象信息 // Person xiaomign = new Person("小明", 28, 90); //使用getter、setter方式设置对象信息 Person xiaoming = new Person(); xiaoming.setName("小明"); xiaoming.setAge(18); xiaoming.setScore(90); } } 复制代码
public class Person { private String name; private Integer age; private Integer score; public String getName() { return name; } public Person setName(String name) { this.name = name; return this; } public Integer getAge() { return age; } public Person setAge(Integer age) { this.age = age; return this; } public Integer getScore() { return score; } public Person setScore(Integer score) { this.score = score; return this; } public Person() { } public Person(String name, Integer age, Integer score) { this.name = name; this.age = age; this.score = score; } @Override public String toString() { return "Person{" + "name='" + name + '/'' + ", age=" + age + ", score=" + score + '}'; } } 复制代码
//流式操作 xiaoming.setName("小明").setAge(20).setScore(100); 复制代码
集合的流式操作是Java8的一个新特性,流式操作不是一个数据结构,不负责任何的数据存储,它更像是一个迭代器,可以有序的获取数据源中的每一个数据,并且可以对这些数据进行一些操作。 流式操作的每一个方法的返回值都是这个流的本身 。
设置数据源
public class Data { /** * 数据源 */ public static ArrayList<Person> getData() { ArrayList<Person> list = new ArrayList<Person>(); list.add(new Person("小明", 18, 100)); list.add(new Person("小丽", 19, 70)); list.add(new Person("小王", 22, 85)); list.add(new Person("小张", 20, 90)); list.add(new Person("小黑", 21, 95)); return list; } } 复制代码
获取数据源的方式
public class Program { public static void main(String[] args) { // 获取数据源方式1 Stream stream = Data.getData().stream(); // 获取数据源方式2 Stream.of(Data.getData()); // 获取数据源方式3 //数据源为数组 } } 复制代码
使用filter自定义条件过滤数据
// 中间操作1: filter // filter是一个过滤器,可以自定义一个过滤条件,将流中满足条件的元素保留 // 查找集合中成绩小于80的学生 List<Person> list = Data.getData().stream() .filter(ele -> ele.getScore() < 80) .collect(Collectors.toList()); System.out.println(list); 复制代码
使用distinct实现去重操作
在数据源中添加重复的数据
list.add(new Person("小黑", 21, 95)); //此时list中有两个小黑 复制代码
在实体类中重写hashCode()和equals()方法
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return Objects.equals(name, person.name) && Objects.equals(age, person.age) && Objects.equals(score, person.score); } @Override public int hashCode() { return Objects.hash(name, age, score); } 复制代码
去重规则:
先判断对象的hashCode()
如果hashCode()相同再判断equals()
// 中间操作2: distinct // distinct: 取出集合中不同的元素 // 去重规则: // 1.先判断对象的hashCode() // 2.如果hashCode()相同再判断equals() Data.getData().stream().distinct().forEach(System.out::println); 复制代码
注意:如果小黑的数据相同却要保存两份,可以在 hashCode()
方法中返回一个随机数,随机数很小概率会相同,为了确保稳定性,可以将 equals()
方法改为返回 false
,这样可以保留两个信息相同的小黑。
使用 sorted()
方法以成绩进行升序排序
要求实体类实现Comparable接口并重写方法
// 中间操作3: sorted // sorted: 对返回的元素进行排序 // sorted(): 要求实体类实现Comparable接口并重写方法 Data.getData().stream().sorted().forEach(System.out::println); 复制代码
在数据源中取前三个数据
// 中间操作4: limit // limit: 限制,只取流中前指定位的数据 // 在数据源中取前三个数据 Data.getData().stream().limit(3).forEach(System.out::println); 复制代码
跳过前三个元素,取后面剩下的元素
// 中间操作5: skip // skip: 跳过 // 跳过前三个元素,取后面剩下的元素 Data.getData().stream().skip(3).forEach(System.out::println); 复制代码
元素映射,用指定的元素替换掉流中的元素
使用map将对象替换为对象的名字
// 中间操作6: map // map: 元素映射,用指定的元素替换掉流中的元素 // 将流中的Person对象替换位他们的姓名 Data.getData().stream().map(ele -> ele.getName()).forEach(System.out::println); 复制代码
转换为List
public class Program { public static void main(String[] args) { // 获取数据源方式1 Stream<Person> stream = Data.getData().stream(); // 最终操作1: collect,配合Collectors使用 // 将集合中的元素转换成List List<Person> list = stream.collect(Collectors.toList()); System.out.println(list); } } 复制代码
运行结果
转换为set
// 将集合中的元素转换为Set Set<Person> set = stream.collect(Collectors.toSet()); System.out.println(set); 复制代码
运行结果
转换为map
// 转换为Map(name为键,score为值) // 方式1 // Map<String, Integer> map = stream.collect(Collectors.toMap( // ele -> ele.getName(), // ele -> ele.getScore() // )); // 方式2 Map<String, Integer> map = stream.collect(Collectors.toMap( Person::getName, Person::getScore )); 复制代码
运行结果
reduce的思想
比如在计算一个数组中的元素的和时,首先会计算前两个数的和,然后拿着前两个数的和与第三个数求和,计算出结果后将三个数的和与第四个数相加,以此类推。
计算数组中数据的和
// 最终操作2: reduce(将数据汇总在一起) Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); Optional<Integer> res = stream1.reduce((n1, n2) -> n1 + n2); // 获取到最终的返回值 System.out.println(res.get()); 复制代码
使用reduce计算Person对象中成绩的和
// 计算Person中Score的和 Optional<Person> res = stream.reduce( (n1, n2) -> new Person().setScore(n1.getScore() + n2.getScore()) ); System.out.println(res.get().getScore()); 复制代码
使用reduce计算Person对象中成绩的和(优化)
// 计算Person中Score的和(使用临时变量,减少性能开销) Person temp = new Person(); Optional<Person> res = stream.reduce( (n1, n2) -> temp.setScore(n1.getScore() + n2.getScore()) ); System.out.println(res.get().getScore()); 复制代码
使用max找出Person中成绩最高的人
// 最终操作3: max和min // 需求1: 找到集合中成绩最高的人的信息 Person max = stream.max( (ele1, ele2) -> ele1.getScore() - ele2.getScore() ).get(); System.out.println(max); 复制代码
使用min找出Person中成绩最低的人
// 需求2: 找到集合中成绩最低的人的信息 Person min = stream.min( (ele1, ele2) -> ele1.getScore() - ele2.getScore() ).get(); System.out.println(min); 复制代码
使用anyMatch查看集合中是否有成绩高于80的人
// 判断集合中是否包含成绩大于80的学员 boolean res1 = stream.anyMatch((ele) -> ele.getScore() > 80); System.out.println(res1); 复制代码
使用allMatch查看集合中的成绩是否全部高于60
//查看集合中的人的成绩是否全部高于60 boolean res2 = stream.allMatch((ele) -> ele.getScore() > 60); System.out.println(res2); 复制代码
使用noneMatch查看集合中的人的分数是否不包含80以下的
boolean res3 = stream.noneMatch((ele) -> ele.getScore() < 80); System.out.println(res3); 复制代码
使用count计算元数据中有多少条数据
// 最终操作5: 求元数据中有多少个元素 long count = stream.count(); System.out.println(count); 复制代码
使用forEach遍历集合中的元素
// 最终操作6: forEach // stream.forEach(ele -> System.out.println(ele)); stream.forEach(System.out::println); 复制代码
// FindFirst: 获取流中的第一个元素 // FindAny: 获取流中任意一个元素(并不是随机获取元素) // 对于串行流,结果等同于findFirst // findAny用于并行流中可能会与findFirst一样,也可能不一样 System.out.println(Data.getData().parallelStream().findFirst()); System.out.println(Data.getData().stream().findFirst()); System.out.println(Data.getData().parallelStream().findAny()); System.out.println(Data.getData().stream().findAny()); 复制代码
为什么会被称为最终操作?
Person max = stream.max( (ele1, ele2) -> ele1.getScore() - ele2.getScore() ).get(); Person min = stream.min( (ele1, ele2) -> ele1.getScore() - ele2.getScore() ).get(); 复制代码
报错信息表示 流正在被处理或者已经被关闭了 ,如果已经被关闭了再次调用当然会报错,这也是为什么叫最终操作的原因。
// 并行流 // 获取并行流的两种方式 Data.getData().stream().parallel(); Data.getData().parallelStream(); 复制代码
// 串行流: 19920ms // 并行流: 12204ms long startTime = System.currentTimeMillis(); //LongStream.rangeClosed(0L, 50000000000L) // .reduce(Long::sum); LongStream.rangeClosed(0L, 50000000000L) .parallel() .reduce(Long::sum); long endTime = System.currentTimeMillis(); System.out.println(endTime - startTime); 复制代码
String[] array = {"hello", "world"}; // 需要获取所有字符 List -> h, e, l, l, o, w, o, r, l, d // Arrays.stream(array) // .map(ele -> ele.split("")) // .forEach(ele -> System.out.println(ele.length)); System.out.println(Arrays.stream(array) .map(ele -> ele.split("")) .flatMap(Arrays::stream) .collect(Collectors.toList())); 复制代码
Collectors是一个工具类,提供着若干个方法,返回一个Collector接口的实现类对象
通过指定的规则获取流中最大的元素
System.out.println(Data.getData().stream() .collect(Collectors.maxBy((ele1, ele2) -> ele1.getScore() - ele2.getScore()))); 复制代码
通过指定的规则获取流中最小的元素
System.out.println(Data.getData().stream() .collect(Collectors.minBy((ele1, ele2) -> ele1.getScore() - ele2.getScore()))); 复制代码
合并,将流中的元素,以字符串的形式拼接起来
// 把Person中的姓名拼成一个字符串 String res1 = Data.getData().stream() .map(Person::getName) .collect(Collectors.joining()); System.out.println(res1); 复制代码
String res2 = Data.getData().stream() .map(Person::getName) .collect(Collectors.joining("-")); System.out.println(res2); 复制代码
String res3 = Data.getData().stream() .map(Person::getName) .collect(Collectors.joining("-", "{", "}")); System.out.println(res3); 复制代码
计算int类型的和,将流中的元素映射为int类型的元素进行求和
将Person对象的成绩进行求和
// 将Person对象的成绩进行求和 System.out.println(Data.getData().stream() .collect(Collectors.summingInt(ele -> ele.getScore()))); 复制代码
计算int类型的平均值
计算不及格学生的平均成绩
System.out.println(Data.getData().stream() .filter(ele -> ele.getScore() < 60) .collect(Collectors.averagingInt(Person::getScore))); 复制代码
将流中的元素映射成int类型的元素,获取这些数据的描述信息
System.out.println(Data.getData().stream() .collect(Collectors.summarizingInt(ele -> ele.getScore()))); 复制代码