Java编码技巧系列之「Stream流为什么这么受宠?」

151 阅读4分钟

前言

Stream流自从随着JDK1.8出来后一直褒贬不一,有的人觉得提高了Java编程效率,消除很多冗余的代码。但是也有一部分人觉得,流式编程虽然爽,但是代码可读性和维护性大大降低了很多。

先说下,我个人的看法是支持前者的。后者说到的影响可读性和维护性确实是存在的,但是这其实是因为个人编码不够规范,没有遵循相关的约定去编写流式代码,造成又臭又长难以维护的代码。

image.png

探究下Stream源码

public interface Stream<T> extends BaseStream<T, Stream<T>> {

    //过滤筛选出匹配的元素,`Predicate`是`断言`返回一个布尔值
    //`Predicate`后续会专门对这个写篇文章介绍
    Stream<T> filter(Predicate<? super T> predicate);

    //入参是一个函数,出参是一个新的流对象
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);

    //下面这几个应该一看就懂了,不多做解释
    IntStream mapToInt(ToIntFunction<? super T> mapper);
    LongStream mapToLong(ToLongFunction<? super T> mapper);
    DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);

    //入参是一个流,出参是一个新的流对象 (和`map`区别是`flatMap`是将两个流合并成一个新的流)
    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

    //下面几个也是一看就懂不多做解释
    IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper);
    LongStream flatMapToLong(Function<? super T, ? extends LongStream> mapper);
    DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper);

    //排序
    Stream<T> sorted();
    //排序 入参是 `Comparator`
    Stream<T> sorted(Comparator<? super T> comparator);

    Stream<T> peek(Consumer<? super T> action);

    //限制返回的元素数量 maxSize
    Stream<T> limit(long maxSize);
    //跳过前面 n 个元素,常常配合 `limit` 使用实现分页
    Stream<T> skip(long n);

    //循环遍历
    void forEach(Consumer<? super T> action);
    void forEachOrdered(Consumer<? super T> action);

    //转数组
    Object[] toArray();
    <A> A[] toArray(IntFunction<A[]> generator);

   //可以做一些累加连续的操作 (具体可看后续的示例)
    T reduce(T identity, BinaryOperator<T> accumulator);
    Optional<T> reduce(BinaryOperator<T> accumulator);

    //元素收集
    <R> R collect(Supplier<R> supplier,
                  BiConsumer<R, ? super T> accumulator,
                  BiConsumer<R, R> combiner);

    <R, A> R collect(Collector<? super T, A, R> collector);

    //最小值
    Optional<T> min(Comparator<? super T> comparator);
    //最大值
    Optional<T> max(Comparator<? super T> comparator);
    //统计数量
    long count();

    //下面几个也不多解释了,见名知意
    boolean anyMatch(Predicate<? super T> predicate);
    boolean allMatch(Predicate<? super T> predicate);
    boolean noneMatch(Predicate<? super T> predicate);
    Optional<T> findFirst();
    Optional<T> findAny();
}

介绍下常用的几个函数

先定义一个Demo类

@Data
@Accessors(chain = true)
public class DemoEntity implements Serializable {

    private static final long serialVersionUID = 4614169948081019080L;

    private String userName;

    private Integer age;

    private Double money;

    private List<Integer> number;

    private int[] ints;

    private double[] doubles;
}

map

map是常用的一个方法,将数组按规则转化成一个新的元素流。


//入参是一个函数( 即`DemoEntity.getAge()` ),获取对象中的`age`属性,生成一个age对象流
List<Integer> newList = list.stream().map(DemoEntity::getAge).collect(Collectors.toList());

flatMap

flatMap也是常用的方法,用法和map类似,区别是它是将两个流合并成一个新的流.

List<Integer> newList2 = list.stream().flatMap(po -> po.getNumber().stream()).collect(Collectors.toList());

distinct

distinct 基于对象equal方法进行元素去重,如果特殊需求,需要重写equal

//distinct 通过equals()去重复
List<DemoEntity> disList = list.stream().distinct().collect(Collectors.toList());

sorted

sorted用于排序,可通过Comparator设置排序规则


//自然排序
list.stream().sorted();
//指定排序 .reversed() 可实现倒序
list.stream().sorted(Comparator.comparing(DemoEntity::getAge).reversed()).collect(Collectors.toList());

toArray

toArray用于将流转为数组

list.stream().toArray();

reduce

reduce可以做一些累加连续操作,第一个参数是累加初始值,第二个是Accumulator函数(第一个参数是上个归并函数的返回值,第二个是Stream中下一个元素)

//累加操作基本类型,下面是简单写法
List<Integer> numbers = Arrays.asList(1, 2, 3);
int total1 = numbers.stream().reduce(0, (total, number) -> total + number);
int total2 = numbers.stream().reduce(0, Integer::sum);

//累加操作实体对象,需要后面声明下
list.stream().reduce(0, (total, entity) -> total + entity.getAge(), Integer::sum);
list.stream().reduce("", (total, entity) -> total + entity.getUserName(), String::concat);

limit&skip

limit用于限制返回条数,skip用于跳过元素

//跳过元素10,限制元素10
list.stream().skip(10).limit(10);

小牛试刀一下

假设有个场景: 需要统计学生考试的 最高分,最低分, 大于60分人数

定义一个学生对象类

/**
 * 学生对象
 *
 * @author: Mock
 * @date: 2023-05-28 20:21:23
 */
@Data
@AllArgsConstructor
public class Student {

    private String name;

    private Integer score;
    
}

测试类

/**
 * 测试类
 *
 * @author: Mock
 * @date: 2023-05-28 20:22:24
 */
@Data
public class StreamTest {

    private final static List<Student> students =
            Lists.newArrayList(
                    new Student("张三", 67),
                    new Student("李四", 77),
                    new Student("王五", 83),
                    new Student("赵六", 91)
            );

    public static void main(String[] args) {
        System.out.println(
                "最高分: " +
                        students.stream().max(Comparator.comparing(Student::getScore))
                                .get().getScore()
        );

        System.out.println(
                "最低分: " +
                        students.stream().min(Comparator.comparing(Student::getScore))
                                .get().getScore()
        );

        System.out.println(
                "大于60分人数: " +
                        students.stream().filter(student -> student.getScore() >= 60)
                                .count()
        );
    }
}

  

运行结果

image.png

image.png

image.png

博客求赞.png