Java8 函数式编程

2023-11-09

函数式编程:这里“函数”应该理解为数学上的函数,即y=f(x)。

函数式编程的理解出发点,比如给Swing中Button添加监听器addListener(Listener接口)为例,没有lambda表达式时一般都是通过匿名内部类new XXXListener()做,由于Java一切皆对象,所以我们传递了一个监听器对象。但是这块语义层面,传递对象并不是最终目的,我们的最终目的就是想传递一个“行为”(比如用户点击Button后的行为),所以采用Lambda表达式这种简洁写法只是在语义层面能够更好一些,但本质还是一样的,lambda也是对象


一、Lambda表达式

1. 函数式接口

函数式接口是只能有一个抽象方法的接口(不论接口中有多少非抽象方法,其抽象方法只能有一个)。Java提供了 @FunctionalInterface 注解用于标记函数式接口,该注解只是检测作用,用于检查一个接口是否为函数式接口,如果不是,则无法编译。Lambda结合函数式接口可以让代码更加简洁明了。JDK8中引入了一个java.util.function包,该包下的接口全部是函数式接口。如果我们想要定义的接口与该包下定义的接口类似,那么拿来即用即可。

1.1 Consumer(消费型接口)

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        //只是返回一个Consumer对象,该对象的accept()方法如下
        return (T t) -> { accept(t); after.accept(t); };
    }
}

1.2 Function(函数型接口)

@FunctionalInterface
public interface Function<T, R> {
    //传入一个T类型,得到一个R类型
    R apply(T t);

//以下都是功能扩展:
    static <T> Function<T, T> identity() {
    //返回参数类型T自身
        return t -> t;
    }

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }
}

1.3 Predicate(断定型接口)

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
 //以下都是功能扩展
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        //返回两个Predicate接口实例的test()方法返回值的&&
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

1.4 Supplier(供给型接口)

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

2.Lambda表达式语法

  1. “->”左侧:方法的参数列表。(参数类型可以省略不写,它可以按照类型自动匹配,若存在相同类型则按形参顺序做匹配。若抽象方法只有一个参数,则“()”可以省略不写,若抽象方法没有参数,则()和->都可以省略不写)
  2. “->”右侧:方法的具体实现(若方法体中只有一条语句则可以省略“{}”,若方法体中只有一条语句,且该语句为return语句,则return关键字可以省略)

Lambda表达式也是对象,它只是使我们实现抽象方法时的代码更加简洁。

public static void main(String[] args) {
	//无参数()和->都省略不写,方法体中只有一条语句则可以省略“{}”,若方法体中只有一条语句,且该语句为return语句,则return关键字可以省略
	Supplier<User> supplierObject = User::new;
	//只有一个参数,()省略不写,只有一条语句,{}省略不写,参数类型不写,自动做类型推断
    Predicate<Integer>  predicateObject = number -> number.equals(10086);
    //两个参数,参数类型不写,自动做类型推断
    BiConsumer<String, User> biConsumerObject = (userId,user) -> {
        System.out.println(userId);
        System.out.println(user);
    };
}

3. 方法引用

Lambda有一个常见的用法,比如想得到一个User的姓名,Lambda的表达式如下:

user -> user.getName()

这种用法非常普遍,因此Java 8 为其提供了一个简单的写法,叫做方法的引用,标准用法为:
ClassName::methodName,需要注意的是,虽然这是一个方法,但不需要在后面加括号。如下:

//
User::getName

二、Stream API

1.惰性求值和及早求值

  1. 返回为Stream对象的API叫做 “惰性求值”(如filter方法),惰性求值不会被立刻执行,它更像是在给及早求值描述一些规则。
  2. 返回一个新集合 或者 其他具体值 的API叫做 及早求值(如count()这种方法),及早求值会立刻被执行。

惰性求值生成一个求值链,然后由一个及早求值返回想要的结果。这个过程和建造者模式有点类似,先一系列操作设置属性和配置,然后最后build()真正返回结果。及早求值方法就是类似与build()方法。

2. 常用的流操作

调用流的API,就相当于将流中的元素交给该API方法进行处理。(Stream中的元素流出来交给lambda,作为函数接口中抽象方法的实参)

流的特点:只能遍历一次 我们可以把流想象成一条流水线,流水线的源头是我们的数据源,数据源中的元素依次被输送到流水线上,我们可以在流水线上对元素进行各种操作。一旦元素走到了流水线的另一头,那么这些元素就被“消费掉了”,我们无法再对这个流进行操作。当然,我们可以从数据源那里再获得一个新的流重新遍历一遍。

流的数据源可以是多种形式,比如集合、数组、以及多个值组合而成。如下:

//1.集合类型
Collection<String> names = new ArrayList<>();
Stream<String> stream = names.stream();

//2.数组,利用Arrays中提供的工具将数组转换为流
String[] names = new String[10];
Stream<String> stream = Arrays.stream(names);

//3.多个值组合
Stream<String> stream = Stream.of("张三","李四","王麻子");

2.1 收集collect

这是一个及早求值的操作,通过传递一个收集器,将流中的元素收集到我们想要的容器中。

public static void main(String[] args) {
	Stream<String> stream = Stream.of("张三","李四","王麻子");
	//将流中的元素收集到列表中
    List<String> names = stream.collect(Collectors.toList());
}

2.2 map

如果一个函数可以将一种类型的值转换为另外一种类型,map操作就可以使用该函数,将一个流中的值转换为一个新的流。这是一个惰性求值的操作。map接收一个Function<T,R>接口的对象,将一个类型的值转换为另一个类型。

Stream<Integer> nameLen = Stream.of("张三","李四","王麻子")
				.map(name -> {
          			return name.length();
          		});

2.3 forEach

它接受一个Consumer接口的对象,遍历流中的元素,是一个及早求值的操作。

Stream.of("张三","李四","王麻子").forEach(name -> {
            System.out.println("姓名:" + name);
        });

2.4 filter

它接受一个断定性接口的对象,将方法返回为false的元素过滤掉,如下,只保留了“张三”。

Stream.of("张三","李四","王麻子").filter(name -> {
            return "张三".equals(name);
        }).forEach(name -> {
            System.out.println("姓名:" + name);
        });

2.5 max和min

Stream上常用的操作之一是求最大值和最小值。而max()和min()正好可以解决这个问题。它接受一个Comparator接口的对象,通过自定义的比较规则求最大、最小值。

3.Optional介绍

Optional是Java8新加入的一个容器,这个容器 只存1个或0个元素,它用于防止出现NullPointException,它提供如下方法:

  • isPresent(): 判断容器中是否有值。
  • ifPresent(Consume lambda): 容器若不为空则执行括号中的Lambda表达式。
  • T get(): 获取容器中的元素,若容器为空则抛出NoSuchElement异常。
  • T orElse(T other): 获取容器中的元素,若容器为空则返回括号中的默认值。

of和ofNullable方法

of 和 ofNullable方法都用于创建包含值的 Optional对象。两个方法的不同之处在于如果你把 null 值作为参数传递进去,of() 方法会抛出NullPointerException。你看,我们并没有完全摆脱 NullPointerException。因此,你应该明确对象不为 null 的时候使用 of()。 如果对象即可能是 null 也可能是非 null,你就应该使用 ofNullable() 方法。

//将user对象放入容器中,但是不清楚user是否为null,如果user不是null,那么获取User的年龄,如果年龄为null,则默认是18岁。
Integer userAge = Optional.ofNullable(user).map(User::getAge).orElse(18);

4 收集器

利用流的collect()方法可以讲流中的元素收集到我们想要的数据结构中,比如前面的List,有时人们还希望将其收集到Set、Map等。JDK已经提供了许多收集器,如下:

  • toSet()
  • toList()
  • toMap()
  • toConcurrentMap()
  • toCollection()

4.1 toMap

/*
1. keyMapper:Key 的映射函数
2. valueMapper:Value 的映射函数
3. mergeFunction:当 Key 冲突时,调用的合并方法
4. mapSupplier:Map 构造器,在需要返回特定的 Map 时使用
*/
public static <T, K, U, M extends Map<K, U>>
    Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
                                Function<? super T, ? extends U> valueMapper,
                                BinaryOperator<U> mergeFunction,
                                Supplier<M> mapSupplier)

//这个方法没有提供mapSupplier参数,默认收集到HashMap中
public static <T, K, U>
    Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                    Function<? super T, ? extends U> valueMapper,
                                    BinaryOperator<U> mergeFunction) {
        return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
    }
//没有mergeFunction和mapSupplier,默认收集到HashMap中,且当键冲突时会抛出异常
public static <T, K, U>
    Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                    Function<? super T, ? extends U> valueMapper) {
        return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
    }

如下,将UserList中的元素收集到TreeMap中:

public static void main(String[] args) {
        List<User> userList = new ArrayList<>();
        userList.add(new User("10086","张三"));
        userList.add(new User("10087","李四"));
		Map<String,User> userMap = userList.stream().collect(Collectors.toMap(user -> user.getUserId() + user.getUserName(), Function.identity(), (key1, key2) -> key1, TreeMap::new));
}

当然Map可以自己指定,集合也可以自己指定,不仅限于Set和List,可以通过如下方式指定:

userList.stream().collect(Collectors.toCollection(TreeSet::new));

4.2 数据分组(groupingBy)

/*
1.classifier:分类器(分类函数)(根据函数返回值来分类)
2.downstream:下游收集器(起名:分组收集器)。每个组的元素将流到下游收集器中。
数据分组后存放在Map中,如下默认将分组后的数据存放在HashMap中
*/
public static <T, K, A, D>
    Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
                                          Collector<? super T, A, D> downstream) {
        return groupingBy(classifier, HashMap::new, downstream);
    }
    
//自定义Map类型,比如可以将分组后的数据存放在TreeMap中等。
public static <T, K, D, A, M extends Map<K, D>>
    Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
                                  Supplier<M> mapFactory,
                                  Collector<? super T, A, D> downstream)
//如下,将UserList按照UserId进行分组,每组是一个Map<String,User>
Map<String, Map<String, User>> userIdMap =
                userList.stream().collect(Collectors.groupingBy(User::getUserId,
                        Collectors.toMap(User::getUserName, Function.identity(), (key1, key2) -> key1, HashMap::new)));
//如下,将UserList按照UserId进行分组,每组是一个List<User>
Map<String, List<User>> userIdMap =
                userList.stream().collect(Collectors.groupingBy(User::getUserId, Collectors.toList()));
//如下,将UserList按照UserId进行分组,每组是一个List<User>,将分组后的数据存放在TreeMap中
TreeMap<String, List<User>> userIdMap =
                userList.stream().collect(Collectors.groupingBy(User::getUserId,TreeMap::new, Collectors.toList()));                

4.3 数据分块(partitioningBy)

数据分块可以将流中元素分为两部分,它使用Predicate对象判断一个元素应该属于哪一部分,并根据布尔值返回一个Map到列表,对于true List中的元素,Predicate返回true,对于其他List中的元素,Predicate返回false。

public static <T>
    Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) {
        return partitioningBy(predicate, toList());
}

如下将UserList中的元素分成两部分,userId为10086的存放在一个List中,剩余其他的存放在一个List中。

public static void main(String[] args) {
        List<User> userList = new ArrayList<>();
        userList.add(new User("10086","张三"));
        userList.add(new User("10087","李四"));
        userList.add(new User("10088","李四"));
        Map<Boolean,List<User>> userMap = userList.stream().collect(Collectors.partitioningBy(user ->  "10086".equals(user.getUserId())));
}

4.3 computeIfAbsent

构建Map时,为给定值计算键值是常用的操作之一,一个经典的例子就是实现一个缓存。传统的处理方式是先试着从Map中取值,如果没有取到,则创建一个新值返回。Java 8引入了一个新方法computeIfAbsent,该方法接受一个Lambda表达式,值不存在时使用该Lambda计算新值,并将新值存入Map中。如下:

private static User getUser(String userId){
	return userCache.computeIfAbsent(userId,this::readUserFromDB);
}

4.4 遍历Map

Java8之前我们遍历Map通常都是先找到Key的集合,然后迭代key集合获取value进行操作。Java 8为Map接口提供了forEach方法,简化我们遍历Map,该方法接受一个BiConsumer对象为参数(该对象接受两个参数,无返回值),通过内部迭代编写出易于阅读的代码。

default void forEach(BiConsumer<? super K, ? super V> action) {
        Objects.requireNonNull(action);
        for (Map.Entry<K, V> entry : entrySet()) {
            K k;
            V v;
            try {
                k = entry.getKey();
                v = entry.getValue();
            } catch(IllegalStateException ise) {
                // this usually means the entry is no longer in the map.
                throw new ConcurrentModificationException(ise);
            }
            action.accept(k, v);
        }
    }

如下,打印Map中的键值对:

userMap.forEach((key,value) -> System.out.println("键:" + key + "\t值:" + value));
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java8 函数式编程 的相关文章

随机推荐

  • std::enable_shared_from_this

    std enable shared from this是一个模板类 能让一个对象 假设其名为 t 且已被一个 std shared ptr 对象 pt 管理 安全地生成其他额外的 std shared ptr 实例 假设名为 pt1 pt2
  • Centos7下添加新硬盘,分区及挂载(包含自动手动挂载)

    一 自动挂载 1 查看当前磁盘信息 fdisk l 可以看到除了当前的第一块硬盘外还有一块vdb的第二块硬盘 接下来需要进行分区 2 分区 fdisk dev vdb 3 初始化物理卷 虚拟组 逻辑卷 注 接下来如果找不到命令 需要安装lv
  • cc1: error: invalid option `abi=aapcs-linux' make[1]: *** [kernel/bounds.s] Error 1 make: *** [prep

    由于内核和busybox编译需要同一个交叉编译器 所以就用来arm linux gcc 3 4 1来编译内核 但是却出现了这样的错误 cc1 error invalid option abi aapcs linux make 1 kerne
  • 大家厚爱

    大家好 很高兴来到这里 希望大家多多交流啊
  • 算法设计与分析部分

    一 算法概述 算法性质 算法是由若干条指令组成的有穷序列 且满足下述4条性质 输入 有零个或多个由外部提供的量作为算法的输入 输出 算法产生至少一个量作为输出 确定性 组成算法的每条指令是清晰的 无歧义的 有限性 算法中每条指令的执行次数是
  • synchronized

    synchronized 1 锁分为类锁和对象锁 类锁的实现方式 1 方法前加 synchronized static 2 synchronized class 对象锁 1 synchronized 2 synchronized objec
  • 【机器学习】线性分类【上】广义线性模型

    有任何的书写错误 排版错误 概念错误等 希望大家包含指正 由于字数限制 分成两篇博客 机器学习 线性分类 上 广义线性模型 机器学习 线性分类 下 经典线性分类算法 1 线性模型 线性模型不仅包括线性回归模型 还包括方差分析模型等 但这里我
  • 特征工程(二)TfidfVectorizer

    将原始数据的word特征数字化为tfidf特征 并将结果保存到本地 article特征可做类似处理 import pandas as pd from sklearn feature extraction text import TfidfV
  • 21天学Python --- 打卡3: Python && Json

    21天学Python 打卡3 Python Json 1 what is json 2 json attribute 3 json model 3 1 类型转换 3 2 Json Python 3 3 Xml Json 4 parse js
  • 已经安装好nginx,如何添加echo模块?

    1 使用命令查看nginx的版本 进入到nginx安装目录下的sbin文件夹 使用命令 nginx V 查看配置参数 2 使用如下命令下载echo模块 下载到那里都可以 wget https github com openresty ech
  • Windwos10启动后 Print Spooler 服务不能自动启动的解决方法

    最近每次启动Windows 10 发现一个奇怪的问题 打印机Print Spooler 服务总是不能自动启动 事实上 Print Spooler 服务的启动类型是 自动 但是偏偏不生效 手动却可以启动 进入控制面板 管理工具 事件查看器 查
  • opencv中利用霍夫变换检测直线对图片进行校正

    图片校正 利用霍夫变换检测直线 校正拍摄倾斜的图片 include
  • 使用日志服务LogHub替换Kafka

    前几天有客户问到 云上有什么服务可以替换Kafka 怀着程序员的一丝小小的骄傲回复 日志服务 原SLS 下LogHub功能可以完全替代Kafka等产品 并且在性能 易用性和稳定性上更佳 但客户将信将疑 于是花了一天时间整理一篇文章 简单从各
  • swoole服务的文件句柄超出系统限制(too many open files)

    最近在项目中遇到一个很奇怪的问题 因为修改配置 redis中缓存的 nginx服务突然报upstream timed out 110 Connection timed out 然后去查为什么会出现这样的问题 发现出问题的服务是一个swool
  • vi批量缩进

    进入vi后 点击v进入VISUAL模式 再使用上下箭头选择行 按 lt gt 操作缩进
  • Tegra X1性能解析

    摘要 它是一个名副其实的性能怪兽 虽然它的图形性能是iPad Air 2上搭载的A8X芯片的两倍 但是耗费的电量却相差不多 腾讯数码讯 编译 Hamish 今天英伟达抢在CES 2015大会召开前发布了新款移动芯片Tegra X1 这是一个
  • qt,设置窗体的颜色和样式

    在 Qt 中 可以使用 QPalette 类来设置窗体的颜色和样式 具体步骤如下 创建一个 QPalette 对象 使用 QColor 类来设置颜色 例如 QColor color 255 255 255 设置为白色 使用 QPalette
  • GD32E103首次烧写程序后J-link无法识别的解决方法

    原因 用的例程的时钟配置与电路板所用晶振频率不一致 导致第一次能识别单片机 烧写程序后 无法再次识别 解决方法 使用J Flash擦除 具体步骤 1 建立Falsh工程 此时BOOT0 0 如果不是复位状态 则无法连接目标板 此时需要将RE
  • 使用openssl命令方式生成公钥、私钥、证书

    以下是在 Windows 环境下使用 OpenSSL 命令生成这些文件的步骤 生成私钥 打开命令提示符 并导航到您希望保存私钥文件的目录 然后执行以下命令以生成私钥文件 例如 private key openssl genpkey algo
  • Java8 函数式编程

    函数式编程 这里 函数 应该理解为数学上的函数 即y f x 函数式编程的理解出发点 比如给Swing中Button添加监听器addListener Listener接口 为例 没有lambda表达式时一般都是通过匿名内部类new XXXL