Java "?" Toán tử để kiểm tra null - Nó là gì? (Không phải Ternary!)


86

Tôi đang đọc một bài báo được liên kết từ một câu chuyện chém gió, và bắt gặp mẩu tin nhỏ này:

Sử dụng phiên bản Java mới nhất, phiên bản này cố gắng làm cho việc kiểm tra con trỏ rỗng dễ dàng hơn bằng cách cung cấp cú pháp viết tắt cho quá trình kiểm tra con trỏ vô tận. Chỉ cần thêm dấu chấm hỏi vào mỗi lệnh gọi phương thức sẽ tự động bao gồm kiểm tra con trỏ null, thay thế một tổ chuột gồm các câu lệnh if-then, chẳng hạn như:

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

Với cái này:

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

Tôi đã tìm kiếm trên internet (được rồi, tôi đã dành ít nhất 15 phút để tìm kiếm các biến thể của "java question mark") và không nhận được gì. Vì vậy, câu hỏi của tôi: có tài liệu chính thức nào về điều này không? Tôi thấy rằng C # có một toán tử tương tự (toán tử "??"), nhưng tôi muốn lấy tài liệu cho ngôn ngữ mà tôi đang làm việc. Hoặc, đây chỉ là cách sử dụng toán tử bậc ba mà tôi Chưa từng thấy bao giờ.

Cảm ơn!

CHỈNH SỬA: Liên kết đến bài viết: http://infoworld.com/d/developer-world/12-programming-mistakes-avoid-292


3
Ít nhất chúng ta có thể có một liên kết đến bài báo?
Karl Knechtel

Và nguồn của đoạn mã?
khachik

5
Bài báo sai. infoworld.com/print/145292 Tôi tin rằng một bản gửi Project Coin đã được gửi cho nó. Nhưng nó không được chọn (vì những lý do được đề cập trong bài viết - nếu bạn muốn làm điều đó, hãy sử dụng C # hoặc thứ gì đó) và chắc chắn không có trong phiên bản hiện tại của ngôn ngữ Java.
Tom Hawtin - tackline

4
Điều đó không giống với C # ?? nhà điều hành: ?? liên kết null, tức là A ?? B == (A != null) ? A : B. Điều này xuất hiện để đánh giá một thuộc tính trên một đối tượng nếu tham chiếu đối tượng không phải là null, tức là A?.B == (A != null) ? A.B : null.
Rup

1
@Erty: là một người dùng khổng lồ của chú thích @NotNull, về cơ bản ở khắp mọi nơi trong mã tôi viết, tôi không còn biết rõ NPE là gì nữa (ngoại trừ khi sử dụng các API mô tả kém). Tuy nhiên, tôi thấy "ký hiệu tắt" này dễ thương và thú vị. Tất nhiên bài báo đúng khi nó nói rằng: Rốt cuộc, nó không loại bỏ được gốc rễ của vấn đề: sự gia tăng của các giá trị null do lập trình nhanh và lỏng lẻo. 'null' không tồn tại ở cấp OOA / OOD. Đó là một thứ vô nghĩa khác mang phong cách riêng của Java mà hầu như có thể giải quyết được. Đối với tôi, đó là @NotNull ở khắp mọi nơi.
Cú phápT3rr0r

Câu trả lời:


78

Ý tưởng ban đầu đến từ Groovy. Nó đã được đề xuất cho Java 7 như một phần của Project Coin: https://wiki.openjdk.java.net/display/Coin/2009+Proposal+TOC (Elvis và các nhà khai thác Null-Safe khác), nhưng vẫn chưa được chấp nhận .

Toán tử Elvis có liên quan ?: được đề xuất để x ?: yviết tắt cho x != null ? x : y, đặc biệt hữu ích khi x là một biểu thức phức tạp.


3
Trong java (nơi không có tính năng tự động ép buộc để null) một cách viết tắt chox!=null ? x : y
Michael Borgwardt

@Michael Borgwardt: điểm tốt, tôi đã nghĩ đến ngữ nghĩa thú vị.
ataylor

50
?là chữ ký của Elvis Presley; các :chỉ đại diện cho một đôi mắt như bình thường. Có lẽ ?:-olà gợi nhiều hơn ...
Andrzej Doyle

4
?:0Phải là toán tử "Không phải null, cũng không phải 0". Làm cho nó như vậy.
azz

3
Thực sự là một sai lầm khi gọi đề xuất như một nhà điều hành Elvis. Giải thích tại mail.openjdk.java.net/pipermail/coin-dev/2009-July/002089.html " Hội nghị không an toàn" là một thuật ngữ tốt hơn để sử dụng.
JustinKSU

62

Cú pháp này không tồn tại trong Java, cũng như dự kiến ​​nó sẽ được đưa vào bất kỳ phiên bản nào sắp tới mà tôi biết.


9
Tại sao lại ủng hộ? Câu trả lời này đúng 100% theo như tôi biết ... nếu bạn biết điều gì khác, vui lòng nói như vậy.
ColinD

6
@Webinator: Nó sẽ không có trong Java 7 hoặc 8 và không có "phiên bản sắp ra mắt" nào khác tại thời điểm này. Tôi cũng thấy rằng nó không chắc sẽ lọt vào được, vì nó khuyến khích những thực hành khá tệ. Tôi cũng không nghĩ rằng "chưa" là cần thiết, vì "không tồn tại trong Java" không giống như "sẽ không bao giờ tồn tại trong Java".
ColinD

9
@Webinator: một số người đăng đã nhận xét rằng một đề xuất đã được gửi nhưng bị từ chối. Như vậy câu trả lời là chính xác 100%. Ủng hộ để chống lại việc bỏ phiếu xuống.
JeremyP

2
@ColinD thực hành không tốt là khi bạn từ bỏ đoạn mã xấu xí này và quyết định sử dụng Optionalmapnội dung. chúng tôi không che giấu vấn đề nếu giá trị là nullable, có nghĩa là đôi khi nó được mong đợi là null và bạn phải xử lý điều đó. đôi khi giá trị mặc định là hoàn toàn hợp lý và nó không phải là một thực tiễn xấu.
M.kazem Akhgary 14/12/18

2
Java chỉ từ chối là hơi hiện đại. Chỉ cần chấm dứt ngôn ngữ này đã.
Plagon


19

Một cách để giải quyết việc thiếu "?" toán tử sử dụng Java 8 mà không có chi phí của try-catch (cũng có thể ẩn một NullPointerExceptionnguồn gốc ở nơi khác, như đã đề cập) là tạo một lớp phương thức "ống dẫn" theo kiểu 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;
    }
}

Sau đó, ví dụ đã cho sẽ trở thành:

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

[BIÊN TẬP]

Sau khi suy nghĩ sâu hơn, tôi đã phát hiện ra rằng thực sự có thể đạt được điều tương tự chỉ bằng cách sử dụng các lớp Java 8 tiêu chuẩn:

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

Trong trường hợp này, thậm chí có thể chọn một giá trị mặc định (như "<no first name>") thay vì nullchuyển nó dưới dạng tham số của orElse.


Tôi thích giải pháp đầu tiên của bạn tốt hơn. Bạn sẽ cải thiện Pipelớp của mình như thế nào để điều chỉnh một orElsechức năng để tôi có thể truyền một đối tượng không null tùy ý vào bên trong orElsephương thức?
ThanosFisherman

@ThanosFisherman Tôi đã thêm orElsephương thức vào Pipelớp.
Helder Pereira


7

Đó thực sự là nhà điều hành cuộc họp an toàn của Groovy . Bạn không thể sử dụng nó trong Java thuần túy (đáng buồn là), vì vậy bài đăng đó chỉ đơn giản là sai (hoặc nhiều khả năng hơi gây hiểu lầm, nếu nó tuyên bố Groovy là "phiên bản Java mới nhất").


2
Vì vậy, bài viết đã sai - cú pháp không tồn tại trong java gốc. Hừ!
Erty Seidohl

nhưng liên kết bị phá vỡ
bvdb

6

Java không có cú pháp chính xác nhưng kể từ JDK-8, chúng tôi có API tùy chọn với các phương thức khác nhau theo ý của chúng tôi. Vì vậy, phiên bản C # với việc sử dụng toán tử điều kiện null :

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

có thể được viết như sau bằng Java với API Tùy chọn :

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

nếu bất kỳ person, getNamehoặc getGivenNamelà null thì null được trả lại.


2

Có thể xác định các phương thức dùng để giải quyết vấn đề này theo một cách gần như tuyệt vời với Java 8 lambda.

Đây là một biến thể của giải pháp H-MANs nhưng nó sử dụng các phương thức nạp chồng với nhiều đối số để xử lý nhiều bước thay vì bắt NullPointerException.

Ngay cả khi tôi nghĩ giải pháp này khá tuyệt, tôi nghĩ tôi thích giải pháp giây của Helder Pereira hơn vì nó không yêu cầu bất kỳ phương pháp sử dụng nào.

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

Tôi không chắc điều này thậm chí sẽ hoạt động; nếu, giả sử, tham chiếu người là null, thời gian chạy sẽ thay thế nó bằng gì? Một người mới? Điều đó sẽ yêu cầu Người có một số khởi tạo mặc định mà bạn mong đợi trong trường hợp này. Bạn có thể tránh các trường hợp ngoại lệ tham chiếu rỗng nhưng bạn vẫn nhận được hành vi không thể đoán trước nếu bạn không lập kế hoạch cho các loại thiết lập này.

Các ?? toán tử trong C # có thể được gọi tốt nhất là toán tử "kết hợp"; bạn có thể xâu chuỗi một số biểu thức và nó sẽ trả về biểu thức đầu tiên không phải là null. Thật không may, Java không có nó. Tôi nghĩ điều tốt nhất bạn có thể làm là sử dụng toán tử bậc ba để thực hiện kiểm tra null và đánh giá một phương án thay thế cho toàn bộ biểu thức nếu bất kỳ thành viên nào trong chuỗi là null:

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

Bạn cũng có thể sử dụng try-catch:

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

1
"thời gian chạy sẽ thay thế nó bằng gì?" ... đọc câu hỏi có thể hữu ích :-P Nó sẽ thay thế nó bằng null. Nói chung, ý tưởng dường như là người đó? .GetName đánh giá là null nếu người đó là null hoặc cho person.getName nếu không. Vì vậy, nó khá giống như thay thế "" bằng null trong tất cả các ví dụ của bạn.
subsub

1
Một NullReferenceException cũng có thể được đưa vào getName()hoặc getGivenName()bạn sẽ không biết nếu bạn chỉ trả về một chuỗi trống cho tất cả các lần xuất hiện.
Jimmy T.

1
Trong Java nó là NullPointerException.
Tuupertunut

trong C #, person?.getName()?.getGivenName() ?? ""là tương đương với ví dụ đầu tiên của bạn, ngoại trừ rằng nếu getGivenName()trở thành vô giá trị, nó sẽ vẫn cung cấp cho""
Austin_Anderson

0

Bạn đã có nó, lệnh gọi null-safe trong 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;
    }
}

Tôi sẽ thử cái này. SI nghi ngờ nghiêm trọng về toàn bộ hoạt động kinh doanh "Không bắt buộc" này. Có vẻ như một vụ hack khó chịu.
ggb667

3
Toàn bộ mục đích của đề xuất của Nhà điều hành Elvis là thực hiện điều này trong một dòng. Cách tiếp cận này không tốt hơn cách tiếp cận "if (! = Null) ở trên. Trên thực tế, tôi cho rằng nó tồi tệ hơn vì nó không phải là một phương pháp thẳng tiến. Ngoài ra, bạn nên tránh ném và bắt lỗi do quá cao.
JustinKSU

Như @Darron đã nói trong một câu trả lời khác, điều tương tự cũng áp dụng ở đây: "Vấn đề với kiểu này là NullPointerException có thể không đến từ nơi bạn mong đợi. Và do đó nó có thể ẩn một lỗi thực sự."
Helder Pereira

0

Nếu ai đó đang tìm kiếm một giải pháp thay thế cho các phiên bản java cũ, bạn có thể thử cái này mà tôi đã viết:

/**
 * 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

Bạn có thể kiểm tra mã mà bạn đã cung cấp và nó sẽ báo lỗi cú pháp, do đó, nó không được hỗ trợ trong Java. Groovy không hỗ trợ nó và nó đã được đề xuất cho Java 7 (nhưng không bao giờ được đưa vào).

Tuy nhiên, bạn có thể sử dụng Tùy chọn được cung cấp trong Java 8. Điều này có thể giúp bạn đạt được điều gì đó tương tự. https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html

Mã mẫu cho tùy chọn


0

Vì Android không hỗ trợ Lambda Functions trừ khi hệ điều hành đã cài đặt của bạn> = 24, chúng tôi cần sử dụng phản chiếu.

// 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

Nếu đây không phải là vấn đề về hiệu suất đối với bạn, bạn có thể viết

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

8
Vấn đề với kiểu này là NullPointerException có thể không đến từ nơi bạn mong đợi. Và do đó nó có thể ẩn một lỗi thực sự.
Darron

2
@Darron, Bạn có thể đưa ra một ví dụ về một getter mà bạn đã viết có thể ném NPE và cách bạn muốn xử lý nó theo cách khác không?
Peter Lawrey

2
bạn chạy nó vào một biến riêng biệt và kiểm tra nó với if như bình thường. Ý tưởng đằng sau nhà điều hành này là để loại bỏ sự xấu xí đó. Darron đúng, giải pháp của bạn có thể ẩn và loại bỏ các ngoại lệ mà bạn muốn loại bỏ. Chẳng hạn như nếu getName()đã ném một ngoại lệ trong nội bộ mà bạn không muốn vứt bỏ.
Mike Miller

2
Không phải bất kỳ ngoại lệ nào, nó sẽ phải là một NullPointerException. Bạn đang cố gắng bảo vệ mình khỏi một tình huống mà bạn chưa bắt đầu giải thích nó có thể xảy ra như thế nào trong một ứng dụng thực tế.
Peter Lawrey

1
Trong một ứng dụng thực Personcó thể là một proxy truy cập DB hoặc một số bộ nhớ không phải heap và có thể có lỗi ở đâu đó ... Không thực tế lắm, nhưng Peter, tôi cá là bạn chưa bao giờ viết một đoạn mã như trên .
maaartinus
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.