วิธีคัดค้านวิธีอ้างอิงกริยา


330

ใน Java 8 คุณสามารถใช้การอ้างอิงเมธอดเพื่อกรองสตรีมตัวอย่างเช่น:

Stream<String> s = ...;
long emptyStrings = s.filter(String::isEmpty).count();

มีวิธีการสร้างการอ้างอิงวิธีการที่เป็นลบล้างของที่มีอยู่คืออะไรเช่น:

long nonEmptyStrings = s.filter(not(String::isEmpty)).count();

ฉันสามารถสร้างnotวิธีการด้านล่าง แต่ฉันสงสัยว่า JDK เสนอสิ่งที่คล้ายกัน

static <T> Predicate<T> not(Predicate<T> p) { return o -> !p.test(o); }

6
JDK-8050818ครอบคลุมการเพิ่มPredicate.not(Predicate)วิธีการคงที่ แต่ปัญหานั้นยังคงเปิดอยู่ดังนั้นเราจะเห็นได้เร็วที่สุดใน Java 12 (ถ้าเคย)
Stefan Zobel

1
ดูเหมือนว่าคำตอบนี้อาจเป็นทางออกที่ดีที่สุดที่ปรับใช้ใน JDK / 11 เช่นกัน
Naman

2
ฉันอยากจะเห็นไวยากรณ์การอ้างอิงวิธีพิเศษสำหรับกรณีนี้: s.filter (String ::! isEmpty)
Mike Twain

คำตอบ:


177

Predicate.not( … )

เสนอวิธีการเพรดิเคต#ใหม่

ดังนั้นคุณสามารถลบล้างวิธีการอ้างอิง:

Stream<String> s = ...;
long nonEmptyStrings = s.filter(Predicate.not(String::isEmpty)).count();

214

ฉันวางแผนที่จะนำเข้าแบบคงที่ต่อไปนี้เพื่ออนุญาตให้ใช้การอ้างอิงวิธีการแบบอินไลน์:

public static <T> Predicate<T> not(Predicate<T> t) {
    return t.negate();
}

เช่น

Stream<String> s = ...;
long nonEmptyStrings = s.filter(not(String::isEmpty)).count();

ปรับปรุง : เริ่มต้นจาก Java-11, JDK นำเสนอโซลูชั่นที่คล้ายกันในตัวเช่นกัน


9
@SaintHill แต่ถ้าอย่างนั้นคุณต้องเขียนมันออกมาตั้งชื่อพารามิเตอร์
flup

21
มันอยู่ในฝรั่งเศษ docs.guava
l ไลบรารี


149

มีวิธีการเขียนการอ้างอิงวิธีการที่อยู่ตรงข้ามของการอ้างอิงวิธีการปัจจุบัน ดูคำตอบของ @ vlasec ด้านล่างซึ่งแสดงให้เห็นว่าโดยการคัดเลือกวิธีการอ้างอิงไปยัง a Predicateและแปลงโดยใช้negateฟังก์ชันอย่างชัดเจน นั่นเป็นวิธีหนึ่งในอีกสองสามวิธีที่ไม่ลำบากเกินไปที่จะทำ

ตรงข้ามกับสิ่งนี้:

Stream<String> s = ...;
int emptyStrings = s.filter(String::isEmpty).count();

นี่คือ:

Stream<String> s = ...;
int notEmptyStrings = s.filter(((Predicate<String>) String::isEmpty).negate()).count()

หรือสิ่งนี้:

Stream<String> s = ...;
int notEmptyStrings = s.filter( it -> !it.isEmpty() ).count();

โดยส่วนตัวแล้วฉันชอบเทคนิคต่อ ๆ ไปเพราะฉันเห็นว่าการอ่านชัดเจนit -> !it.isEmpty()กว่าการใช้คำกริยาอย่างชัดเจนในระยะยาวจากนั้นคัดค้าน

เราสามารถทำเพรดิเคตและใช้ซ้ำ

Predicate<String> notEmpty = (String it) -> !it.isEmpty();

Stream<String> s = ...;
int notEmptyStrings = s.filter(notEmpty).count();

หรือถ้ามีคอลเล็กชันหรืออาร์เรย์ให้ใช้ for-loop ซึ่งเป็นแบบง่ายมีค่าใช้จ่ายน้อยกว่าและ * อาจเร็วกว่า:

int notEmpty = 0;
for(String s : list) if(!s.isEmpty()) notEmpty++;

* หากคุณต้องการทราบว่าอะไรเร็วกว่านี้ให้ใช้ JMH http://openjdk.java.net/projects/code-tools/jmhและหลีกเลี่ยงรหัสเกณฑ์มาตรฐานมือยกเว้นจะหลีกเลี่ยงการเพิ่มประสิทธิภาพ JVM ทั้งหมด - ดูJava 8: ประสิทธิภาพของสตรีม vs คอลเล็กชัน

** ฉันเริ่มสะเก็ดระเบิดเพื่อบอกว่าเทคนิค for-loop เร็วขึ้น มันกำจัดการสร้างกระแสมันกำจัดโดยใช้วิธีการโทรอื่น (ฟังก์ชั่นเชิงลบสำหรับเพรดิเคต) และกำจัดรายการ / ตัวสะสมชั่วคราว ดังนั้นสิ่งเล็ก ๆ น้อย ๆ ที่ถูกบันทึกไว้โดยโครงสร้างสุดท้ายที่อาจทำให้เร็วขึ้น

ฉันคิดว่ามันง่ายและดีกว่าแม้ว่าจะไม่เร็วกว่า หากงานเรียกร้องให้ใช้ค้อนและตะปูอย่านำเลื่อยโซ่และกาวมาด้วย! ฉันรู้ว่าคุณบางคนมีปัญหากับเรื่องนี้

รายการสินค้าที่ต้องการ: ฉันต้องการเห็นStreamฟังก์ชั่นJava พัฒนาขึ้นเล็กน้อยตอนนี้ผู้ใช้ Java คุ้นเคยกับพวกเขามากขึ้น ตัวอย่างเช่นวิธีการ 'นับ' ในสตรีมสามารถยอมรับPredicateเพื่อให้สามารถทำสิ่งนี้ได้โดยตรงดังนี้:

Stream<String> s = ...;
int notEmptyStrings = s.count(it -> !it.isEmpty());

or

List<String> list = ...;
int notEmptyStrings = lists.count(it -> !it.isEmpty());

ทำไมคุณถึงบอกว่ามันเป็นจำนวนมากได้เร็วขึ้น ?
José Andias

@ JoséAndias (1) เร็วขึ้นหรือเร็วกว่านี้อีกไหม? (2) ถ้าเป็นเช่นนั้นทำไม คุณตั้งใจทำอะไร
ผู้ประสานงาน

3
ฉันขอให้คุณให้รายละเอียดเกี่ยวกับ "ทำงานได้เร็วขึ้นมาก" คำถาม: (1) มันเร็วกว่าหรือเร็วกว่ามาก? (2) ถ้าเป็นเช่นนั้นทำไม คุณตั้งใจทำอะไร ได้รับคำตอบที่ดีกว่าจากคุณผู้เขียนข้อความ ฉันไม่คิดว่ามันจะเร็วหรือช้า ขอบคุณ
José Andias

2
จากนั้นฉันจะโยนสิ่งนี้ออกเพื่อการพิจารณาของคุณ - กำจัดการสร้างสตรีมมันจะกำจัดโดยใช้วิธีการโทรอื่น (ฟังก์ชั่นเชิงลบสำหรับเพรดิเคต) และกำจัดรายการ / ตัวสะสม ดังนั้นสิ่งเล็ก ๆ น้อย ๆ ที่ถูกบันทึกไว้โดยโครงสร้างสุดท้าย ฉันไม่แน่ใจว่ามันจะเร็วกว่าหรือเร็วกว่านี้ แต่ฉันคิดว่ามันเร็วกว่ามาก แต่บางที 'มาก' เป็นเรื่องส่วนตัว การเขียนโค้ดในภายหลังง่ายกว่าการทำเพรดิเคตและสตรีมเชิงลบเพื่อนับเป็นเส้นตรง ความชอบของฉัน
ผู้ประสานงาน

4
negate () ดูเหมือนเป็นทางออกที่ดีที่สุด น่าเสียดายที่มันไม่คงที่เหมือนPredicate.negate(String::isEmpty);ไม่มีการหล่อที่ยุ่งยาก
Joel Shemtov

91

Predicateมีวิธีการand, และornegate

แต่String::isEmptyไม่ได้เป็นPredicateก็เป็นเพียงแลมบ์ดาและยังอาจจะกลายเป็นอะไรเช่นString -> Boolean การอนุมานประเภทคือสิ่งที่ต้องเกิดขึ้นก่อน วิธีอนุมานพิมพ์โดยปริยาย แต่ถ้าคุณปฏิเสธมันก่อนที่จะผ่านมันไปเป็นอาร์กิวเมนต์มันจะไม่เกิดขึ้นอีก ดังที่ @axtavt กล่าวถึงการอนุมานที่ชัดเจนสามารถใช้เป็นวิธีที่น่าเกลียด:Function<String, Boolean>filter

s.filter(((Predicate<String>) String::isEmpty).negate()).count()

มีวิธีอื่น ๆ ที่แนะนำในคำตอบอื่น ๆ ด้วยnotวิธีการแบบคงที่และแลมบ์ดาน่าจะเป็นความคิดที่ดีที่สุด นี้สรุปTL; DRส่วน


อย่างไรก็ตามหากคุณต้องการความเข้าใจที่ลึกซึ้งยิ่งขึ้นเกี่ยวกับการอนุมานประเภทแลมบ์ดาฉันต้องการอธิบายให้ลึกซึ้งยิ่งขึ้นโดยใช้ตัวอย่าง ดูสิ่งเหล่านี้และลองคิดดูว่าเกิดอะไรขึ้น:

Object obj1                  = String::isEmpty;
Predicate<String> p1         = s -> s.isEmpty();
Function<String, Boolean> f1 = String::isEmpty;
Object obj2                  = p1;
Function<String, Boolean> f2 = (Function<String, Boolean>) obj2;
Function<String, Boolean> f3 = p1::test;
Predicate<Integer> p2        = s -> s.isEmpty();
Predicate<Integer> p3        = String::isEmpty;
  • obj1 ไม่ได้คอมไพล์ - lambdas จำเป็นต้องอนุมานอินเตอร์เฟสที่ใช้งานได้ (= ด้วยวิธีนามธรรมหนึ่งวิธี)
  • p1 และ f1 ทำงานได้ดีแต่ละชนิดมีความแตกต่างกัน
  • obj2 ปลดเปลื้องPredicateไปObject- โง่ แต่ถูกต้อง
  • f2 ล้มเหลวขณะทำงาน - คุณไม่สามารถส่งPredicateไปถึงFunctionได้มันไม่เกี่ยวกับการอนุมานอีกต่อไป
  • f3 ใช้งานได้ - คุณเรียกวิธีการของเพรดิเคตtestที่กำหนดโดยแลมบ์ดา
  • p2 ไม่ได้รวบรวม - Integerไม่มีisEmptyวิธี
  • p3 ไม่ได้รวบรวมอย่างใดอย่างหนึ่ง - ไม่มีString::isEmptyวิธีคงที่กับการIntegerโต้แย้ง

ฉันหวังว่าสิ่งนี้จะช่วยให้เข้าใจมากขึ้นเกี่ยวกับวิธีการทำงานของประเภทการอนุมาน


45

การสร้างคำตอบและประสบการณ์ส่วนตัวของผู้อื่น:

Predicate<String> blank = String::isEmpty;
content.stream()
       .filter(blank.negate())

4
ที่น่าสนใจ - คุณจะไม่สามารถทำงานแบบอินไลน์::อ้างอิงอย่างหนึ่งอาจต้องการ ( String::isEmpty.negate()) แต่ถ้าคุณกำหนดให้กับตัวแปรแรก (หรือโยนPredicate<String>แรก) ว่างาน ฉันคิดว่าแลมบ์ดา w / !จะอ่านได้มากที่สุดในกรณีส่วนใหญ่ แต่มันมีประโยชน์ที่จะรู้ว่าอะไรที่สามารถและไม่สามารถรวบรวมได้
Joshua Goldberg

2
@JoshuaGoldberg ฉันอธิบายว่าในคำตอบของฉัน: การอ้างอิงวิธีการไม่ใช่ Predicate ด้วยตัวเอง ที่นี่การหล่อจะทำโดยตัวแปร
Vlasec

17

อีกทางเลือกหนึ่งคือการใช้แลมบ์ดาแคสติ้งในบริบทที่ไม่ชัดเจนในชั้นหนึ่ง:

public static class Lambdas {
    public static <T> Predicate<T> as(Predicate<T> predicate){
        return predicate;
    }

    public static <T> Consumer<T> as(Consumer<T> consumer){
        return consumer;
    }

    public static <T> Supplier<T> as(Supplier<T> supplier){
        return supplier;
    }

    public static <T, R> Function<T, R> as(Function<T, R> function){
        return function;
    }

}

... จากนั้นนำเข้าคลาสยูทิลิตี้แบบคงที่:

stream.filter(as(String::isEmpty).negate())

1
ฉันแปลกใจที่งานนี้ - แต่ดูเหมือนว่า JDK จะสนับสนุน Predicate <T> เหนือ Function <T, Boolean> แต่คุณจะไม่ได้ให้ Lambdas ส่งอะไรไปยัง Function <T, Boolean>
Vlasec

มันใช้งานได้กับสตริง แต่ไม่ใช่สำหรับรายการ: ข้อผิดพลาด: (20, 39) java: การอ้างอิงถึงตามที่ชัดเจนทั้งสองวิธี <T> เป็น (java.util.function.Consumer <T>) ใน com.strands.sbs.function แลมบ์ดาและเมธอด <T, R> as (java.util.function.Function <T, R>) ใน com.strands.sbs.function.Lambdas จับคู่
Daniel Pinyol

แดเนียลอาจเกิดขึ้นได้หากคุณพยายามใช้วิธีการโอเวอร์โหลด :)
39499 Askar Kalykov

ตอนนี้ฉันเข้าใจการอนุมานได้ดีกว่าเดิมฉันเข้าใจว่ามันทำงานอย่างไร โดยทั่วไปจะพบเพียงตัวเลือกเดียวที่ใช้งานได้ ดูน่าสนใจฉันไม่รู้ว่ามีชื่อที่ดีกว่าที่ไม่ทำให้หม้อไอน้ำ
Vlasec

12

ไม่ควรPredicate#negateเป็นสิ่งที่คุณกำลังมองหา?


คุณต้องได้รับPredicateก่อน
Sotirios Delimanolis

21
คุณต้องโยนString::isEmpty()ไปPredicate<String>ก่อน - มันน่าเกลียดมาก
axtavt

3
@assylias ใช้เป็นและPredicate<String> p = (Predicate<String>) String::isEmpty; p.negate()
Sotirios Delimanolis

8
@SotiriosDelimanolis ฉันรู้ แต่นั่นก็เอาชนะวัตถุประสงค์ - ฉันอยากจะเขียนs -> !s.isEmpty()ในกรณีนี้!
assylias

@assylias: ใช่ฉันเชื่อว่านั่นเป็นความคิดที่แท้จริง ที่เพิ่งเขียนแลมบ์ดามือยาวนั่นคือทางเลือกที่ตั้งใจไว้
Louis Wasserman

8

ในกรณีนี้คุณสามารถใช้org.apache.commons.lang3.StringUtilsและทำ

int nonEmptyStrings = s.filter(StringUtils::isNotEmpty).count();

6
ไม่คำถามคือวิธีคัดค้านการอ้างอิงวิธีการใด ๆ และใช้String::isEmptyเป็นตัวอย่าง มันยังคงเป็นข้อมูลที่เกี่ยวข้องหากคุณมีกรณีการใช้งานนี้ แต่หากตอบเฉพาะกรณีการใช้งาน String เท่านั้นก็ไม่ควรยอมรับ
Anthony Drogon

4

ฉันได้เขียนระดับสาธารณูปโภคสมบูรณ์ (แรงบันดาลใจจากข้อเสนอของ Askar) ที่สามารถใช้ Java 8 แสดงออกแลมบ์ดาและเปิดให้ (ถ้ามี) ลงในพิมพ์มาตรฐาน Java 8 java.util.functionแลมบ์ดาที่กำหนดไว้ในแพคเกจ ตัวอย่างเช่นคุณสามารถทำได้:

  • asPredicate(String::isEmpty).negate()
  • asBiPredicate(String::equals).negate()

เนื่องจากจะมีความคลุมเครือมากมายหากวิธีการแบบคงที่ทั้งหมดจะตั้งชื่อเพียงas()ฉันเลือกที่จะเรียกวิธีการ "เป็น" ตามด้วยชนิดที่ส่งคืน สิ่งนี้ทำให้เราสามารถควบคุมการตีความแลมบ์ดาได้อย่างสมบูรณ์ ด้านล่างเป็นส่วนแรกของคลาสยูทิลิตี้ (ค่อนข้างใหญ่) ซึ่งเผยรูปแบบที่ใช้

ดูคลาสที่สมบูรณ์ที่นี่ (ที่สำคัญ)

public class FunctionCastUtil {

    public static <T, U> BiConsumer<T, U> asBiConsumer(BiConsumer<T, U> biConsumer) {
        return biConsumer;
    }

    public static <T, U, R> BiFunction<T, U, R> asBiFunction(BiFunction<T, U, R> biFunction) {
        return biFunction;
    }

     public static <T> BinaryOperator<T> asBinaryOperator(BinaryOperator<T> binaryOperator) {
        return binaryOperator;
    }

    ... and so on...
}

4

คุณสามารถใช้Predicatesจากคราสคอลเลกชัน

MutableList<String> strings = Lists.mutable.empty();
int nonEmptyStrings = strings.count(Predicates.not(String::isEmpty));

หากคุณไม่สามารถเปลี่ยนสตริงจากList:

List<String> strings = new ArrayList<>();
int nonEmptyStrings = ListAdapter.adapt(strings).count(Predicates.not(String::isEmpty));

หากคุณจะต้องปฏิเสธของคุณยังสามารถใช้String.isEmpty()StringPredicates.notEmpty()

หมายเหตุ: ฉันเป็นผู้บริจาคให้กับ Eclipse Collections



0

หากคุณใช้ Spring Boot (2.0.0+) คุณสามารถใช้:

import org.springframework.util.StringUtils;

...
.filter(StringUtils::hasLength)
...

ซึ่งทำ: return (str != null && !str.isEmpty());

ดังนั้นมันจะมีผลกระทบด้านลบที่จำเป็นสำหรับ isEmpty

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