首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

JAVA基础之5-函数式接口的实现

编程知识
2024年09月12日 18:09

之所以单独把这个列出来,是因为本人被一个源码给震撼了。

所以,本人目的是看看这个震撼实现,并模仿,最后把常规的实现也贴上,让读者可以看到相对完整的实现

注:本文代码基于JDK17

 

一、让人震撼的代码

Collectors.toList()

 public static <T>
    Collector<T, ?, List<T>> toList() {
        return new CollectorImpl<>(ArrayList::new, List::add,
                                   (left, right) -> { left.addAll(right); return left; },
                                   CH_ID);
    }

我们看下CollectorImpl的构造器:

CollectorImpl(Supplier<A> supplier,
                      BiConsumer<A, T> accumulator,
                      BinaryOperator<A> combiner,
                      Set<Characteristics> characteristics) {
            this(supplier, accumulator, combiner, castingIdentity(), characteristics);
        }

第二个参数是BiConsumer<A, T>,再看下BiConsumer的接口方法:

void accept(T t, U u);

 

按照正常的逻辑,toList()调用CollectorImpl的时候,应该传递一个有两个参数的方法,但是List.add只有一个参数。

List.add有2个实现:

boolean add(E e)
void add(int index, E element);

很明显,不可能指向那个两个参数的实现,因为参数类型明显不匹配,而只能指向 boolean add(E e)

但问题add(E e)看起来更不配,因为它只有一个参数。

现实是,编译器不会报告错误,而且能得到正确的结果。

为什么了?

思来想去,只能说JCP改了规则--为了达到目的,JCP不惜违背常规允许有独特的实现

在以往的源码中,我们看到的好像都是要求参数个数和类型匹配的?

 

二、我的模仿和可能的解释

为了确认这种独特的函数式接口实现,我做了个一个测试,在测试代码中:

1.创建一个类似ArrayList的类

2.写了一段测试代码,验证奇特的实现

具体代码如下:


package study.base.oop.interfaces.functional.shockingimplement;

/**
* 中学生
* @param name
* @param age
* @param gender
*/
public record MiddleStudent(
String name, Integer age, String gender
) {
}


package study.base.oop.interfaces.functional.shockingimplement;

import java.util.function.BiConsumer;

/**
* 用于演示令人震惊的 lambda 表达式*
* <br/>
* <br/> 作为一个对比,可以看看 {@linkplain study.base.oop.interfaces.functional.stdimplement.impl.StudentSortImpl 函数式接口的几种基本实现 }
* @author lzfto
* @date 2024/09/12
*/
public class ShockingList {
private MiddleStudent[] room;
public ShockingList() {
this.room = new MiddleStudent[10];
}

public void add(MiddleStudent student) {
expand();
//查找room最后一个不为null的位置,然后添加student
for (int i = 0; i < room.length - 1; i++) {
if (this.room[i] == null) {
this.room[i] = student;
return;
}
}
System.out.println("超出房间容量,无法插入新的成员");
}

private void expand(){
//如果room的最后一个不是null,那么room扩容10个位置
if (this.room[this.room.length-1] != null) {
MiddleStudent[] temp = new MiddleStudent[this.room.length + 10];

//room的元素全部复制到temp中,然后this.room指向temp
for (int i = 0; i < this.room.length; i++) {
temp[i] = this.room[i];
}
this.room = temp;
}
}

public static void main(String[] args) {
/**
* 演示这种奇怪的BiConsumer的用法,或者说是 郎打语法
*/
ShockingList list = new ShockingList();
list.add(new MiddleStudent("张三", 18, ""));
BiConsumer<ShockingList, MiddleStudent> consumer = ShockingList::add;
consumer.accept(list, new MiddleStudent("李四", 19, ""));
for (MiddleStudent middleStudent : list.room) {
if(middleStudent != null){
System.out.println(middleStudent);
}
}
}
}
 

测试后,输出的结果如下图:

 

根据java的例子和我自己的编写例子,我只能得出这样的某种猜测:

如果函数式接口方法F要求2个参数(R ,T),那么当引用对象方法(假定对象称为 Test,方法是 shockMe) 实现函数式接口的时候,允许引用这样的接口:

1.Test.ShockMe可以有一个参数,类型为T,ShockMe的方法返回类型同F的返回类型,或者都是Void.class

2.Test本身是R类型

那么JCP认为这是合规的。

根据这种推测,那么可能也允许:F有n个参数,但是ShockMe有n-1个参数的情况。暂时未验证。

 

JCP为什么要允许这种的实现可行了?大概是为了向后兼容,不想浪费已有的各种实现。

我们反过来想,如果不允许这样,那么JAVA应该怎么办?

以toList()为例,那么就必须增加一个实现方法,或者额外写几个工具类。JCP不知道出于什么考虑,想出了这个比较其它的实现。

虽然这种实现有其好处:向后兼容,不浪费。但也造成代码不容易看懂(是的,我迷惑了很久)。

不知道其它语言是否有类似的情况。

三、函数式接口标准5个实现

以下代码,在我的其它文章也有:JAVA基础之四-郎打表达式、函数式接口、流的简介

为了方便,重复一次

package study.base.oop.interfaces.functional.stdimplement.impl;

import study.base.oop.interfaces.functional.stdimplement.Face;
import study.base.oop.interfaces.functional.stdimplement.IFace;
import study.base.oop.interfaces.functional.stdimplement.Isort;
import study.base.oop.interfaces.functional.stdimplement.Sort;

/**
 * 本类主要演示了函数式接口的几种实现方式:
 * </br>
 * </br> 1.使用实现类  - 最传统的
 * </br> 2.使用Lambda表达式 - 还是比较方便的
 * </br> 3.使用匿名类 - 和郎打差不多
 * </br> 4.方法引用 -   应用另外一个同形方法(多式对实例)
 * </br> 5.构造器引用 -  应用另外一个同形构造方法
 * </br> 6.静态方法引用 -  应用另外一个同形静态方法
 * @author lzf
 */
public class StudentSortImpl implements Isort {

    @Override
    public int add(int a, int b) {
        int total = a + b;
        System.out.println(total);
        this.doSomething(a,b);
        return total;
    }

    public static void main(String[] args) {
        // 1.0 函数式接口的传统实现-类实现
        System.out.println("1.函数式接口的实现方式一:实现类");
        Isort sort = new StudentSortImpl();
        sort.add(10, 20);

        // 函数式接口的实现二-朗打方式
        System.out.println("2.函数式接口的实现方式一:朗打表达式");
        // 2.1 有返回的情况下,注意不要return语句,只能用于单个语句的
        // 如果只有一个参数,可以省掉->前的小括弧
        // 如果有返回值,某种情况下,也可以省略掉后面的花括弧{}
        // 有 return的时候
        // a->a*10
        // (a)->{return a*10} 要花括弧就需要加return
        // (a,b)->a+b
        // (a,b)->{return a+b;}
        Isort sort2 = (a, b) -> a + b;
        Isort sort3 = (a, b) -> {
            return a * 10 + b;
        };

        // 2.2 有没有多条语句都可以使用 ->{}的方式
        Isort sort4 = (a, b) -> {
            a += 10;
            return a + b;
        };
        
        int a=10;
        int b=45;
        int total=sort2.add(a, b)+sort3.add(a, b)+sort4.add(a, b);
        System.out.println("总数="+total);

        // 3 使用 new+匿名函数的方式来实现
        System.out.println("3.函数式接口的实现方式一:匿名类");
        Isort sort5 = new Isort() {
            @Override
            public int add(int a, int b) {
                int total = a * a + b;
                System.out.println(total);
                return total;
            }

        };
        sort5.add(8, 2);

        // 4.0 基于方法引用-利用已有的方法,该方法必须结构同接口的方式一致
        // 在下例中,从另外一个类实例中应用,而该实例仅仅是实现了方法,但是没有实现接口
        // 可以推测:编译的时候,通过反射或者某些方式实现的。具体要看编译后的字节码
        System.out.println("4.函数式接口的实现方式一:方法引用");
        Sort otherClassSort=new Sort();
        Isort methodSort = otherClassSort::add;
        methodSort.add(90, 90);
        
        // 5.0 基于构造函数
        // 这种方式下,要求构造函数返回的对象类型同函数接口的返回一致即可,当然参数也要一致
        System.out.println("5.函数式接口的实现方式一:构造函数引用");
        IFace conSort=Face::new;
        
        Face face=conSort.show(10, 90);
        face.write();
        //小结:基于方法和基于构造函数的实现,应该仅仅是为了stream和函数式服务,和朗打没有什么关系
        //这个最主要是为了编写一个看起来简介的表达式。
        // 6.0 基于静态方法
        System.out.println("6.函数式接口的实现方式一:静态方法引用");
        Isort staticSort=Integer::sum;
        int total2=staticSort.add(1,2);
        System.out.println("total2="+total2);
    }
}
                                  

 

四、小结

JCP对于函数式接口的这种迷惑实现,让我感到震惊。

这种震惊让我认为:不排除可能还有更奇葩的实现。 如果有,以后再补上。

最后,我也有点好奇其它常用的语言是否有这种实现  -- 毕竟这个编辑器和编译器出了难题。

From:https://www.cnblogs.com/lzfhope/p/18410855
本文地址: http://shuzixingkong.net/article/1956
0评论
提交 加载更多评论
其他文章 41岁的大龄程序员,苟着苟着,要为以后做打算了
最近看到 薰衣草写的《写在临近40岁的年龄》 多少有点感慨,直到看到初九写的《40岁大龄失业程序猿,未来该何去何从》 ,有点绷不住了,如果说薰衣草写的有点抒情,离心里的距离还有点远,而初九的情况简直像照进现实的镜子,映射出未来可能遇到的困境。 许多人在想:“活人难道真会被尿憋死?”觉得桥到床头自然直
简单聊聊 CORS 攻击与防御
我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。 本文作者:霁明 什么是CORS CORS(跨域资源共享)是一种基于HTTP头的机制,可以放宽浏览器的同源策略,实现不同域名网站之间的通信。 前置知识 同源定义:协议、域
简单聊聊 CORS 攻击与防御 简单聊聊 CORS 攻击与防御 简单聊聊 CORS 攻击与防御
【解题报告】P8478 「GLR-R3」清明
P8478 「GLR-R3」清明 参考了出题人题解和 xcyyyyyy 大神的题解,强推前两篇。 拿到题完全没思路怎么办??? 人类智慧的巅峰,思维量的登峰造极。 换句话说就是非人题目,不过不得不说 GLR 的题是真的好,难度也是真的高。 首先我们需要看懂题面,这是第一个难点。 题面大意如下: 对于
【解题报告】P8478 「GLR-R3」清明
SpringCloud入门(二)服务间调用和案例
一、微服务拆分注意事项微服务拆分注意事项:1.单一职责:不同微服务,不要重复开发相同业务2.数据独立:不要访问其它微服务的数据库3.面向服务:将自己的业务暴露为接口,供其它微服务调用 1.微服务需要根据业务模块拆分,做到单一职责,不要重复开发相同业务2.微服务可以将业务暴露为接口,供其它微服务使用3
SpringCloud入门(二)服务间调用和案例 SpringCloud入门(二)服务间调用和案例
Java怎么把多个对象的list的数据合并
1.示例一:创建几个包含Person对象的List,并将它们合并成一个新的List 在Java中,将多个对象的List合并通常涉及到遍历这些List并将它们的元素添加到一个新的List中。这里,我将给出一个详细的代码示例,该示例将展示如何将多个包含相同类型对象的List合并成一个List。 假设我们
搭建ipv6并发代理池
声明 本文章中所有内容仅供学习交流,抓包内容、敏感网址、数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除! 学习目标 ounter(lineipv6代理池学习 前置环境配置 要求linux系统。我是pve下的ubuntugolang的
搭建ipv6并发代理池 搭建ipv6并发代理池 搭建ipv6并发代理池
LinkedHashMap原理详解—从LRU缓存机制说起
写在前面 从一道Leetcode题目说起 首先,来看一下Leetcode里面的一道经典题目:146.LRU缓存机制,题目描述如下: 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类: LRUCache(int capacity) 以 正整数 作为容
LinkedHashMap原理详解—从LRU缓存机制说起 LinkedHashMap原理详解—从LRU缓存机制说起 LinkedHashMap原理详解—从LRU缓存机制说起
Qml 实现仿前端的 Notification (悬浮出现页面上的通知消息)
在前端中一般称它为 Notification 或 Message,但本质是一种东西,即:悬浮弹出式的消息提醒框。 这种组件一般具有以下特点: 1、全局/局部显示:它不依赖于具体的页面元素,可以在整个页面的任意位置显示。 2、自动消失:默认情况下,消息会在一定时间后自动消失,也可以设置为不自动消失。
Qml 实现仿前端的 Notification (悬浮出现页面上的通知消息) Qml 实现仿前端的 Notification (悬浮出现页面上的通知消息) Qml 实现仿前端的 Notification (悬浮出现页面上的通知消息)