วิธีรับค่าที่ไม่ใช่ค่าแรกใน Java


154

มีCOALESCEฟังก์ชันJava เทียบเท่ากับ Java หรือไม่? นั่นคือมีวิธีใดที่จะส่งกลับค่าที่ไม่ใช่ศูนย์แรกของตัวแปรหลายตัวหรือไม่?

เช่น

Double a = null;
Double b = 4.4;
Double c = null;

ฉันต้องการอย่างใดมีคำสั่งที่จะส่งกลับค่า null ไม่ใช่ครั้งแรกของa, bและc- ในกรณีนี้ก็จะกลับมาbหรือ 4.4 (บางอย่างเช่นเมธอด sql - return COALESCE(a,b,c)) ฉันรู้ว่าฉันสามารถทำได้อย่างชัดเจนกับสิ่งที่ชอบ:

return a != null ? a : (b != null ? b : c)

แต่ฉันสงสัยว่ามีฟังก์ชั่นในตัวที่ได้รับการยอมรับในการทำสิ่งนี้หรือไม่


3
คุณไม่ควรต้องการฟังก์ชั่นแบบนี้เนื่องจากคุณจะไม่สามารถคำนวณ 'c' ได้หาก 'b' มีคำตอบที่คุณต้องการ เช่นคุณจะไม่สร้างรายการคำตอบที่เป็นไปได้เพียงเพื่อเก็บไว้
ปีเตอร์ Lawrey

Caveat: ไม่ใช่การลัดวงจร RDBMS ทั้งหมดใน COALESCE Oracle เพิ่งเริ่มทำมัน
Adam Gent

3
@ BrainSlugs83 อย่างจริงจัง? Java ควร?
Dmitry Ginzburg

คำตอบ:


108

ไม่ไม่มี

สิ่งที่ใกล้เคียงที่สุดที่คุณจะได้รับคือ:

public static <T> T coalesce(T ...items) {
    for(T i : items) if(i != null) return i;
    return null;
}

ด้วยเหตุผลที่มีประสิทธิภาพคุณสามารถจัดการกับกรณีทั่วไปดังนี้

public static <T> T coalesce(T a, T b) {
    return a == null ? b : a;
}
public static <T> T coalesce(T a, T b, T c) {
    return a != null ? a : (b != null ? b : c);
}
public static <T> T coalesce(T a, T b, T c, T d) {
    return ...
}

3
เหตุผลด้านประสิทธิภาพที่ฉันกล่าวถึงข้างต้นคือการจัดสรรอาร์เรย์จะเกิดขึ้นทุกครั้งที่คุณเรียกใช้รุ่น var arg ของวิธีการ อาจเป็นเรื่องสิ้นเปลืองสำหรับสิ่งของที่เต็มไปด้วยมือซึ่งฉันสงสัยว่าจะเป็นการใช้งานทั่วไป
les2

เย็น. ขอบคุณ ในกรณีที่ว่าฉันอาจจะยึดติดอยู่กับผู้ประกอบการตามเงื่อนไขที่ซ้อนกันในกรณีนี้มันเป็นครั้งเดียวที่มันจะต้องมีการใช้และวิธีการที่ผู้ใช้กำหนดจะ overkill ...
froadie

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

10
ระวัง: ในcoalesce(a, b)ถ้าbเป็นนิพจน์ที่ซับซ้อนและaไม่ได้null, bจะยังคงได้รับการประเมิน นี่ไม่ใช่กรณีสำหรับตัวดำเนินการ?: แบบมีเงื่อนไข ดูคำตอบนี้
ปาง

สิ่งนี้ต้องมีการโต้แย้งทุกครั้งเพื่อคำนวณล่วงหน้าก่อนที่จะมีการรวมตัวกันไร้จุดหมายด้วยเหตุผลด้านประสิทธิภาพ
Ivan G.


59

หากมีเพียงสองตัวแปรที่จะตรวจสอบและคุณกำลังใช้ฝรั่งคุณสามารถใช้MoreObjects.firstNonNull (T แรก T วินาที)


49
Objects.firstNonNull ใช้เวลาเพียงสองข้อโต้แย้ง ไม่มีค่า varargs ที่เทียบเท่าใน Guava นอกจากนี้มันจะส่ง NullPointerException ถ้า args ทั้งสองเป็นโมฆะ - สิ่งนี้อาจหรืออาจไม่เป็นที่ต้องการ

2
ความคิดเห็นที่ดีเจค NullPointerException นี้มักจะ จำกัด การใช้ Objects.firstNonNull อย่างไรก็ตามมันเป็นวิธีการของ Guava ที่จะหลีกเลี่ยงค่าว่าง
Anton Shchastnyi

4
วิธีการนี้เลิกใช้แล้วและทางเลือกที่แนะนำคือMoreObjects.firstNonNull
davidwebster48

1
ถ้าไม่ต้องการ NPE ให้ดูคำตอบนี้
OrangeDog

51

หากมีเพียงสองการอ้างอิงเพื่อทดสอบและคุณใช้ Java 8 คุณสามารถใช้

Object o = null;
Object p = "p";
Object r = Optional.ofNullable( o ).orElse( p );
System.out.println( r );   // p

หากคุณนำเข้าแบบคงที่ตัวเลือกการแสดงออกไม่ได้เลวร้ายเกินไป

น่าเสียดายที่กรณีของคุณที่มี "ตัวแปรหลายตัว" ไม่สามารถทำได้ด้วยวิธีการที่เป็นตัวเลือก คุณสามารถใช้:

Object o = null;
Object p = null;
Object q = "p";

Optional<Object> r = Stream.of( o, p, q ).filter( Objects::nonNull ).findFirst();
System.out.println( r.orElse(null) );   // p

23

จากคำตอบของ LES2 คุณสามารถกำจัดการซ้ำซ้อนในเวอร์ชั่นที่มีประสิทธิภาพโดยการเรียกฟังก์ชั่นโอเวอร์โหลด:

public static <T> T coalesce(T a, T b) {
    return a != null ? a : b;
}
public static <T> T coalesce(T a, T b, T c) {
    return a != null ? a : coalesce(b,c);
}
public static <T> T coalesce(T a, T b, T c, T d) {
    return a != null ? a : coalesce(b,c,d);
}
public static <T> T coalesce(T a, T b, T c, T d, T e) {
    return a != null ? a : coalesce(b,c,d,e);
}

5
+1 สำหรับสวย ไม่แน่ใจเกี่ยวกับประสิทธิภาพที่ได้รับประโยชน์จากลูปแบบง่าย ๆ แต่ถ้าคุณจะออกมาแสดงประสิทธิภาพเล็ก ๆ แบบนี้มันก็อาจจะดูดี
Carl Manaster

3
วิธีนี้ทำให้เจ็บปวดน้อยลงและมีข้อผิดพลาดน้อยลงที่จะเขียนตัวแปรที่มากเกินไป!
les2

2
varargsจุดของรุ่นที่มีประสิทธิภาพคือการไม่ต้องเสียหน่วยความจำจัดสรรอาร์เรย์โดยใช้ ที่นี่คุณจะสูญเสียความทรงจำด้วยการสร้างเฟรมสแต็กสำหรับการcoalesce()โทรแต่ละซ้อน การเรียกcoalesce(a, b, c, d, e)จะสร้างเฟรมสแต็กถึง 3 เฟรมเพื่อคำนวณ
ลุค

10

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

ฟังก์ชั่นบางอย่างเช่นนี้

public static <T> T coalesce(T ...items) 

ควรใช้ แต่ก่อนที่จะรวบรวมเป็นรหัส byte ควรมี preprocessor ซึ่งค้นหาการใช้งานของ„ coalesce function“ นี้และแทนที่มันด้วยการสร้างเช่น

a != null ? a : (b != null ? b : c)

อัปเดต 2014-09-02:

ขอบคุณ Java 8 และ Lambdas มีความเป็นไปได้ที่จะมีการรวมตัวกันอย่างแท้จริงใน Java! รวมถึงคุณสมบัติที่สำคัญ: นิพจน์เฉพาะจะได้รับการประเมินเมื่อจำเป็นเท่านั้น - หากนิพจน์ก่อนหน้านี้ไม่เป็นโมฆะจะไม่มีการประเมินผลต่อไปนี้ (ไม่มีการเรียกใช้เมธอด

ฉันเขียนบทความเกี่ยวกับเรื่องนี้Java 8: coalesce - hledámeneNULLové hodnoty - (เขียนเป็นภาษาเช็ก แต่ฉันหวังว่าตัวอย่างโค้ดจะเข้าใจได้สำหรับทุกคน)


1
บทความที่ดี - มันจะดีถ้ามีมันเป็นภาษาอังกฤษ
ควอนตัม

1
มีบางอย่างเกี่ยวกับหน้าบล็อกที่ไม่สามารถทำงานกับ Google Translate :-(
HairOfTheDog

5

ด้วย Guava คุณสามารถทำได้:

Optional.fromNullable(a).or(b);

ซึ่งไม่เคยโยน NPE ถ้าทั้งสองaและมีbnull

แก้ไข: ฉันผิดมันโยน NPE วิธีที่ถูกต้องตามที่แสดงความคิดเห็นโดยMichal Čizmaziaคือ:

Optional.fromNullable(a).or(Optional.fromNullable(b)).orNull();

1
เฮ้มันทำ:java.lang.NullPointerException: use Optional.orNull() instead of Optional.or(null)
Michal Čizmazia

1
นี่เป็นกลอุบาย:Optional.fromNullable(a).or(Optional.fromNullable(b)).orNull()
Michal Čizmazia

4

เพื่อความสมบูรณ์กรณี "ตัวแปรหลายตัว" นั้นเป็นไปได้จริง ๆ แต่ไม่ได้ดูดีเลย ตัวอย่างเช่นสำหรับตัวแปรo, pและq:

Optional.ofNullable( o ).orElseGet(()-> Optional.ofNullable( p ).orElseGet(()-> q ) )

โปรดทราบการใช้งานของorElseGet()การเข้าร่วมการกรณีที่o, pและqไม่ได้เป็นตัวแปร แต่การแสดงออกทั้งแพงหรือไม่พึงประสงค์ผลข้างเคียง

ในกรณีทั่วไปมากที่สุด coalesce(e[1],e[2],e[3],...,e[N])

coalesce-expression(i) ==  e[i]  when i = N
coalesce-expression(i) ==  Optional.ofNullable( e[i] ).orElseGet(()-> coalesce-expression(i+1) )  when i < N

สิ่งนี้สามารถสร้างนิพจน์ที่ยาวเกินไป แต่ถ้าเรามีความพยายามที่จะย้ายไปยังโลกที่ไม่มีnullแล้วv[i]เป็นส่วนใหญ่อาจได้จากประเภทนี้เมื่อเทียบกับเพียงOptional<String> Stringในกรณีนี้,

result= o.orElse(p.orElse(q.get())) ;

หรือในกรณีของนิพจน์:

result= o.orElseGet(()-> p.orElseGet(()-> q.get() ) ) ;

นอกจากนี้หากคุณกำลังจะย้ายไปยังรูปแบบการทำงาน-เปิดเผยo, pและqควรเป็นชนิดSupplier<String>เหมือนใน:

Supplier<String> q= ()-> q-expr ;
Supplier<String> p= ()-> Optional.ofNullable(p-expr).orElseGet( q ) ;
Supplier<String> o= ()-> Optional.ofNullable(o-expr).orElseGet( p ) ;

และจากนั้นทั้งหมดcoalesceลดลงเพียงเพื่อo.get()ช่วยลดเพียงเพื่อ

สำหรับตัวอย่างที่เป็นรูปธรรมมากขึ้น:

Supplier<Integer> hardcodedDefaultAge= ()-> 99 ;
Supplier<Integer> defaultAge= ()-> defaultAgeFromDatabase().orElseGet( hardcodedDefaultAge ) ;
Supplier<Integer> ageInStore= ()-> ageFromDatabase(memberId).orElseGet( defaultAge ) ;
Supplier<Integer> effectiveAge= ()-> ageFromInput().orElseGet( ageInStore ) ;

defaultAgeFromDatabase(), ageFromDatabase()และageFromInput()จะกลับมาแล้วOptional<Integer>เป็นธรรมชาติ

และจากนั้นcoalesceกลายเป็นeffectiveAge.get()หรือเพียงแค่effectiveAgeถ้าเรามีความสุขกับSupplier<Integer>ถ้าเรามีความสุขกับ

IMHO กับ Java 8 เราจะเห็นโครงสร้างของโค้ดมากขึ้นเรื่อย ๆ เนื่องจากมันอธิบายตัวเองได้ดีและมีประสิทธิภาพในเวลาเดียวกันโดยเฉพาะอย่างยิ่งในกรณีที่ซับซ้อนมากขึ้น

ฉันคิดถึงคลาสLazy<T>ที่เรียกใช้Supplier<T>เพียงครั้งเดียว แต่ขี้เกียจรวมถึงความสอดคล้องในคำจำกัดความของOptional<T>(เช่นOptional<T>- Optional<T>โอเปอเรเตอร์หรือแม้แต่Supplier<Optional<T>>)



3

วิธีการเกี่ยวกับการใช้ซัพพลายเออร์เมื่อคุณต้องการหลีกเลี่ยงการประเมินวิธีการที่แพง?

แบบนี้:

public static <T> T coalesce(Supplier<T>... items) {
for (Supplier<T> item : items) {
    T value = item.get();
    if (value != null) {
        return value;
    }
    return null;
}

แล้วใช้มันเช่นนี้

Double amount = coalesce(order::firstAmount, order::secondAmount, order::thirdAmount)

นอกจากนี้คุณยังสามารถใช้วิธีการโอเวอร์โหลดสำหรับการโทรด้วยอาร์กิวเมนต์สองสามหรือสี่

นอกจากนี้คุณยังสามารถใช้สตรีมกับสิ่งนี้:

public static <T> T coalesce2(Supplier<T>... s) {
    return Arrays.stream(s).map(Supplier::get).filter(Objects::nonNull).findFirst().orElse(null);
}

ทำไมต้องตัดอาร์กิวเมนต์แรกใน a Supplierถ้ามันจะถูกตรวจสอบอยู่แล้ว? เพื่อความเท่าเทียมกัน?
Inego

0

เกี่ยวกับ:

firstNonNull = FluentIterable.from(
    Lists.newArrayList( a, b, c, ... ) )
        .firstMatch( Predicates.notNull() )
            .or( someKnownNonNullDefault );

Java ArrayList ช่วยให้รายการ null เป็นไปอย่างสะดวกและการแสดงออกนี้สอดคล้องกันโดยไม่คำนึงถึงจำนวนวัตถุที่ต้องพิจารณา (ในรูปแบบนี้วัตถุทั้งหมดที่พิจารณาจะต้องเป็นประเภทเดียวกัน)


-3
Object coalesce(Object... objects)
{
    for(Object o : object)
        if(o != null)
            return o;
    return null;
}

2
พระเจ้าฉันเกลียดนายพล ฉันเห็นสิ่งที่คุณหมายถึงทันทีจากค้างคาว ฉันต้องดู @ LES2 สองครั้งเพื่อหาว่าเขากำลังทำสิ่งเดียวกัน (และอาจจะ "ดีกว่า")! +1 เพื่อความชัดเจน
Bill K

ใช่ยาชื่อสามัญเป็นวิธีที่จะไป แต่ฉันไม่ได้ทั้งหมดที่คุ้นเคยกับความซับซ้อน
Eric

10
ใช้เวลาในการเรียนรู้ข้อมูลทั่วไป :-) มีความแตกต่างเล็กน้อยระหว่างตัวอย่างของ @ LES2 และสิ่งนี้นอกเหนือจาก T แทน Object -1 สำหรับการสร้างฟังก์ชั่นที่จะบังคับให้หล่อค่าตอบแทนกลับไปที่ Double นอกจากนี้สำหรับการตั้งชื่อวิธี Java ในตัวพิมพ์ใหญ่ทั้งหมดซึ่งอาจใช้ได้ใน SQL แต่ไม่ได้มีรูปแบบที่ดีใน Java
Avi

1
ฉันตระหนักว่าตัวพิมพ์ใหญ่ทั้งหมดเป็นแนวปฏิบัติที่ไม่ดี ฉันแค่แสดง OP วิธีการเขียนฟังก์ชั่นภายใต้ชื่อที่พวกเขาร้องขอ เห็นด้วยการโยนกลับไปDoubleไกลจากอุดมคติ ฉันไม่ทราบว่าฟังก์ชั่นคงที่สามารถได้รับพารามิเตอร์ประเภท ฉันคิดว่ามันเป็นแค่ชั้นเรียน
Eric
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.