【硬核】源码 + 案例分享 JDK8 新特性中的四大函数式接口

简介: 交代一下这次分享技术的背景!因为我是后端开发(会点前端),在工作中使用的开发语言就是 Java。然,技术的更新迭代速度太快了就拿 Java 来说都已经更新到 JDK17,而我相信大部分公司使用的还停留在 JDK8 这个阶段甚至 JDK7 的也不在少数。而我现阶段开发的项目使用的版本是 JDK11 ,项目中有着大量的 JDK8 新特性特别是流式编程和 Lambda 表达式的应用。而 Lambda 表达式要想写的好的话,那下面要谈的这四大函数式接口就是前提,所以废话不多说了,往下看吧,肯定能让你有所收获的。


一、Consumer:消费型接口


源码如下:


image.png

@FunctionalInterface 注解说明该接口是一个函数式接口(接口中只有一个抽象方法)。


参数 T 就是我们要处理的对象类型。


功能:实现 Consumer 接口中的 accept 方法,对类型 T 的对象进行任意操作(自己实现处理逻辑),所以这个接口也称之为消费性接口。


使用:


方式一


创建一个类,实现该接口

class MyConsumer<T> implements Consumer<T> {
    @Override
    public void accept(T t) {
        // 自己的处理逻辑
        System.out.println("======开始做一下事情,对传入的 T======");
        System.out.println("正在对 T(" + t + ") 做事情...");
        System.out.println("======end======");
    }
}


测试

public class ConsumerTest {
    @Test
    public void test() {
        MyConsumer<String> stringMyConsumer = new MyConsumer<>();
        List<String> stringList = Arrays.asList("a", "b", "c", "d");
        // 遍历
        for (String s : stringList) {
            // 处理每一个元素
            stringMyConsumer.accept(s);
        }
    }
}


这种方式完全没有利用到 JDK8 的特性,只是用了它提供的一个接口而已,那下面我们就换种方式。


方式二


直接一步到位:


public class ConsumerTest {
    @Test
    public void test2() {
        List<String> stringList = Arrays.asList("a", "b", "c", "d");
        // 利用 Lambda 表达式
        stringList.forEach(str -> {
            System.out.println("我可以对 str(" + str + ") 做任何事情");
        });
    }
}


如果上面不理解哪里用到了消费型接口,那我拆分一下


public class ConsumerTest {
    @Test
    public void test3() {
        List<String> stringList = Arrays.asList("a", "b", "c", "d");
        handle(stringList, s -> {
            // 内部类参数,实现处理逻辑
            System.out.println("我可以对 s(" + s + ") 做任何事情,而且我还知道他是什么类型");
        });
    }
    public <T> void handle(List<T> list, Consumer<T> consumer) {
        for (T t : list) {
            // 调用处理方法
            consumer.accept(t);
        }
    }
}


是不是非常清晰了!

其实我们在 JDK8 中经常使用的 forEach 遍历就是一种消费型接口的实现模式。


二、Supplier:供给型接口


源码:


image.png


功能:提供指定类型的数据出去,所以也称该接口为供给型接口。

使用


方式一


  1. 创建一个类,实现该接口
@Data
class MySupplier<T> implements Supplier<T>{
    private String name;
    @Override
    public T get() {
        // 我将提供一个指定 T 类型数据出去
        // 这里的 T 我先内定为 MySupplier 类型进行编写案例
        MySupplier<T> t = new MySupplier<>();
        t.setName("哈哈!");
        return (T) t;
    }
}


测试

public class SupplierTest {
    @Test
    public void test1(){
        MySupplier<MySupplier> stringMySupplier = new MySupplier<>();
        System.out.println(stringMySupplier.get());
    }
}
// SupplierTest.MySupplier(name=哈哈!)


还是老样子,这种方式也是没有利用 JDK8 特性,那我们改造一下

方式二


public class SupplierTest {
    @Test
    public void test2(){
        Supplier<String> stringSupp = () ->{
            // 你的逻辑
            return "调用get方法,我才给你数据";
        };
        System.out.println(stringSupp.get());
    }
}
// 调用get方法,我才给你数据


说一下我的理解,这个类型接口主要的就是提供数据,那么在项目中的话我可以事先定义好一组数据,在特定的时候通过事先定义好的对象去 get 就行。


场景:


用户购买商品,进入业务,判断用户购买的条件是否满足附送礼品的条件,如果满足,那么判断礼品种类(A,B,C等),然后根据 Supplier 类型接口实现类获取对应的礼品返回给用户购买的商品列表中。这样的好处就是用户不满足条件时是不会给项目创建礼品对象的,只有 get 方法调用,才会生成对象形成一个懒加载的效果。


三、Function:函数型接口


源码:


image.png


参数 R 就是该接口方法返回的类型。


功能:传入一个 T 类型对象,调用 apply 方法进行处理,最终于返回处理后的数据类型为 R


使用


方式一


  1. 编写一个类,实现该接口
class MyFunction<T, R> implements Function<T, R> {
    @Override
    public R apply(T t) {
        System.out.println("处理逻辑....【" + t + "】");
        return null;
    }
}


2.测试

public class FunctionTest {
    @Test
    public void test1() {
        MyFunction<String, String> myFunction = new MyFunction<>();
        myFunction.apply("J3-白起...");
    }
}
// 处理逻辑....【J3-白起...】


老样子,我们用新特性改造一下。


方式二


public class FunctionTest {
    @Test
    public void test2() {
        Function<String, String> myFunction = (t) ->{
            System.out.println("处理逻辑....【" + t + "】");
            return "success";
        };
        myFunction.apply("J3-白起...");
    }
}
// 处理逻辑....【J3-白起...】


上面就是这个接口的简单使用,那我再来写个案例方便理解。


案例


需求:根据一个姓名列表,获取对应姓名长度的列表,实现代码如下:


public class FunctionTest {
    @Test
    public void test3() {
        List<String> nameList = Arrays.asList("J3-baiqi", "shaheshagn", "sunwukong");
        List<Integer> nameLengthList = functionHandle(nameList, String::length);
        System.out.println(nameLengthList);
        // [8, 10, 9]
    }
    /**
     * 获取列表中,姓名的长度
     *
     * @param nameList 名字列表
     * @param function 名字长度列表
     * @return
     */
    public List<Integer> functionHandle(List<String> nameList, Function<String, Integer> function) {
        List<Integer> nameLengthList = new ArrayList<>(nameList.size());
        nameList.forEach(name -> nameLengthList.add(function.apply(name)));
        return nameLengthList;
    }
}


讲解一下:


我分两个地方说明,一个是实参、一个是形参。


先说形参,对于该需求,我抽了一个方法出来,专门实现获取列表中元素的长度。功能很明确就是获取一个长度值,但具体是如何实现,我不管,我只要传入数据给你,你返回我需要的数字就行,所以我将形参定义为 Function 接口,在要获取数字的的时候,调用接口的 apply 方法即可。


对于实参,调用方法时,我需要传递 Function 接口类型对象进去,我可以写一个实现类,然后实现 apply 方法,接着通过各种新奇古怪的技术,获取传入的对象长度就行。但既然是 JDK8 那我就完全可以用新特性去实现(方法引用)。


场景:这个在项目中使用的场景是非常多的,我经常是需要抽取方法,然而方法上的形参我会定义成一个接口形成规范,因为我不管实现,我只管调用就行。以后有其他同事使用我的方法时,你自己去实现我形参中的接口就行,然后我的方法就会根据你实现的接口获取数据进而给你想要的结果。


四、Predicate:断定型接口


源码:


image.png


功能:传入指定类型对象,返回 true 或 false,所以也称该接口为断定型接口。


使用


方式一


  1. 编写一个类,并实现该接口
class MyPredicate<T> implements Predicate<T> {
    @Override
    public boolean test(T t) {
        // 判断如果传入的类型 T 是 String 那么判断是否含有 "J3" 字样
        if (t instanceof String) {
            return ((String) t).contains("J3");
        }
        if (t instanceof Integer) {
            return ((Integer) t) == 18;
        }
        // 如果是 Integer 类型,判断是否等于 18
        return false;
    }
}


2.测试


public class PredicateTest {
    @Test
    public void test1(){
        MyPredicate<String> stringMyPredicate = new MyPredicate<>();
        System.out.println(stringMyPredicate.test("J3-白起"));
        System.out.println(stringMyPredicate.test("白起"));
        MyPredicate<Integer> integerMyPredicate = new MyPredicate<>();
        System.out.println(integerMyPredicate.test(18));
        System.out.println(integerMyPredicate.test(28));
    }
}
//true
//false
//true
//false
// 本人依旧是 J3 依旧是18,哈哈!


老样子,看一下下面改造的样子


方式二

public class PredicateTest {
    @Test
    public void test2() {
        Predicate<String> myStringPredicate = (t) -> t.contains("J3");
        System.out.println(myStringPredicate.test("J3-白起"));
        System.out.println(myStringPredicate.test("白起"));
        Predicate<Integer> myIntegerPredicate = (t) -> t == 18;
        System.out.println(myIntegerPredicate.test(18));
        System.out.println(myIntegerPredicate.test(28));
    }
}   


这个类型接口关键点在于返回的结果是一个 boolean 类型,那它的定位就非常清楚了,用于判断而已,具体你要判断什么,这就取决于你了。


需求案例:


现在我们有个系统是分 pc 和 app 端的,用户注册后信息进入业务中,就要区别出来是哪个端的用户注册。这就可以用到 Predicate 接口了,将用户信息传给 test,然后就可以编写判断逻辑根据对应的信息区别出是 pc 还是 app 了。


五、最后


最后,画一张表简单归纳一下上面分析的四个接口如下:


image.png


好了,今天的内容到这里就结束了,关注我,我们下期见


由于博主才疏学浅,难免会有纰漏,假如你发现了错误或偏见的地方,还望留言给我指出来,我会对其加以修正。


如果你觉得文章还不错,你的转发、分享、点赞、留言就是对我最大的鼓励。


感谢您的阅读,十分欢迎并感谢您的关注。


^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

目录
相关文章
|
1月前
|
Oracle Java 关系型数据库
CentOS 7.6操作系统部署JDK实战案例
这篇文章介绍了在CentOS 7.6操作系统上通过多种方式部署JDK的详细步骤,包括使用yum安装openjdk、基于rpm包和二进制包安装Oracle JDK,并提供了配置环境变量的方法。
230 80
|
8天前
|
Java
让星星⭐月亮告诉你,jdk1.8 Java函数式编程示例:Lambda函数/方法引用/4种内建函数式接口(功能性-/消费型/供给型/断言型)
本示例展示了Java中函数式接口的使用,包括自定义和内置的函数式接口。通过方法引用,实现对字符串操作如转换大写、数值转换等,并演示了Function、Consumer、Supplier及Predicate四种主要内置函数式接口的应用。
14 1
|
27天前
|
容器
jdk8新特性-详情查看文档
jdk8新特性-详情查看文档
35 3
|
13天前
|
存储 安全 Java
JDK1.8 新的特性
JDK1.8 新的特性
15 0
|
1月前
|
编解码 安全 Java
jdk8新特性-接口和日期处理
jdk8新特性-接口和日期处理
|
2月前
|
算法 安全 Java
深入JDK源码:揭开ConcurrentHashMap底层结构的神秘面纱
【8月更文挑战第24天】`ConcurrentHashMap`是Java并发编程中不可或缺的线程安全哈希表实现。它通过精巧的锁机制和无锁算法显著提升了并发性能。本文首先介绍了早期版本中使用的“段”结构,每个段是一个带有独立锁的小型哈希表,能够减少线程间竞争并支持动态扩容以应对高并发场景。随后探讨了JDK 8的重大改进:取消段的概念,采用更细粒度的锁控制,并引入`Node`等内部类以及CAS操作,有效解决了哈希冲突并实现了高性能的并发访问。这些设计使得`ConcurrentHashMap`成为构建高效多线程应用的强大工具。
48 2
|
2月前
|
Oracle Java 关系型数据库
JDK8到JDK29版本升级的新特性问题之未来JDK的升级是否会成为必然趋势,如何理解
JDK8到JDK29版本升级的新特性问题之未来JDK的升级是否会成为必然趋势,如何理解
|
1月前
|
Java 编译器 API
JDK8新特性--lambda表达式
JDK8的Lambda表达式是Java语言的一大进步。它为Java程序提供了更多的编程方式,让代码更加简洁,也让函数式编程的概念在Java中得到了体现。Lambda表达式与Java 8的其他新特性,如Stream API、新的日期时间API一起,极大地提高了Java编程的效率和乐趣。随着时间的流逝,Java开发者对这些特性的理解和应用将会越来越深入,进一步推动Java语言和应用程序的发展。
11 0
|
1月前
|
Java
安装JDK18没有JRE环境的解决办法
安装JDK18没有JRE环境的解决办法
191 3
|
2月前
|
Java 关系型数据库 MySQL
"解锁Java Web传奇之旅:从JDK1.8到Tomcat,再到MariaDB,一场跨越数据库的冒险安装盛宴,挑战你的技术极限!"
【8月更文挑战第19天】在Linux上搭建Java Web应用环境,需安装JDK 1.8、Tomcat及MariaDB。本指南详述了使用apt-get安装OpenJDK 1.8的方法,并验证其版本。接着下载与解压Tomcat至`/usr/local/`目录,并启动服务。最后,通过apt-get安装MariaDB,设置基本安全配置。完成这些步骤后,即可验证各组件的状态,为部署Java Web应用打下基础。
51 1