java8函数式编程学习(一):lambada表达式和stream流的使用

简介

jdk8的新特性中的lambada和stream流的简单使用。

函数式编程

优点

  • 代码的可读性更高,更简洁,开发更快速,易于理解
  • 更高效率的处理大数据量的集合

底层使用了并行流,简化了多线程的用法,不用自己去编写复杂的多线程,而是调用函数式编程的方法,可以高效率的处理大数据集合。

思想

和面向对象不同,不用关注哪个对象完成了什么操作,而是关注对数据做了什么操作,类似于数学中的函数。

lambada表达式

可以对某些匿名内部类进行简化,是函数式编程的一个重要体现,让我们不用关注什么是对象,而是关注数据的操作。

格式

(参数)->{代码}

核心原则是:可推导可省略

如果类型可以推导出来,就省略类型,方法名可以推导出来,就省略方法名。

例子一

java 复制代码
public class lambadaTest {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello,christ");
            }
        }).start();
        //使用lambada表达式编写
        new Thread(()->{
            System.out.println("it's too dark");
        }).start();
    }
}

省略掉的

java 复制代码
new Thread(new Runnable() {
            @Override
            public void run() {}}

其实是多余的格式化的代码,类名方法名都是因为Java格式的限制,lambada只关注它的参数和方法体

例子二

java 复制代码
package com;

import java.util.function.IntBinaryOperator;

public class lambadaTest2 {
    public static void main(String[] args) {
        int i =calculateNum(new IntBinaryOperator() {
            @Override
            public int applyAsInt(int left, int right) {
                return left + right;
            }
        });
        //使用lambada改造
        int j =calculateNum((int left, int right)->{
                return left + right;
            });
        System.out.println(i);
        System.out.println(j);

    }
    //IntBinaryOperator是一个接口,里面只有一个方法applyAsInt
    public static int calculateNum(IntBinaryOperator operator){
        int a = 10;
        int b = 20;
        return operator.applyAsInt(a,b);
    }
}

对于一个匿名内部类,可以在方法名上alt+回车,查看是否可以替换成lambada表达式。

对于一个lambada表达式,也可以还原成普通的匿名内部类。

省略规则

  • 参数类型可以省略
  • 方法体只有一句话时,大括号,return,代码后面的分号都可省略
  • 方法只有一个参数时,小括号可以省略

例子二中的代码

java 复制代码
        int j =calculateNum((int left, int right)->{
                return left + right;
            });

可以省略成

java 复制代码
        int j =calculateNum((left,right)->
                 left + right);

Stream流

stream流使用的是函数式编程,可以更方便的对集合或者数组进行链状流式的操作。

例子

创建一个maven项目

pom.xml文件
xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.24</version>
    </dependency>
</dependencies>
    <groupId>org.example</groupId>
    <artifactId>streamTest</artifactId>
    <version>1.0-SNAPSHOT</version>


</project>
Author类
java 复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class Author {
    private Long id;
    private String name;
    private Integer age;
    private String intro;
    private List<Book> books;
}
Book类
java 复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class Author {
    private Long id;
    private String name;
    private Integer age;
    private String intro;
    private List<Book> books;
}
StreamTest类
java 复制代码
package com;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class StreamTest1{
    public static void main(String[] args) {
        List<Author> authors  = getAuthors();
        //使用流操作,打印出年龄大于25的作者,名字去重
        authors.stream()
                .distinct()
                .filter(author -> author.getAge()>25)
                .forEach(author -> System.out.println(author.getName()));
    }
    private static List<Author> getAuthors() {
        Author author1 = new Author(1L, "作者1", 23, "简介1", null);
        Author author2 = new Author(2L, "作者2", 24, "简介2", null);
        Author author3 = new Author(3L, "作者3", 27, "简介3", null);
        Author author4 = new Author(3L, "作者3", 27, "简介3", null);
        List<Book> books1 = new ArrayList<>();
        List<Book> books2 = new ArrayList<>();
        List<Book> books3 = new ArrayList<>();
        books1.add(new Book(1L, "书名1", "分类1", 100, "信息1"));
        books1.add(new Book(2L, "书名2", "分类1", 99, "信息2"));

        books2.add(new Book(3L, "书名3", "分类2", 98, "信息3"));
        books2.add(new Book(3L, "书名4", "分类2", 97, "信息4"));
        books2.add(new Book(4L, "书名5", "分类1,分类2", 96, "信息5"));

        books3.add(new Book(5L, "书名6", "分类1,分类2", 95, "信息6"));
        books3.add(new Book(6L, "书名7", "分类3", 94, "信息7"));
        books3.add(new Book(6L, "书名8", "分类1,分类2,分类3", 93, "信息8"));

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

        return new ArrayList<>(Arrays.asList(author1, author2, author3,author4));
    }
}

注意:这个去重distinct是根据author类中的@EqualsAndHashCode去判断的,也就是author4要和author3一模一样,才可以起效,一样的id,一样的书籍著作

输出结果
idea进行debug

有专门的stream流工具,可以看到每一步stream流操作的结果。

常用操作

创建stream流
集合:

集合对象.stream()

java 复制代码
package com;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class StreamTest4 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.stream()
        	.forEach(i-> System.out.println(i));
    }
}
数组

Arrays.stream(数组对象)

Stream.of(数组对象)

java 复制代码
package com;

import java.util.stream.Stream;

public class StreamTest3 {
    public static void main(String[] args) {
        Integer[] arr =  {1,2,3,4,5,6};
        Stream.of(arr)
      		  .forEach(arr3-> System.out.println(arr3));
    }
}
map集合

先把map集合使用entrySet()方法转换成set对象,然后这个set对象.stream()转换成流对象。

java 复制代码
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;

public class StreamTest2 {
    public static void main(String[] args) {
        Map<String,String> map = new HashMap<>();
        map.put("one","1");
        map.put("two","2");
        map.put("three","3");
        Set<Map.Entry<String, String>> entries = map.entrySet();
        entries.stream()
               .forEach(entry-> System.out.println(entry.getKey()+"----"+entry.getValue()));
    }
}
filter

可以对stream流中的元素进行过滤。

java 复制代码
package com;

import java.util.stream.Stream;

public class StreamTest5 {
    public static void main(String[] args) {
        Integer[] arr =  {1,2,3,4,5,6};
        Stream.of(arr)
                .filter(arr3->arr3>3)
                .forEach(arr3-> System.out.println(arr3));
    }
}
map

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

在上面那个author,book的例子中

java 复制代码
    public static void main(String[] args) {
        List<Author> authors  = getAuthors();
        authors.stream()
                .map(author -> author.getName())
                .forEach(author-> System.out.println(author));
                }

从debug中,可以看到,stream流中的元素已经从author转变为string

使用map进行计算

java 复制代码
    public static void main(String[] args) {
        List<Author> authors  = getAuthors();
        authors.stream()
                .map(age -> age.getAge())
                .map(age->age+10)
                .forEach(author-> System.out.println(author));
                }
distinct

可以去除流中的重复数据,它依赖于object中的equals方法,所以流中的元素的对象要重写equals方法。

java 复制代码
package com;
import java.util.stream.Stream;

public class StreamTest6 {
    public static void main(String[] args) {
        Integer[] arr =  {1,1,2,2,3,3,4,4,5,6};
        Stream.of(arr)
                .distinct() 
                .forEach(arr3-> System.out.println(arr3));
    }
}
sorted

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

java 复制代码
import java.util.stream.Stream;

public class StreamTest7 {
    public static void main(String[] args) {
        Integer[] arr =  {5,3,1,2,6};
        Stream.of(arr)
                .sorted()
                .forEach(arr3-> System.out.println(arr3));
    }
}

如果比较的是对象,对象要实现comparable接口,重写compareto方法。

limit

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

java 复制代码
package com;

import java.util.stream.Stream;

public class StreamTest8 {
    public static void main(String[] args) {
        Integer[] arr =  {1,2,3,4,5};
        Stream.of(arr)
                .limit(2)
                .forEach(arr3-> System.out.println(arr3));
    }
}
skip

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

java 复制代码
package com;

import java.util.stream.Stream;

public class StreamTest9 {
    public static void main(String[] args) {
        Integer[] arr =  {1,2,3,4,5};
        Stream.of(arr)
                .skip(2)
                .forEach(arr3-> System.out.println(arr3));
    }
}
flatmap

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

在上面那个author,book的例子中

java 复制代码
        List<Author> authors  = getAuthors();
        authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .forEach(book -> System.out.println(book));

如果是使用map,那只能转换成book的集合

上面 author.getBooks()返回的是一个list对象,而这个匿名类是需要返回stream类型的,所以还需要把list转换成stream流。

而这个会把集合中的每个元素都变成stream流中的元素。

在上面那个author,book的例子中,输出书籍的分类

java 复制代码
authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .flatMap(book->Arrays.stream(book.getCategory().split(",")))
                .forEach(category-> System.out.println(category));
                }

终结操作

使用stream流必须要有终结操作,不然其他的操作都不会执行。使用了流方法之后,stream流就关闭了,不能继续使用链状方式继续调用方法了。

foreach

对stream流中的元素进行遍历操作,通过传入的参数去指定对遍历到的元素进行具体的操作。上面的例子都用到了。

count

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

java 复制代码
package com;

import java.util.stream.Stream;

public class StreamTest10 {
    public static void main(String[] args) {
        Integer[] arr =  {1,2,3,4,5};
        long count = Stream.of(arr)
                .count();
        System.out.println(count);
    }
}
max

获得stream流中的最大值

java 复制代码
package com;

import java.util.Optional;
import java.util.stream.Stream;

public class StreamTest11 {
    public static void main(String[] args) {
        Integer[] arr =  {1,2,3,4,5};
        Optional<Integer> max = Stream.of(arr)
                .max((a, b) -> a - b);
        System.out.println(max.get());
    }
}
min

获得stream流中的最小值

java 复制代码
package com;

import java.util.Optional;
import java.util.stream.Stream;

public class StreamTest12 {
    public static void main(String[] args) {
        Integer[] arr =  {1,2,3,4,5};
        Optional<Integer> min = Stream.of(arr)
                .min((a, b) -> a - b);
        System.out.println(min.get());
    }
}
collect

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

在上面那个author,book的例子中:

获取一个存放所有作者名字的list集合

java 复制代码
        List<Author> authors  = getAuthors();
        List<String> nameList = authors.stream()
                .map(author -> author.getName())
                .collect(Collectors.toList());
        System.out.println(nameList);

获取一个所有书名的set集合

java 复制代码
        List<Author> authors  = getAuthors();
        Set<Book> bookSet = authors.stream()
                .flatMap(author -> author.getBooks().stream())
                .collect(Collectors.toSet());
        System.out.println(bookSet);

获取一个map集合,map的key是作者的名字,value是List< book>

java 复制代码
        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

判断stream流中是否有任意符合匹配条件的元素,返回boolean。

java 复制代码
package com;

import java.util.Optional;
import java.util.stream.Stream;

public class StreamTest13 {
    public static void main(String[] args) {
        Integer[] arr =  {1,2,3,4,5};
        boolean b = Stream.of(arr)
                .anyMatch(a -> a == 1);
        System.out.println(b);
    }
}
allMatch

判断stream流中所有的元素都符合匹配条件,返回boolean。

java 复制代码
public class StreamTest14 {
    public static void main(String[] args) {
        Integer[] arr =  {1,2,3,4,5};
        boolean b = Stream.of(arr)
                .allMatch(a -> a > 0);
        System.out.println(b);
    }
}
noneMatch

判断stream流中所有的元素都不符合匹配条件,返回boolean。

java 复制代码
package com;

import java.util.stream.Stream;

public class StreamTest15 {
    public static void main(String[] args) {
        Integer[] arr =  {1,2,3,4,5};
        boolean b = Stream.of(arr)
                .noneMatch(a -> a < 0);
        System.out.println(b);
    }
}
findany

获取stream流中的任意一个元素。

java 复制代码
package com;

import java.util.Optional;
import java.util.stream.Stream;

public class StreamTest16 {
    public static void main(String[] args) {
        Integer[] arr =  {1,2,3,4,5};
        Optional<Integer> a = Stream.of(arr)
                .findAny();
        System.out.println(a.get());
    }
}

奇怪的是一直返回1.

findfirst

获取流中的第一个元素。

java 复制代码
package com;

import java.util.Optional;
import java.util.stream.Stream;

public class StreamTest17 {
    public static void main(String[] args) {
        Integer[] arr =  {1,2,3,4,5};
        Optional<Integer> a = Stream.of(arr)
                .findFirst();
        System.out.println(a.get());
    }
}

reduce归并

对stream流中的数据按照指定的计算方式计算出一个结果。会传入一个初始值,这个初始值会和stream流中的元素(循环遍历每一个)进行计算,得到的结果赋值给这个初始值变量,继续下一个元素(下一轮的循环),最后得到一个结果。

它的底层代码是:

在上面那个author,book的例子中:

计算作者的年龄的总和

java 复制代码
        List<Author> authors  = getAuthors();
        Integer sun = authors.stream()
                .map(author -> author.getAge())
                .reduce(0, (result, element) -> result + element);
        System.out.println(sun);

解析:

reduce(0, (result, element) -> result + element)这行代码中的0就是初始值。

有点类似于

java 复制代码
package com;

import java.util.Optional;
import java.util.stream.Stream;

public class StreamTest18 {
    public static void main(String[] args) {
        Integer[] arr =  {1,2,3,4,5};
        int sum = 0;
        for(Integer i:arr){
            sum = sum + i;
        }
        System.out.println(sum);
    }
}
使用reduce求最大值
java 复制代码
package com;

import java.util.ArrayList;
import java.util.List;

public class StreamTest19 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(2);
        list.add(3);
        list.add(1);
        Integer min = list.stream()
                .reduce(Integer.MIN_VALUE, (result, element) -> result < element ? element : result);
        System.out.println(min);
    }
}
传入一个参数的reduce方法

底层的实现是:

相当于是把stream流中的第一个元素赋值给了result。

stream流的注意事项

  • 如果没有终结操作,流的操作是不会开始执行的
  • 流式一次性的,使用了终结操作以后,是不能再使用了
  • 对stream流里面的元素做了很多的处理操作,但这些都不会影响到原来的集合中的元素的。
相关推荐
菜的不敢吱声19 分钟前
swift学习第4天
服务器·学习·swift
李慕婉学姐4 小时前
【开题答辩过程】以《基于JAVA的校园即时配送系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·开发语言·数据库
想进部的张同学4 小时前
hilinux-3599---设备学习---以及部署yolo
学习·yolo·海思
HyperAI超神经5 小时前
【vLLM 学习】Rlhf
人工智能·深度学习·学习·机器学习·vllm
奋进的芋圆5 小时前
Java 延时任务实现方案详解(适用于 Spring Boot 3)
java·spring boot·redis·rabbitmq
sxlishaobin5 小时前
设计模式之桥接模式
java·设计模式·桥接模式
model20055 小时前
alibaba linux3 系统盘网站迁移数据盘
java·服务器·前端
荒诞硬汉6 小时前
JavaBean相关补充
java·开发语言
提笔忘字的帝国6 小时前
【教程】macOS 如何完全卸载 Java 开发环境
java·开发语言·macos
2501_941882486 小时前
从灰度发布到流量切分的互联网工程语法控制与多语言实现实践思路随笔分享
java·开发语言