Skip to content

Java 8关键特性回顾

参考资料:

Java 8是Oracle 公司于 2014 年 3 月 18 日发布的,距离今天已经过了近十年的时间了,Java并没有就此止步,而是继续不断发展壮大,几乎每隔6个月,就会冒出一个新版本,最新的版本已经快要迭代到Java 20了,与Java 8相差了足足十来个版本,但是由于Java 8的稳定和生态完善(目前仍是LTS长期维护版本),依然有很多公司在坚持使用Java 8,不过随着SpringBoot 3.0的到来,现在强制要求使用Java 17版本(同样也是LTS长期维护版本),下一个Java版本的时代,或许已经临近了。

Spring3.0

面向对象思想需要关注的是用什么对象完成什么事情,而「函数式编程」思想之关注对数据进行了什么操作,优点是:

  • 代码简洁,开发快速
  • 接近自然语言,易于理解
  • 易于“并发编程”

Lambda表达式

「Lambda」表达式,是 JDK8 中的一个语法糖,可以对某些匿名内部类的写法进行简化,让我们只用关注对数据进行了什么操作。

基本语法为:

java
(参数列表) -> {代码}

例子:

java
public static void main(String[] args) {
    // 匿名内部类
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("Hello World");
        }
    }).start();
    
    // lambda
    new Thread(() -> System.out.println("Hello World")).start();
}

在最开始不会写 lambda 表达式的时候,均可以采用匿名内部类的方式写,写完后,idea 有快捷键:

快捷键

lambda 表达式的省略规则:

  • 参数类型可以省略
  • 方法体只有一句代码时,大括号、return、分号 都可以省略
  • 方法只有一个参数时,小括号可以省略
  • 这些规则记不住,也可以不记

Stream流

Java 8 中的 Stream 流用于「对集合和数组进行链状流式」的操作,使用函数式编程的模式:

1、数据准备

java
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode//用于后期的去重使用
public class Author {
    //id
    private Long id;
    //姓名
    private String name;
    //年龄
    private Integer age;
    //简介
    private String intro;
    //作品
    private List<Book> books;

}

@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode//用于后期的去重使用
public class Book {
    //id
    private Long id;
    //书名
    private String name;
    //分类
    private String category;//"哲学,小说"
    //评分
    private Integer score;
    //简介
    private String intro;

}

private static List<Author> getAuthors() {
    //数据初始化
    Author author = new Author(1L, "蒙多", 33, "一个从菜刀中明悟哲理的祖安人", null);
    Author author2 = new Author(2L, "亚拉索", 15, "狂风也追逐不上他的思考速度", null);
    Author author3 = new Author(3L, "易", 14, "是这个世界在限制他的思维", null);
    Author author4 = new Author(3L, "易", 14, "是这个世界在限制他的思维", null);

    //书籍列表
    List<Book> books1 = new ArrayList<>();
    List<Book> books2 = new ArrayList<>();
    List<Book> books3 = new ArrayList<>();

    books1.add(new Book(1L, "刀的两侧是光明与黑暗", "哲学,爱情", 88, "用一把刀划分了爱恨"));
    books1.add(new Book(2L, "一个人不能死在同一把刀下", "个人成长,爱情", 99, "讲述如何从失败中明悟真理"));

    books2.add(new Book(3L, "那风吹不到的地方", "哲学", 85, "带你用思维去领略世界的尽头"));
    books2.add(new Book(3L, "那风吹不到的地方", "哲学", 85, "带你用思维去领略世界的尽头"));
    books2.add(new Book(4L, "吹或不吹", "爱情,个人传记", 56, "一个哲学家的恋爱观注定很难把他所在的时代理解"));

    books3.add(new Book(5L, "你的剑就是我的剑", "爱情", 56, "无法想象一个武者能对他的伴侣这么的宽容"));
    books3.add(new Book(6L, "风与剑", "个人传记", 100, "两个哲学家灵魂和肉体的碰撞会激起怎么样的火花呢?"));
    books3.add(new Book(6L, "风与剑", "个人传记", 100, "两个哲学家灵魂和肉体的碰撞会激起怎么样的火花呢?"));

    author.setBooks(books1);
    author2.setBooks(books2);
    author3.setBooks(books3);
    author4.setBooks(books3);

    List<Author> authorList = new ArrayList<>(Arrays.asList(author, author2, author3, author4));
    return authorList;
}

2、快速入门

需求: 打印所有年龄小于18的作家的名字,注意去重:

java
public static void main(String[] args) {
    List<Author> authorList = getAuthors();

    authorList.stream() //先把集合转成流
            .filter(author -> author.getAge() > 18) // 过滤年龄大于18的作者
            .distinct() // 去重
            .forEach(author -> System.out.println(author.getName()));// 输出作者名称
}

3、创建流

  • 单列集合,集合对象.stream()
java
private static void test01() {
    List<Author> authors = getAuthors();
    Stream<Author> stream = authors.stream();
    //把集合转换成流
    stream
            //去重
            .distinct()
            //过滤条件
            .filter(author -> author.getAge() < 18)
            //打印
            .forEach(author -> System.out.println(author.getName() + "今年" + author.getAge() + "岁"));
}
  • 数组,Arrays.stream(数组) 或者使用 Stream.of 来创建:
java
private static void test02() {
    Integer[] arr = {1, 2, 3, 4, 5};
    Stream<Integer> stream1 = Arrays.stream(arr);
    stream1.distinct()
            .filter(integer -> integer > 2)
            .forEach(integer -> System.out.println(integer));

    Stream<Integer> stream2 = Stream.of(arr);
    stream2.distinct()
            .filter(integer -> integer < 2)
            .forEach(integer -> System.out.println(integer));
}
  • 双列集合,转换成单列集合后再创建:
java
private static void test03() {
    Map<String, Integer> map = new HashMap<>();
    map.put("蜡笔小新", 5);
    map.put("zyy", 22);
    map.put("zqh", 23);
    
    Stream<Map.Entry<String, Integer>> stream = map.entrySet().stream();
    stream.distinct()
            .filter(entry -> entry.getValue() > 5)
            .forEach(entry -> System.out.println(entry.getKey() + "==" + entry.getValue()));
}

4、中间操作

  • filter

filter 可以对流中的元素进行条件过滤,符合过滤条件的才能继续留在流中:

java
// 打印所有姓名长度大于1的作家的姓名
private static void test04() {
    getAuthors().stream()
            .distinct()
            .filter(author -> author.getName().length() > 1)
            .forEach(author -> System.out.println(author.getName()));
}
  • map

map 可以把对流中的元素进行计算或转换:

java
// 打印所有作家的姓名
private static void test05() {
    getAuthors().stream()
            .distinct()
            .map(Author::getName)
            .forEach(System.out::println);
}
  • distinct

distinct 可以去除流中的重复元素:

java
//打印所有作家的姓名,并且要求其中不能有重复元素
private static void test06() {
    List<Author> authors = getAuthors();
    authors.stream()
            .distinct()
            .forEach(author -> System.out.println(author.getName()));
}

distinct() 方法是依赖 Object 的 equals() 方法来判断是否是相同对象的,所以需要注意重写 equals() 方法。

  • sorted

sorted 可以对流中的元素进行排序:

java
//对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素
private static void test07() {
    getAuthors().stream()
            .distinct()
            .sorted((o1, o2) -> o2.getAge() - o1.getAge())
            .forEach(author -> System.out.println(author.getName() + "==" + author.getAge()));
}

如果调用空参sorted() 方法,需要流中的元素是实现了 Comparable :

JAVA
private static void test07_1() {
    List<Author> authors = getAuthors();
    //对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素
    authors.stream()
            .distinct()
            //降序
            .sorted()
            .forEach(author -> System.out.println(author.getAge()));
}

@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode//用于后期的去重使用
public class Author implements Comparable<Author> {
    //id
    private Long id;
    //姓名
    private String name;
    //年龄
    private Integer age;
    //简介
    private String intro;
    //作品
    private List<Book> books;

    @Override
    public int compareTo(Author o) {
        //比较-本体的是降序
        return o.getAge() - this.getAge();
    }
}
  • limit

limit 可以设置流的最大长度,超出的部分将被抛弃:

java
// 对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素,然后打印其中年龄最大的两个作家的姓名
private static void test08() {
    List<Author> authors = getAuthors();
    authors.stream()
            .distinct()
            .sorted((o1, o2) -> o2.getAge() - o1.getAge())
            .limit(2)
            .forEach(author -> System.out.println(author.getName()));
}
  • skip

skip 跳过流中的前n个元素,返回剩下的元素:

JAVA
// 打印除了年龄最大的作家外的其他作家,要求不能有重复元素,并且按照年龄降序排序
private static void test09() {
    List<Author> authors = getAuthors();
    authors.stream()
            .distinct()
            .sorted((o1, o2) -> o2.getAge() - o1.getAge())
            .skip(1)
            .forEach(author -> System.out.println(author.getName()));
}
  • flatMap

map 只能把一个对象转换成另一个对象来作为流中的元素,而 flatMap 可以把一个对象转换成多个对象作为流中的元素:

JAVA
// 打印所有书籍的名字。要求对重复的元素进行去重
private static void test10() {
    getAuthors().stream()
            .distinct()
            .flatMap(author -> author.getBooks().stream())
            .distinct()
            .forEach(book -> System.out.println(book.getName()));
}

// 打印现有数据的所有分类。要求对分类进行去重。不能出现这样格式:哲学,爱情
private static void test11() {
    getAuthors().stream()
            .flatMap(author -> author.getBooks().stream())
            .distinct()
            .flatMap(book -> Arrays.stream(book.getCategory().split(",")))
            .distinct()
            .forEach(name -> System.out.println(name));
}

5、终结操作

  • forEach

forEach 对流中的元素进行遍历操作,通过传入的参数去指定对遍历到的元素进行什么具体操作:

JAVA
// 输出所有作家的名字
private static void test12() {
    List<Author> authors = getAuthors();
    authors.stream()
            .distinct()
            .map(author -> author.getName())
            .forEach(name -> System.out.println(name));
}
  • count

count 可以用来获取当前流中元素的个数:

JAVA
// 打印这些作家所出书籍的数目,注意删除重复元素
private static void test13() {
    System.out.println(getAuthors().stream()
            .distinct()
            .flatMap(author -> author.getBooks().stream())
            .distinct()
            .count());
}
  • min&max

可以用来获取流中的最值:

JAVA
// 分别获取这些作家的所出书籍的最高分和最低分并打印
private static void test14() {
    List<Author> authors = getAuthors();
    Optional<Integer> max = authors.stream()
            .flatMap(author -> author.getBooks().stream())
            .map(book -> book.getScore())
            .distinct()
            .max((score1, score2) -> score1 - score2);

    Optional<Integer> min = authors.stream()
            .flatMap(author -> author.getBooks().stream())
            .map(book -> book.getScore())
            .distinct()
            .min((score1, score2) -> score1 - score2);
    System.out.println("最高分:" + max.get());
    System.out.println("最低分:" + min.get());
}
  • collect

把当前流转换成一个集合:

JAVA
//获取一个存放所有作者名字的List集合
private static void test15() {
    List<Author> authors = getAuthors();
    List<String> nameList = authors.stream()
            .map(author -> author.getName())
            .collect(Collectors.toList());
    System.out.println(nameList);
}

//获取一个map集合,map的key为作者名,value为List<Book>
private static void test17() {
    List<Author> authors = getAuthors();
    Map<String, List<Book>> collect = authors.stream()
            .distinct()
            .collect(Collectors.toMap(author -> author.getName(), author -> author.getBooks()));
    System.out.println(collect);
}
  • anyMatch

可以用来判断是否有任意符合匹配条件的元素,结果为boolean类型:

JAVA
//判断是否有年龄在29以上的作家
private static void test19() {
    List<Author> authors = getAuthors();
    boolean flag = authors.stream()
            .map(author -> author.getAge())
            .anyMatch(age -> age > 29);
    System.out.println(flag);
}
  • allMatch

可以用来判断是否都符合匹配条件,结果为boolean类型。如果都符合结果为true,否者结果为false:

JAVA
//判断是否所有的作家都是成年人
private static void test20() {
    List<Author> authors = getAuthors();
    boolean flag = authors.stream()
            .map(author -> author.getAge())
            .allMatch(age -> age >= 18);
    System.out.println(flag);
}
  • noneMatch

可以判断流中的元素是否都不符合匹配条件。如果都不符合结果为true,否则结果为false:

JAVA
//判断作家是否都没有超过100岁的
private static void test21() {
    List<Author> authors = getAuthors();
    boolean flag = authors.stream()
            .map(author -> author.getAge())
            .noneMatch(age -> age > 100);
    System.out.println(flag);
}
  • finaAny

获取流中的任意一个元素。该方法没有办法保证获取的一定是流中的第一个元素:

JAVA
//获取任意一个大于18岁的作家,如果存在输出他的名字
private static void test22() {
    List<Author> authors = getAuthors();
    Optional<Author> optional = authors.stream()
            .filter(author -> author.getAge() > 18)
            .findAny();
    optional.ifPresent(author -> System.out.println(author.getName()));
}
  • findFirst

获取流中的第一个元素:

JAVA
//获取一个年龄最小的作家,并输出他的姓名
private static void test23() {
    List<Author> authors = getAuthors();
    Optional<Author> optional = authors.stream()
            .sorted((o1, o2) -> o1.getAge() - o2.getAge())
            .findFirst();
    optional.ifPresent(author -> System.out.println(author.getName()));
}
  • reduce

对流中的数据按照你指定的计算方式计算出一个结果(缩减操作),传入一个初始值,它会按照我们的计算方式依次拿流中的元素和初始化值进行计算,计算结果再和后面的元素计算:

JAVA
//使用reduce求所有作者年龄的和
private static void test24() {
    Integer reduce = getAuthors().stream()
            .distinct()
            .map(author -> author.getAge())
            .reduce(0, (result, element) -> result + element);
    System.out.println(reduce);
}

6、注意事项

  • 惰性求值(如果没有终结操作,没有中间操作是不会得到执行的)
  • 流是一次性的(一旦一个流对象经过一个终结操作后。这个流就不能再被使用)
  • 不会影响原数据(我们在流中可以多数据做很多处理。但是正常情况下是不会影响原来集合中的元素的。这往往也是我们期望的)

Optional

在编写代码的时候出现最多的就是空指针异常,在很多情况下我们需要做各种非空的判断:

java
public static void main(String[] args) {
    Author author = getAuthor();
    if (author != null) {
        System.out.println(author.getName());
    }
}

public static Author getAuthor() {
    Author author = new Author(1L, "蒙多", 33, "一个从菜刀中明悟真理的祖安人", null);
    return null;
}

尤其是对象中的属性还是一个对象的情况下,这种判断会更多,而过多的判断语句会让我们的代码显得臃肿不堪。

所以在 JDK 8 中引入了 Optional,养成使用 Optional 的习惯后可以写出更优雅的代码来避免空指针异常。并且,在很多函数式编程相关的API中也都用到了 Optional,如果不会使用 Optional 也会对函数式编程的学习造成影响。

1、使用

Optional 就好像是包装类,可以把具体数据封装 Optional 对象内部。然后使用 Optional 中封装好的方法操作封装进去的数据就可以非常优雅的避免空指针异常。

一般使用 Optional.ofNullable() ,无论传入的参数是否为null都不会出现问题:

JAVA
private static void test2() {
    Optional<Author> optional = getAuthorOptional();
    //非空打印
    optional.ifPresent(author1 -> System.out.println(author1.getName()));
}

public static Optional<Author> getAuthorOptional() {
    Author author = new Author(1L, "蒙多", 33, "一个从菜刀中明悟真理的祖安人", null);
    return Optional.ofNullable(author);
}

如果确定一个对象不是空的则可以使用 Optional.of() 来把数据封装成 Optional 对象:

java
Author author = new Author();
Optional<Author> authorOptional = Optional.of(author);

一定要注意,使用 Optional.of() 时传入的参数必须不为null:

optional.of

2、安全消费

可以使用 ifPresent() 方法对来消费,这个方法会判断其内封装的数据是否为空,不为空时才会执行具体的消费代码:

java
private static void test1() {
    Author author = getAuthor();
    Optional<Author> optional = Optional.ofNullable(author);
    optional.ifPresent(author1 -> System.out.println(author1.getName()));
}

3、获取值

可以使用 get() 方法获取,但是不推荐。因为当Optional内部的数据为空的时候会出现异常:

optional.get

安全的获取值,使用 Optional.orElseGet() 获取数据并且设置数据为空时的默认值。如果数据不为空就能获取到该数据,如果为空则根据你传入的参数来创建对象作为默认值返回:

JAVA
private static void test5() {
    Optional<Author> authorOptional = getAuthorOptional();
    Author author = authorOptional.orElseGet(() -> new Author(5L, "zyy", 22, "纯纯的码农", null));
    System.out.println(author.getName());
}

Optional.orElseThrow() ,如果数据不为空就能获取到该数据,如果为空则根据传入的参数来创建异常抛出:

java
private static void test6() {
    Optional<Author> authorOptional = getAuthorOptional();
    try {
        Author author = authorOptional.orElseThrow((Supplier<Throwable>) () -> new RuntimeException("author为空"));
        System.out.println(author.getName());
    } catch (Throwable e) {
        e.printStackTrace();
    }
}

4、过滤

可以使用 filter() 方法对数据进行过滤。如果原本是有数据的,但是不符合判断,也会变成一个无数据的 Optional 对象:

java
private static void test7() {
    Optional<Author> authorOptional = getAuthorOptional();
    authorOptional
            .filter(author -> author.getAge() > 20)
            .ifPresent(author -> System.out.println(author.getName()));
}

5、判断

可以使用 isPresent() 方法进行是否存在数据的判断。如果为空返回值为false,如果不为空,返回值为 true。但是这种方式并不能体现 Optional 的好处,更推荐使用isPresent方法

java
//判断
private static void test8() {
    Optional<Author> authorOptional = getAuthorOptional();
    if (authorOptional.isPresent()) {
        System.out.println(authorOptional.get().getName());
    }
}

6、数据转换

Optional.map() 可以对数据进行转换,并且转换得到的数据也是被 Optional 包装好的,保证了使用安全:

java
// 数据转换
private static void test9() {
    Optional<Author> authorOptional = getAuthorOptional();
    Optional<List<Book>> books = authorOptional.map(author -> author.getBooks());
    books.ifPresent(books1 -> books1.forEach(book -> System.out.println(book.getName())));
}

函数式接口

「函数式接口」是指,只有一个抽象方法的接口。

JDK 的函数式接口都加上 @FunctionalInterface 注解进行标识,但是无论是否加上该注解只要接口中只有一个抽象方法,都是函数式接口。

1、常见的函数式接口

在 jdk 中,函数式接口基本上都在 java.util.function 包中:

函数式接口
  • consumer
java
@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}
  • function
java
@FunctionalInterface
public interface Function<T, R> {

    /**
     * 计算转换的接口,传入参数计算,返回结果
     */
    R apply(T t);
}
  • Predicate
java
@FunctionalInterface
public interface Predicate<T> {

    /**
     * 判断接口,对传入的参数条件判断,返回判断结果
     */
    boolean test(T t);
}
  • Predicate
java
@FunctionalInterface
public interface Supplier<T> {

    /**
     * 生产型接口,在方法中创建对象,把创建好的对象返回
     */
    T get();
}

2、默认方法

  • and

在使用 Predicate 接口的时候可能需要进行判断条件的拼接,and 方法相当于是使用 && 来拼接两个判断条件:

java
//打印作家中年龄大于17并且姓名长度大于1的作家
private static void test28() {
    List<Author> authors = getAuthors();
    authors.stream()
            .filter((
                    (Predicate<Author>) author -> author.getAge() > 17)
                    .and(author -> author.getName().length() > 1))
            .forEach(author -> System.out.println(author.getName() + " : " + author.getAge()));
}
  • or

在使用 Predicate 接口的时候可能需要进行判断条件的拼接,or 方法相当于是使用 || 来拼接两个判断条件:

java
//打印作家中年龄大于17或者姓名的长度小于2的作家。
private static void test29() {
    List<Author> authors = getAuthors();
    authors.stream()
            .filter((
                    (Predicate<Author>) author -> author.getAge() > 17)
                    .or(author -> author.getName().length() < 2))
            .forEach(author -> System.out.println(author.getAge() + " : " + author.getName()));
}
  • negate

在使用 Predicate 接口的时候可能需要进行判断条件的拼接,negate 方法相当于是在判断添加前面加了个 ! 表示取反:

java
//打印作家中年龄不大于17的作家
private static void test30() {
    List<Author> authors = getAuthors();
    authors.stream()
            .filter(((Predicate<Author>) author -> author.getAge() > 17).negate())
            .forEach(author -> System.out.println(author.getName() + " : " + author.getAge()));
}

方法引用

在使用 Lambda 时,如果方法体只有一个方法调用的话(包括构造方法),可以用方法引用进一步简化代码。

1、语法

在使用 lambda 时不需要考虑什么时候用方法引用,用哪种方法引用,方法引用的格式是什么。

只需要在写完 lambda 方法发现方法体只有一行代码,并且是方法的调用时使用快捷键尝试是否能够转换成方法引用即可。格式为:

java
类名或者对象名::方法名

2、示例

  • 引用类的静态方法
java
/**
 * 类名::方法名
 *
 * 在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的静态方法,
 * 并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个静态方法中,这个时候我们就可以引用类的静态方法
 */
private static void test31() {
    List<Author> authors = getAuthors();
    //优化前
    authors.stream()
            .map(author -> author.getAge())
            .filter(age -> age > 20)
            .map(age -> String.valueOf(age))
            .forEach(s -> System.out.println(s));

    //优化后
    authors.stream()
            .map(Author::getAge)
            .filter(age -> age > 20)
            .map(String::valueOf)
            .forEach(System.out::println);
}
  • 引用对象的实例方法
java
/**
 * 对象名::方法名
 *
 * 在重写方法的时候,方法体中只有一行代码,并且这话代码是调用了某个对象的成员方法,
 * 并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用对象的实例方法
 */
private static void test32() {
    List<Author> authors = getAuthors();
    StringBuilder sb = new StringBuilder();
    //优化前
    authors.stream()
            .map(author -> author.getName())
            .forEach(name -> sb.append(name));

    //优化后
    authors.stream()
            .map(Author::getName)
            .forEach(sb::append);

    System.out.println(sb.toString());
}
  • 引用类的实例方法
java
public class MethodDemo {

    interface UseString {
        String use(String str, int start, int length);
    }

    public static String subAuthorName(String str, UseString useString) {
        int start = 0;
        int length = 1;
        return useString.use(str, start, length);
    }

    public static void main(String[] args) {
        //优化前
        subAuthorName("三更草堂", new UseString() {
            @Override
            public String use(String str, int start, int length) {
                return str.substring(start,length);
            }
        });
        //优化后
        subAuthorName("三更草堂", String::substring);
    }
}
  • 构造器引用
java
private static void test33() {
    List<Author> authors = getAuthors();
    //优化前
    authors.stream()
            .map(new Function<Author, String>() {
                @Override
                public String apply(Author author) {
                    return author.getName();
                }
            })
            .map(new Function<String, StringBuilder>() {
                @Override
                public StringBuilder apply(String name) {
                    return new StringBuilder(name);
                }
            })
            .map(new Function<StringBuilder, String>() {
                @Override
                public String apply(StringBuilder sb) {
                    return sb.append("三更").toString();
                }
            })
            .forEach(new Consumer<String>() {
                @Override
                public void accept(String name) {
                    System.out.println(name);
                }
            });

    //优化后
    authors.stream()
            .map(Author::getName)
            .map(StringBuilder::new)
            .map(sb -> sb.append("三更").toString())
            .forEach(System.out::println);
}

高级用法

1、基本数据类型优化

Stream 的方法由于都使用了泛型,所以涉及到的参数和返回值都是引用数据类型。

JDK5中引入的自动装箱和自动拆箱让我们在使用对应的包装类时就好像使用基本数据类型一样方便,但是装箱和拆箱肯定是要消耗时间的。

所以为了能够对这部分的时间消耗进行优化,Stream还提供了专门针对基本数据类型的方法:

  • mapToInt,mapToLong,mapToDouble,flatMapToInt,flatMapToDouble
java
private static void test34() {
    List<Author> authors = getAuthors();
    //map
    authors.stream()
            .map(Author::getAge)
            .forEach(new Consumer<Integer>() {
                @Override
                public void accept(Integer age) {
                    System.out.println(age);
                }
            });
    //mapToInt
    authors.stream()
            .mapToInt(author -> author.getAge())
            .forEach(new IntConsumer() {
                @Override
                public void accept(int age) {
                    System.out.println(age);
                }
            });
}

2、并行流

当流中有大量元素时,可以使用「并行流」去提高操作的效率。

其实并行流就是把任务分配给多个线程去完成,如果自己去用代码实现的话其实会非常的复杂,并且要求你对并发编程有足够的理解和认识。而如果使用 Stream 的话,只需要修改一个方法的调用就可以使用并行流来实现,从而提高效率。

  • parallel() 方法可以把串行流转换成并行流
java
private static void test35() {
    //串行流
    Integer[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    Stream<Integer> stream1 = Stream.of(arr);
    Integer sum1 = stream1
            .filter(num -> num > 5)
            .reduce((result, element) -> result + element)
            .get();
    System.out.println(sum1);

    //并行流
    Stream<Integer> stream2 = Stream.of(arr);
    Integer sum2 = stream2
            .parallel()
            .filter(num -> num > 5)
            .reduce((result, element) -> result + element)
            .get();
    System.out.println(sum2);   
}

也可以通过 parallelStream() 直接获取并行流对象:

java
private static void test36() {
    //串行流对象
    List<Author> authors1 = getAuthors();
    authors1.stream()
            .map(author -> author.getAge())
            .map(age -> age + 10)
            .filter(age -> age > 18)
            .map(age -> age + 2)
            .forEach(System.out::println);

    //并行流对象
    List<Author> authors2 = getAuthors();
    authors2.parallelStream()
            .map(author -> author.getAge())
            .map(age -> age + 10)
            .filter(age -> age > 18)
            .map(age -> age + 2)
            .forEach(System.out::println);
}