Lambda Expression และวิธีการทั่วไป


111

สมมติว่าฉันมีอินเทอร์เฟซทั่วไป:

interface MyComparable<T extends Comparable<T>>  {
    public int compare(T obj1, T obj2);
}

และวิธีการsort:

public static <T extends Comparable<T>> 
       void sort(List<T> list, MyComparable<T> comp) {
    // sort the list
}

ฉันสามารถเรียกใช้วิธีนี้และส่งนิพจน์แลมบ์ดาเป็นอาร์กิวเมนต์:

List<String> list = Arrays.asList("a", "b", "c");
sort(list, (a, b) -> a.compareTo(b));

ที่จะทำงานได้ดี

แต่ตอนนี้ถ้าฉันทำให้อินเทอร์เฟซไม่ใช่แบบทั่วไปและวิธีการทั่วไป:

interface MyComparable {
    public <T extends Comparable<T>> int compare(T obj1, T obj2);
}

public static <T extends Comparable<T>> 
       void sort(List<T> list, MyComparable comp) {
}

จากนั้นเรียกสิ่งนี้:

List<String> list = Arrays.asList("a", "b", "c");
sort(list, (a, b) -> a.compareTo(b));

มันไม่ได้คอมไพล์ มันแสดงข้อผิดพลาดที่แลมบ์ดานิพจน์ว่า:

"วิธีการกำหนดเป้าหมายเป็นแบบทั่วไป"

ตกลงเมื่อฉันรวบรวมโดยใช้javacมันแสดงข้อผิดพลาดต่อไปนี้:

SO.java:20: error: incompatible types: cannot infer type-variable(s) T#1
        sort(list, (a, b) -> a.compareTo(b));
            ^
    (argument mismatch; invalid functional descriptor for lambda expression
      method <T#2>(T#2,T#2)int in interface MyComparable is generic)
  where T#1,T#2 are type-variables:
    T#1 extends Comparable<T#1> declared in method <T#1>sort(List<T#1>,MyComparable)
    T#2 extends Comparable<T#2> declared in method <T#2>compare(T#2,T#2)
1 error

จากข้อความแสดงข้อผิดพลาดนี้ดูเหมือนว่าคอมไพเลอร์จะไม่สามารถสรุปอาร์กิวเมนต์ประเภทได้ เป็นอย่างนั้นหรือ? ถ้าใช่เหตุใดจึงเกิดขึ้นเช่นนี้

ฉันลองวิธีต่างๆค้นหาผ่านอินเทอร์เน็ต จากนั้นฉันพบบทความ JavaCodeGeeksซึ่งแสดงวิธีการดังนั้นฉันจึงลอง:

sort(list, <T extends Comparable<T>>(a, b) -> a.compareTo(b));

ซึ่งไม่ได้ผลอีกครั้งตรงกันข้ามกับสิ่งที่บทความอ้างว่าได้ผล อาจเป็นไปได้ว่ามันเคยทำงานในงานสร้างเริ่มต้นบางอย่าง

คำถามของฉันคือ: มีวิธีใดในการสร้างแลมบ์ดานิพจน์สำหรับวิธีการทั่วไปหรือไม่? ฉันสามารถทำได้โดยใช้การอ้างอิงวิธีการโดยสร้างวิธีการ:

public static <T extends Comparable<T>> int compare(T obj1, T obj2) {
    return obj1.compareTo(obj2);
}

ในบางชั้นเรียนพูดSOและส่งผ่านเป็น:

sort(list, SO::compare);

คำตอบ:


117

คุณไม่สามารถใช้การแสดงออกแลมบ์ดาสำหรับอินเตอร์เฟซที่ใช้งานได้ถ้าวิธีการในการที่อินเตอร์เฟซที่ใช้งานได้มีพารามิเตอร์ชนิด ดูส่วน§15.27.3ใน JLS8 :

นิพจน์แลมบ์ดาเข้ากันได้กับ [.. ] กับประเภทเป้าหมายTถ้าTเป็นประเภทอินเตอร์เฟสที่ใช้งานได้ (§9.8) และนิพจน์สอดคล้องกับประเภทฟังก์ชันของ [.. ] T. [.. ] นิพจน์แลมบ์ดามีความสอดคล้องกันด้วยประเภทฟังก์ชันหากทั้งหมดต่อไปนี้เป็นจริง:

  • ประเภทฟังก์ชั่นมีพารามิเตอร์ชนิดไม่มี
  • [.. ]

47
อย่างไรก็ตามข้อ จำกัด นี้ใช้ไม่ได้กับการอ้างอิงวิธีการกับวิธีการทั่วไป คุณสามารถใช้การอ้างอิงวิธีการกับวิธีการทั่วไปด้วยอินเทอร์เฟซการทำงานทั่วไป
Brian Goetz

17
ฉันแน่ใจว่ามีเหตุผลที่ดีสำหรับข้อ จำกัด นี้ มันคืออะไร?
Sandro

6
@Sandro: ไม่มีไวยากรณ์สำหรับการประกาศพารามิเตอร์ประเภทสำหรับนิพจน์แลมบ์ดา และไวยากรณ์ดังกล่าวจะซับซ้อนมาก โปรดทราบว่าตัวแยกวิเคราะห์ยังคงต้องสามารถบอกนิพจน์แลมบ์ดาดังกล่าวด้วยพารามิเตอร์ประเภทนอกเหนือจากโครงสร้าง Java ทางกฎหมายอื่น ๆ ดังนั้นคุณต้องหันไปใช้วิธีการอ้างอิง เมธอดเป้าหมายสามารถประกาศพารามิเตอร์ชนิดโดยใช้ไวยากรณ์ที่กำหนดขึ้น
Holger

2
@Holger ยังคงซึ่งพารามิเตอร์ประเภทสามารถอนุมานได้โดยอัตโนมัติคอมไพลเลอร์สามารถถอดรหัสประเภท capure ได้เช่นเดียวกับเมื่อคุณประกาศเช่น Set <?> และทำการตรวจสอบประเภทด้วยประเภทที่จับได้เหล่านั้น แน่นอนว่าทำให้เป็นไปไม่ได้ที่จะให้เป็นพารามิเตอร์ประเภทในร่างกาย แต่ถ้าคุณต้องการสิ่งนั้นการใช้การอ้างอิงวิธีการเป็นทางเลือกที่ดี
WorldSEnder

17

เมื่อใช้การอ้างอิงวิธีฉันพบวิธีอื่นในการส่งผ่านอาร์กิวเมนต์:

List<String> list = Arrays.asList("a", "b", "c");        
sort(list, Comparable::<String>compareTo);

3

เพียงชี้คอมไพเลอร์เวอร์ชันที่เหมาะสมของตัวเปรียบเทียบทั่วไปด้วย (Comparator<String>)

ดังนั้นคำตอบจะเป็น

sort(list, (Comparator<String>)(a, b) -> a.compareTo(b));


2
incompatible types: java.util.Comparator<java.lang.String> cannot be converted to MyComparableและMyComparableไม่ใช่แบบทั่วไป (ไม่มีประเภท) ดังนั้น(MyComparable<String>)จะใช้ไม่ได้เช่นกัน
user85421

1
ไม่รู้ว่าคุณกำลังพิมพ์รหัส @CarlosHeuberger อย่างไร แต่มันใช้ได้กับฉันเป็นอย่างดีนี่คือสิ่งที่ฉันกำลังมองหา
Ivan Perales M.

@IvanPeralesM. หลังจาก 2 เดือน ... ฉันคัดลอกและวางรหัสของคุณและคัดลอกและวางเหนือบรรทัดบนบรรทัดการจัดเรียงของคุณ - เพียงแค่นั้นที่นี่: ideone.com/YNwBbF ! แน่ใจหรือว่าพิมพ์โค้ดข้างบนนี้ถูกต้อง? ใช้Compartor?
user85421

ไม่ฉันไม่ฉันใช้แนวคิดเบื้องหลังคำตอบเพื่อโยนฟังก์ชันเพื่อบอกคอมไพล์ว่าเป็นประเภทใดและใช้งานได้จริง
Ivan Perales M.

@IvanPeralesM. ถ้าอย่างนั้นคุณมีปัญหาอะไรกับ "การพิมพ์" ของฉัน? คำตอบตามที่โพสต์ไม่ได้ผล
user85421

0

คุณหมายถึงอะไรแบบนี้:

<T,S>(T t, S s)->...

แลมด้าชนิดนี้คืออะไร? คุณไม่สามารถแสดงสิ่งนั้นใน Java ได้ดังนั้นจึงไม่สามารถเขียนนิพจน์นี้ในแอปพลิเคชันฟังก์ชันได้และนิพจน์จะต้องประกอบกันได้

สำหรับสิ่งนี้จำเป็นต้องใช้งานคุณจะต้องได้รับการสนับสนุนสำหรับRank2 typesใน Java

วิธีการได้รับอนุญาตให้เป็นแบบทั่วไป แต่คุณไม่สามารถใช้เป็นนิพจน์ได้ อย่างไรก็ตามพวกเขาสามารถลดการแสดงออกของแลมบ์ดาได้โดยการเชี่ยวชาญประเภททั่วไปที่จำเป็นทั้งหมดก่อนที่คุณจะผ่านไปได้ClassName::<TypeName>methodName


1
"Of what type is this lambda? You couldn't express that in Java..."ประเภทจะอนุมานโดยใช้บริบทเช่นเดียวกับแลมด้าอื่น ๆ ประเภทของแลมบ์ดาไม่ได้แสดงออกอย่างชัดเจนในแลมด้าเอง
Kröw
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.