ETJava Beta | Java    注册   登录
  • 搜索:
  • JAVA基础之5-函数式接口的实现

    发表于      阅读(1)     博客类别:Crawler     转自:https://www.cnblogs.com/lzfhope/p/18410855
    如有侵权 请联系我们删除  (页面底部联系我们)  

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

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

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

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

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