1、Stream流介绍
Stream流用来简化集合和数组,在Java8中,得益于Lambda所带来的的函数式编程,引入了一个全新的Stream概念,把集合和数组转换成Stream流,再用Stream流里面的方法。
2、Stream编程初体验
public class Demo15Stream {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("张三");
list.add("张三方");
list.add("赵敏");
list.add("张小华");
//获取流
list.stream()
//过滤姓张
.filter(s -> s.startsWith("张"))
//过滤长度为3
.filter(s -> s.length()==3)
//逐一打印
.forEach(s -> System.out.println(s));
}
}
3、流式思想概述
Stream是一个来自数据源的元素队列,Stream并不会存储元素,而是按需计算,数据源流的来源可以是集合和数组。
4、获取流
有如下几种方式获取流:
- 所有Collection集合都可以通过stream默认方法获取流。
- Stream接口的静态方法of可以获取数组对应的流。
4.1 根据Collection获取流
public static void test(){
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
Set<String> set = new HashSet<>();
Stream<String> stream1 = set.stream();
}
4.2、根据Map获取流
public static void testMap(){
Map<String, String> map = new HashMap<>();
Stream<String> stream = map.keySet().stream();
Stream<String> stream1 = map.values().stream();
Stream<Map.Entry<String, String>> stream2 = map.entrySet().stream();
}
4.3、根据数组获取流
public static void testArr(){
String[] arr = {"张三", "李四", "王五", "赵柳"};
Stream<String> arr1 = Stream.of(arr);
}
5、常用方法
- 延迟方法:返回值类型仍然是Stream接口自身类型的方法,因此支持链式调用。
- 终结方法:返回值类型不再是Stream接口自身类型的方法,终结方法包括count和forEach。
5.1、逐一处理:foreach
该方法接收一个Consumer接口函数,会将每一个流元素交给该函数进行处理。
@Test
public void testForEach(){
Stream<String> stream = Stream.of("张三", "李四", "王五");
stream.forEach(s->System.out.println(s));
}
5.2、过滤:filter
通过filter方法可以将一个流转换为留一个子集流,该方法接收一个Predicate函数是接口作为参数。
@Test
public void testFilter(){
Stream<String> stream = Stream.of("张三", "李四", "王五");
stream.filter(s -> s.startsWith("张")).forEach(s -> System.out.println(s));
}
5.3、映射:map
如果需要将流中的元素映射到另一个流中,可以使用map方法,该方法接收一个Function函数式接口作为参数,可以将当前流中的T类型转换为另一种R类型。
@Test
public void testMap(){
Stream<String> stringStream = Stream.of("11", "22", "9");
Stream<Integer> result = stringStream.map((s -> Integer.parseInt(s)));
result.forEach(s->System.out.println(s));
}
5.4、统计个数:count
使用count方法来统计流中的元素的个数。
@Test
public void testCount(){
Stream<String> stream = Stream.of("张无忌", "李四", "王五", "张三丰");
System.out.println(stream.filter(s -> s.startsWith("张")).count());
}
5.5、取用前几个:limit
limit可以对流进行截取,支取前几个,参数是一个long类型,如果集合当前长度大于参数,则进行截取,否则不进行操作。
@Test
public void testLimit(){
Stream<String> stream = Stream.of("张无忌", "李四", "王五", "张三丰");
stream.limit(3).forEach(s->System.out.println(s));
}
5.6 跳过前几个:skip
如果希望跳过前几个元素,则可以使用skip方法获取一个截取之后的新流。参数是一个long类型,如果流的当前长度大于n,则跳过前n个,否则,将会得到一个长度为0的空流。
@Test
public void testSkip(){
Stream<String> stream = Stream.of("张无忌", "李四", "王五", "张三丰");
stream.skip(2).forEach(s-> System.out.println(s));
}
5.7 组合:concat
如果有两个流,希望合并成为一个流,则可以使用Stream接口的静态方法concat。
@Test
public void testConcat(){
Stream<String> streamZ = Stream.of("张三");
Stream<String> streamL = Stream.of("李四");
Stream<String> concatStream = Stream.concat(streamZ, streamL);
concatStream.forEach(s-> System.out.println(s));
}
6、方法引用
6.1、方法引用符
双冒号::为引用运算符,而它所在的表达式被称为方法引用,如果lambda要表达的函数方案已经存在于某个方法的实现中,则可以通过双冒号来引用该方法作为lambda的替代者。
6.2、通过对象名引用成员方法
一个类中已经存在了一个成员方法
public class MethodRefObject {
public void printUpperCaseString(String s){
System.out.println(s.toUpperCase());
}
}
函数式接口定义:
@FunctionalInterface
public interface Printable {
public abstract void print(String s);
}
当需要使用printUpperCase方法时,已经具有了MethodRefObject类的实例,因此可以通过对象名引用成员方法。
public class Demo01Functional {
public static void printString(Printable p){
p.print("hello");
}
public static void main(String[] args) {
MethodRefObject methodRefObject = new MethodRefObject();
printString(methodRefObject::printUpperCaseString);
}
}
6.3、通过类名称引用静态方法
函数式接口:
@FunctionalInterface
public interface Calcable {
int calsAbs(int number);
}
类名称引用静态方法
public class Demo02Functional {
public static int method(int num, Calcable c) {
return c.calsAbs(num);
}
public static void main(String[] args) {
//abs计算绝对值的静态方法也是已经存在的
//所以我们可以直接通过类名引用静态方法
int absNum = method(-22, Math::abs);
System.out.println(absNum);
}
}
6.4 通过super引用成员方法
如果存在继承关系,当lambda表达式中需要出现super调用时,可以使用方法引用进行替代。 函数式接口:
@FunctionalInterface
public interface Greetable {
void greet();
}
父类Human:
public class Human {
public void sayHello(){
System.out.println("hello,我是Human");
}
}
子类Man,使用super引用成员方法:
public class Man extends Human{
@Override
public void sayHello() {
System.out.println("hello,我是Man");
}
public void method(Greetable greetable){
greetable.greet();
}
public void show(){
method(super::sayHello);
}
public static void main(String[] args){
new Man().show();
}
}
6.5 通过this引用成员方法
this代表当前对象,如果需要引用的方法就是当前类中的成员方法,则可以使用this::成员方法的格式来使用方法引用。 函数式接口
@FunctionalInterface
public interface Richable {
public void buy();
}
定义一个Husband类:
public class Husband {
private void buyHouse(){
System.out.println("买套房子");
}
public void marry(Richable richable){
richable.buy();
}
public void beHappy(){
marry(this::buyHouse);
}
public static void main(String[] args) {
new Husband().beHappy();
}
}
6.6 类的构造器引用
由于构造器的名称与类名完全一样,构造器引用可以使用类名::new的格式表示。 定义一个Person类。
public class Person {
private String name;
public Person(String name){
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + ''' +
'}';
}
}
创建函数式接口:
@FunctionalInterface
public interface PersonBuilder {
Person builderPerson(String name);
}
public class Demo04Functional {
public static void printName(String name, PersonBuilder personBuilder){
Person person = personBuilder.builderPerson(name);
System.out.println(person);
}
public static void main(String[] args) {
//构造方法new Person(String name) 已知
//创建对象已知 就可以使用Person引用new创建对象
printName("张三", Person::new);
}
}
6.7 数组的构造器引用
数组是Object的子类对象,因此同样具有构造器,只是语法稍微不同。 定义函数式接口:
@FunctionalInterface
public interface ArrayBuilder {
int[] builderArray(int size);
}
public class Demo05Functional {
public static int[] createArray(int size, ArrayBuilder arrayBuilder){
return arrayBuilder.builderArray(size);
}
public static void main(String[] args){
//用方法引用优化Lambda表达式
//已知创建的就是int[]数组
//数组的长度也是已知的
//就可以使用方法引用
//int[]引用new,根据参数传递的长度来创建数组
int[] array = createArray(5, int[]::new);
System.out.println(Arrays.toString(array));
}
}