函数式编程汇总

目录

一 . Lambda 表达式

实例

省略规则

二. Stream 流

案例数据准备

入门实例

        调试技巧

常用操作

创建流

1. 单例集合

2. 数组

3. 双列集合

中间操作

1. filter

2. map

3. distinct

4. sorted

5. limit

7. flatMap

终结操作

1. forEach

2. count

3. max & min

4. collect

查找与匹配

1. anyMatch

2. allMatch

3. noneMatch

4. findAny

5. firstAny

reduce 归并

实例

注意事项:

三. Optional

1. 创建对象

2. 安全消费值

3. 获取值

4. 安全获取值

5. 过滤

6. 判断

7. 数据转换

四. 函数式接口

常见函数式接口

常见默认方法

五. 方法引用

1. 推荐用法

2. 基本格式

3. 语法详解(了解)

引用静态方法

引用对象的实例方法

构造器引用

高级用法

并行流


一 . Lambda 表达式

        Lambda 表达式可以对某些匿名内部类的写法进行简化. 它是函数式编程的一个重要体现, 让我们不用太关注什么是对象, 而是更关心对数据的处理.

基本格式:

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

实例:

例子1: 当我们在创建一个线程的时

没有使用 Lambda 表达式:

        new Thread(new Runnable() {@Overridepublic void run() {System.out.println("我使用了匿名内部类的方式");}}).start();结果: 我使用了匿名内部类的方式

使用 Lambda 了表达式:

        new Thread(() -> {System.out.println("使用 Lambda 了表达式");}).start();结果: 使用 Lambda 了表达式

例子2: 

没有使用 Lambda 表达式:

    @Testvoid test2() {int i = calculateNum(new IntBinaryOperator() {@Overridepublic int applyAsInt(int left, int right) {return left + right;}});System.out.println(i);}public static int calculateNum(IntBinaryOperator operator) {int a = 10;int b = 20;return operator.applyAsInt(a, b);}结果: 30

使用 Lambda 了表达式:

    @Testvoid test2() {int i = calculateNum((int left, int right) -> {return left + right;});System.out.println(i);}public static int calculateNum(IntBinaryOperator operator) {int a = 10;int b = 20;return operator.applyAsInt(a, b);}结果: 30

tips: 上面代码出现的 IntBinaryOperator 是一个接口, 并且有一个 applyAsInt 方法


例子3:

没有使用 Lambda 表达式:

    @Testvoid test3() {printNum(new IntPredicate() {@Overridepublic boolean test(int value) {return value % 2 == 0;}});}public static void printNum(IntPredicate predicate) {int[] arr = {1, 2, 3 ,4, 5, 6, 7, 8, 9, 10};for (int i : arr) {if (predicate.test(i)) {System.out.println(i);}}}结果: 
2
4
6
8
10

使用 Lambda 了表达式:

    @Testvoid test3() {printNum((int value) -> {return value % 2 == 0;});}public static void printNum(IntPredicate predicate) {int[] arr = {1, 2, 3 ,4, 5, 6, 7, 8, 9, 10};for (int i : arr) {if (predicate.test(i)) {System.out.println(i);}}}结果:
2
4
6
8
10

tips: 上面代码出现的 IntPredicate 也是一个接口, 并且有一个 test 方法

例子4:

没有使用 Lambda 表达式:

    @Testvoid test4() {Integer integer = typeConver(new Function<String, Integer>() {@Overridepublic Integer apply(String s) {return Integer.valueOf(s);}});System.out.println(integer);}public static <R> R typeConver(Function<String, R> function) {String str = "12345";R result = function.apply(str);return result;}结果: 12345

使用 Lambda 了表达式:

    @Testvoid test4() {Integer integer = typeConver((String s) -> {return Integer.valueOf(s);});System.out.println(integer);}public static <R> R typeConver(Function<String, R> function) {String str = "12345";R result = function.apply(str);return result;}结果: 12345

我们还可以让它返回一个 String 类型的值:

    @Testvoid test4() {String str = typeConver((String s) -> {return s + "上山打老虎";});System.out.println(str);}public static <R> R typeConver(Function<String, R> function) {String str = "12345";R result = function.apply(str);return result;}结果: 12345上山打老虎

tips: 上面代码出现的 Function<T, R> 也是一个接口, 并且有一个 apply 方法

例子5:

没有使用 Lambda 表达式:

    @Testvoid test5() {foreachArr(new IntConsumer() {@Overridepublic void accept(int value) {System.out.print(value + " ");}});}public static void foreachArr(IntConsumer consumer) {int[] arr = {1, 2, 3 ,4, 5, 6, 7, 8, 9, 10};for (int i : arr) {consumer.accept(arr[i]);}}结果: 1 2 3 4 5 6 7 8 9 10 

使用了 Lambda 表达式:

    @Testvoid test5() {foreachArr((int value) -> {System.out.print(value + " ");});}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);}}

tips: 上面代码出现的 IntConsumer 也是一个接口, 并且有一个 accept 方法

Lambda 的简写:

Lambda 表达式 只关注 参数列表 和 方法体, 其他的一律不过问.

省略规则:

1. 参数类型可以省略

2. 方法体如果只有一局代码时, 大括号"{}" 和 return 还有 分号 “;” 可以省略

3. 方法只有一个参数时,参数列表的小括号可以省略

如果上述规则觉得有些复杂,可以不用记,可以先使用函数式编程把代码敲出来,让后将鼠标移动到 对象名 上,让后按住 art + enter 键,选 "Replace with lambda"。 

然后就变成简化后的 Lambda 表达式了

二. Stream 流

        Java8 的 Stream 使用的是函数式编程模式, 它可以被用来对集合或数组进行链状流式的操作. 可以更方便的让我们对数组或集合进行操作.

案例数据准备:

@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode // 用于后期的去重使用
public class Book {// idprivate Long id;// 分类private String category;// 书名private String name;// 评分private Double score;// 简介private String intro;
}

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

创建一个测试类

    // 初始化一些数据private static List<Author> getAuthors() {Author author1 = new Author(1L, "周杰伦", 18, "my introduction 1", null);Author author2 = new Author(2L, "周星驰", 19, "my introduction 2", null);Author author3 = new Author(3L, "周润发", 20, "my introduction 3", null);Author author4 = new Author(4L, "周迅", 17, "my introduction 4", null);Author author5 = new Author(5L, "周一", 16, "my introduction 4", null);List<Book> books1 = new ArrayList<>();List<Book> books2 = new ArrayList<>();List<Book> books3 = new ArrayList<>();// 上面是作者和书books1.add(new Book(1L, "类别,分类啊", "书名1", 45D, "这是简介哦"));books1.add(new Book(2L, "高效,天啊", "书名2", 84D, "这是简介哦"));books1.add(new Book(3L, "喜剧,高效", "书名3", 83D, "这是简介哦"));books2.add(new Book(5L, "天啊", "书名4", 65D, "这是简介哦"));books2.add(new Book(6L, "高效", "书名5", 89D, "这是简介哦"));books3.add(new Book(7L, "久啊,天啊", "书名6", 45D, "这是简介哦"));books3.add(new Book(8L, "高效", "书名7", 44D, "这是简介哦"));books3.add(new Book(9L, "喜剧,高效", "书名8", 81D, "这是简介哦"));author1.setBooks(books1);author2.setBooks(books2);author3.setBooks(books3);author4.setBooks(books3);author5.setBooks(books2);return new ArrayList<>(Arrays.asList(author1, author2, author3, author4, author5));}private Author getAuthor() {Author author = new Author(1L, "周杰伦", 18, "my introduction 1", null);List<Book> books1 = new ArrayList<>();books1.add(new Book(1L, "类别,分类啊", "书名1", 45D, "这是简介哦"));books1.add(new Book(2L, "高效,天啊", "书名2", 84D, "这是简介哦"));books1.add(new Book(3L, "喜剧,高效", "书名3", 83D, "这是简介哦"));author.setBooks(books1);return author;}

入门实例:

例子1: 

现在需要打印所有年龄小于 18 的作家的名字, 并且要注意去重

(使用函数式编程)

    @Testvoid test1() {// 我们可以调用 getAuthors 方法获取到作家集合.List<Author> authors = getAuthors();// 现在需要打印所有年龄小于 18 的作家的名字, 并且要注意去重authors.stream() // 把对象转换成流.distinct() // 去重.filter(new Predicate<Author>() { // 筛选小于 18 岁的作者@Overridepublic boolean test(Author author) {return author.getAge() < 18;}}).forEach(new Consumer<Author>() { // 打印@Overridepublic void accept(Author author) {System.out.println(author);}});}结果: 
Author(id=4, name=周迅, age=17, info=my introduction 4, books=[Book(id=7, name=久啊, category=书名6, score=45.0, intro=这是简介哦), Book(id=8, name=高效, category=书名7, score=44.0, intro=这是简介哦), Book(id=9, name=喜剧, category=书名8, score=81.0, intro=这是简介哦)])
Author(id=5, name=周一, age=16, info=my introduction 4, books=[Book(id=5, name=天啊, category=书名4, score=65.0, intro=这是简介哦), Book(id=6, name=高效, category=书名5, score=89.0, intro=这是简介哦)])

(使用 Lambda 表达式, 结果同上)

    @Testvoid test1() {// 我们可以调用 getAuthors 方法获取到作家集合.List<Author> authors = getAuthors();// 现在需要打印所有年龄小于 18 的作家的名字, 并且要注意去重// 筛选小于 18 岁的作者// 打印authors.stream() // 把集合转换成流.distinct() // 去重.filter(author -> author.getAge() < 18).forEach(author -> System.out.println(author));}

调试技巧:

我们再进行 Stream 调试的时候, 可以点击下图指示的按钮

就会出现下图的画面:

然会就可以看到一些调试信息了 ~~ 

常用操作:

创建流

1. 单例集合:

集合对象.stream()
List<Author> authors = getAuthors();
Stream<Author> system = authors.stream();

2. 数组

Arrags.stream(数组), 或者使用 Stream.of 来创建
Integer[] arr = {1, 2, 3, 4, 5};
Stream<Integer> system1 = Arrays.stream(arr);
Stream<Integer> system2 = Stream.of(arr);

3. 双列集合

转换成单例集合后再创建        
Map<String, Integer> map = new HashMap<>();
map.put("鸣人", 1);
map.put("佐助", 2);
map.put("小樱", 3);
Stream<Map.Entry<String, Integer>> stream = map.entrySet().stream();

中间操作

1. filter

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

例如: 

        打印所有姓名长度大于 1 的作家的名字

        authors.stream().filter(author -> author.getName().length() > 2).forEach(author -> System.out.println(author));

2. map

        map 可以把元素进行计算或者转换.

        // 打印所有作家的姓名, 并在姓名后加 "***" (将 Author类 转换成 String类)authors.stream().map(author -> author.getName()).map(name -> name + "***").forEach(author -> System.out.println(author));结果:
周杰伦***
周星驰***
周润发***
周迅***
周一***

3. distinct

        distinct 可以去除流中重复元素

        // 打印所有作家元素, 并且要求不能有重复元素authors.stream().distinct().forEach(author -> System.out.println(author));

注意: distinct 方法是依赖 Objects 的 equals 方法来判断是否是相同的对象的, 所以需要注意重写 equals 方法. 这个重写我们使用了 Lombok 的注解来实现了.

4. sorted

        sorted 可以对流中的对象进行排序

注意 : 如果使用空参的 sorted 方法, 在使用这个方法前, 对象要实现 Comparable 接口, 并重写 compareTo 方法:

        // 对流中的作家元素按照年龄进行升序排序.authors.stream().sorted().forEach(author -> System.out.println(author));

或者使用一个含有 Comparator 比较器的 sorted 方法:

        // 对流中的作家元素按照年龄进行升序排序.authors.stream().sorted((o1, o2) -> o1.getAge() - o2.getAge()).forEach(author -> System.out.println(author));

5. limit

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

        // 对流中的作家元素按照年龄进行升序排序. 然后打印年龄最小的两个作家authors.stream().sorted((o1, o2) -> o1.getAge() - o2.getAge()).limit(2L).forEach(author -> System.out.println(author));

6. skip

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

        // 对流中的作家元素按照年龄进行升序排序. 然后打印除了年龄最小的两个作家之外的其他作家authors.stream().sorted((o1, o2) -> o1.getAge() - o2.getAge()).skip(2L).forEach(author -> System.out.println(author));

7. flatMap

        flatMap 可以把一个对象转换成多个对象作为流中的元素.

例子1:

        // 打印所有书籍的名字// 匿名内部类版authors.stream().flatMap(new Function<Author, Stream<Book>>() {@Overridepublic Stream<Book> apply(Author author) {return author.getBooks().stream();}}).distinct().forEach(new Consumer<Book>() {@Overridepublic void accept(Book book) {System.out.println(book.getName());}});// Lambda 表达式版authors.stream().flatMap((Function<Author, Stream<Book>>) author -> author.getBooks().stream()).distinct().forEach(book -> System.out.println(book.getName()));

根据调试结果可以看出一个 Author 对象转换成多个 Book 对象作为流中的元素.

例子2:

        // 打印所有书籍的分类, 要求对分类进行去重, 不能出现这种格式: 高效,天啊authors.stream().flatMap(author -> author.getBooks().stream()).distinct().flatMap(book ->  Arrays.stream(book.getCategory().split(","))).distinct().forEach(category -> System.out.println(category));

终结操作

1. forEach

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

        // 打印所有作家的姓名authors.stream().forEach(author -> System.out.println(author.getName()));

2. count

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

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

3. max & min

        可以获取流中的最值

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

4. collect

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

例子1:

        // 获取一个存放所有作者名字的 List 集合List<String> authorsName = authors.stream().map(author -> author.getName()).collect(Collectors.toList());System.out.println(authorsName);

例子2:

        // 获取一个所有书名的 Set 集合Set<String> booksName = authors.stream().flatMap(author -> author.getBooks().stream()).map(book -> book.getName()).collect(Collectors.toSet());System.out.println(booksName);

例子3:

        // 获取一个 Map 集合, map 的 key 作为 作者名, value 为 List<Book>// 匿名内部类版Map<String, List<Book>> map = authors.stream().distinct().collect(Collectors.toMap(new Function<Author, String>() {@Overridepublic String apply(Author author) {return author.getName();}}, new Function<Author, List<Book>>() {@Overridepublic List<Book> apply(Author author) {return author.getBooks();}}));// Lambda 表达式版Map<String, List<Book>> map = authors.stream().distinct().collect(Collectors.toMap(author -> author.getName(), author -> author.getBooks()));

注意: key 值不能重复, 不然会报错!!!

查找与匹配

1. anyMatch

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

        // 判断是否有年龄在 19 岁以上的作家boolean b = authors.stream().anyMatch(author -> author.getAge() > 19);System.out.println(b);

2. allMatch

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

        // 判断是否所有年龄在 19 岁以上的作家boolean b = authors.stream().allMatch(author -> author.getAge() > 19);System.out.println(b);

3. noneMatch

        noneMatch 可以用来判断是否所有元素都不符合匹配条件, 结果为 boolean 类型, 都不符合返回 true, 都不符合返回 false

        boolean b = authors.stream().noneMatch(author -> author.getAge() > 100);System.out.println(b);

4. findAny

        findAny 获取流中的任意一个元素.

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

5. firstAny

        firstAny 获取流中的第一个元素.

        // 获取一个年龄最小的作家, 如果存在就输出它的姓名Optional<Author> optionalAuthor = authors.stream().sorted((o1, o2) -> o1.getAge() - o2.getAge()).findFirst();optionalAuthor.ifPresent(author -> System.out.println(author.getName()));

reduce 归并

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

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

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

T result = identity; // 相当于初始值
for (T element : this stream) {result = accumulator.apply(result, element);
}
return result;// 你可以把这段代码想象成
int[] arr = {1, 2, 3, 4, 5};
int sum = 0;
for (int i : arr) {sum = sum + i;
}
return sum;

实例:

例子1:

        // 使用 reduce 求所有作家年龄的和// 使用匿名内部类方式Integer ageCount = authors.stream()// 先把 Author 对象转换为 Integer 对象.map(author -> author.getAge())// 此处的 0 相当于初始值 identity.reduce(0, new BinaryOperator<Integer>() {@Overridepublic Integer apply(Integer result, Integer element) {return result+ element;}});System.out.println(ageCount);// 使用Lambda表达式Integer ageCount = authors.stream()// 先把 Author 对象转换为 Integer 对象.map(author -> author.getAge()).reduce(0, (result, element) -> result+ element);System.out.println(ageCount);

例子2:

        // 使用 reduce 求作者年龄的最大值// 使用匿名内部类方式Integer maxAge = authors.stream().map(author -> author.getAge()).reduce(Integer.MIN_VALUE, new BinaryOperator<Integer>() {@Overridepublic Integer apply(Integer result, Integer element) {return result < element ? element : result;}});System.out.println(maxAge);        // 使用Lambda表达式Integer maxAge = authors.stream().map(author -> author.getAge()).reduce(Integer.MIN_VALUE, (result, element) -> result < element ? element : result);System.out.println(maxAge);

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

    boolean foundAny = false;T result = null;for (T element : this stream) {if (!foundAny) { // 第一次遍历走到这里foundAny = true; // 将 foundAny 置为true, 相当于这里面的代码只执行一次result = element; // 将第一个元素给到 result}else // 其余的遍历走下面这行代码result = accumulator.apply(result, element);}return foundAny ? Optional.of(result) : Optional.empty();

例子3:

        // 使用 reduce 求作者年龄的最小值// 使用匿名内部类方式Optional<Integer> minAge = authors.stream().map(author -> author.getAge()).reduce(new BinaryOperator<Integer>() {@Overridepublic Integer apply(Integer result, Integer element) {return result > element ? element : result;}});minAge.ifPresent(age -> System.out.println(age));// 使用Lambda表达式Optional<Integer> minAge = authors.stream().map(author -> author.getAge()).reduce((result, element) -> result > element ? element : result);minAge.ifPresent(age -> System.out.println(age));

注意事项:

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

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

3. 不会影响原数据 (我们在流中可以多数据地做很多处理, 但是正常情况下(不调用原数据的set方法等)是不会影响原来集合中的元素的)

三. Optional

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

        比如:

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

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

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

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

1. 创建对象

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


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

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

        你可能会觉得还要加一行代码来封装数据比较麻烦。但是如果改造下 getAuthor 方法,让其的返回值就是封装好的 Optional 的话,我们在使用时就会方便很多。
        而且在实际开发中我们的数据很多是从数据库获取的。Mybatis 从 3.5 版本可以也已经支持 Optional 了。 我们可以直接把 dao 方法的返回值类型定义成 Optional 类型,MyBastis 会自己把数据封装成Optional对象返回。封装的过程也不需要我们自己操作。

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

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

但是一定要注意,如果使用of的时候传入的参数必须不为 null。

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

Optional.empty()

2. 安全消费值

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


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

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

3. 获取值

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

        Author authors = new Author();Optional<Author> authorOptional = Optional.of(authors);System.out.println(authorOptional.get().getName());

4. 安全获取值

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

  • orElseGet

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

        Author authors = getAuthor();Optional<Author> authorOptional = Optional.ofNullable(authors);System.out.println(authorOptional.orElseGet(new Supplier<Author>() {@Overridepublic Author get() {return new Author(); // 为空时的默认返回值}}).getName());

获取数据,如果数据不为空就能获取到该数据。如果为空则根据你传入的参数来创建异常抛出。

  • orElseThrow
        Author authors = getAuthor();Optional<Author> authorOptional = Optional.ofNullable(authors);System.out.println(authorOptional.orElseThrow(new Supplier<Throwable>() {@Overridepublic Throwable get() {return new RuntimeException("数据为空"); // 为空时抛出异常}}).getName());

5. 过滤

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

        Author authors = getAuthor();Optional<Author> authorOptional = Optional.ofNullable(authors);Optional<Author> newAuthor = authorOptional.filter(new Predicate<Author>() {@Overridepublic boolean test(Author author) {return author.getAge() > 18;}});

6. 判断

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

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

7. 数据转换

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


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

        Author authors = getAuthor();Optional<Author> authorOptional = Optional.ofNullable(authors);authorOptional.map(new Function<Author, List<Book>>() {@Overridepublic List<Book> apply(Author author) {return author.getBooks();}}).ifPresent(books -> System.out.println(books));

四. 函数式接口

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

常见函数式接口

  • Consumer消费接口

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

  • Function计算转换接口

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

  • Predicate判断接口

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

  • Supplier生产型接口

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

常见默认方法

  • and

我们在使用 Predicate 接口时候可能需要进行判断条件的拼接。而 and 方法相当于是使用 && 来拼接两个判断条件
例如: 
打印作家中年龄大于17并且姓名的长度大于1的作家。
 

        List<Author> authors = getAuthors();Stream<Author> authorStream = authors.stream();authorStream.filter(new Predicate<Author>() {@Overridepublic boolean test(Author author) {return author.getAge() > 18;}}.and(new Predicate<Author>() {@Overridepublic boolean test(Author author) {return author.getName().length() > 2;}})).forEach(author -> System.out.println(author));// 简写List<Author> authors = getAuthors();Stream<Author> authorStream = authors.stream();authorStream.filter((Predicate<Author>) author -> author.getAge() > 18 && author.getName().length() > 2).forEach(author -> System.out.println(author));

  • or

我们在使用 Predicate 接口时候可能需要进行判断条件的拼接。而 or 方法相当于是使用| |来拼接两个判断条件。
例如:
打印作家中年龄大于17或者姓名的长度小于2的作家。

        List<Author> authors = getAuthors();Stream<Author> authorStream = authors.stream();authorStream.filter(new Predicate<Author>() {@Overridepublic boolean test(Author author) {return author.getAge() > 18;}}.or(new Predicate<Author>() {@Overridepublic boolean test(Author author) {return author.getName().length() > 2;}})).forEach(author -> System.out.println(author));// 简写List<Author> authors = getAuthors();Stream<Author> authorStream = authors.stream();authorStream.filter((Predicate<Author>) author -> author.getAge() > 18 || author.getName().length() > 2).forEach(author -> System.out.println(author));

  • negate

Predicate 接口中的方法。negate 方法相当于是在判断添加前面加了个 ! 表示取反
例如:
打印作家中年龄不大于17的作家。

        List<Author> authors = getAuthors();Stream<Author> authorStream = authors.stream();authorStream.filter(new Predicate<Author>() {@Overridepublic boolean test(Author author) {return author.getAge() > 17;}}.negate()).forEach(author -> System.out.println(author));

五. 方法引用

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


1. 推荐用法

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

转换成


2. 基本格式

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


3. 语法详解(了解)

引用静态方法

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

格式:

类名::方法名

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

例如:
如下代码就可以用方法引用进行简化

        // 使用方法引用前Author authors = getAuthor();Optional<Author> authorOptional = Optional.ofNullable(authors);authorOptional.map(author -> author.getAge()).map(new Function<Integer, String>() {@Overridepublic String apply(Integer age) {return String.valueOf(age);}});// 使用方法引用后Author authors = getAuthor();Optional<Author> authorOptional = Optional.ofNullable(authors);authorOptional.map(author -> author.getAge()).map(String::valueOf);

引用对象的实例方法

格式:

对象名::方法名

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

例如:

        // 使用方法引用前List<Author> authors = getAuthors();Stream<Author> authorStream = authors.stream();StringBuilder sb = new StringBuilder();authorStream.map(author -> author.getName()).forEach(new Consumer<String>() {@Overridepublic void accept(String name) {sb.append(name);}});

优化后:

        // 使用方法引用前List<Author> authors = getAuthors();Stream<Author> authorStream = authors.stream();StringBuilder sb = new StringBuilder();authorStream.map(author -> author.getName()).forEach(sb::append);

引用类的实例方法

格式:

类名::方法名

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


 

    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);}@Testvoid testMethodReference() {// 优化前subAuthorName("周杰伦", new UseString() {@Overridepublic String use(String str, int start, int length) {return str.substring(start, length); // 第一个对象把后面两个按照顺序传入了,所以可以直接简化// 优化后subAuthorName("周杰伦", String::substring); }});}

构造器引用

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

格式:

类名::new

使用前提

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

        // 优化前List<Author> authors = getAuthors();Stream<Author> authorStream = authors.stream();authorStream.map(author -> author.getName()).map(new Function<String, StringBuilder>() {@Overridepublic StringBuilder apply(String name) {return new StringBuilder(name);}}).map(stringBuilder -> stringBuilder.append("好酷哦").toString()).forEach(str -> System.out.println(str));// 优化后List<Author> authors = getAuthors();Stream<Author> authorStream = authors.stream();authorStream.map(author -> author.getName()).map(StringBuilder::new).map(stringBuilder -> stringBuilder.append("好酷哦").toString()).forEach(str -> System.out.println(str));

高级用法

基本数据类型优化

        我们之前用到的很多Stream的方法由于都使用了泛型。所以涉及到的参数和返回值都是引用数据类型。
        即使我们操作的是整数小数,但是实际用的都是他们的包装类。JDK5中引入的自动装箱和自动拆箱让我们在使用对应的包装类时就好像使用基本数据类型一样方便。 但是你一定要知道装箱和拆箱肯定是要消耗时间的。虽然这个时间消耗很下。但是在大量的数据不断的重复装箱拆箱的时候,你就不能无视这个时间损耗了。
        所以为了让我们能够对这部分的时间消耗进行优化。Stream还提供了 很多专门针对基本数据类型的方法。


例如: mapToInt,mapToLong,mapToDouble,flatMapTolnt,flatMapToDouble等。

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

并行流

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

parallel 可以把串行流改成并行流.

        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5 ,6, 7, 8, 9, 10);Integer sum = stream.parallel().peek(new Consumer<Integer>() {@Overridepublic void accept(Integer num) {System.out.println("数字" + num + " 的线程为 " + Thread.currentThread().getName());}}).filter(num -> num > 5).reduce((result, element) -> result + element).get();结果: 
数字7 的线程为 main
数字6 的线程为 main
数字2 的线程为 main
数字1 的线程为 ForkJoinPool.commonPool-worker-11
数字8 的线程为 main
数字4 的线程为 main
数字10 的线程为 ForkJoinPool.commonPool-worker-4
数字5 的线程为 ForkJoinPool.commonPool-worker-11
数字3 的线程为 ForkJoinPool.commonPool-worker-9
数字9 的线程为 ForkJoinPool.commonPool-worker-2

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

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

以上内容参考自 up 主 --- 三更草堂的视频

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/135232.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

再战SDRAM与资料整理。

总之只要阅读操作手册&#xff0c;按照时序来&#xff0c;完全不难&#xff01; 器件记录&#xff1a; 小梅哥AC620上SDRAM&#xff1a;M12L2561616A-6TG2T 其的存储空间为16M*16256MB&#xff0c;第二行的数字则与其速度等级有关&#xff1b;其分为&#xff1a; 4bank*16bit…

ES6的代理模式 | Proxy

&#x1f3ac; 岸边的风&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! 目录 正文 语法 Handler 对象常用的方法 handler.get 可撤消的Proxy Proxy的应用场景 校验器 私有属性 为什么要…

【eXtplorer】本地搭建免费在线文件管理器并实现在外远程登录

文章目录 1. 前言2. eXtplorer网站搭建2.1 eXtplorer下载和安装2.2 eXtplorer网页测试2.3 cpolar的安装和注册 3.本地网页发布3.1.Cpolar云端设置3.2.Cpolar本地设置 4.公网访问测试5.结语 1. 前言 通过互联网传输文件&#xff0c;是互联网最重要的应用之一&#xff0c;无论是…

Java的XWPFTemplate工具类导出word.docx的使用

依赖 <!-- word导出 --><dependency><groupId>com.deepoove</groupId><artifactId>poi-tl</artifactId><version>1.7.3</version></dependency><!-- 上面需要的依赖--><dependency><groupId>org.ap…

【MySQL】基础SQL语句——库的操作

文章目录 一. 创建数据库1.1 基础语句1.2 字符集和校验规则1.3 校验规则对读取数据的影响 二. 查看数据库三. 修改数据库四. 删除数据库及备份4.1 删除4.2 备份和还原 结束语 一. 创建数据库 1.1 基础语句 最简洁的创建数据库的SQL语句是&#xff1a; create database db_nam…

Linux设备驱动模型之platform设备

Linux设备驱动模型之platform设备 上一章节介绍了Linux字符设备驱动&#xff0c;它是比较基础的&#xff0c;让大家理解Linux内核的设备驱动是如何注册、使用的。但在工作中&#xff0c;个人认为完全手写一个字符设备驱动的机会比较少&#xff0c;更多的都是基于前人的代码修修…

深入理解Serverless架构:构建无服务器应用的完全指南

&#x1f482; 个人网站:【工具大全】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 Serverless架构是一种现…

AOSP Android 系统源码编译出的framework.jar和android.jar之间的区别

简介 AOSP&#xff08;Android Open Source Project&#xff09;编译出的 android.jar 和 framework.jar 都是 Android 平台开发中的重要组件&#xff0c;但它们有不同的作用和用途&#xff1a; android.jar&#xff1a; 用途&#xff1a;android.jar 包含了 Android API 的定…

远程计算机控制软件

远程控制软件允许您连接和控制位于不同位置的设备&#xff0c;对于 IT 帮助台技术人员来说&#xff0c;这是一个很好的工具&#xff0c;可以通过与用户协作、与他们聊天以及安全地访问他们的文件来轻松排除故障和修复远程设备。使用远程控制软件&#xff0c;距离不再是提供技术…

LVS + Keepalived群集

文章目录 1. Keepalived工具概述1.1 什么是Keepalived1.2 工作原理1.3 Keepailved实现原理1.4 Keepalived体系主要模块及其作用1.5 keepalived的抢占与非抢占模式 2. 脑裂现象 &#xff08;拓展&#xff09;2.1 什么是脑裂2.2 脑裂的产生原因2.3 如何解决脑裂2.4 如何预防脑裂 …

VMware虚拟机如何设置网络

一直没弄明白怎么能让虚拟机正常上网和访问&#xff0c;最近总结一个小经验 要在宿主机访问虚拟机电脑服务器&#xff0c;要设置成nat格式&#xff0c;虚拟机可以上网&#xff0c;宿主机访问虚拟机上的ip即可访问虚拟机里的服务器&#xff0c;也就是这样设置就行。 这时候ip不…

ubunutu20/18/22 编译android 5相关的问题汇总-千里马framework开源代码平板编译过程

hi&#xff0c;粉丝朋友们&#xff1a; 闲鱼50块钱淘到了一个开源平板&#xff0c;注意这个平板是有源码的&#xff0c;可以进行相关的编译修改。哈哈哈&#xff0c;马哥这边就体验了一下50块钱平板是否可以拿来做framework呢&#xff1f; 哈哈&#xff0c;说好就开干了&#x…

Centos nginx配置文档

1、安装nginx: yum install nginx 2、Nginx常用命令 查看版本:nginx -v 启动:nginx -c /etc/nginx/nginx.conf 重新加载配置:nginx -s reload 停止:nginx -s stop 3、Nginx反向代理配置 nginx配置详解 1、Nginx配置图 详情可以查看:http://nginx.org/ru/docs/example…

华为云云服务器云耀L实例评测 | 智能不卡顿:如何实现流畅的业务运行

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

Typora偏好设置中图床的配置文件点击打开没有反应

Typora偏好设置中图床的配置文件点击打开没有反应 突然发现Typora偏好设置中图床打开配置文件点击没有反应&#xff0c;如下按钮所示 可能是因为系统不知道用什么软件打开json&#xff0c;直接进入配置文件json目录&#xff0c;一般位置在C:\Users\<your_user_name>\.pi…

Qt 围炉札记

文章目录 一、Qt 调试二、vscode 与 Qt1、安装插件&#xff1a;2、设置中配置插件 一、Qt 调试 【Qt调试技巧】Profile配置使用及一些坑 QT运行时的Debug、Release、Profile选项区别 Qt Creator release版本进行调试 【Qt调试技巧】如何在Release下调试Qt程序&#xff1f; …

辅助驾驶功能开发-功能规范篇(21)-4-XP行泊一体方案功能规范

XPilot Parking 自动泊车系统 • 超级自动泊车辅助(Super AutoParking Assist)、语音控制泊车辅助(Autoparking with Speech) - 产品定义 超级自动泊车辅助是⼀个增强的自动泊车辅助系统。在超级自动泊车辅助系统中,识别车位将会变得实时可见, 并且不可泊入的⻋位也将…

GIS前端-地图事件编程

GIS前端-地图事件编程 图层操作事件地图状态事件交互事件弹出框事件导出PDF 在地图上的一切操作均要采用地图事件机制来实现&#xff0c;即通过鼠标、键盘等交互&#xff0c;触发地图相关事件&#xff0c;进而调用相关功能接口函数实现相应的GIS功能。在具体的实现过程中&#…

利用transform和border 创造简易图标,以适应uniapp中多字体大小情况下的符号问题

html: <text class"icon-check"></text> css: .icon-check {border: 2px solid black;border-left: 0;border-top: 0;height: 12px;width: 6px;transform-origin: center;transform: rotate(45deg);} 实际上就是声明一个带边框的div 将其中相邻的两边去…

驱动开发,IO多路复用(select,poll,epoll三种实现方式的比较)

1.IO多路复用介绍 在使用单进程或单线程情况下&#xff0c;同时处理多个输入输出请求&#xff0c;需要用到IO多路复用&#xff1b;IO多路复用有select/poll/epoll三种实现方式&#xff1b;由于不需要创建新的进程和线程&#xff0c;减少了系统资源的开销&#xff0c;减少了上下…