写点什么

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

作者:EquatorCoco
  • 2024-09-13
    福建
  • 本文字数:4110 字

    阅读完需:约 13 分钟

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


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

注:本文代码基于 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 个实现


以下代码,


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


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


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


文章转载自:正在战斗中

原文链接:https://www.cnblogs.com/lzfhope/p/18410855

体验地址:http://www.jnpfsoft.com/?from=infoq

用户头像

EquatorCoco

关注

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
JAVA基础之5-函数式接口的实现_Java_EquatorCoco_InfoQ写作社区