Java“?” ตัวดำเนินการสำหรับการตรวจสอบ null - มันคืออะไร? (ไม่ใช่ Ternary!)


88

ฉันกำลังอ่านบทความที่เชื่อมโยงจากเรื่อง slashdot และเจอเรื่องเล็กน้อยนี้:

ใช้ Java เวอร์ชันล่าสุดซึ่งพยายามทำให้การตรวจสอบตัวชี้ null ง่ายขึ้นโดยนำเสนอไวยากรณ์ชวเลขสำหรับการทดสอบตัวชี้ที่ไม่มีที่สิ้นสุด เพียงแค่เพิ่มเครื่องหมายคำถามลงในการเรียกแต่ละวิธีโดยอัตโนมัติจะรวมการทดสอบตัวชี้ค่าว่างโดยแทนที่รังของหนูของประโยคคำสั่ง if-then เช่น:

    public String getPostcode(Person person) {
      String ans= null;
      if (person != null) {
        Name nm= person.getName();
        if (nm!= null) {
          ans= nm.getPostcode();
        }
      }
      return ans
    } 

ด้วยสิ่งนี้:

public String getFirstName(Person person) {
      return person?.getName()?.getGivenName();
    } 

ฉันระบาดในอินเทอร์เน็ต (โอเคฉันใช้เวลาอย่างน้อย 15 นาทีในการใช้ googling รูปแบบต่างๆใน "java question mark") และไม่มีอะไรเลย ดังนั้นคำถามของฉัน: มีเอกสารอย่างเป็นทางการเกี่ยวกับเรื่องนี้หรือไม่? ฉันพบว่า C # มีตัวดำเนินการที่คล้ายกัน (ตัวดำเนินการ "??") แต่ฉันต้องการได้รับเอกสารสำหรับภาษาที่ฉันใช้งานอยู่หรือนี่เป็นเพียงการใช้ตัวดำเนินการด้านท้ายที่ฉัน ไม่เคยเห็นมาก่อน

ขอบคุณ!

แก้ไข: ลิงก์ไปยังบทความ: http://infoworld.com/d/developer-world/12-programming-mistakes-avoid-292


3
อย่างน้อยเราขอลิงค์ไปยังบทความได้ไหม
Karl Knechtel

1
และที่มาของตัวอย่างข้อมูล?
khachik

5
บทความไม่ถูกต้อง infoworld.com/print/145292ฉันเชื่อว่ามีการส่งการส่ง Project Coin แต่ไม่ได้เลือกไว้ (ด้วยเหตุผลที่กล่าวถึงในบทความ - หากคุณต้องการทำสิ่งนั้นให้ใช้ C # หรืออะไรสักอย่าง) และแน่นอนว่าไม่ได้อยู่ในภาษา Java เวอร์ชันปัจจุบัน
Tom Hawtin - แท

4
ไม่เหมือนกับ C # ?? ตัวดำเนินการ: ?? nulls coalesces A ?? B == (A != null) ? A : Bคือ A?.B == (A != null) ? A.B : nullนี้จะปรากฏขึ้นในการประเมินสถานที่ให้บริการบนวัตถุถ้าอ้างอิงวัตถุไม่ได้เป็นโมฆะเช่น
Rup

1
@Erty: ในฐานะผู้ใช้รายใหญ่ของคำอธิบายประกอบ @NotNull ซึ่งโดยทั่วไปแล้วทุกที่ในโค้ดที่ฉันเขียนฉันไม่รู้ดีอีกต่อไปว่า NPE คืออะไร (ยกเว้นเมื่อใช้ API ที่ออกแบบไม่ดี) แต่ฉันพบว่า "สัญลักษณ์ทางลัด" นี้น่ารักและน่าสนใจ แน่นอนว่าบทความนั้นถูกต้องเมื่อระบุ: ท้ายที่สุดแล้วมันไม่ได้กำจัดต้นตอของปัญหานั่นคือการเพิ่มจำนวนของค่า null เนื่องจากการเขียนโปรแกรมที่รวดเร็วและหลวม 'null' ไม่มีอยู่ที่ระดับ OOA / OOD มันเป็นเรื่องไร้สาระของ Java ที่แปลกใหม่ซึ่งส่วนใหญ่สามารถแก้ไขได้ สำหรับฉันมัน@NotNullทุกที่
SyntaxT3rr0r

คำตอบ:


80

ความคิดดั้งเดิมมาจากความโกลาหล เสนอให้ Java 7 เป็นส่วนหนึ่งของ Project Coin: https://wiki.openjdk.java.net/display/Coin/2009+Proposals+TOC (Elvis and Other Null-Safe Operators) แต่ยังไม่ได้รับการยอมรับ .

ตัวดำเนินการเอลวิสที่เกี่ยวข้อง?: ถูกเสนอให้x ?: yจดชวเลขx != null ? x : yโดยเฉพาะเมื่อ x เป็นนิพจน์ที่ซับซ้อน


3
ใน java (ที่ไม่มีการบังคับอัตโนมัติให้เป็นโมฆะ) ชวเลขสำหรับx!=null ? x : y
Michael Borgwardt

@ Michael Borgwardt: จุดที่ดีฉันคิดถึงความหมายที่น่าสนใจ
ataylor

50
?เป็น Quiff ลายเซ็นของ Elvis Presley; :เพียงหมายถึงคู่ของตาตามปกติ บางทีอาจ?:-oเป็นเรื่องที่น่าสนใจมากขึ้น ...
Andrzej Doyle

4
?:0ควรเป็นโอเปอเรเตอร์ "Not null หรือ 0" ทำให้เป็นเช่นนั้น
azz

3
จริงๆแล้วเป็นความผิดพลาดที่อ้างถึงข้อเสนอในฐานะผู้ดำเนินการของเอลวิส คำอธิบายที่mail.openjdk.java.net/pipermail/coin-dev/2009-July/002089.html "Null-safe dereferencing" เป็นคำที่ดีกว่าในการใช้
JustinKSU

63

ไวยากรณ์นี้ไม่มีอยู่ใน Java และไม่มีกำหนดให้รวมอยู่ในเวอร์ชันที่กำลังจะมาถึงที่ฉันรู้จัก


9
ทำไมต้องโหวตลด? คำตอบนี้ถูกต้อง 100% เท่าที่ฉันรู้ ... ถ้าคุณรู้อะไรที่แตกต่างออกไปโปรดพูดเช่นนั้น
ColinD

6
@Webinator: มันจะไม่อยู่ใน Java 7 หรือ 8 และไม่มี "เวอร์ชันที่กำลังจะมาถึง" อื่น ๆ ในขณะนี้ ฉันยังพบว่ามันไม่น่าเป็นไปได้ที่มันจะเข้ามาเนื่องจากมันส่งเสริมการปฏิบัติที่ค่อนข้างแย่ ฉันไม่คิดว่า "ยัง" เป็นสิ่งจำเป็นเนื่องจาก "ไม่มีอยู่ใน Java" ไม่เหมือนกับ "จะไม่มีอยู่ใน Java"
ColinD

9
@Webinator: ผู้โพสต์หลายคนแสดงความคิดเห็นว่ามีการส่งข้อเสนอ แต่ถูกปฏิเสธ ดังนั้นคำตอบจึงถูกต้อง 100% การโหวตเพื่อต่อต้านการโหวตลง
JeremyP

2
การปฏิบัติที่ไม่ดีของ @ColinD คือเมื่อคุณยอมแพ้กับรหัสที่น่าเกลียดนี้และตัดสินใจที่จะใช้Optionalและmapสิ่งต่างๆ เราไม่ได้ซ่อนปัญหาหากค่าเป็นโมฆะซึ่งหมายความว่าบางครั้งคาดว่าจะเป็นโมฆะและคุณต้องจัดการกับสิ่งนั้น บางครั้งค่าเริ่มต้นก็สมเหตุสมผลอย่างสมบูรณ์และไม่ใช่แนวทางปฏิบัติที่ไม่ดี
M.kazem Akhgary

2
Java ปฏิเสธที่จะทันสมัย เพียงแค่ยุติภาษานี้แล้ว
Plagon


19

วิธีหนึ่งในการแก้ปัญหาการขาด "?" ตัวดำเนินการที่ใช้ Java 8 โดยไม่มีค่าใช้จ่ายในการ try-catch (ซึ่งอาจซ่อนNullPointerExceptionต้นกำเนิดจากที่อื่นตามที่กล่าวไว้ได้เช่นกัน) คือการสร้างคลาสเพื่อ "ไปป์" เมธอดในสไตล์ Java-8-Stream

public class Pipe<T> {
    private T object;

    private Pipe(T t) {
        object = t;
    }

    public static<T> Pipe<T> of(T t) {
        return new Pipe<>(t);
    }

    public <S> Pipe<S> after(Function<? super T, ? extends S> plumber) {
        return new Pipe<>(object == null ? null : plumber.apply(object));
    }

    public T get() {
        return object;
    }

    public T orElse(T other) {
        return object == null ? other : object;
    }
}

จากนั้นตัวอย่างที่กำหนดจะกลายเป็น:

public String getFirstName(Person person) {
    return Pipe.of(person).after(Person::getName).after(Name::getGivenName).get();
}

[แก้ไข]

เมื่อคิดต่อไปฉันพบว่าเป็นไปได้ที่จะบรรลุสิ่งเดียวกันโดยใช้คลาส Java 8 มาตรฐานเท่านั้น:

public String getFirstName(Person person) {
    return Optional.ofNullable(person).map(Person::getName).map(Name::getGivenName).orElse(null);
}

ในกรณีนี้มันเป็นไปได้ที่จะเลือกค่าเริ่มต้น (เช่น"<no first name>") แทนโดยผ่านเป็นพารามิเตอร์ของnullorElse


ฉันชอบวิธีแก้ปัญหาแรกของคุณดีกว่า คุณจะปรับปรุงPipeชั้นเรียนของคุณอย่างไรเพื่อปรับorElseฟังก์ชันการทำงานเพื่อที่ฉันจะสามารถส่งผ่านวัตถุที่ไม่ใช่ค่าว่างภายในorElseเมธอดได้โดยพลการ
ThanosFisherman

@ThanosFisherman ฉันได้เพิ่มorElseวิธีการในPipeชั้นเรียนแล้ว
Helder Pereira


7

ที่จริงผู้ประกอบการปลอดภัย dereference Groovy ของ คุณไม่สามารถใช้ใน Java ล้วน ๆ ได้ (น่าเศร้า) ดังนั้นโพสต์นั้นจึงไม่ถูกต้อง (หรืออาจทำให้เข้าใจผิดได้เล็กน้อยหากอ้างว่า Groovy เป็น "Java เวอร์ชันล่าสุด")


2
ดังนั้นบทความจึงไม่ถูกต้อง - ไวยากรณ์ไม่มีอยู่ใน Java ดั้งเดิม อืม.
Erty Seidohl

แต่ลิงค์เสีย
bvdb

6

Java ไม่มีไวยากรณ์ที่แน่นอนแต่สำหรับ JDK-8 เรามีAPI เสริมพร้อมวิธีการต่างๆที่เราจำหน่าย ดังนั้นเวอร์ชัน C # ที่ใช้ตัวดำเนินการเงื่อนไขว่าง :

return person?.getName()?.getGivenName(); 

สามารถเขียนได้ดังต่อไปนี้ใน Java ด้วยOptional API :

 return Optional.ofNullable(person)
                .map(e -> e.getName())
                .map(e -> e.getGivenName())
                .orElse(null);

ถ้ามีของperson, getNameหรือgetGivenNameเป็นโมฆะแล้ว null จะถูกส่งกลับ


2

เป็นไปได้ที่จะกำหนดวิธีการใช้งานซึ่งแก้ปัญหานี้ได้ด้วย Java 8 lambda

นี่คือรูปแบบของการแก้ปัญหา H-MANsNullPointerExceptionแต่จะใช้วิธีการมากเกินไปกับการขัดแย้งหลายที่จะจัดการกับหลายขั้นตอนของการจับแทน

แม้ว่าฉันจะคิดว่าวิธีนี้เป็นวิธีที่ดี แต่ฉันคิดว่าฉันชอบวินาทีของ Helder Pereiraหนึ่งวินาทีเนื่องจากไม่ต้องใช้วิธีการใด ๆ

void example() {
    Entry entry = new Entry();
    // This is the same as H-MANs solution 
    Person person = getNullsafe(entry, e -> e.getPerson());    
    // Get object in several steps
    String givenName = getNullsafe(entry, e -> e.getPerson(), p -> p.getName(), n -> n.getGivenName());
    // Call void methods
    doNullsafe(entry, e -> e.getPerson(), p -> p.getName(), n -> n.nameIt());        
}

/** Return result of call to f1 with o1 if it is non-null, otherwise return null. */
public static <R, T1> R getNullsafe(T1 o1, Function<T1, R> f1) {
    if (o1 != null) return f1.apply(o1);
    return null; 
}

public static <R, T0, T1> R getNullsafe(T0 o0, Function<T0, T1> f1, Function<T1, R> f2) {
    return getNullsafe(getNullsafe(o0, f1), f2);
}

public static <R, T0, T1, T2> R getNullsafe(T0 o0, Function<T0, T1> f1, Function<T1, T2> f2, Function<T2, R> f3) {
    return getNullsafe(getNullsafe(o0, f1, f2), f3);
}


/** Call consumer f1 with o1 if it is non-null, otherwise do nothing. */
public static <T1> void doNullsafe(T1 o1, Consumer<T1> f1) {
    if (o1 != null) f1.accept(o1);
}

public static <T0, T1> void doNullsafe(T0 o0, Function<T0, T1> f1, Consumer<T1> f2) {
    doNullsafe(getNullsafe(o0, f1), f2);
}

public static <T0, T1, T2> void doNullsafe(T0 o0, Function<T0, T1> f1, Function<T1, T2> f2, Consumer<T2> f3) {
    doNullsafe(getNullsafe(o0, f1, f2), f3);
}


class Entry {
    Person getPerson() { return null; }
}

class Person {
    Name getName() { return null; }
}

class Name {
    void nameIt() {}
    String getGivenName() { return null; }
}

1

ฉันไม่แน่ใจว่ามันจะได้ผล ถ้าพูดว่าการอ้างอิงบุคคลเป็นโมฆะรันไทม์จะแทนที่ด้วยอะไร คนใหม่? นั่นจะต้องให้บุคคลนั้นมีการเริ่มต้นเริ่มต้นบางอย่างที่คุณคาดหวังในกรณีนี้ คุณอาจหลีกเลี่ยงข้อยกเว้นการอ้างอิงที่เป็นโมฆะ แต่คุณยังคงได้รับพฤติกรรมที่คาดเดาไม่ได้หากคุณไม่ได้วางแผนสำหรับการตั้งค่าประเภทนี้

ที่ ?? ตัวดำเนินการใน C # อาจเรียกว่าตัวดำเนินการ "coalesce" ได้ดีที่สุด คุณสามารถเชื่อมโยงหลายนิพจน์และจะส่งคืนนิพจน์แรกที่ไม่เป็นโมฆะ น่าเสียดายที่ Java ไม่มี ฉันคิดว่าสิ่งที่ดีที่สุดที่คุณทำได้คือใช้ตัวดำเนินการ ternary เพื่อทำการตรวจสอบค่าว่างและประเมินทางเลือกแทนนิพจน์ทั้งหมดหากสมาชิกใด ๆ ในห่วงโซ่เป็นโมฆะ:

return person == null ? "" 
    : person.getName() == null ? "" 
        : person.getName().getGivenName();

คุณยังสามารถใช้ try-catch:

try
{
   return person.getName().getGivenName();
}
catch(NullReferenceException)
{
   return "";
}

1
"รันไทม์จะแทนที่ด้วยอะไร" ... การอ่านคำถามอาจช่วยได้ :-P มันจะแทนที่ด้วย null โดยทั่วไปความคิดดูเหมือนจะเป็นบุคคลนั้น? .getName ประเมินว่าเป็นโมฆะถ้าบุคคลนั้นเป็นโมฆะหรือเป็น person.getName ถ้าไม่ใช่ ดังนั้นจึงค่อนข้างเหมือนกับการแทนที่ "" ด้วย null ในทุกตัวอย่างของคุณ
subsub

1
NullReferenceException อาจถูกโยนเข้ามาgetName()หรือgetGivenName()คุณจะไม่รู้ว่าคุณเพียงแค่ส่งคืนสตริงว่างสำหรับเหตุการณ์ทั้งหมด
Jimmy T.

1
ใน Java คือ NullPointerException
Tuupertunut

ใน C # person?.getName()?.getGivenName() ?? ""เท่ากับตัวอย่างแรกของคุณโดยมีข้อยกเว้นว่าหากgetGivenName()ส่งคืนค่าว่างก็จะยังคงให้""
Austin_Anderson

0

ที่นั่นคุณมีการเรียกใช้ null-safe ใน Java 8:

public void someMethod() {
    String userName = nullIfAbsent(new Order(), t -> t.getAccount().getUser()
        .getName());
}

static <T, R> R nullIfAbsent(T t, Function<T, R> funct) {
    try {
        return funct.apply(t);
    } catch (NullPointerException e) {
        return null;
    }
}

ฉันจะต้องลองสิ่งนี้ SI มีข้อสงสัยอย่างจริงจังเกี่ยวกับธุรกิจ "ทางเลือก" ทั้งหมดนี้ ดูเหมือนแฮ็คที่น่ารังเกียจ
ggb667

3
จุดประสงค์ทั้งหมดของข้อเสนอตัวดำเนินการของเอลวิสคือทำสิ่งนี้ในบรรทัดเดียว แนวทางนี้ไม่ดีไปกว่าแนวทาง "if (! = null) ข้างต้นอันที่จริงฉันจะเถียงว่ามันแย่กว่านี้เนื่องจากไม่ใช่การส่งต่ออย่างตรงไปตรงมานอกจากนี้คุณควรหลีกเลี่ยงการขว้างปาและจับข้อผิดพลาดอันเนื่องมาจากค่าใช้จ่าย
JustinKSU

ดังที่ @Darron กล่าวไว้ในคำตอบอื่นเช่นเดียวกันกับที่นี่: "ปัญหาของรูปแบบนี้คือ NullPointerException อาจไม่ได้มาจากที่ที่คุณคาดไว้และด้วยเหตุนี้จึงอาจซ่อนข้อผิดพลาดจริง"
Helder Pereira

0

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

/**
 * Strong typed Lambda to return NULL or DEFAULT VALUES instead of runtime errors. 
 * if you override the defaultValue method, if the execution result was null it will be used in place
 * 
 * 
 * Sample:
 * 
 * It won't throw a NullPointerException but null.
 * <pre>
 * {@code
 *  new RuntimeExceptionHandlerLambda<String> () {
 *      @Override
 *      public String evaluate() {
 *          String x = null;
 *          return x.trim();
 *      }  
 *  }.get();
 * }
 * <pre>
 * 
 * 
 * @author Robson_Farias
 *
 */

public abstract class RuntimeExceptionHandlerLambda<T> {

    private T result;

    private RuntimeException exception;

    public abstract T evaluate();

    public RuntimeException getException() {
        return exception;
    }

    public boolean hasException() {
        return exception != null;
    }

    public T defaultValue() {
        return result;
    }

    public T get() {
        try {
            result = evaluate();
        } catch (RuntimeException runtimeException) {
            exception = runtimeException;
        }
        return result == null ? defaultValue() : result;
    }

}

0

คุณสามารถทดสอบโค้ดที่คุณให้มาและมันจะทำให้เกิดข้อผิดพลาดทางไวยากรณ์ดังนั้นจึงไม่รองรับใน Java Groovy รองรับและได้รับการเสนอสำหรับ Java 7 (แต่ไม่รวม)

อย่างไรก็ตามคุณสามารถใช้ตัวเลือกที่มีให้ใน Java 8 สิ่งนี้อาจช่วยคุณในการบรรลุบางสิ่งในบรรทัดที่คล้ายกัน https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html

ตัวอย่างรหัสสำหรับตัวเลือก


0

เนื่องจาก Android ไม่รองรับ Lambda Functions เว้นแต่ว่าระบบปฏิบัติการที่คุณติดตั้งไว้คือ> = 24 เราจึงจำเป็นต้องใช้การสะท้อนกลับ

// Example using doIt function with sample classes
public void Test() {
    testEntry(new Entry(null));
    testEntry(new Entry(new Person(new Name("Bob"))));
}

static void testEntry(Entry entry) {
    doIt(doIt(doIt(entry,  "getPerson"), "getName"), "getName");
}

// Helper to safely execute function 
public static <T,R> R doIt(T obj, String methodName) {
    try {
       if (obj != null) 
           return (R)obj.getClass().getDeclaredMethod(methodName).invoke(obj);
    } catch (Exception ignore) {
    }
    return null;
}
// Sample test classes
    static class Entry {
        Person person;
        Entry(Person person) { this.person = person; }
        Person getPerson() { return person; }
    }

    static class Person {
        Name name;
        Person(Name name) { this.name = name; }
        Name getName() { return name; }
    }

    static class Name {
        String name;
        Name(String name) { this.name = name; }
        String getName() {
            System.out.print(" Name:" + name + " ");
            return name;
        }
    }
}

-4

หากนี่ไม่ใช่ปัญหาด้านประสิทธิภาพสำหรับคุณคุณสามารถเขียนได้

public String getFirstName(Person person) {
  try {
     return person.getName().getGivenName();
  } catch (NullPointerException ignored) {
     return null;
  }
} 

8
ปัญหาของสไตล์นี้คือ NullPointerException อาจไม่ได้มาจากที่ที่คุณคาดหวังไว้ ดังนั้นจึงอาจซ่อนข้อผิดพลาดจริง
Darron

2
@ ดาร์รอนคุณสามารถยกตัวอย่างของ getter ที่คุณเขียนซึ่งสามารถโยน NPE ได้และคุณต้องการจัดการมันแตกต่างกันอย่างไร?
Peter Lawrey

2
คุณเรียกใช้มันเป็นตัวแปรแยกต่างหากและทดสอบด้วย if ตามปกติ แนวคิดเบื้องหลังตัวดำเนินการนี้คือการกำจัดความน่าเกลียดนั้น Darron พูดถูกวิธีการแก้ปัญหาของคุณอาจซ่อนและทิ้งข้อยกเว้นที่คุณต้องการโยนทิ้ง เช่นหากgetName()มีข้อยกเว้นภายในที่คุณไม่ต้องการทิ้ง
Mike Miller

2
ไม่มีข้อยกเว้นใด ๆ มันจะต้องเป็น NullPointerException คุณกำลังพยายามป้องกันตัวเองจากสถานการณ์ที่คุณยังไม่ได้เริ่มอธิบายว่ามันอาจเกิดขึ้นได้อย่างไรในแอปพลิเคชันจริง
Peter Lawrey

1
ในแอปพลิเคชันจริงPersonอาจเป็นพร็อกซีที่เข้าถึงฐานข้อมูลหรือหน่วยความจำที่ไม่ใช่ฮีปและอาจมีข้อผิดพลาดบางแห่ง ... ไม่เหมือนจริงมากนัก แต่ปีเตอร์ฉันพนันได้เลยว่าคุณไม่เคยเขียนโค้ดเหมือนข้างบน .
maaartinus
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.