Toán tử phím tắt “hoặc-gán” (| =) trong Java


89

Tôi có một tập hợp dài các phép so sánh phải làm trong Java và tôi muốn biết liệu một hoặc nhiều trong số chúng có đúng hay không. Chuỗi so sánh dài và khó đọc, vì vậy tôi đã chia nhỏ nó ra để dễ đọc và tự động chuyển sang sử dụng toán tử phím tắt |=thay vì sử dụng negativeValue = negativeValue || boolean.

boolean negativeValue = false;
negativeValue |= (defaultStock < 0);
negativeValue |= (defaultWholesale < 0);
negativeValue |= (defaultRetail < 0);
negativeValue |= (defaultDelivery < 0);

Tôi hy vọng negativeValuelà đúng nếu bất kỳ giá trị <something> nào mặc định là âm. Điều này có hợp lệ không? Nó sẽ làm những gì tôi mong đợi? Tôi không thể thấy nó được đề cập trên trang web hoặc stackoverflow của Sun, nhưng Eclipse dường như không có vấn đề với nó và mã biên dịch và chạy.


Tương tự, nếu tôi muốn thực hiện một số giao điểm hợp lý, tôi có thể sử dụng &=thay thế &&không?


13
Tại sao bạn không thử nó?
Roman

Đây là logic boolean chung, không chỉ Java. vì vậy bạn có thể tra cứu nó ở những nơi khác. Và tại sao bạn không thử nó?
Dykam

16
@Dykam: Không, có hành vi cụ thể ở đây. Java có thể chọn tạo | = ngắn mạch, như vậy nó sẽ không đánh giá RHS nếu LHS đã đúng - nhưng nó không.
Jon Skeet

2
@Jon Skeet: Việc đoản mạch sẽ thích hợp cho ||=toán tử không tồn tại , nhưng |=là dạng kết hợp của toán tử hoặc bitwise.
David Thornley

1
@Jon Skeet: Chắc chắn rồi, nhưng việc tạo |=đoản mạch sẽ không phù hợp với các toán tử gán ghép khác, vì a |= b;sẽ không giống như a = a | b;, với cảnh báo thông thường về việc đánh giá ahai lần (nếu nó quan trọng). Đối với tôi, có vẻ như quyết định lớn về hành vi ngôn ngữ không có ||=, vì vậy tôi thiếu ý kiến ​​của bạn.
David Thornley

Câu trả lời:


204

Đây |=là một toán tử gán ghép ( JLS 15.26.2 ) cho toán tử logic boolean |( JLS 15.22.2 ); không bị nhầm lẫn với điều kiện-hoặc ||( JLS 15.24 ). Ngoài ra còn có &=^=tương ứng với phiên bản gán ghép của lôgic boolean &^tương ứng.

Nói cách khác, boolean b1, b2hai điều này tương đương nhau:

 b1 |= b2;
 b1 = b1 | b2;

Sự khác biệt giữa các toán tử logic ( &|) so với các đối tác có điều kiện của chúng ( &&||) là các toán tử trước đây không "ngắn mạch"; cái sau làm. Đó là:

  • &| luôn đánh giá cả hai toán hạng
  • &&||đánh giá toán hạng bên phải theo điều kiện ; toán hạng bên phải chỉ được đánh giá nếu giá trị của nó có thể ảnh hưởng đến kết quả của phép toán nhị phân. Điều đó có nghĩa là toán hạng bên phải KHÔNG được đánh giá khi:
    • Toán hạng bên trái của &&đánh giá làfalse
      • (bởi vì bất kể toán hạng bên phải được đánh giá là gì, toàn bộ biểu thức là false)
    • Toán hạng bên trái của ||đánh giá làtrue
      • (bởi vì bất kể toán hạng bên phải được đánh giá là gì, toàn bộ biểu thức là true)

Vì vậy, quay trở lại câu hỏi ban đầu của bạn, vâng, cấu trúc đó hợp lệ, và mặc dù |=không phải là một phím tắt tương đương cho =||, nó tính toán những gì bạn muốn. Vì phía bên tay phải của |=toán tử trong cách sử dụng của bạn là một phép toán so sánh số nguyên đơn giản, thực tế là |không ngắn mạch là không đáng kể.

Có những trường hợp, khi bạn muốn hoặc thậm chí là bắt buộc, nhưng kịch bản của bạn không phải là một trong số đó.

Thật không may là không giống như một số ngôn ngữ khác, Java không có &&=||=. Điều này đã được thảo luận trong câu hỏi Tại sao Java không có các phiên bản gán ghép của các toán tử điều kiện-và và điều kiện-hoặc? (&& =, || =) .


+1, rất kỹ lưỡng. Có vẻ hợp lý khi một trình biên dịch có thể chuyển đổi thành toán tử ngắn mạch, nếu nó có thể xác định rằng RHS không có tác dụng phụ. bất kỳ manh mối về điều đó?
Carl

Tôi đọc rằng khi RHS là tầm thường và SC không cần thiết, các toán tử SC "thông minh" thực sự chậm hơn một chút. Nếu đúng, thì sẽ thú vị hơn khi tự hỏi liệu một số trình biên dịch có thể chuyển đổi SC thành NSC trong một số trường hợp nhất định hay không.
polygenelubricants, 22/03

@polygenelubricants các toán tử bị đoản mạch liên quan đến một nhánh của một số loại dưới mui xe, vì vậy nếu không có mẫu thân thiện với dự đoán nhánh đối với các giá trị chân lý được sử dụng với các toán tử và / hoặc kiến ​​trúc đang sử dụng sẽ không có dự đoán nhánh tốt / bất kỳ ( và giả sử trình biên dịch và / hoặc máy ảo không tự thực hiện bất kỳ tối ưu hóa liên quan nào), thì có, các nhà khai thác SC sẽ giới thiệu một số độ chậm so với không đoản mạch. Cách tốt nhất để tìm ra trình biên dịch có tác dụng gì là biên dịch một chương trình sử dụng SC so với NSC và sau đó so sánh mã bytecode để xem nó có khác nhau không.
JAB

Ngoài ra, không nên nhầm lẫn với các Bitwise OR đó là cũng |
user253751

16

Nó không phải là toán tử "phím tắt" (hoặc đoản mạch) theo cách || và && là (ở chỗ họ sẽ không đánh giá RHS nếu họ đã biết kết quả dựa trên LHS) nhưng nó sẽ làm những gì bạn muốn về mặt làm việc .

Ví dụ về sự khác biệt, mã này sẽ ổn nếu textlà null:

boolean nullOrEmpty = text == null || text.equals("")

trong khi điều này sẽ không:

boolean nullOrEmpty = false;
nullOrEmpty |= text == null;
nullOrEmpty |= text.equals(""); // Throws exception if text is null

(Rõ ràng bạn có thể làm "".equals(text)cho trường hợp cụ thể đó - tôi chỉ đang cố gắng chứng minh nguyên tắc.)


3

Bạn chỉ có thể có một tuyên bố. Được thể hiện qua nhiều dòng, nó đọc gần như chính xác như mã mẫu của bạn, chỉ ít bắt buộc hơn:

boolean negativeValue
    = defaultStock < 0 
    | defaultWholesale < 0
    | defaultRetail < 0
    | defaultDelivery < 0;

Đối với các biểu thức đơn giản nhất, việc sử dụng |có thể nhanh hơn ||vì mặc dù nó tránh thực hiện phép so sánh nhưng nó có nghĩa là sử dụng một nhánh ngầm và điều đó có thể đắt hơn nhiều lần.


Tôi đã bắt đầu chỉ với một, nhưng như đã nêu trong câu hỏi ban đầu, tôi cảm thấy rằng "Chuỗi so sánh dài và khó đọc, vì vậy tôi đã chia nó ra để dễ đọc". Ngoài ra, trong trường hợp này, tôi quan tâm đến việc tìm hiểu hành vi của | = hơn là làm cho đoạn mã cụ thể này hoạt động.
David Mason

1

Mặc dù nó có thể là quá mức cần thiết cho vấn đề của bạn, nhưng thư viện Guava có một số cú pháp hay với Predicates và đánh giá ngắn mạch của hoặc / và Predicates.

Về cơ bản, các so sánh được chuyển thành các đối tượng, được đóng gói thành một bộ sưu tập và sau đó được lặp lại. Đối với hoặc các vị từ, lần truy cập true đầu tiên trả về từ lần lặp và ngược lại đối với và.


1

Nếu đó là về khả năng đọc, tôi đã có khái niệm tách dữ liệu được kiểm tra khỏi logic kiểm tra. Mẫu mã:

// declare data
DataType [] dataToTest = new DataType[] {
    defaultStock,
    defaultWholesale,
    defaultRetail,
    defaultDelivery
}

// define logic
boolean checkIfAnyNegative(DataType [] data) {
    boolean negativeValue = false;
    int i = 0;
    while (!negativeValue && i < data.length) {
        negativeValue = data[i++] < 0;
    }
    return negativeValue;
}

Mã trông dài dòng hơn và dễ hiểu hơn. Bạn thậm chí có thể tạo một mảng trong cuộc gọi phương thức, như thế này:

checkIfAnyNegative(new DataType[] {
    defaultStock,
    defaultWholesale,
    defaultRetail,
    defaultDelivery
});

Nó dễ đọc hơn 'chuỗi so sánh' và cũng có lợi thế về hiệu suất của việc đoản mạch (với chi phí phân bổ mảng và gọi phương thức).

Chỉnh sửa: Có thể dễ dàng đọc hơn nữa bằng cách sử dụng các tham số varargs:

Chữ ký phương thức sẽ là:

boolean checkIfAnyNegative(DataType ... data)

Và cuộc gọi có thể trông như thế này:

checkIfAnyNegative( defaultStock, defaultWholesale, defaultRetail, defaultDelivery );

1
Phân bổ mảng và một cuộc gọi phương thức là một chi phí khá lớn cho việc đoản mạch, trừ khi bạn có một số thao tác đắt tiền trong các phép so sánh (ví dụ trong câu hỏi tuy rẻ). Phải nói rằng, hầu hết thời gian khả năng bảo trì của mã sẽ vượt trội hơn các cân nhắc về hiệu suất. Tôi có thể sẽ sử dụng một cái gì đó như thế này nếu tôi thực hiện so sánh khác nhau ở một loạt các vị trí khác nhau hoặc so sánh nhiều hơn 4 giá trị, nhưng đối với một trường hợp, nó hơi dài dòng đối với thị hiếu của tôi.
David Mason

@DavidMason Tôi đồng ý. Tuy nhiên, hãy nhớ rằng hầu hết các máy tính hiện đại sẽ nuốt loại chi phí đó trong vòng chưa đầy vài mili. Cá nhân tôi sẽ không quan tâm đến chi phí cho đến khi các vấn đề về hiệu suất, điều này có vẻ hợp lý . Ngoài ra, tính chi tiết của mã là một lợi thế, đặc biệt là khi javadoc không được cung cấp hoặc tạo bởi JAutodoc.
Krzysztof Jabłoński

1

Đây là một bài viết cũ nhưng để cung cấp một góc nhìn khác cho người mới bắt đầu, tôi xin đưa ra một ví dụ.

Tôi nghĩ rằng trường hợp sử dụng phổ biến nhất cho một toán tử ghép tương tự sẽ là +=. Tôi chắc rằng tất cả chúng ta đã viết một cái gì đó như thế này:

int a = 10;   // a = 10
a += 5;   // a = 15

Mục đích của việc này là gì? Mục đích là tránh viết sẵn và loại bỏ mã lặp lại.

Vì vậy, dòng tiếp theo thực hiện hoàn toàn tương tự, tránh nhập biến b1hai lần trên cùng một dòng.

b1 |= b2;

1
Khó phát hiện ra những sai sót trong thao tác và tên người vận hành, đặc biệt là khi tên dài. longNameOfAccumulatorAVariable += 5; so với longNameOfAccumulatorAVariable = longNameOfAccumulatorVVariable + 5;
Andrei

0
List<Integer> params = Arrays.asList (defaultStock, defaultWholesale, 
                                       defaultRetail, defaultDelivery);
int minParam = Collections.min (params);
negativeValue = minParam < 0;

Tôi nghĩ rằng tôi thích hơn, negativeValue = defaultStock < 0 || defaultWholesale < 0v.v.
Jon Skeet

Tôi thậm chí còn có nhiều hơn 4 tham số và tiêu chí giống nhau cho tất cả chúng thì tôi thích giải pháp của mình, nhưng để dễ đọc, tôi sẽ tách nó thành nhiều dòng (tức là tạo Danh sách, tìm giá trị nhỏ nhất, so sánh giá trị nhỏ nhất với 0) .
La Mã

Điều đó trông hữu ích cho một số lượng lớn các so sánh. Trong trường hợp này, tôi nghĩ rằng việc giảm độ rõ ràng không đáng để tiết kiệm khi đánh máy, v.v.
David Mason

0

|| boolean logic HOẶC
| bitwise HOẶC

| = bitwise bao gồm OR và toán tử gán

Lý do tại sao | = không shortcircit là bởi vì nó thực hiện một cách bitwise HOẶC không phải là OR logic. Điều đó có nghĩa là:

C | = 2 tương tự như C = C | 2

Hướng dẫn cho các toán tử java


KHÔNG có bitwise HOẶC với boolean! Toán tử |là số nguyên theo chiều dọc bit OR nhưng cũng là logic OR - xem Đặc tả Ngôn ngữ Java 15.22.2.
user85421

Bạn đúng vì đối với một bit đơn (boolean) bitwise và OR logic là tương đương. Mặc dù trên thực tế, kết quả là như nhau.
oneklc
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.