简述Stream流及方法引用

本文深入探讨了Java8中引入的Stream API,解释了Stream的基本概念、如何从集合或数组中获取Stream,以及Stream操作的特性。文章详细介绍了Stream的延迟方法和终结方法,包括过滤、映射、限制、跳过元素等操作,以及如何使用collect方法进行收集和转换。此外,还讲解了方法引用的使用,包括成员方法引用、静态方法引用、构造器引用等。

本人小白一枚,欢迎大家一起讨论学习,如有错误,还望大家指教。

简述:

在Java 8中,得益于Lambda所带来的函数式编程,由此引入了一个全新的Stream概念。Stream被定义为支持聚合操作源的一系列元素序列,这里的源指的是向Stream提供数据的CollectionsArrays,Stream保持数据在源中的顺序,这种聚合操作使我们能对源的操作更加简单和便利,注意这里的流和IO中流的概念完全不同,不要混淆。
Stream(流)是一个数据源的元素队列。这里要强调一下,Stream不能被重用,一但它被使用一次,流将会被关闭。

  • 元素是特定类型的对象,形成一个队列。Java中的Stream并不会存储元素,而是按需计算。
  • 数据源的来源可以是集合或是数组等。

和Collection操作不用,Stream操作还有两个基础的特征:

  • Pipelining:中间操作都会返回流对象本身,这样多个操作可以串联成一个管道,如同流式风格。这样做可以对操作进行优化,比如延迟执行和短路。
  • 内部迭代:以前对集合遍历都是通过Iterator(迭代器)或者是增强for的方式,显示的在集合外部进行迭代,这叫做外部迭代。Stream提供了内部迭代的方式,流可以直接调用遍历方法。

获取流:

获取流有以下常用的方式:

  • 所有的Collection集合都可以通过stream默认方法获得例:Stream<String> stream = new ArrayList<String>().stream();
  • Stream接口的静态方法of可以获取数组对应的流例:Stream<String> stream = Stream.of(new String[]{"AAA", "BBB"});

这里注意一下Map集合,java.util.Map接口不是Collection的子接口,且其K-V数据结构不符合流元素的单一特征,所以获取对应的流需要分key、value或entry等情况。

public static void main(String[] args) {
    Map<String, String> map = new HashMap<>();
    
    // 获取key的Stream
    Stream<String> keyStream = map.keySet().stream();
    // 获取value的Stream
    Stream<String> valueStream = map.values().stream();
    // 获取entry的Stream
    Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
}

常用方法:

操作流的方法有很多,这些方法可以被分为两种:

  • 延迟方法: 返回值是stream接口自身类型的方法,因此支持链式调用。
  • 终结方法: 返回值类型不再是stream接口自身类型的方法,因此不再支持链式调用。

延迟方法:

  • void forEach(Consumer<? super T> action):对流中每个元素逐一处理,该Consumer接口是个消费型接口,该接口中包含抽象方法void accept(T t),意为消费一个指定泛型的数据。
  • Stream<T> filter(Predicate<? super T> predicate):可以通过该方法将一个流转换成另一个子集流,该Predicate接口是个断言型接口,该接口中包含抽象方法boolean test(T t)
  • <R> Stream<R> map(Function<? super T,? extends R> mapper):可以将流中的元素映射到另一个流中,该Function接口是个函数型接口,该接口中包含抽象方法R apply(T t),可以将T类型转换成R类型,这种转换操作称为映射。
  • Stream<T> limit(long maxSize):可以对流进行截取,只取用前n个。如果当前集合长度大于参数则进行截取,否则不进行操作。
  • Stream<T> skip(long n):可以指定跳过前几个元素,截取之后的流。如果流当前的长度大于n,则跳过前n个,否则得到一个长度为0的空流。
  • static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b):可以将两个流合并成一个流。

终结方法:

  • <R,A> R collect(Collector<? super T,A,R> collector):该方法可以将流转换成其他形式,接收一个Collector接口的实现,用于给Stream中的元素汇总的方法。用collect方法进行收集。方法参数为Collector。Collector可以由Collectors中的toList(),toSet(),toMap(Function(T,R) key,Function(T,R) value)等静态方法实现。
   public static void main(String[] args) {
         List<User> users = Arrays.asList(
                new User("张三", 19, 1000),
                new User("张三", 58, 2000),
                new User("李四", 38, 3000),
                new User("赵五", 48, 4000)
        );
        List<String> collect = users.stream().map(x -> x.getName()).collect(Collectors.toList());
        Set<String> collect1 = users.stream().map(x -> x.getName()).collect(Collectors.toSet());
        Map<Integer, String> collect2 = users.stream().collect(Collectors.toMap(x -> x.getAge(), x -> x.getName()));
        System.out.println(collect);
        System.out.println(collect1);
        System.out.println(collect2);
    }

[张三, 张三, 李四, 赵五]
[李四, 张三, 赵五]
{48=赵五, 19=张三, 38=李四, 58=张三}

  • long count():用来统计流中元素的个数。

例子:将如下的两个集合按照以下要求进行操作。

public static void main(String[] args) {
        // 第一支队伍
        ArrayList<String> one = new ArrayList<>();
        one.add( "迪丽热巴");
        one.add( "宋远桥");
        one.add("苏星河");
        one.add( "石破天");
        one.add( "石中玉");
        one.add("老子");
        one.add("庄子");
        one.add( "洪七公");

        // 第二支队伍
        ArrayList<String> two = new ArrayList<>();
        two.add("古力娜扎");
        two.add( "张无忌" );
        two.add("赵丽颖");
        two.add("张E丰");
        two.add("尼古拉斯赵四");
        two.add("张天爱");
        two.add("张二狗");
        two.add("张二狗");

        // 只要名字长度为3并且只要前三个人。
        List<String> list = one.stream().filter(s -> s.length() == 3).limit(3).collect(Collectors.toList());
        list.stream().forEach(s -> System.out.println(s));
        System.out.println("----------------------");
        // 只要姓张并且不要前2个,这里注意一下set集合存储不了相同元素。
        Set<String> set = two.stream().filter(s -> s.startsWith("张")).skip(2).collect(Collectors.toSet());
        set.stream().forEach(s -> System.out.println(s));
        System.out.println("----------------------");
        // 将两个集合合并,并根据姓名创建Person对象,并将其打印
        Stream.concat(list.stream(), set.stream()).map(s -> new Person(s)).forEach(s -> System.out.println(s));
    }

在这里插入图片描述

方法引用简述:

双冒号::为引用运算符,而它所在的表达式被称为方法引用。如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者。如果使用Lambda,那么根据“可推导就是可省略”的原则,无需指定参数类型,也无需指定的重载形式,它们将会被自动推导,而如果使用方法引用,也可以根据上下文推导。注意:函数式接口是Lambda的基础,而方法引用是Lambda的孪生兄弟。

一、通过方法名引用成员方法

class MethodRefobject {
    public void printUpperCase(String str) {
        System.out.println(str.toUpperCase());
    }
}

@FunctionalInterface
interface Printable {
    void print(String str);
}

public class Demo5 {
    public static void main(String[] args) {
        // 通过对象名引用方法
        printString("aaa", new MethodRefobject()::printUpperCase);
    }

    public static void printString(String str, Printable printable) {
        printable.print(str);
    }
}

AAAAA

二、通过类名称引用静态方法

@FunctionalInterface
interface Calcable {
    int calc(int num);
}


public class Demo6 {
    public static void main(String[] args) {
        // 类名称引用静态方法
        System.out.println(method(-100, Math::abs));
        // Lambda表达式
        System.out.println(method(100, num -> Math.abs(num)));
    }

    public static int method(int num, Calcable calcable) {
        return calcable.calc(num);
    }
}

100
100

三、通过super引用成员方法
如果存在继承关系,当Lambda中需要出现super调用时,可以使用方法引用进行替代。

@FunctionalInterface
interface Greetable {
    void greet();
}

class Fu {
    public void method() {
        System.out.println("父类中的方法!");
    }
}

class Zi extends Fu {
    @Override
    public void method() {
        System.out.println("子类中的方法!");
    }

    public void ziMethod(Greetable greetable) {
        greetable.greet();
    }

    public void show() {
        // 使用super关键字引用父类方法
        ziMethod(() -> super.method());
        // 创建父类对象调用父类方法
        ziMethod(() -> new Fu().method());
        // 使用方法引用
        ziMethod(super::method);
    }
}

四、通过this引用成员方法
this代表当前对象,如果需要引用的方法就是当前类中的成员方法,那么可以使用“this::成员方法”的格式来使用方法引用。
五、通过类的构造器引用
由于构造器的名称与类名完全一样,并不固定。所以构造器引用使用“类名称::new”的格式来表示。

public class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
}
interface PersonBuilder {
    Person BuildPerson(String name);
}

public class Demo8 {
    public static void main(String[] args) {
        // 使用Lambda表达式
        System.out.println(printPerson("刘灰灰", name -> new Person(name)));
        // 使用构造器引用
        System.out.println(printPerson("刘灰灰", Person::new));
    }

    public static String printPerson(String name, PersonBuilder personBuilder) {
        return personBuilder.BuildPerson(name).toString();
    }
}

六、数组的构造器引用
数组也是Object的子类对象,所以同样具有构造器,知识语法稍有不同。如果对应到Lambda的使用场景中时,需要一个函数式接口。

@FunctionalInterface
interface ArrayBuilder {
    int[] buildArray(int length);
}

public class Demo9 {
    public static void main(String[] args) {
        initArray(10, length -> new int[length]);
        initArray(10, int[]::new);
    }
    
    public static int[] initArray(int length, ArrayBuilder builder) {
        return builder.buildArray(length);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值