สตรีม. min () และ .max () ของ Java 8: ทำไมคอมไพล์นี้


215

หมายเหตุ: คำถามนี้มาจากลิงก์ตายซึ่งเป็นคำถาม SO ก่อนหน้า แต่นี่จะไป ...

ดูรหัสนี้ ( หมายเหตุ: ฉันรู้ว่ารหัสนี้จะไม่ "ทำงาน" และInteger::compareควรใช้ - ฉันเพิ่งแยกมันออกจากคำถามที่เชื่อมโยง ):

final ArrayList <Integer> list 
    = IntStream.rangeClosed(1, 20).boxed().collect(Collectors.toList());

System.out.println(list.stream().max(Integer::max).get());
System.out.println(list.stream().min(Integer::min).get());

ตาม Javadoc ของ.min()และข้อโต้แย้งของทั้งสองควรจะเป็น.max() Comparatorแต่ที่นี่การอ้างอิงวิธีการเป็นวิธีการคงที่ของIntegerชั้นเรียน

ดังนั้นทำไมการรวบรวมนี้เลย


6
หมายเหตุว่ามันไม่ได้ทำงานอย่างถูกต้องก็ควรจะใช้Integer::compareแทนและInteger::max Integer::min
Christoffer Hammarström

@ ChristofferHammarströmฉันรู้แล้ว โปรดทราบว่าฉันพูดก่อนที่จะแยกรหัส "ฉันรู้ว่ามันเป็นเรื่องไร้สาระ"
fge

3
ฉันไม่ได้พยายามที่จะแก้ไขคุณฉันกำลังบอกคนทั่วไป คุณทำให้มันเสียงเช่นถ้าคุณคิดว่าส่วนหนึ่งที่เป็นเรื่องเหลวไหลคือว่าวิธีการไม่ได้เป็นวิธีการของInteger Comparator
Christoffer Hammarström

คำตอบ:


242

ให้ฉันอธิบายสิ่งที่เกิดขึ้นที่นี่เพราะมันไม่ชัดเจน!

ก่อนอื่นStream.max()ยอมรับอินสแตนซ์Comparatorเพื่อให้รายการในสตรีมสามารถนำมาเปรียบเทียบกันเพื่อค้นหาค่าต่ำสุดหรือสูงสุดในลำดับที่ดีที่สุดที่คุณไม่จำเป็นต้องกังวลมากเกินไป

ดังนั้นคำถามคือทำไมจึงเป็นที่Integer::maxยอมรับ หลังจากทั้งหมดมันไม่ใช่ตัวเปรียบเทียบ!

คำตอบคือวิธีการที่ฟังก์ชั่นแลมบ์ดาใหม่ทำงานใน Java 8 ขึ้นอยู่กับแนวคิดที่รู้จักกันอย่างไม่เป็นทางการในรูปแบบ "single abstract method" หรือ "SAM" อินเตอร์เฟส แนวคิดคืออินเทอร์เฟซใด ๆ ที่มีวิธีนามธรรมหนึ่งสามารถนำไปใช้โดยอัตโนมัติโดยแลมบ์ดาใด ๆ หรือการอ้างอิงวิธีการ - ซึ่งมีลายเซ็นวิธีการที่ตรงกับวิธีการหนึ่งในอินเตอร์เฟซ ดังนั้นตรวจสอบComparatorอินเทอร์เฟซ (เวอร์ชันง่าย):

public Comparator<T> {
    T compare(T o1, T o2);
}

หากวิธีการกำลังมองหาComparator<Integer>แล้วก็เป็นหลักมองหาลายเซ็นนี้:

int xxx(Integer o1, Integer o2);

ผมใช้ "xxx" เพราะชื่อวิธีที่ไม่ได้ใช้เพื่อวัตถุประสงค์ในการจับคู่

ดังนั้นทั้งสองInteger.min(int a, int b)และInteger.max(int a, int b)อยู่ใกล้พอที่ autoboxing จะทำให้สิ่งนี้ปรากฏComparator<Integer>ในบริบทของเมธอด


28
หรืออีกทางหนึ่ง: list.stream().mapToInt(i -> i).max().get().
assylias

13
@assylias คุณต้องการที่จะใช้.getAsInt()แทนแต่เป็นคุณจะจัดการกับget() OptionalInt
skiwi

... เมื่อสิ่งที่เราพยายามเท่านั้นคือให้ตัวเปรียบเทียบที่กำหนดเองกับmax()ฟังก์ชัน!
Manu343726

มันเป็นมูลค่า noting ว่า "SAM อินเตอร์เฟซ" เรียกว่าจริง "ฟังก์ชั่นการเชื่อมต่อ" และมองไปที่เอกสารเราจะเห็นว่ามันตกแต่งด้วยคำอธิบายประกอบComparator @FunctionalInterfaceมัณฑนากรนี่คือความมหัศจรรย์ที่ช่วยให้Integer::maxและจะได้รับการดัดแปลงให้เป็นInteger::min Comparator
Chris Kerekes

2
@ChrisKerekes มัณฑนากร@FunctionalInterfaceมีวัตถุประสงค์หลักเพื่อใช้เป็นเอกสารประกอบเท่านั้นเนื่องจากคอมไพเลอร์สามารถทำสิ่งนี้ได้อย่างมีความสุขกับส่วนต่อประสานใด ๆ ด้วยวิธีนามธรรมเดียว
เดินทางที่หลงผิดใน

117

Comparatorเป็นอินเทอร์เฟซที่ใช้งานได้และInteger::maxเป็นไปตามอินเทอร์เฟซนั้น (หลังจากนำมาพิจารณาโดยอัตโนมัติ / ไม่ได้พิจารณา) มันต้องใช้intค่าสองค่าและส่งกลับค่าint- ตามที่คุณคาดหวัง a Comparator<Integer>ถึง (อีกครั้งการเหล่ที่จะละเว้นความแตกต่างของจำนวนเต็ม / int)

แต่ผมจะไม่ได้คาดหวังว่ามันจะทำสิ่งที่ถูกต้องให้ที่Integer.maxไม่สอดคล้องกับความหมายComparator.compareของ และโดยทั่วไปมันใช้งานไม่ได้จริง ๆ ตัวอย่างเช่นทำการเปลี่ยนแปลงเพียงเล็กน้อย:

for (int i = 1; i <= 20; i++)
    list.add(-i);

... และตอนนี้maxค่าคือ -20 และminค่าคือ -1

ทั้งสองสายควรใช้Integer::compare:

System.out.println(list.stream().max(Integer::compare).get());
System.out.println(list.stream().min(Integer::compare).get());

1
ฉันรู้เกี่ยวกับอินเตอร์เฟซที่ใช้งานได้; และฉันรู้ว่าทำไมมันถึงให้ผลลัพธ์ที่ผิด ฉันเพียงแค่สงสัยว่าในโลกคอมไพเลอร์ไม่ได้ตะโกนใส่ฉัน
FGE

6
@fge: มันเป็นกล่องที่คุณไม่ชัดเจนเกี่ยวกับ? (ฉันไม่ได้ดูส่วนนั้นอย่างแน่นอน) A Comparator<Integer>จะมีint compare(Integer, Integer)... มันไม่น่าเชื่อว่า Java อนุญาตให้วิธีการอ้างอิงของint max(int, int)การแปลงเป็น ...
Jon Skeet

7
@fge: คอมไพเลอร์ควรจะรู้ความหมายของInteger::max? จากมุมมองของคุณผ่านฟังก์ชั่นที่ตรงตามข้อกำหนดนั่นคือทั้งหมดที่มันสามารถทำได้
Mark Peters

6
@fge: โดยเฉพาะอย่างยิ่งถ้าคุณเข้าใจส่วนหนึ่งของสิ่งที่เกิดขึ้น แต่มีความสนใจเกี่ยวกับแง่มุมหนึ่งโดยเฉพาะมันคุ้มค่าที่ชัดเจนในคำถามเพื่อหลีกเลี่ยงคนเสียเวลาอธิบายบิตที่คุณรู้อยู่แล้ว
Jon Skeet

20
Comparator.compareผมคิดว่าปัญหาพื้นฐานคือลายเซ็นประเภทของ มันควรกลับenumของไม่ใช่{LessThan, GreaterThan, Equal} intด้วยวิธีนี้อินเทอร์เฟซที่ใช้งานได้จะไม่ตรงกันและคุณจะได้รับข้อผิดพลาดในการคอมไพล์ IOW: ลายเซ็นประเภทของComparator.compareไม่จับภาพความหมายของการเปรียบเทียบวัตถุสองอย่างเพียงพอและดังนั้นอินเทอร์เฟซอื่น ๆ ที่ไม่มีอะไรเกี่ยวข้องกับการเปรียบเทียบวัตถุโดยบังเอิญมีลายเซ็นประเภทเดียวกัน
Jörg W Mittag

19

ใช้งานได้เนื่องจากInteger::minแก้ไขการใช้Comparator<Integer>อินเทอร์เฟซ

อ้างอิงวิธีการInteger::minแก้ไขที่จะInteger.min(int a, int b)มีมติIntBinaryOperatorและสันนิษฐาน Autoboxing BinaryOperator<Integer>เกิดขึ้นที่ใดที่หนึ่งทำให้มันเป็น

และmin()รับผิดชอบmax()วิธีการของการStream<Integer>ขอให้Comparator<Integer>อินเตอร์เฟซที่จะดำเนินการ ตอนนี้แก้ไขนี้ไปวิธีการเดียว
Integer compareTo(Integer o1, Integer o2)ชนิดBinaryOperator<Integer>ไหน

BinaryOperator<Integer>และทำให้ความมหัศจรรย์ได้เกิดขึ้นเป็นวิธีการทั้งสองเป็น


มันไม่ถูกต้องทั้งหมดที่จะกล่าวว่าการดำเนินการInteger::min Comparableมันไม่ใช่ประเภทที่สามารถใช้งานอะไรก็ได้ Comparableแต่มันคือการประเมินเป็นวัตถุซึ่งดำเนิน
Lii

1
@Lii ขอบคุณฉันแก้ไขมันตอนนี้
skiwi

Comparator<Integer>เป็นอินเทอร์เฟซแบบนามธรรมเดียว (หรือที่รู้จักกันว่า "ฟังก์ชั่น") และInteger::minปฏิบัติตามสัญญาของตนดังนั้นแลมบ์ดาสามารถตีความได้เช่นนี้ ฉันไม่รู้ว่าคุณเห็น BinaryOperator เข้ามาเล่นที่นี่ได้อย่างไร (หรือ IntBinaryOperator อย่างใดอย่างหนึ่ง) - ไม่มีความสัมพันธ์ย่อยระหว่างนั้นและ Comparator
Paŭlo Ebermann

2

นอกเหนือจากข้อมูลที่ได้รับจากเดวิดเมตรลอยด์หนึ่งสามารถเพิ่มว่ากลไกที่ช่วยให้นี้เรียกว่าพิมพ์เป้าหมาย

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

เป้าหมายของนิพจน์คือตัวแปรที่กำหนดผลลัพธ์หรือพารามิเตอร์ที่ส่งผลลัพธ์

นิพจน์แลมบ์ดาและการอ้างอิงเมธอดได้รับการกำหนดประเภทที่ตรงกับประเภทของเป้าหมายหากพบประเภทดังกล่าว

ดูที่ส่วนการอนุมานประเภทใน Java Tutorial สำหรับข้อมูลเพิ่มเติม


1

ฉันมีข้อผิดพลาดกับอาร์เรย์ที่รับค่าสูงสุดและค่าต่ำสุดดังนั้นวิธีแก้ไขของฉันคือ:

int max = Arrays.stream(arrayWithInts).max().getAsInt();
int min = Arrays.stream(arrayWithInts).min().getAsInt();
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.