之所以單獨把這個列出來,是因為本人被一個原始碼給震撼了。
所以,本人目的是看看這個震撼實現,並模仿,最後把常規的實現也貼上,讓讀者可以看到相對完整的實現
注:本文程式碼基於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對於函式式介面的這種迷惑實現,讓我感到震驚。
這種震驚讓我認為:不排除可能還有更奇葩的實現。 如果有,以後再補上。
最後,我也有點好奇其它常用的語言是否有這種實現 -- 畢竟這個編輯器和編譯器出了難題。