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

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

注:本文代码基于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对于函数式接口的这种迷惑实现,让我感到震惊。

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

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

JAVA基础之5-函数式接口的实现的更多相关文章

  1. Java基础之抽象类与接口

    Java基础之抽象类与接口 对于面向对象编程来说,抽象是它的一大特征之一.在Java中,可以通过两种形式来体现OOP的抽象:接口和抽象类.这两者有太多相似的地方,又有太多不同的地方.很多人在初学的时候 ...

  2. java8 Lambda表达式的新手上车指南(1)--基础语法和函数式接口

    背景 java9的一再推迟发布,似乎让我们恍然想起离发布java8已经过去了三年之久,java8应该算的上java语言在历代版本中变化最大的一个版本了,最大的新特性应该算得上是增加了lambda表达式 ...

  3. Java基础 -- Collection和Iterator接口的实现

    Collection是描述所有序列容器(集合)共性的根接口,它可能被认为是一个“附属接口”,即因为要表示其他若干个接口的共性而出现的接口.另外,java.util.AbstractCollection ...

  4. 程序猿的日常——Java基础之抽象类与接口、枚举、泛型

    再次回顾这些基础内容,发现自己理解的又多了一点.对于一些之前很模糊的概念,渐渐的清晰起来. 抽象类与接口 抽象类通常是描述一些对象的通用方法和属性,并且默认实现一些功能,它不能被实例化.接口仅仅是描述 ...

  5. 恕我直言你可能真的不会java第8篇-函数式接口

    一.函数式接口是什么? 所谓的函数式接口,实际上就是接口里面只能有一个抽象方法的接口.我们上一节用到的Comparator接口就是一个典型的函数式接口,它只有一个抽象方法compare. 只有一个抽象 ...

  6. Java基础之浅谈接口

    前言 前几篇文章我们已经把Java的封装.继承.多态学习完了,现在我们开始比较便于我们实际操作的学习,虽然它也是Java基础部分,但是其实入门容易,精通很难. 我认真的给大家整理了一下这些必须学会.了 ...

  7. Java的lamda表达式/函数式接口/流式计算

    在我们看他人code的时候经常会看到,可能会经常看到lambda表达式,函数式接口,以及流式计算.在刚接触这些新功能时,也觉得真的有必要吗?但是现在写多了,发现这个功能确实能简化代码结构,提升编码效率 ...

  8. 黑马程序员——JAVA基础之抽象和接口 , 模版方法设计模式

    ------- android培训.java培训.期待与您交流! ---------- 抽象定义:           抽象就是从多个事物中将共性的,本质的内容抽取出来.           例如:狼 ...

  9. java基础(四)-----抽象类与接口

    抽象类与接口是java语言中对抽象概念进行定义的两种机制,正是由于他们的存在才赋予java强大的面向对象的能力.他们两者之间对抽象概念的支持有很大的相似,甚至可以互换,但是也有区别. 一.抽象类 我们 ...

  10. Java基础(十)接口(interface)

    1.接口的概念 在Java中,接口不是类,而是对类的一组需求描述,这些类要遵从接口描述. 例如:Array类中的sort方法可以对对象数组进行排序,但要求满足下列前提:对象所属的类必须实现了Compa ...

随机推荐

  1. 机器学习策略篇:详解处理数据不匹配问题(Addressing data mismatch)

    处理数据不匹配问题 如果您的训练集来自和开发测试集不同的分布,如果错误分析显示有一个数据不匹配的问题该怎么办?这个问题没有完全系统的解决方案,但可以看看一些可以尝试的事情.如果发现有严重的数据不匹配问 ...

  2. Django REST framework的10个常见组件

    Django REST framework的10个常见组件: 权限组件 认证组件 访问频率限制组件 序列化组件 路由组件 视图组件 分页组件 解析器组件 渲染组件 版本组件

  3. JAVA私有构造函数---java笔记

    在Java中,构造函数是一种特殊的方法,它用于初始化新创建的对象.当我们创建一个类的实例时,构造函数会自动被调用. 构造函数可以有不同的访问修饰符,如public.protected.default( ...

  4. Codeforces Round 953 (Div. 2)

    Codeforces Round 953 (Div. 2) 闲来无事水题解. A . B . C 显然 \(k\) 是偶数.考虑 \(k\) 的上界,\(p_{1}=n,p_{n}=1\),产生 \( ...

  5. Zabbix 5.0 LTS 配置企业微信(Webhook)自动发送告警信息

    依据前面文章<Zabbix 5.0 LTS URL 健康监测>环境,实现企业微信(Webhook)自动发送告警信息. 一.创建企业微信机器人 先在自己的企业微信群里创建一个机器人,并获取其 ...

  6. 6、Git之团队协作机制

    6.1.团队内协作 6.1.1.创建本地库 如上图所示,一个名叫刘备的人,在本地电脑中创建了一个项目,并使用 git 来维护. 6.1.2.推送本地库到代码托管中心 如上图所示,刘备想让别人也能看到自 ...

  7. 【Spring Data JPA】04 JPQL和原生SQL

    @Transactional注解 让Spring处理事务 不需要自己每次都手动开启提交回滚 FINDONE & GETONE的区别? findone是立即加载 getone是延迟加载,配合事务 ...

  8. Jupyter Lab和Jupyter Notebook的区别

    JupyterLab与Jupyter Notebook:详细比较 简介 Jupyter Notebook是一个开源的Web应用程序,允许用户创建和共享包含实时代码.方程.可视化和解释性文本的文档.Ju ...

  9. MAML —— Model-Agnostic Meta-Learning for Fast Adaptation of Deep Networks

    论文地址: https://arxiv.org/abs/1703.03400 官方代码: 有监督学习: https://github.com/cbfinn/maml 强化学习: https://git ...

  10. 【模板】最近公共祖先:LCA算法

    LCA最近公共祖先 \[\begin{align} 要求 \ 给出一个树和他的根节点\text{root} \quad给出Q个询问 回答\text {LCA}(a,b) \end{align} \] ...