当前位置:   article > 正文

Java8函数式编程(Lambda表达式,Stream流,Optional)_java8 函数式编程

java8 函数式编程

目录

一.函数式编程思想

二.lambda表达式

1.概念

2.Lambda表达式对接口的要求

3.Lambda表达式的语法

4.函数引用

4.1引用一个静态方法

4.2引用一个非静态方法

4.3引用构造方法 

5.特殊的函数引用

6.闭包

三.Stream流

创建流

中间操作

filter

map

distinct

sorted

limit

skip

flatMap

终结操作

forEach

count

max&min

collect

查找与匹配

reduce归并

注意事项 

四.Optional

1.引入

2.使用

2.1创建对象

2.2安全消费值

2.3安全获取值

2.4过滤

2.5数据转换

四.Stream高级用法

1.基本数据类型优化

2.并行流


一.函数式编程思想

  • 面向对象思想主要是关注对象能完成什么事情,函数式编程思想就像函数式,主要是针对数据操作;
  • 代码简洁容易理解,方便于并发编程,不需要过分关注线程安全问题

二.lambda表达式

1.概念

lambda表达式是Java8的一个新特性,从本质上来讲是一个匿名函数可以使用这个匿名函数实现接口中的方法,并且非常简洁。

通常来讲,使用 lambda表达式 是为了简化接口实现的。关于接口实现,可以有很多种方式来实现。例如设计接口的实现类、使用匿名内部类。但是lambda表达式比这两种方式都简单。可以把lambda看成JDK8中的一个语法糖。

使用lambda表达式可以替换内部类。但是两者的原理和作用域范围是不同的

  • 内部类编译后会生成一个新的class文件,但Lambda表达式并不会
  • 内部类中,会创建一个新的作用域范围,在这个作用域范围之内,你可以定义新的变量,并且可以用this引用它。
  • 但是在Lambda表达式中,并没有定义新的作用域范围,如果在Lambda表达式中使用this,则指向的是外部类。

如下:

  1. private String value = "Outer scope value";
  2. public String scopeExperiment() {
  3. Usage usage = new Usage() {
  4. String value = "Inner class value";
  5. @Override
  6. public String method(String string) {
  7. return this.value;
  8. }
  9. };
  10. String result = usage.method("");
  11. Usage usageLambda = parameter -> {
  12. String value = "Lambda value";
  13. return this.value;
  14. };
  15. String resultLambda = usageLambda.method("");
  16. return "Results: result = " + result +
  17. ", resultLambda = " + resultLambda;
  18. }

上面的例子将会输出“Results: result = Inner class value, resultLambda = Outer scope value”

2.Lambda表达式对接口的要求

并不是所有的接口都可以使用lambda表达式来简洁实现的。lambda表达式毕竟只是一个匿名方法。当实现的接口中的方法过多或者过少的时候,lambda表达式都是不适用的。

lambda表达式只能实现函数式接口。即一个接口中,要求实现类必须实现的抽象方法,有且只有一个,这样的接口就是函数式接口。JDK的函数式接口都加上了@FunctionalInterface 注解进行标识。但是无论是否加上该注解,只要接口中只有一个抽象方法,都是函数式接口。

函数式接口的使用:一般可以作为 方法的参数 和 返回值类型

3.Lambda表达式的语法

(参数)->{方法体}
  • 参数部分︰方法的参数列表,要求和实现的接口中的方法参数部分一致,包括参数的数量和类型
  • 方法体部分∶方法的实现部分,如果接口中定义的方法有返回值,则在实现的时候,注意返回值的返回。
  • ->:分隔参数部分和方法体部分。 

  • 因为接口中规定了方法参数类型,所以实际写lambda表达式的时候可以省略 参数类型。如果只有一个参数,可以省略()
  • 另外如果方法体里只有一个语句,那么可以省略掉 {},如果该语句是return语句,需要把return也省略掉

省略如下:

4.函数引用

lambda表达式是为了简化接口的实现的。在lambda表达式中,不应该出现比较复杂的逻辑。如果在lambda表达式中出现了过于复杂的逻辑,会对程序的可读性造成非常大的影响。

如果在lambda表达式中需要处理的逻辑比较复杂,一般情况会单独的写一个方法。在lambda表达式中直接引用这个方法即可。或者,在有些情况下,我们需要在lambda表达式中实现的逻辑,在另外一个地方已经写好了。此时我们就不需要再单独写一遍,只需要直接引用这个已经存在的方法即可。

函数引用:引用一个已经存在的方法,使其替代lambda表达式完成接口的实现。

4.1引用一个静态方法

接口依然是上面的MultipleParametersReturn,引用如下 

需要注意的是引用的 方法的参数返回值类型 要与 接口 对应

4.2引用一个非静态方法

4.3引用构造方法 

Person类

  1. public class Person {
  2. private Integer age;
  3. private String name;
  4. public Person() {
  5. }
  6. public Person(Integer age) {
  7. this.age = age;
  8. }
  9. public Person(Integer age, String name) {
  10. this.age = age;
  11. this.name = name;
  12. }
  13. @Override
  14. public String toString() {
  15. return "Person{" +
  16. "age=" + age +
  17. ", name='" + name + '\'' +
  18. '}';
  19. }
  20. }

PersonWithNoParam接口

  1. @FunctionalInterface
  2. public interface PersonWithNoParam {
  3. Person getPersonWithNoParam();
  4. }

PersonWithOneParam接口

  1. @FunctionalInterface
  2. public interface PersonWithOneParam {
  3. Person getPersonWithOneParam(Integer age);
  4. }

PersonWithDoubleParams接口

  1. @FunctionalInterface
  2. public interface PersonWithDoubleParams {
  3. Person getPersonWithDoubleParams(Integer age,String name);
  4. }

Demo

  1. public class Demo1 {
  2. public static void main(String[] args) {
  3. //lambda表达式实现PersonWithNoParam接口
  4. PersonWithNoParam t1=Person::new;//无参构造函数引用
  5. System.out.println(t1.getPersonWithNoParam());
  6. //lambda表达式实现PersonWithOneParam接口
  7. PersonWithOneParam t2=Person::new;//单参构造函数引用
  8. System.out.println(t2.getPersonWithOneParam(10));
  9. //lambda表达式实现PersonWithDoubleParams接口
  10. PersonWithDoubleParams t3=Person::new;//多参构造函数引用
  11. System.out.println(t3.getPersonWithDoubleParams(10,"小王"));
  12. }
  13. }

lambda表达式会自动根据 方法的参数类型 匹配Person类中对应的构造方法

5.特殊的函数引用

如果在使用lambda表达式实现某些接口的时候,lambda表达式的参数列表中包含了某一个对象,此时方法体中,直接使用这个对象调用它的某一个方法就可以完成整体的逻辑并且其他的参数可以作为调用方法的参数。此时,可以对这种实现进行简化。

Person类

  1. public class Person {
  2. private Integer age;
  3. private String name;
  4. public Person() {
  5. }
  6. public Person(Integer age) {
  7. this.age = age;
  8. }
  9. public Person(Integer age, String name) {
  10. this.age = age;
  11. this.name = name;
  12. }
  13. public void setAge(Integer age) {
  14. this.age = age;
  15. }
  16. @Override
  17. public String toString() {
  18. return "Person{" +
  19. "age=" + age +
  20. ", name='" + name + '\'' +
  21. '}';
  22. }
  23. }

PersonSpecial接口

  1. public interface PersonSpecial {
  2. void setAge (Person person,Integer age);
  3. }

Demo2

  1. public class Demo2 {
  2. public static void main(String[] args) {
  3. //简化前
  4. PersonSpecial t=(person,age)->person.setAge(age);
  5. //用对象的特殊引用简化后
  6. PersonSpecial t2=Person::setAge;
  7. Person person = new Person();
  8. t2.setAge(person,13);
  9. System.out.println(person);
  10. }
  11. }

6.闭包

如果某一个外部局部变量被用在了某一个代码块中,这其实就形成了对这个局部变量的包围,我们称"闭包"。这个时候这个变量可以读取,但不允许被改变,即final(逻辑上要求,并不强制要求final修饰)

  • 函数可以访问函数外部的局部变量,并且与它建立联系,可以修改变量和读取到外部对局部变量的修改。首先lambda表达式可以使用表达式外的变量,但要求使用的变量是final的(逻辑上要求,并不强制要求final修饰)
  • 内部类也是,要求使用的外部的局部变量是final的(逻辑上要求,并不强制要求final修饰)

如下编译器会报错:

那有什么好的解决办法呢?既能让编译器不发出警告,又能修改变量的值。如下:

  1. 把 a 变量声明为 全局变量
  2. 把 a 变量声明为 AtomicInteger。AtomicInteger 可以确保 int 值的修改是原子性的,可以使用 set() 方法设置一个新的 int 值,get() 方法获取当前的 int 值。
  3. 使用数组

三.Stream流

Java8的 使用的是函数式编程模式,可以被用来对集合或数组进行链状流式的操作。有别于IO流,这里是针对集合操作数据的流

创建流

单列集合: 集合对象.stream() 

  1. List<String>s=new ArrayList<>();
  2. Stream<String> stream = s.stream();

数组:`Arrays.stream(数组) `或者使用`Stream.of`来创建

  1. Integer[] arr={1,2,3,4,5};
  2. //创建流 法一
  3. Stream<Integer>stream=Arrays.stream(arr);
  1. //创建流 法二
  2. Stream<Integer>stream=Stream.of(arr);

双列集合:转换成单列集合后再创建

  1. Map<String,Integer> map = new HashMap<>();
  2. map.put("蜡笔小新",19);
  3. map.put("黑子",17);
  4. map.put("日向翔阳",16);
  5. Stream<Map.Entry<String, Integer>> stream = map.entrySet().stream();

Map的entrySet() 方法返回一个实现Map.Entry接口的对象集合。集合中每个对象都是底层Map中一个特定的键/值对。一般情况下,要输出Map中的key 和 value  是先得到key的集合keySet(),然后再迭代(循环)由每个key得到每个value。values()方法是获取集合中的所有值,不包含键,没有对应关系。而Entry可以一次性获得这两个值。Map.Entry中的常用方法如下所示:

  • Object getKey(): 返回条目的关键字
  • Object getValue(): 返回条目的值
  • Object setValue(Object value): 将相关映像中的值改为value,并且返回旧值
     

中间操作

filter

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

例如:留下所有字符串长度大于2的

我们可以先用匿名内部类的方式实现看看

  1. List<String>strings=new ArrayList<>();
  2. strings.add("abcde");
  3. strings.add("ac");
  4. strings.stream().filter(new Predicate<String>() {
  5. @Override
  6. public boolean test(String s) {
  7. return s.length()>2;
  8. }
  9. })

Predicate 接口的泛型参数和集合元素类型是一样的,并且所实现的方法参数也是一样的

集合会把每个元素传给test方法,如果test方法返回true,该元素就会被保留,返回false则过滤掉

现在我们可以试着用lambda表达式简化一下

strings.stream().filter(s-> s.length()>2)

注:因为这里还没有加上流的终结操作,所以实际上是不会生效的(下同),终结操作后面会讲到

map

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

Author类

  1. @Data
  2. @NoArgsConstructor
  3. @AllArgsConstructor
  4. @EqualsAndHashCode//用于后期的去重使用
  5. public class Author {
  6. //id
  7. private Long id;
  8. //姓名
  9. private String name;
  10. //年龄
  11. private Integer age;
  12. //简介
  13. private String intro;
  14. //作品
  15. private List<Book> books;
  16. }

Book类

  1. import lombok.AllArgsConstructor;
  2. import lombok.Data;
  3. import lombok.EqualsAndHashCode;
  4. import lombok.NoArgsConstructor;
  5. @Data
  6. @NoArgsConstructor
  7. @AllArgsConstructor
  8. @EqualsAndHashCode//用于后期的去重使用
  9. public class Book {
  10. //id
  11. private Long id;
  12. //书名
  13. private String name;
  14. //分类
  15. private String category;
  16. //评分
  17. private Integer score;
  18. //简介
  19. private String intro;
  20. }

数据初始化

  1. private static List<Author> getAuthors() {
  2. //数据初始化
  3. Author author = new Author(1L,"蒙多",33,"一个从菜刀中明悟哲理的祖安人",null);
  4. Author author2 = new Author(2L,"亚拉索",15,"狂风也追逐不上他的思考速度",null);
  5. Author author3 = new Author(3L,"易",14,"是这个世界在限制他的思维",null);
  6. Author author4 = new Author(3L,"易",14,"是这个世界在限制他的思维",null);
  7. //书籍列表
  8. List<Book> books1 = new ArrayList<>();
  9. List<Book> books2 = new ArrayList<>();
  10. List<Book> books3 = new ArrayList<>();
  11. books1.add(new Book(1L,"刀的两侧是光明与黑暗","哲学,爱情",88,"用一把刀划分了爱恨"));
  12. books1.add(new Book(2L,"一个人不能死在同一把刀下","个人成长,爱情",99,"讲述如何从失败中明悟真理"));
  13. books2.add(new Book(3L,"那风吹不到的地方","哲学",85,"带你用思维去领略世界的尽头"));
  14. books2.add(new Book(3L,"那风吹不到的地方","哲学",85,"带你用思维去领略世界的尽头"));
  15. books2.add(new Book(4L,"吹或不吹","爱情,个人传记",56,"一个哲学家的恋爱观注定很难把他所在的时代理解"));
  16. books3.add(new Book(5L,"你的剑就是我的剑","爱情",56,"无法想象一个武者能对他的伴侣这么的宽容"));
  17. books3.add(new Book(6L,"风与剑","个人传记",100,"两个哲学家灵魂和肉体的碰撞会激起怎么样的火花呢?"));
  18. books3.add(new Book(6L,"风与剑","个人传记",100,"两个哲学家灵魂和肉体的碰撞会激起怎么样的火花呢?"));
  19. author.setBooks(books1);
  20. author2.setBooks(books2);
  21. author3.setBooks(books3);
  22. author4.setBooks(books3);
  23. List<Author> authorList = new ArrayList<>(Arrays.asList(author,author2,author3,author4));
  24. return authorList;
  25. }

例如:只取所有作家的姓名

我们可以先用匿名内部类的方式实现看看

  1. //打印所有作家的姓名
  2. List<Author> authors = getAuthors();
  3. authors.stream().map(new Function<Author, String>() {
  4. @Override
  5. public String apply(Author author) {
  6. return author.getName();
  7. }
  8. })

Function里第一个泛型参数和集合元素类型是一样的,第二个是要转换成的目标类型。上面一段代码将Author集合转成了只包含Name的String集合。

lambda表达式简化如下:

  1. //打印所有作家的姓名
  2. List<Author> authors = getAuthors();
  3. authors.stream().map(author -> author.getName())

distinct

可以去除流中的重复元素。

例如:对作家进行去重。

  1. List<Author> authors = getAuthors();
  2. authors.stream().distinct()

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

sorted

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

例如:对流中的元素按照年龄进行升序排序,并且要求不能有重复的元素。

当我们在调用sorted时会发现它有两种传参形式:

如果调用空参的sorted()方法,需要流中的元素是实现了Comparable接口,否则会抛出java.lang.ClassCastException异常。  

所以这里我们先让Author实现 Comparable 接口

  1. @Data
  2. @NoArgsConstructor
  3. @AllArgsConstructor
  4. @EqualsAndHashCode//用于后期的去重使用
  5. public class Author implements Comparable<Author> {
  6. //id
  7. private Long id;
  8. //姓名
  9. private String name;
  10. //年龄
  11. private Integer age;
  12. //简介
  13. private String intro;
  14. //作品
  15. private List<Book>books;
  16. @Override
  17. public int compareTo(Author o) {
  18. return this.getAge()-o.getAge();
  19. }
  20. }

接下来就可以排序了

  1. List<Author> authors = getAuthors();
  2. // 对流中的元素按照年龄进行升序排序,并且要求不能有重复的元素。
  3. authors.stream()
  4. .distinct()
  5. .sorted()

当然我们也可以传入对 Comparator 接口的实现,可以用lambda表达式简化

  1. List<Author> authors = getAuthors();
  2. // 对流中的元素按照年龄进行升序排序,并且要求不能有重复的元素。
  3. authors.stream()
  4. .distinct()
  5. .sorted(new Comparator<Author>() {
  6. @Override
  7. public int compare(Author o1, Author o2) {
  8. return o1.getAge()-o2.getAge();
  9. }
  10. })

limit

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

例如:对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素,只保留其中年龄最大的两个作家

  1. List<Author> authors = getAuthors();
  2. authors.stream()
  3. .distinct()
  4. .sorted((o1, o2) -> o2.getAge()-o1.getAge())
  5. .limit(2)

skip

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

例如:打印除了年龄最大的作家外的其他作家,要求不能有重复元素,并且按照年龄降序排序。

  1. // 打印除了年龄最大的作家外的其他作家,要求不能有重复元素,并且按照年龄降序排序。
  2. List<Author> authors = getAuthors();
  3. authors.stream()
  4. .distinct()
  5. .sorted((o1, o2) -> o2.getAge()-o1.getAge())
  6. .skip(1)

flatMap

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

map和flatMap区别在哪里,我们具体来看一个例子

例:保留所有书籍的名字。要求对重复的元素进行去重。

使用 map 转换后流里面的元素实际上是List<Book>集合而非Book(可以用后面的forEach终端操作打印试试)

  1. List<Author> authors = getAuthors();
  2. authors.stream().map(author->author.getBooks()).distinct()

想要将流里的元素变成Book对象,可以使用flatMap

  1. //打印所有书籍的名字。要求对重复的元素进行去重。
  2. List<Author> authors = getAuthors();
  3. authors.stream()
  4. .flatMap(author -> author.getBooks().stream())
  5. .distinct()
  6. .forEach(book -> System.out.println(book.getName()));

注:所有的中间操作返回的都是一个Stream对象,所以可以一直链式编程

终结操作

forEach

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

例子:输出所有作家的名字

  1. // 输出所有作家的名字
  2. List<Author> authors = getAuthors();
  3. authors.stream()
  4. .map(author -> author.getName())
  5. .distinct()
  6. .forEach(name-> System.out.println(name));

count

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

例子:打印这些作家的所出书籍的数目,注意删除重复元素。

  1. // 打印这些作家的所出书籍的数目,注意删除重复元素。
  2. List<Author> authors = getAuthors();
  3. long count = authors.stream()
  4. .flatMap(author -> author.getBooks().stream())
  5. .distinct()
  6. .count();
  7. System.out.println(count);

max&min

可以用来获取流中的最值,返回Optional(后面会讲到)

例子:分别获取这些作家的所出书籍的最高分和最低分并打印。

  1. // 分别获取这些作家的所出书籍的最高分和最低分并打印。
  2. //Stream<Author> -> Stream<Book> ->Stream<Integer> ->求值
  3. List<Author> authors = getAuthors();
  4. Optional<Integer> max = authors.stream()
  5. .flatMap(author -> author.getBooks().stream())
  6. .map(book -> book.getScore())
  7. .max(new Comparator<Integer>() {//比较规则
  8. @Override
  9. public int compare(Integer o1, Integer o2) {
  10. return o1-o2;
  11. }
  12. });
  13. System.out.println(max.get());

collect

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

例子:获取一个存放所有作者名字的List集合。

  1. // 获取一个存放所有作者名字的List集合。
  2. List<Author> authors = getAuthors();
  3. List<String> nameList = authors.stream()
  4. .map(author -> author.getName())
  5. .collect(Collectors.toList());
  6. System.out.println(nameList);

例子:获取一个所有书名的Set集合。

  1. // 获取一个所有书名的Set集合。
  2. List<Author> authors = getAuthors();
  3. Set<Book> books = authors.stream()
  4. .flatMap(author -> author.getBooks().stream())
  5. .collect(Collectors.toSet());
  6. System.out.println(books);

例子:获取一个Map集合,map的key为作者名,value为List<Book>

  1. // 获取一个Map集合,map的key为作者名,value为List<Book>
  2. List<Author> authors = getAuthors();
  3. Map<String, List<Book>> map = authors.stream()
  4. .distinct()
  5. .collect(Collectors.toMap(author -> author.getName(), author -> author.getBooks()));
  6. System.out.println(map);

查找与匹配

anyMatch

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

例子:判断是否有年龄在29以上的作家

  1. // 判断是否有年龄在29以上的作家
  2. List<Author> authors = getAuthors();
  3. boolean flag = authors.stream()
  4. .anyMatch(author -> author.getAge() > 29);
  5. System.out.println(flag);

allMatch

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

例子:判断是否所有的作家都是成年人

  1. // 判断是否所有的作家都是成年人
  2. List<Author> authors = getAuthors();
  3. boolean flag = authors.stream()
  4. .allMatch(author -> author.getAge() >= 18);
  5. System.out.println(flag);

noneMatch

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

例子:判断作家是否都没有超过100岁的。

  1. // 判断作家是否都没有超过100岁的。
  2. List<Author> authors = getAuthors();
  3. boolean b = authors.stream()
  4. .noneMatch(author -> author.getAge() > 100);
  5. System.out.println(b);

findAny

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

例子:获取任意一个年龄大于18的作家,如果存在就输出他的名字

  1. // 获取任意一个年龄大于18的作家,如果存在就输出他的名字
  2. List<Author> authors = getAuthors();
  3. Optional<Author> optionalAuthor = authors.stream()
  4. .filter(author -> author.getAge()>18)
  5. .findAny();
  6. //如果存在
  7. optionalAuthor.ifPresent(author -> System.out.println(author.getName()));

findFirst

获取流中的第一个元素。

例子:获取一个年龄最小的作家,并输出他的姓名。

  1. // 获取一个年龄最小的作家,并输出他的姓名。
  2. List<Author> authors = getAuthors();
  3. Optional<Author> first = authors.stream()
  4. .sorted((o1, o2) -> o1.getAge() - o2.getAge())
  5. .findFirst();
  6. //如果存在
  7. first.ifPresent(author -> System.out.println(author.getName()));

reduce归并

对流中的数据按照你指定的计算方式计算出一个结果。(缩减操作)

reduce的作用是把stream中的元素给组合起来,我们可以传入一个初始值,它会按照我们的计算方式依次拿流中的元素和初始化值进行计算,计算结果再和后面的元素计算。

reduce有好几种重载形式,两个参数的重载形式

内部的计算方式如下: 

  1. T result = identity;
  2. for (T element : this stream)
  3. result = accumulator.apply(result, element)
  4. return result;

其中 identity 就是我们可以通过方法参数传入的初始值,accumulator的apply具体进行什么计算也是我们通过方法参数来确定的。

我们来看一个例子:使用reduce求所有作者年龄的和

  1. // 使用reduce求所有作者年龄的和
  2. List<Author> authors = getAuthors();
  3. Integer sum = authors.stream()
  4. .distinct()
  5. .map(author -> author.getAge())
  6. .reduce(0, new BinaryOperator<Integer>() {
  7. @Override
  8. public Integer apply(Integer result, Integer element) {
  9. return result+element;
  10. }
  11. });//.reduce(0, (result, element) -> result + element);
  12. System.out.println(sum);

一个参数的重载形式

内部的计算方式如下: 

  1. boolean foundAny = false;
  2. T result = null;
  3. for (T element : this stream) {
  4. if (!foundAny) {
  5. foundAny = true;
  6. result = element;
  7. }
  8. else
  9. result = accumulator.apply(result, element);
  10. }
  11. return foundAny ? Optional.of(result) : Optional.empty();

实际上就是把第一个元素作为初始化值,再拿和这个变量进行相应的计算

如果用一个参数的重载方法去求最小值代码如下:

  1. // 使用reduce求所有作者中年龄的最小值
  2. List<Author> authors = getAuthors();
  3. Optional<Integer> minOptional = authors.stream()
  4. .map(author -> author.getAge())
  5. .reduce((result, element) -> result > element ? element : result);
  6. minOptional.ifPresent(age-> System.out.println(age));

注意事项 

  • 惰性求值(如果没有终结操作,没有中间操作是不会得到执行的)

  • 流是一次性的(一旦一个流对象经过一个终结操作后。这个流就不能再被使用)

  • 不会影响原数据(我们在流中可以多数据做很多处理。但是正常情况下是不会影响原来集合中的元素的。这往往也是我们期望的)

四.Optional

1.引入

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

  1. Author author = getAuthor();
  2. if(author!=null){
  3. System.out.println(author.getName());
  4. }

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

所以在JDK8中引入了Optional,养成使用Optional的习惯后你可以写出更优雅的代码来避免空指针异常,并且在很多函数式编程相关的API中也都用到了Optional。

2.使用

2.1创建对象

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

我们一般使用Optional静态方法ofNullable来把数据封装成一个Optional对象。无论传入的参数是否为null都不会出现问题。

  1. Author author = getAuthor();
  2. Optional<Author> authorOptional = Optional.ofNullable(author);

2.2安全消费值

我们获取到一个Optional对象后肯定需要对其中的数据进行使用。这时候我们可以使用其ifPresent方法对来消费其中的值。

这个方法会判断其内封装的数据是否为空,不为空时才会执行具体的消费代码。这样使用起来就更加安全了。

例如,以下写法就优雅的避免了空指针异常:

  1. Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
  2. authorOptional.ifPresent(author -> System.out.println(author.getName()));

2.3安全获取值

不推荐使用get,因为当Optional内部的数据为空的时候会出现异常。而是使用Optional提供的以下方法。

  • orElseGet:获取数据并且设置数据为空时的默认值。如果数据不为空就能获取到该数据。如果为空则根据你传入的参数来创建对象作为默认值返回。
  1. Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
  2. Author author1 = authorOptional.orElseGet(() -> new Author(3L,"易",14,"是这个世界在限制他的思维",null));//默认值
  • orElseThrow:获取数据,如果数据不为空就能获取到该数据。如果为空则根据你传入的参数来创建异常抛出。
  1. Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
  2. try {
  3. Author author = authorOptional.orElseThrow((Supplier<Throwable>) () -> new RuntimeException("author为空"));
  4. System.out.println(author.getName());
  5. } catch (Throwable throwable) {
  6. throwable.printStackTrace();
  7. }

这里的异常处理在spring框架中可以统一处理

2.4过滤

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

  1. Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
  2. if (authorOptional.isPresent()) {
  3. System.out.println(authorOptional.get().getName());
  4. }

2.5数据转换

Optional还提供了map可以让我们的对数据进行转换,并且转换得到的数据也还是被Optional包装好的,保证了我们的使用安全。

例如我们想获取作家的书籍集合:

  1. Optional<Author> authorOptional = getAuthorOptional();
  2. Optional<List<Book>> optionalBooks = authorOptional.map(author -> author.getBooks());
  3. optionalBooks.ifPresent(books -> System.out.println(books));

四.Stream高级用法

1.基本数据类型优化

我们之前用到的很多Stream的方法都使用了泛型,所以涉及到的参数和返回值都是引用数据类型。JDK5中引入的自动装箱和自动拆箱让我们在使用对应的包装类时就好像使用基本数据类型一样方便。 但是如果在大量数据下可能需要不断的重复装箱、拆箱,这个时间损耗就不能忽略了。

为了让我们能够对这部分的时间消耗进行优化。Stream还提供了很多专门针对基本数据类型的方法。例如:mapToInt,mapToLong,mapToDouble,flatMapToInt,flatMapToDouble等。

优化前:

  1. List<Author> authors = getAuthors();
  2. authors.stream()
  3. .map(author -> author.getAge())
  4. .map(new Function<Integer, Integer>() {
  5. @Override
  6. public Integer apply(Integer age) {
  7. return age+10;
  8. }
  9. })
  10. .filter(new Predicate<Integer>() {
  11. @Override
  12. public boolean test(Integer age) {
  13. return age>18;
  14. }
  15. })
  16. .map(new Function<Integer, Integer>() {
  17. @Override
  18. public Integer apply(Integer age) {
  19. return age+2;
  20. }
  21. })
  22. .forEach(System.out::println);

优化后

  1. List<Author> authors = getAuthors();
  2. authors.stream()
  3. .mapToInt(author -> author.getAge())
  4. .map(age -> age + 10)
  5. .filter(age->age>18)
  6. .map(age->age+2)
  7. .forEach(System.out::println);

2.并行流

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

  • parallel方法可以把 串行流 转换成 并行流

补充:peek()方法有助于调试

  1. Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
  2. Integer sum = stream.parallel()//并行流
  3. .peek(new Consumer<Integer>() {
  4. @Override
  5. public void accept(Integer num) {
  6. System.out.println(num+Thread.currentThread().getName());
  7. }
  8. })
  9. .filter(num -> num > 5)
  10. .reduce((result, ele) -> result + ele)//求和
  11. .get();
  12. System.out.println(sum);

运行后:

去掉parallel()后就是单线程串行流,运行如下:

  • parallelStream直接获取并行流对象
  1. List<Author> authors = getAuthors();
  2. authors.parallelStream()
  3. .map(author -> author.getAge())
  4. .map(age -> age + 10)
  5. .filter(age->age>18)
  6. .map(age->age+2)
  7. .forEach(System.out::println);

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/289782
推荐阅读