java8中两大最为重要的改变一个是lambda表达式,另外一个则是Stream API,Stream是java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以进行非常复杂的查找、过滤和映射数据等操作。使用Stream API对集合数据进行操作,就类似使用sql进行的数据库查询,也可以使用Stream API来并行执行操作。简而言之,Stream API提供了一种高效且易于使用的处理数据的方式。
什么是Stream?流Stream是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列,集合讲的是数据,而流讲的是计算。
注意:
- Stream自己不会存储数据
- Stream自己不会改变源对象。相反它会返回一个持有结果的新Stream
- Stream是操作是延迟执行的。这意味着这它们会等到需要结果的时候才执行。
Stream的操作之3个步骤
- 创建流Stream:用一个数据源(集合数组等)获取一个流
- 中间操作:一个中间操作链对数据进行处理
- 终止操作:执行中间操作并产生结果
一:创建Stream方法:
1.java8中的Collection接口被扩展提供了两个获取流的方法,default Stream<E> stream()返回一个串行流,default Stream<E> parallelStream()返回一个并行流;
2.Java8 中的Arrays 的静态方法stream() 可以获取数组流:public static <T> Stream<T> stream(T[] array);
3.可以使用静态方法Stream.of(), 通过显示值创建一个流。它可以接收任意数量的参数:public static<T> Stream<T> of(T... values);
4.可以使用静态方法Stream.iterate() 和Stream.generate(), 创建无限流。public static<T> Stream<T> iterate和 public static<T> Stream<T> generate
二:Stream的中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!
而在终止操作时一次性全部处理,称为“惰性求值”。
常用中间操作如下
方法 | 描述 |
filter(Predicate p) | 接收Lambda ,从流中排除某些元素。 |
distinct() | 筛选,通过流所生成元素的hashCode() 和equals() 去除重复元素 |
limit(long maxSize) | 截断流,使其元素不超过给定数量 |
skip(long n) | 跳过元素,返回一个扔掉了前n 个元素的流。若流中元素不足n 个,则返回一个空流。与limit(n) 互补 |
map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素 |
mapToDouble(ToDoubleFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的DoubleStream。 |
mapToInt(ToIntFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream。 |
mapToLong(ToLongFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的LongStream。 |
flatMap(Function f) | 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流 |
sorted() | 产生一个新流,其中按自然顺序排序 |
sorted(Comparatorcom p) | 产生一个新流,其中按比较器顺序排序 |
三:Stream的终止操作:终止操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是void
下面贴上代码强化一下概念的理解
创建stream的方式以及中间操作
package com.atguigu.test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;
import org.junit.Test;
import com.atguigu.java8.Employee;
public class TestStream {
//创建流
@Test
public void test1() {
//1 Collection接口提供的两个方法创建流
List<String> list=new ArrayList<>();
Stream<String> stream1=list.stream();
Stream<String> stream2=list.parallelStream();
//2 通过Arrays的静态方法stream方法得到数组流
Integer[] numArray=new Integer[10];
Stream<Integer> stream3=Arrays.stream(numArray);
//3 使用静态方法Stream.of(), 通过显示值创建一个流
Stream<String> stream4=Stream.of("hello","world","hello","stream");
//4 创建无限流
//public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
//第一个参数为起始点,后面的是具体的一元运算,这里是产生以0为起点的无线偶数
Stream<Integer> stream5=Stream.iterate(0, (x)->x+2);
stream5.forEach(System.out::println);//这里的forEach为Stream的终止操作
Stream<Double> stream6=Stream.generate(Math::random);//产生无线随机数
stream6.limit(20).forEach(System.out::println);//limit为中间操作,表示截取前20个
}
//中间操作
/*
筛选与切片
filter——接收 Lambda , 从流中排除某些元素。
limit——截断流,使其元素不超过给定数量。
skip(n) —— 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
distinct——筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
*/
List<Employee> list=Arrays.asList(
new Employee(101,"张三",18,9999.99),
new Employee(102,"李四",20,6666.66),
new Employee(103,"王五",25,11111.11),
new Employee(104,"赵六",40,55555.55),
new Employee(105,"田七",50,99999.99),
new Employee(105,"田七",50,99999.99)
);
@Test
public void test2() {
list.stream()
.filter((e)->e.getAge()<30)
.forEach(System.out::println);
System.out.println("--------------");
Stream<Employee> stream=list.stream()
.filter((e)->{
System.out.println("会不会打印我");
return e.getAge()<30;
});
//下面这句终止操作不执行的话,上面那个打印是不会输出的,因为惰性求值
stream.forEach(System.out::println);
}
@Test
public void test3() {
list.stream()
.filter((e)->{
System.out.println("短路");//&& ||
return e.getAge()>18;
})
.limit(2)//由于limit的原因虽然过滤的年龄大于18的结果有4个,但是找到符合条件的2个后就不继续找了
.forEach(System.out::println);
}
@Test
public void test4() {
//虽然工资大于10000的结果有3个,但是跳过前2个,所以只会打印最后一个
list.stream().filter((e)->e.getSalary()>10000).skip(2).forEach(System.out::println);
}
@Test
public void test5() {
//这里的distinct跟具体的流对象有关,必须重写hashcode和equals方法
list.stream().distinct().forEach(System.out::println);
}
//中间操作
/*
映射
map——接收 Lambda , 将元素转换成其他形式或提取信息。接收一个函数作为参数,
该函数会被应用到每个元素上,并将其映射成一个新的元素。
flatMap——接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
*/
@Test
public void test6() {
//将list里面的每一个employee传进去,提取里面的工资信息并打印
list.stream().map((e)->e.getSalary()).forEach(System.out::println);
List<String> strList=Arrays.asList("aaa","bbb","ccc","ddd","eee");
//将list中的每个字符串传进去进行大写转换
strList.stream().map((e)->e.toUpperCase()).forEach(System.out::println);
Stream<Stream<Character>> ssc=strList.stream().map((e)->{
//将strList里的每一个字符串传进去转为List<Character>
List<Character> temp=new ArrayList<>();
for (Character c : e.toCharArray()) {
temp.add(c);
}
//然后返回Stream<Character>,注意返回的一个一个的Stream
return temp.stream();
});
//所以ssc得类型为Stream<Stream<Character>>就好比是大流里面包含着一个一个的小流
//所以ssc要遍历需如下
ssc.forEach((sc)->sc.forEach(System.out::println));
System.out.println("---------------------------");
strList.stream().flatMap((e)->{
List<Character> temp=new ArrayList<>();
for (Character c : e.toCharArray()) {
temp.add(c);
}
return temp.stream();
}).forEach(System.out::println);
//注意上面提取元素信息的操作是一样的,但是打印时flatMap却少了一层打印
//因为flatMap把各个的小流包含的元素整合成一个大流将这些元素全部包含了而不是流中有流了
//这里好比Conllection的add与addAll
List mylist=new ArrayList<>();
mylist.add("1");
mylist.add("2");
mylist.add(3);
List mylist2=new ArrayList<>();
mylist2.add("aaa");
mylist2.add(mylist);
System.out.println(mylist2);//[aaa, [1, 2, 3]]
List mylist3=new ArrayList<>();
mylist3.add("aaa");
mylist3.addAll(mylist);
System.out.println(mylist3);//[aaa, 1, 2, 3]
}
/*
sorted()——自然排序
sorted(Comparator com)——定制排序
*/
@Test
public void test7() {
list.stream().map(Employee::getAge).sorted().forEach(System.out::println);
System.out.println("--------------");
list.stream().sorted((e1,e2)->{
if(e1.getAge()==e2.getAge()) {
return e1.getName().compareTo(e2.getName());
}else {
return Integer.compare(e1.getAge(), e2.getAge());
}
}).forEach(System.out::println);
}
}