JAVA基礎之5-函式式介面的實現

正在战斗中發表於2024-09-12

之所以單獨把這個列出來,是因為本人被一個原始碼給震撼了。

所以,本人目的是看看這個震撼實現,並模仿,最後把常規的實現也貼上,讓讀者可以看到相對完整的實現

注:本文程式碼基於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對於函式式介面的這種迷惑實現,讓我感到震驚。

這種震驚讓我認為:不排除可能還有更奇葩的實現。 如果有,以後再補上。

最後,我也有點好奇其它常用的語言是否有這種實現 -- 畢竟這個編輯器和編譯器出了難題。

相關文章