函数式编程-Stream流

概述

为什么学

  • 能看懂公司代码
  • 大数据量下处理集合效率高
  • 代码可读性高
  • 消灭嵌套地狱

函数式编程思想

概念

面向对象思想需要关注用什么对象完成什么事情,而函数式编程思想类似于我们数学中的函数。主要关注的是对数据进行了什么操作。

优点

  • 代码简洁,开发快速
  • 接近自然语音,便于理解
  • 易于"并发编程"

Lambda表达式

概述

Lamabda是JDK8中的语法糖。他可以对某些匿名的内部类的写法进行简化,它是函数式编程思想的一个重要体现,让我们不同关注是什么对象,而是更关注我们对数据进行了什么操作

核心原则

可推导可省略

基本格式

(参数列表)->

  1. 例子一

    创建线程并启动时可以使用匿名内部类的写法

    new Thread(new Runnable() {
        public void run() {
            System.out.println("线程的run方法被执行了!");
        }
    }).start();
    

    可以使用Lambda的格式进行修改

    new Thread(()-> {
        System.out.println("线程的run方法被执行了!");
    }).start();
    
  2. 例子二

    现在方法定义如下,其中IntBinaryOperator是一个接口。

    public static int calculateNum(IntBinaryOperator operator){
        int a=10;
        int b=10;
        return operator.applyAsInt(a,b);
    }
    
    //匿名内部类的写法调用该方法
    int i = calculateNum(new IntBinaryOperator() {
        @Override
        public int applyAsInt(int left, int right) {
            return left + right;
        }
    });
    System.out.println(i);
    
    //lambda写法
    int i = calculateNum((left, right) -> left + right);
    System.out.println(i);
    
  3. 例子3

    现有方法定义如下,其中IntPredicate是一个接口。

    public static void printNum(IntPredicate intPredicate){
        int[] arr={1,2,3,4,5,6};
        for (int i : arr) {
            if (intPredicate.test(i)) {
                System.out.println(i);
            }
        }
    }
    
    //匿名内部类的写法调用该方法
    printNum(new IntPredicate() {
        @Override
        public boolean test(int value) {
            return value%2==0;
        }
    });
    
    //lambda写法
    printNum(value -> value%2==0);
    
  4. 例子四

    现有方法定义如下,其中Function是一个接口。

    public static <R> R typeConvert(Function<String,R> function){
        String str="1235";
        R result=function.apply(str);
        return result;
    }
    //匿名内部类的写法调用该方法
    Integer integer = typeConvert(new Function<String, Integer>() {
    
        @Override
        public Integer apply(String s) {
            return Integer.parseInt(s);
        }
    });
    System.out.println(integer);
    
    //lambda写法
    Integer integer = typeConvert(s -> Integer.parseInt(s));
    System.out.println(integer);
    
  5. 例子五

    现有方法定义如下,其中IntConsumer是一个接口。

    public static void foreachArr(IntConsumer consumer){
        int[] arr={1,2,3,4,5,6,7,8,9,10};
        for (int i : arr) {
            consumer.accept(i);
        }
    }
    //匿名内部类的写法调用该方法
    foreachArr(new IntConsumer() {
        @Override
        public void accept(int value) {
            System.out.println(value);
        }
    });
    //lambda写法
    foreachArr(value -> System.out.println(value));
    

省略规则

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

Stream流

概述

Java8的Stream使用的是函数式编程思想,如同他的名字一样,它可以被用来对集合或者数组进行链状流式操作,可以方便的让我们对集合或者数组操作。

案例数据准备

<dependencies>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
        </dependency>
    </dependencies>
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode //用于后期的去重
public class Author {

    private Long id;

    private String name;

    private Integer age;

    private String intro;

    private List<Book> books;
}

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class Book {
    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,"两个哲学家的灵魂和肉体的碰撞会激起怎样的火花呢?"));

    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;
}

快速入门

需求

我们可以调用getAuthors方法获取到作家的集合。现在需要打印所有年龄小于18的作家的名字,并且注意去重

实现

//打印所有年龄小于18的作家的名字,并且注意去重
List<Author> authors = getAuthors();
authors.stream() //把集合转换成流
    .distinct() //去重
    .filter(author -> author.getAge()<18) //筛选年龄<18
    .forEach(author -> System.out.println(author.getName())); //遍历打印名字

常用操作

创建流

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

List<Author> authors = getAuthors();
Stream<Author> stream = authors.stream();

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

Integer[] arr={1,2,3,4,5};
Stream<Integer> stream = Arrays.stream(arr);
Stream<Integer> stream = Stream.of(arr);

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

Map<String,Integer> map=new HashMap<>();
map.put("蜡笔小新",19);
map.put("海贼王",12);
map.put("一休哥",25);

Stream<Map.Entry<String, Integer>> stream = map.entrySet().stream();

中间操作

filter

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

//打印所有姓名长度大于1的作家姓名
List<Author> authors = getAuthors();
authors.stream()
    .filter(author -> author.getName().length()>1)
    .forEach(author -> System.out.println(author.getName()));

map

可以对流中的元素进行计算和转换(数据类型的转换:例如:Author->String,Author->age)

 //打印所有作家姓名
List<Author> authors = getAuthors();
authors.stream()
    .map(author -> author.getName())
    .forEach(s -> System.out.println(s));
//作家的年龄+10
authors.stream()
    .map(author -> author.getAge())
    .map(age->age+10)
    .forEach(age-> System.out.println(age));

image-20220222132853866

distinct

可以去除流中的重复元素

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

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

sorted

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

//对流中的元素按照年龄降序进行排序,并且要求不能有重复的元素
 List<Author> authors = getAuthors();
//方法一:
//authors.stream()
//        .distinct()
//        .sorted()
//        .forEach(author -> System.out.println(author.getAge()));
//方法二
authors.stream()
    .distinct()
    .sorted((o1, o2) -> o2.getAge()-o1.getAge())
    .forEach(author -> System.out.println(author.getAge()));

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

limit

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

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

skip

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

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

flatmap

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

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

//例子2:打印现有数据的所有分类。要求对分类进行去重。不能出现这种格式:哲学,爱情
List<Author> authors = getAuthors();
authors.stream()
    .flatMap(author -> author.getBooks().stream())
    .distinct()
    .flatMap(book ->Stream.of( book.getCategory().split(",")))
    .distinct()
    .forEach(category -> System.out.println(category));

image-20220222143330319

image-20220222143408461

终结操作

forEach

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

//输出所有作家的姓名
List<Author> authors = getAuthors();
authors.stream()
    .map(author -> author.getName())
    .distinct()
    .forEach(name-> System.out.println(name));

count

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

//打印作家的所有书籍的数目,注意删除重复的元素
List<Author> authors = getAuthors();
long count = authors.stream()
    .flatMap(author -> author.getBooks().stream())
    .distinct()
    .count();
System.out.println(count);

max&min

可以用来获取流中的最大或者最小值

//分别获取这些作家的所出书籍的最高分最低分并打印
List<Author> authors = getAuthors();
Optional<Integer> max = authors.stream()
    .flatMap(author -> author.getBooks().stream())
    .map(book -> book.getScore())
    .max((score1, score2) -> score1 - score2);
Optional<Integer> min = authors.stream()
    .flatMap(author -> author.getBooks().stream())
    .map(book -> book.getScore())
    .min((score1, score2) -> score1 - score2);
System.out.println(max.get());
System.out.println(min.get());

collect

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

//获取一个存放所有名字的List集合
List<Author> authors = getAuthors();
List<String> nameList = authors.stream()
    .map(author -> author.getName())
    .collect(Collectors.toList());
System.out.println(nameList);
//获取一个所有书名的Set集合
List<Author> authors = getAuthors();
Set<String> bookNameSet = authors.stream()
    .flatMap(author -> author.getBooks().stream())
    .map(book -> book.getName())
    .collect(Collectors.toSet());
System.out.println(bookNameSet);
//获取一个map集合,map的key为作者名字,value为List<Book>
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);

image-20220222151909342

查找与匹配

anyMatch

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

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

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

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

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

//判断作家是否都没用超过100岁的
List<Author> authors = getAuthors();
boolean b = authors.stream()
    .noneMatch(author -> author.getAge() >100);
System.out.println(b);
findAny

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

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

获取流中的第一个元素

//获取一个年龄最小的作家,并输出他的姓名
List<Author> authors = getAuthors();
Optional<Author> first = authors.stream()
    .sorted((s1, s2) -> s1.getAge() - s2.getAge())
    .findFirst();
first.ifPresent(author -> System.out.println(author.getName()+":"+author.getAge()));

reduce归并

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

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

reduce两个参数重载形式的内部计算方式:

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

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

//使用reduce求所有作者年龄的和
List<Author> authors = getAuthors();
Integer sum = authors.stream()
    .map(author -> author.getAge())
    .reduce(0, (result, element) -> result + element);
System.out.println(sum);

//使用reduce求所有的作者中年龄的最大值
List<Author> authors = getAuthors();
Integer max = authors.stream()
    .map(author -> author.getAge())
    .reduce(Integer.MIN_VALUE, (result, element) -> element > result ? element : result);
System.out.println(max);

//使用reduce求所有的作者年龄的最小值
List<Author> authors = getAuthors();
Integer min = authors.stream()
    .map(author -> author.getAge())
    .reduce(Integer.MAX_VALUE, (result, element) -> element < result ? element : result);
System.out.println(min);

reduce一个参数重载的内部计算方式:

	 *	   boolean foundAny = false;
     *     T result = null;
     *     for (T element : this stream) {
     *         if (!foundAny) {
     *             foundAny = true;
     *             result = element;
     *         }
     *         else
     *             result = accumulator.apply(result, element);
     *     }
     *     return foundAny ? Optional.of(result) : Optional.empty();

一个参数重载的方法求最小值

//使用reduce求所有的作者年龄的最小值
List<Author> authors = getAuthors();
Optional<Integer> min = authors.stream()
    .map(author -> author.getAge())
    .reduce((result, element) -> element < result ? element : result);
min.ifPresent(value-> System.out.println(value));

注意事项

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

Optional

概述

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

例如:

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

尤其是对象中的属性还是一个对象的情况下,这种判断会更多。

而过多的判断语句会让我们的代码显得臃肿不堪。

所以在JDK8中引入了Optional,养成使用Optional的习惯后你可以写出更优雅的代码来避免空指针异常

并且在很多的函数式编程相关的API中页都用到Optional,如果不会使用Optional也会对函数式编程的学习造成影响。

使用

创建对象

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

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

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

你可能觉得还得加一行代码来封装数据比较麻烦,但是如果改造一下getAuthor方法,让其的返回值就是封装好的Optional的话,我们在使用的时候就会方便很多。

而且在实际的开发中我们的数据很多都是从数据库中获取的。Mybatis3.5版本可以也已经支持Optional了。我们可以直接把dao方法的返回值类型定义成Optional类型,Mybatis会自己把数据封装成Optional对象返回。封装的过程也不需要我们自己操作。

如果你确认一个对象不为空则可以使用Optional的静态方法of来把数据封装成Optional对象

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

但是一定要注意,如果使用of的时候传入的参数必须不为null。(尝试下传入空会出现什么结果)

如果一个方法的返回值类型是Optional类型。而如果我们经判断发现某次计算得到的返回值为null,这个时候就需要把null封装成Optional对象返回。这时则可以使用Optional的静态方法empty来进行封装。

Optional.empty()

安全消费值

我们获取到一个Optional对象后肯定需要对其中的数据进行使用,这时候我们可以使用其ifPresent方法对消费其中的值。这个方法会判断其内封装的数据是否为空,不为空时才会执行具体的消费代码,这样使用起来就更加的安全了。

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

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

获取值

如果我们想获取自己进行处理可以使用get方法获取,但是不推荐。因为当Optional内部的数据为空会出现异常

安全获取值

如果我们期望安全的获取值。我们不推荐使用get方法,而是使用Optional提供的以下方法

  • orElseGet

    获取数据并设置数据为空时候的默认值。如果数据不为空就能获取到该数据,如果为空则根据传入的参数来创建对象作为默认值返回

    Optional<Author> authorOptional = getAuthorOptional();
    Author author1 = authorOptional.orElseGet(() -> new Author());
    
    System.out.println(author1.getName());
    
  • orElseThrow

    获取数据,如果数据不为空就能获取到该数据,如果为空则根据你传的参数来创建异常抛出,就可以应用Spring做异常的统一捕获

    Optional<Author> authorOptional = getAuthorOptional();
    try {
          Author author1 = authorOptional.orElseThrow(() -> new RuntimeException("author为空"));
          System.out.println(author1.getName());
      } catch (Throwable throwable) {
          throwable.printStackTrace();
      }
    

过滤

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

Optional<Author> authorOptional = getAuthorOptional();
authorOptional.filter(author -> author.getAge()>3)
    .ifPresent(author -> System.out.println(author.getName()));

判断

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

Optional<Author> authorOptional = getAuthorOptional();
if(authorOptional.isPresent()){
    System.out.println(authorOptional.get().getName());
    System.out.println(authorOptional.get().getAge());
}

数据转换

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

Optional<Author> authorOptional = getAuthorOptional();
authorOptional.map(author -> author.getBooks())
    .ifPresent(books -> System.out.println(books));

函数式接口

概述

只有一个抽象方法的接口称为函数式接口

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

常见的函数式接口

  • Consumer

    根据其中的抽象方法的参数列表和返回值的类型知道,我们可以在方法中对传入的参数进行消费

    @FunctionalInterface
    public interface Consumer<T> {
    
        /**
         * Performs this operation on the given argument.
         *
         * @param t the input argument
         */
        void accept(T t)
    }
    
  • Function计算转换接口

    根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数进行计算或者转换。把结果返回

    @FunctionalInterface
    public interface Function<T, R> {
    
        /**
         * Applies this function to the given argument.
         *
         * @param t the function argument
         * @return the function result
         */
        R apply(T t);
    }
    
  • Predicate

    根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数条件判断,返回判断结果

    @FunctionalInterface
    public interface Predicate<T> {
    
        /**
         * Evaluates this predicate on the given argument.
         *
         * @param t the input argument
         * @return {@code true} if the input argument matches the predicate,
         * otherwise {@code false}
         */
        boolean test(T t);
    }
    
  • Supplier生产型接口

    根据其中的抽象方法的参数列表和返回值类型可以知道,我们可以在方法中创建对象,把创建好的对象返回

    @FunctionalInterface
    public interface Supplier<T> {
    
        /**
         * Gets a result.
         *
         * @return a result
         */
        T get();
    }
    

常用的默认方法

  • and

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

    //打印作家年龄大于17并且姓名长度大于1的作家
    List<Author> authors = getAuthors();
    authors.stream().filter(new Predicate<Author>() {
        @Override
        public boolean test(Author author) {
            return author.getAge()>17;
        }
    }.and(new Predicate<Author>() {
        @Override
        public boolean test(Author author) {
            return author.getName().length()>1;
        }
    })).forEach(author -> System.out.println(author.getName()+":"+author.getAge()));
    
  • or

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

    //打印作家年龄大于17或者姓名长度小于2的作家
    List<Author> authors = getAuthors();
    authors.stream()
        .filter(new Predicate<Author>() {
            @Override
            public boolean test(Author author) {
                return author.getAge()>17;
            }
        }.or(new Predicate<Author>() {
            @Override
            public boolean test(Author author) {
                return author.getName().length()<2;
            }
        })).forEach(author -> System.out.println(author.getName()+":"+author.getAge()));
    
  • negate

    Predicate接口中的方法,negate方法相当于是在判断添加前面加了个!表示取反

    //打印年龄不小于17的作家
    List<Author> authors = getAuthors();
    authors.stream()
        .filter(new Predicate<Author>() {
            @Override
            public boolean test(Author author) {
                return author.getAge()<17;
            }
        }.negate())
        .forEach(author -> System.out.println(author.getName()+":"+author.getAge()));
    

方法引用

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

推荐用法

我们在使用lambda时不需要考虑什么时候用方法引用,用那种方法引用,方法引用的格式是什么。我们只需要在写完lambda方法发现方法体只有一行代码,并且是方法调用的时候用快捷键尝试是否能够转换成方法引用即可

当我们方法引用使用的多了慢慢的也可以直接写出方法引用

基本格式

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

语法详解(了解)

引用静态方法

其实就是引用类的静态方法

格式

类名::方法名

使用前提

如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的静态方法,并且我们要重写的抽象方法中所有的参数都按照顺序传入了这个静态方法中,这个时候我们就可以引用类中的静态方法

以下代码就可以使用方法引用进行优化

List<Author> authors = getAuthors();
authors.stream()
    .map(author -> author.getAge())
    .map(age ->String.valueOf(age));

注意:如果我们所写的重新的方法是没有参数的,调用方法也是没有参数的也相当于符合上规则

优化后的代码:

List<Author> authors = getAuthors();
authors.stream()
    .map(author -> author.getAge())
    .map(String::valueOf);

引用对象的实例方法

格式

对象名::方法名

使用前提

如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个对象的成员方法,并且我们要重写的抽象方法中所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用对象的实例方法

例如:

List<Author> authors = getAuthors();
StringBuilder sb = new StringBuilder();
authors.stream()
    .map(author -> author.getName())
    .forEach(name->sb.append(name));

优化后

List<Author> authors = getAuthors();
StringBuilder sb = new StringBuilder();
authors.stream()
    .map(author -> author.getName())
    .forEach(sb::append);

引用类的实例方法

格式

类名::方法名

使用前提

如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了第一个参数的成员方法,并且我们要重写的抽象方法中声音的所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用类的实例方法

例如:

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("hing", new UseString() {
            @Override
            public String use(String str, int start, int length) {
                return str.substring(start,length);
            }
        });
    }
}

优化后:

public static void main(String[] args) {
    subAuthorName("hing", String::substring);
}

构造器使用

如果方法体中的一行代码是构造器的话就可以使用构造器引用

格式

类名::new

使用前提

如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的构造方法,并且我们要重写的抽象方法中所有的参数都按照顺序传入了这个构造方法中,这个时候我们就可以引用构造器

例如:

List<Author> authors = getAuthors();
authors.stream()
    .map(author -> author.getName())
    .map(name-> new StringBuilder(name))
    .map(sb->sb.append("hing").toString())
    .forEach(str-> System.out.println(str));

优化后

List<Author> authors = getAuthors();
authors.stream()
    .map(author -> author.getName())
    .map(StringBuilder::new)
    .map(sb->sb.append("hing").toString())
    .forEach(str-> System.out.println(str));

高级用法

基本数据类型优化

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

即使我们操作的是整数小数,但是实际用的都是他们的包装类。JDK5中引用的自动装箱和自动拆箱让我们在使用对应的包装类时候就好像使用基本的数据类型一样方便。但是你一定知道装箱和拆箱需要消耗时间。虽然这个时间消耗很小。但是大量的数据不断的重复拆箱装箱的时候,就不能无视时间损耗了。

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

例如:mapToInt,mapTolong,mapToDouble,flatMapToInt,flatMapToDouble等

List<Author> authors = getAuthors();
authors.stream()
    .map(author -> author.getAge())
    .map(age-> age+10)
    .filter(age -> age>18)
    .map(age->age+2)
    .forEach(age -> System.out.println(age));
//优化
authors.stream()
    .mapToInt(author ->author.getAge())
    .map(age-> age+10)
    .filter(age -> age>18)
    .map(age->age+2)
    .forEach(age -> System.out.println(age));

并行流

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

parallel串行流转换成并行流,也可以通过parallelStream直接获取并行流

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = stream.filter(num -> num > 5)
        .reduce((result, element) -> result + element)
        .get();
System.out.println(sum);

//串行流转换成并行流
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = stream.parallel()
    .peek(num-> System.out.println(num+Thread.currentThread().getName()))
    .filter(num -> num > 5)
    .reduce((result, element) -> result + element)
    .get();
System.out.println(sum);
posted @   地球小星星  阅读(55)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示