Stream API 从1.8引入的api, 都和集合有关。这些方法操作集合更方便:
例子:写一个方法找list集合中的所有偶数,并返回一个新的list集合;
代码如下:
public class Demo1 {
public static void main(String[] args) {
// 写一个方法找list集合中的所有偶数,并返回一个新的list集合
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
find(list);
System.out.println(find(list));
}
private static List find(List<Integer> list) {
List<Integer> list1 = new ArrayList<>();
for (Integer integer : list) {
if (integer % 2 == 0) {
list1.add(integer);
}
}
return list1;
}
}
上面是求找偶数的方法,假设我们需要找奇数, 偶数乘2的数值等等要求,有一个新要求我们就要定义一个新方法,非常麻烦,所以引入了Stream api中的一些接口,操作方法,使用更方便。
以下是找偶数使用Stream api中的操作
public class StreamDemo {
public static void main(String[] args) {
List<Integer> list= Arrays.asList(1,2,3,4,5);
//把集合变为流
Stream<Integer> stream = list.stream();
//调用流中的filter过滤方法,把奇数过滤掉,保留偶数
Stream<Integer> integerStream = stream.filter(new Predicate<Integer>() {
@Override
public boolean test(Integer i) {
return i % 2 == 0;
}
});
//采用lambda表达式
Stream<Integer> integerStream1 = stream.filter(i -> i % 2 == 0);
//收集数据,Collector是一个收集器,toList()是转换为集合
List<Integer> collect = integerStream.collect(Collectors.toList());
System.out.println(collect);
}
}
在Stream api 中,我们可以在匿名内部类处或者lambda表达式处更改操作求奇数,而不用再定义方法,其中Predicate是断言接口, filter是过滤方法,接下来将详细介绍几种常用的重要的接口,方法。注意在Stream中都要采用Collector收集器收集数据,才能打印出数据。
重要接口
1.Predicate 断言接口
对应 一个lambda表达式,一个参数,返回结果是boolean
2.Function 函数接口
对应的lambda 一个参数,一个返回结果,二者类型可以不一致
3.BiPridicate 双参数断言
对应的lambda 两个参数,返回结果是boolean
4.Consumer消费接口
一个参数,没有结果
5.Biconsumer 双参数消费接口
两个参数,没有结果
6.Supplier 生产接口
没有参数,返回一个结果
Stream api
1.filter()
filter()进行过滤的,lambda返回为true就会留下,返回为false的就会过滤掉。
其需要的参数是Predicate 断言接口 ,多用于当需要返回结果为boolean类型时的操作,可以改变集合中元素的个数
2.map() 映射的
lambda把原有的元素转换为另一个元素,不会改变个数。其需要的参数是Function函数接口,即参数类型和返回结果可以不一致,故可以用来进行转变类型操作。比如可以把String类型通过map转变为数组类型。
3.flatMap() 扁平化映射
其需要的参数是Function函数接口,即参数类型和返回结果可以不一致,故可以用来进行转变类型操作。比如可以把String[]类型通过flatMap转变为流。
注:map()和flatMap()的区别:map()是10个元素的映射,结果得到的仍然是10个元素,flatMap()中是每个元素又转成小的流,内含多个子元素,所以10 个元素的flatMap的最终结果会多于10。
4.forEach()遍历流
接收一个Consumer
5.count()求个数
6.dintinct()去除重复
7.分组
Collectors中的groupingBy()方法是用来分组的,把分组规则传进去即可。最后面有此分组代码,可以参考,分别列举了不采用Stream分组的常规分组方法,采用Stream分组的方法;可以看出常规方法比较麻烦,Stream分组方法比较简单,尤其是当元素多的时候。
Stream api中重要的思想:
流水线思想 pipeline:把流中的数据一个接一个处理,每个数据会经过后续的filter,map等方法依次调用。
在整个执行过程中,lambda表达式是懒惰的,不执行终结方法的话,不会触发lambda的执行。
终结方法:collect ,sum,max,min…
运算过程中不会改变原始集合,收集器会生成新的集合对象。
例子
把最后终结方法的两行代码注释掉,输出语句没有一个执行:
public class Stream2 {
public static void main(String[] args) {
List<Integer> list= Arrays.asList(1,2,3,4,5);
Stream<Integer> stream1 = list.stream();
Stream<Integer> stream2=stream1.filter(i->{
System.out.println("lambda表达式filter执行了");return i%2!=0;
});//求奇数
Stream<Integer> stream3 = stream2.map(i -> {
System.out.println("lambda表达式map执行了");
return i * 2;});//奇数再乘2
// List<Integer> collect = stream3.collect(Collectors.toList());
// System.out.println(collect);
}
}
9.流的生 成:
1.用集合生成
list.stream();
2.用数字生成
IntStream.of()
代码:
public class IntStreamDemo {
public static void main(String[] args) {
//当流操作的是纯数字时,可以采用数字流
//可以求和,最大最小值,平均值
int sum = IntStream.of(1, 2, 3, 4, 5, 6,7).sum();
System.out.println(sum);
OptionalInt max = IntStream.of(1, 2, 3, 4, 5, 6, 7).max();
System.out.println(max);
OptionalInt min = IntStream.of(1, 2, 3, 4, 5, 6, 7).min();
System.out.println(min);
OptionalDouble average = IntStream.of(1, 2, 3, 4, 5, 6, 7).average();
System.out.println(average);
}
}
3.用数组生成
Arrays.stream(数组)// 把数组变成流
代码:
public class ArrayStream {
public static void main(String[] args) {
String[] s={
"1","2","3","4","5"};
Stream<String> stream = Arrays.stream(s);
List<String> collect = stream.collect(Collectors.toList());
System.out.println(collect);
}
}
4.用文件生成
Files.lines()//把文件中的每一行读取出来作为元素。
代码:
public class FileStream {
public static void main(String[] args) throws IOException {
Stream<String> lines = Files.lines(Paths.get("heroes.txt"), Charset.forName("utf-8"));
lines.forEach(string-> System.out.println(string));
}
}
5.使用生产者接口
Stream.generate();用limit()限制生成的流的长度。其参数是supplier接口,无参数,返回一个结果;
代码:
public class GenerateStream {
public static void main(String[] args) {
Stream<Integer> limit = Stream.generate(() -> 5).limit(3);
limit.forEach(i-> System.out.println(i));
Random random = new Random();
Stream.generate(()->random.nextInt(100)).limit(5).forEach(a-> System.out.println(a));
}
}
10方法引用
实际上是对lambda表达式的扩展,就是可以把lambda表达式的形式转为方法引用的形式:
方法引用的格式:
1.对象::方法名
例如:System.out.println
Stream<Integer> limit = Stream.generate(() -> 5).limit(3);
// limit.forEach(i-> System.out.println(i));//正常输出形式
//类名::方法名
limit.forEach(System.out::println);//方法引用形式
forEach方法里要的是Consumer接口,一个参数,没有返回结果,而System.out类中的println也是一个参数,没有返回值,所以可以把lambda表达式写成方法引用的形式。
2.类名::静态方法名
Class ABC{
public static Boolean aaa(Integer x){
//可以写好多代码
Return x%2==0;
}
}
可以使用ABC::aaa代替其lambda表达式形式。
3.类名::new
可以取代Supplier接口的lambda
例如()->new Student()可以替换为:Student::new
建议:当有复杂逻辑时,lambda表达式的形式比较时,可以采用方法引用的第二种形式,把方法封装起来,调用时非常简单。
具体代码如下:
public class StreamYinYong {
public static void main(String[] args) {
Stream<Integer> limit = Stream.generate(() -> 5).limit(3);
//第一种形式:类名::方法名
//limit.forEach(i-> System.out.println(i));//正常输出形式
limit.forEach(System.out::println);//方法引用形式
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer