Làm thế nào để có được giá trị khác null đầu tiên trong Java?


153

Có một hàm Java tương đương với hàm của SQL COALESCEkhông? Đó là, có cách nào để trả về giá trị không null đầu tiên của một số biến không?

ví dụ

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

Tôi muốn bằng cách nào đó có một tuyên bố rằng sẽ trả về giá trị null không đầu a, bc- trong trường hợp này, nó sẽ quay trở lại b, hoặc 4.4. (Một cái gì đó giống như phương pháp sql - return COALESCE(a,b,c)). Tôi biết rằng tôi có thể làm điều đó một cách rõ ràng với một cái gì đó như:

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

Nhưng tôi tự hỏi nếu có bất kỳ chức năng tích hợp, được chấp nhận để thực hiện điều này.


3
Bạn không cần một hàm như thế này vì bạn thực sự sẽ không tính 'c' nếu 'b' có câu trả lời bạn muốn. tức là bạn sẽ không xây dựng một danh sách các câu trả lời có thể chỉ để giữ một câu trả lời.
Peter Lawrey

Hãy cẩn thận: Không phải tất cả RDBMS ngắn mạch trên COALESCE. Oracle chỉ vừa mới bắt đầu làm điều đó.
Adam Gent

3
@ BrainSlugs83 Nghiêm túc? Java nên?
Dmitry Ginzburg

Câu trả lời:


108

Không, không có.

Gần nhất bạn có thể nhận được là:

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

Vì lý do hiệu quả, bạn có thể xử lý các trường hợp phổ biến như sau:

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
các lý do hiệu quả mà tôi đã đề cập ở trên là việc phân bổ mảng sẽ xảy ra mỗi khi bạn gọi phiên bản var arg của phương thức. điều này có thể gây lãng phí cho các mặt hàng đầy đủ, mà tôi nghi ngờ sẽ được sử dụng phổ biến.
les2

Mát mẻ. Cảm ơn. Trong trường hợp đó, tôi có thể dính vào các toán tử có điều kiện lồng nhau trong trường hợp này vì đây là lần duy nhất nó phải được sử dụng và phương thức do người dùng xác định sẽ là quá mức ...
froadie

8
Tôi vẫn sẽ kéo nó ra thành một phương thức trợ giúp riêng thay vì để lại một khối điều kiện "trông đáng sợ" trong mã - "điều đó làm gì?" theo cách đó, nếu bạn cần sử dụng lại nó, bạn có thể sử dụng các công cụ tái cấu trúc trong IDE của mình để di chuyển phương thức sang lớp tiện ích. có phương thức được đặt tên giúp ghi lại ý định của mã, đây luôn là một điều tốt, IMO. (và chi phí hoạt động của phiên bản không var-args có lẽ hầu như không thể đo lường được.)
les2

10
Xem ra: Trong coalesce(a, b), nếu blà một biểu thức phức tạp và akhông null, bvẫn được đánh giá. Đây không phải là trường hợp cho toán tử có điều kiện ?: Xem câu trả lời này .
Pang

điều này đòi hỏi mọi lý lẽ phải được tính toán trước khi kêu gọi kết hợp, vô nghĩa vì lý do hiệu suất
Ivan G.


59

Nếu chỉ có hai biến để kiểm tra và bạn đang sử dụng Guava, bạn có thể sử dụng MoreObjects.firstNonNull (T đầu tiên, T giây) .


49
Object.firstNonNull chỉ mất hai đối số; không có varargs tương đương trong ổi. Ngoài ra, nó ném ra một NullPulumException nếu cả hai đối số đều là null - điều này có thể hoặc không thể mong muốn.

2
Nhận xét tốt, Jake. NullPulumException này thường hạn chế sử dụng Object.firstNonNull. Tuy nhiên, đó là cách tiếp cận của Guava để tránh null.
Anton Shchastnyi

4
Phương pháp đó hiện không được chấp nhận và phương án được đề xuất là MoreObjects.firstNonNull
davidwebster48

1
Nếu NPE không mong muốn, thì hãy xem câu trả lời này
OrangeDog

51

Nếu chỉ có hai tham chiếu để kiểm tra và bạn đang sử dụng Java 8, bạn có thể sử dụng

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

Nếu bạn nhập tĩnh Tùy chọn, biểu thức không quá tệ.

Thật không may, trường hợp của bạn với "một số biến" là không thể với phương thức Tùy chọn. Thay vào đó bạn có thể sử dụng:

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

Theo câu trả lời của LES2, bạn có thể loại bỏ một số sự lặp lại trong phiên bản hiệu quả, bằng cách gọi hàm quá tải:

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 cho đẹp. Không chắc chắn về lợi ích hiệu quả qua vòng lặp đơn giản, nhưng nếu bạn sẽ tìm ra bất kỳ hiệu quả nhỏ nào theo cách này, nó cũng có thể rất hay.
Carl Manaster

3
cách này làm cho nó ít đau đớn hơn và ít bị lỗi hơn khi viết các biến thể quá tải!
les2

2
Điểm của phiên bản hiệu quả là không lãng phí bộ nhớ phân bổ một mảng bằng cách sử dụng varargs. Ở đây, bạn đang lãng phí bộ nhớ bằng cách tạo khung ngăn xếp cho mỗi coalesce()cuộc gọi lồng nhau . Gọi coalesce(a, b, c, d, e)sẽ tạo tối đa 3 khung ngăn xếp để tính toán.
Luke

10

Tình huống này đòi hỏi một số tiền xử lý. Bởi vì nếu bạn viết một hàm (phương thức tĩnh) chọn giá trị không null đầu tiên, nó sẽ đánh giá tất cả các mục. Đó là vấn đề nếu một số mục là các cuộc gọi phương thức (có thể là các cuộc gọi phương thức tốn kém thời gian). Và phương thức này được gọi ngay cả khi bất kỳ mục nào trước chúng không phải là null.

Một số chức năng như thế này

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

nên được sử dụng nhưng trước khi biên dịch thành mã byte nên có một bộ tiền xử lý tìm các cách sử dụng hàm „kết hợp này và thay thế nó bằng cách xây dựng như

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

Cập nhật 2014-09 / 02:

Nhờ có Java 8 và Lambdas, có khả năng có sự kết hợp thực sự trong Java! Bao gồm tính năng quan trọng: các biểu thức cụ thể chỉ được ước tính khi cần - nếu trước đó không phải là null, thì các biểu thức sau không được đánh giá (các phương thức không được gọi, tính toán hoặc hoạt động của đĩa / mạng không được thực hiện).

Tôi đã viết một bài viết về nó Java 8: coalesce - hledáme neNULLové hodnoty - (viết bằng tiếng Séc, nhưng tôi hy vọng rằng các ví dụ mã có thể hiểu được cho mọi người).


1
Bài viết hay - thật tuyệt khi có tiếng Anh.
lượng tử

1
Có điều gì đó về trang blog không hoạt động với Google Dịch. :-(
HairOfTheDog

5

Với ổi bạn có thể làm:

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

mà không ném NPE nếu cả hai abđược null.

EDIT: Tôi đã sai, nó ném NPE. Cách chính xác như nhận xét của Michal izmazia là:

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

1
Này, nó có:java.lang.NullPointerException: use Optional.orNull() instead of Optional.or(null)
Michal izmazia

1
Đây là một mẹo nhỏ:Optional.fromNullable(a).or(Optional.fromNullable(b)).orNull()
Michal izmazia

4

Chỉ để hoàn thiện, trường hợp "một vài biến" thực sự có thể, mặc dù không thanh lịch chút nào. Ví dụ, đối với các biến o, pq:

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

Xin lưu ý việc sử dụng các orElseGet()tham dự vào các trường hợp đó o, pq không biến nhưng biểu thức hoặc đắt tiền hoặc với mong muốn tác dụng phụ.

Trong trường hợp chung nhất 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

Điều này có thể tạo ra các biểu thức quá dài. Tuy nhiên, nếu chúng ta đang cố gắng di chuyển đến một thế giới mà không có null, thì v[i]có lẽ hầu hết đã thuộc loại Optional<String>, trái ngược với đơn giản String. Trong trường hợp này,

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

hoặc trong trường hợp biểu thức:

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

Hơn nữa, nếu bạn cũng đang di chuyển đến một phong cách chức năng-declarative, o, p, và qnên loại Supplier<String>như trong:

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

Và sau đó toàn bộ coalescegiảm đơn giản để o.get().

Đối với một ví dụ cụ thể hơn:

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()sẽ trở lại Optional<Integer>, một cách tự nhiên.

Và sau đó coalescetrở thành effectiveAge.get()hoặc đơn giản effectiveAgenếu chúng ta hài lòng với a Supplier<Integer>.

IMHO, với Java 8, chúng ta sẽ thấy ngày càng nhiều mã được cấu trúc như thế này, vì nó cực kỳ tự giải thích và hiệu quả cùng một lúc, đặc biệt là trong các trường hợp phức tạp hơn.

Tôi nhớ một lớp Lazy<T>chỉ gọi một lần Supplier<T>duy nhất, nhưng uể oải, cũng như sự nhất quán trong định nghĩa của Optional<T>(tức là Optional<T>- Optional<T>toán tử, hoặc thậm chí Supplier<Optional<T>>).


4

Bạn có thể thử điều này:

public static <T> T coalesce(T... t) {
    return Stream.of(t).filter(Objects::nonNull).findFirst().orElse(null);
}

Dựa trên phản hồi này


3

Làm thế nào về việc sử dụng các nhà cung cấp khi bạn muốn tránh đánh giá một số phương pháp đắt tiền?

Như thế này:

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

Và sau đó sử dụng nó như thế này:

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

Bạn cũng có thể sử dụng các phương thức quá tải cho các cuộc gọi với hai, ba hoặc bốn đối số.

Ngoài ra, bạn cũng có thể sử dụng các luồng với nội dung như sau:

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

Tại sao bọc đối số đầu tiên trong một Suppliernếu nó sẽ được kiểm tra nào? Vì lợi ích của sự đồng nhất?
Inego

0

Làm thế nào về:

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

Java ArrayList thuận tiện cho phép các mục nhập null và biểu thức này phù hợp bất kể số lượng đối tượng được xem xét. (Trong mẫu này, tất cả các đối tượng được coi là cần phải cùng loại.)


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

2
Chúa tôi ghét thuốc generic. Tôi thấy những gì bạn có nghĩa là ngay lập tức. Tôi đã phải nhìn vào @ LES2 hai lần để nhận ra rằng anh ấy đang làm điều tương tự (và có lẽ là "tốt hơn")! +1 cho rõ ràng
Bill K

Vâng, thuốc generic là con đường để đi. Nhưng tôi không quen thuộc lắm với những điều phức tạp.
Eric

10
Thời gian để học khái quát :-). Có rất ít sự khác biệt giữa ví dụ của @ LES2 và điều này, ngoài T thay vì Object. -1 để xây dựng hàm sẽ buộc ép giá trị trả về thành Double. Ngoài ra, để đặt tên một phương thức Java trong tất cả các mũ, có thể tốt trong SQL, nhưng không phải là phong cách tốt trong Java.
Avi

1
Tôi nhận ra rằng tất cả các mũ là thực hành xấu. Tôi chỉ cho OP cách viết một hàm theo tên mà họ yêu cầu. Đồng ý, các diễn viên trở lại Doublelà xa lý tưởng. Tôi chỉ không biết rằng các hàm tĩnh có thể được cung cấp các tham số loại. Tôi nghĩ đó chỉ là lớp học.
Eric
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.