Szukaj…


Wprowadzenie

Operatory w języku programowania Java to specjalne symbole, które wykonują określone operacje na jednym, dwóch lub trzech operandach, a następnie zwracają wynik.

Uwagi

Operator to symbol (lub symbole), który mówi programowi Java, aby wykonał operację na jednym, dwóch lub trzech operandach . Operator i jego operandy tworzą wyrażenie (zobacz temat Wyrażenia). Operandy operatora są same w sobie wyrażeniami.

W tym temacie opisano około 40 różnych operatorów zdefiniowanych przez Javę. Osobny temat Wyrażenia wyjaśnia:

  • jak operatory, operandy i inne rzeczy są łączone w wyrażenia,
  • w jaki sposób wyrażenia są oceniane, oraz
  • jak działają pisanie wyrażeń, konwersje i ocena wyrażeń.

Operator konkatenacji ciągów (+)

Symbol + może oznaczać trzy różne operatory w Javie:

  • Jeśli przed + nie ma argumentu, to jest to jednoargumentowy operator Plus.
  • Jeśli są dwa operandy i oba są numeryczne. jest to binarny operator dodawania.
  • Jeśli są dwa operandy i przynajmniej jeden z nich jest String , to jest to binarny operator konkatenacji.

W prostym przypadku operator konkatenacji łączy dwa ciągi, dając trzeci ciąg. Na przykład:

String s1 = "a String";
String s2 = "This is " + s1;    // s2 contains "This is a String"

Gdy jeden z dwóch operandów nie jest ciągiem, jest konwertowany na String w następujący sposób:

  • Operand, którego typ jest typem pierwotnym, jest konwertowany tak, jakby toString() dla wartości w ramce.

  • Operand, którego typem jest typ referencyjny, jest konwertowany przez wywołanie metody toString() operandu. Jeśli operand ma null lub jeśli toString() zwraca null , wówczas zamiast niego używany jest literał "null" .

Na przykład:

int one = 1;
String s3 = "One is "  + one;         // s3 contains "One is 1"
String s4 = null + " is null";        // s4 contains "null is null"
String s5 = "{1} is " + new int[]{1}; // s5 contains something like
                                      // "{} is [I@xxxxxxxx"

Wyjaśnienie dla przykładu s5 jest takie, że toString() na typach tablic jest dziedziczona z java.lang.Object , a zachowanie polega na tworzeniu łańcucha składającego się z nazwy typu i kodu mieszającego tożsamość obiektu.

Operator konkatenacji jest określony w celu utworzenia nowego obiektu String , z wyjątkiem przypadku, gdy wyrażenie jest wyrażeniem stałym. W tym drugim przypadku wyrażenie jest oceniane w typie kompilacji, a jego wartość środowiska wykonawczego jest równoważna literałowi ciągu. Oznacza to, że przy dzieleniu długiego ciągu literalnego takiego jak ten nie ma narzutu:

String typing = "The quick brown fox " +
                "jumped over the " +
                "lazy dog";           // constant expression

Optymalizacja i wydajność

Jak wspomniano powyżej, z wyjątkiem wyrażeń stałych, każde wyrażenie konkatenacji ciągu tworzy nowy obiekt String . Rozważ ten kod:

public String stars(int count) {
    String res = "";
    for (int i = 0; i < count; i++) {
        res = res + "*";
    }
    return res;
}

W powyższej metodzie każda iteracja pętli utworzy nowy String znaków, który jest o jeden znak dłuższy niż poprzednia iteracja. Każda konkatenacja kopiuje wszystkie znaki w ciągach argumentów, tworząc nowy String . Zatem stars(N) będą:

  • utwórz N nowych obiektów String i wyrzuć wszystkie oprócz ostatniego,
  • skopiuj N * (N + 1) / 2 znaki oraz
  • generuje O(N^2) bajtów śmieci.

Jest to bardzo kosztowne dla dużych N Rzeczywiście, każdy kod łączący łańcuchy w pętli może mieć ten problem. Lepszym sposobem na napisanie tego byłoby:

public String stars(int count) {
    // Create a string builder with capacity 'count' 
    StringBuilder sb = new StringBuilder(count);
    for (int i = 0; i < count; i++) {
        sb.append("*");
    }
    return sb.toString();
}

Idealnie powinieneś ustawić pojemność StringBuilder , ale jeśli to nie jest praktyczne, klasa automatycznie powiększy tablicę podkładu, którą konstruktor używa do przechowywania znaków. (Uwaga: implementacja rozszerza wykładniczo tablicę podkładową. Ta strategia utrzymuje tę liczbę znaków kopiowaną do O(N) zamiast O(N^2) .)

Niektóre osoby stosują ten wzór do wszystkich konkatenacji łańcuchów. Jest to jednak niepotrzebne, ponieważ JLS pozwala kompilatorowi Java na optymalizację łączenia łańcuchów w ramach jednego wyrażenia. Na przykład:

String s1 = ...;
String s2 = ...;    
String test = "Hello " + s1 + ". Welcome to " + s2 + "\n";

będzie zazwyczaj zoptymalizowany przez kompilator kodu bajtowego do czegoś takiego;

StringBuilder tmp = new StringBuilder();
tmp.append("Hello ")
tmp.append(s1 == null ? "null" + s1);
tmp.append("Welcome to ");
tmp.append(s2 == null ? "null" + s2);
tmp.append("\n");
String test = tmp.toString();

(Kompilator JIT może to zoptymalizować, jeśli może wywnioskować, że s1 lub s2 nie mogą mieć null ). Należy jednak pamiętać, że ta optymalizacja jest dozwolona tylko w obrębie jednego wyrażenia.

Krótko mówiąc, jeśli martwisz się wydajnością konkatenacji łańcuchów:

  • Wykonaj optymalizację ręczną, jeśli wykonujesz powtarzane konkatenacje w pętli (lub podobnej).
  • Nie optymalizuj ręcznie pojedynczego wyrażenia konkatenacji.

Operatory arytmetyczne (+, -, *, /,%)

Język Java zapewnia 7 operatorów, którzy wykonują arytmetykę na liczbach całkowitych i zmiennoprzecinkowych.

  • Istnieją dwa + operatory:
    • Operator dodawania binarnego dodaje jedną liczbę do drugiej. (Istnieje również operator binarny + , który wykonuje konkatenację łańcuchów. Jest to opisane w osobnym przykładzie).
    • Jednoargumentowy operator plus nie robi nic poza aktywowaniem promocji numerycznej (patrz poniżej)
  • Są dwa - operatorzy:
    • Operator odejmowania binarnego odejmuje jedną liczbę od drugiej.
    • Jednoargumentowy operator minus odpowiada odjęciu jego operandu od zera.
  • Binarny operator mnożenia (*) mnoży jedną liczbę przez drugą.
  • Operator podziału binarnego (/) dzieli jedną liczbę na drugą.
  • Operator reszty binarnej 1 (%) oblicza resztę, gdy jedna liczba jest dzielona przez drugą.

1. Często jest to niepoprawnie nazywane operatorem „modułu”. „Remainder” to termin używany przez JLS. „Moduł” i „pozostałość” to nie to samo.

Typ argumentu i wyniku oraz promocja numeryczna

Operatory wymagają operandów numerycznych i dają wyniki numeryczne. Typami operandów może być dowolny prymitywny typ liczbowy (tj. byte , short , char , int , long , float lub double ) lub dowolny numeryczny typ otoki zdefiniowany w java.lang ; tj. ( Byte , Character , Short , liczba Integer , Long , Float lub Double .

Typ wyniku określa się na podstawie typów operandów lub operandów w następujący sposób:

  • Jeśli jeden z operandów jest double lub Double , wówczas typ wyniku jest double .
  • W przeciwnym razie, jeśli jeden z operandów jest float lub Float , wówczas typ wyniku jest float .
  • W przeciwnym razie, jeśli jeden z operandów jest long lub Long , typ wyniku jest long .
  • W przeciwnym razie typ wyniku to int . Obejmuje to byte , short i char argumenty, jak i `int.

Rodzaj wyniku operacji określa, w jaki sposób przeprowadzana jest operacja arytmetyczna i w jaki sposób operandy są obsługiwane

  • Jeśli typ wyniku jest double , operandy są promowane do double , a operacja jest wykonywana przy użyciu 64-bitowej (podwójnej precyzji binarnej) arytmetyki zmiennoprzecinkowej IEE 754.
  • Jeśli typem wyniku jest liczba float , operandy są promowane do wartości float , a operacja jest wykonywana przy użyciu arytmetyki zmiennoprzecinkowej IEE 754 32-bitowej (pojedynczej precyzji).
  • Jeśli typ wyniku jest long , operandy są promowane do long , a operacja jest wykonywana przy użyciu 64-bitowej arytmetyki liczb całkowitych ze znakiem z uzupełnieniem dwójkowym.
  • Jeśli typem wyniku jest int , operandy są promowane do int , a operacja jest wykonywana przy użyciu 32-bitowej arytmetyki liczb całkowitych ze znakiem uzupełnienia dwójkowego.

Promocja odbywa się w dwóch etapach:

  • Jeśli typ argumentu jest typem opakowania, wartość argumentu jest rozpakowywana do wartości odpowiedniego typu operacji podstawowej.
  • W razie potrzeby typ pierwotny jest promowany do wymaganego typu:
    • Promocja liczb do int lub long jest mniej strat.
    • Awans liczby float na double jest bezstratny.
    • Awans liczby całkowitej na wartość zmiennoprzecinkową może prowadzić do utraty precyzji. Konwersja jest wykonywana przy użyciu semantyki „zaokrągleń do najbliższego” IEE 768.

Znaczenie podziału

Operator / dzieli operand lewy n ( dywidenda ) i operand prawy d ( dzielnik ) i daje wynik q ( iloraz ).

Podział liczb całkowitych Java zaokrągla się do zera. Sekcja 15.17.2 JLS określa zachowanie podziału liczb całkowitych Java w następujący sposób:

Iloraz wytworzony dla argumentów n i d jest liczbą całkowitą q której wielkość jest tak duża, jak to możliwe, przy spełnieniu |d ⋅ q| ≤ |n| . Ponadto q jest dodatnie, gdy |n| ≥ |d| i n i d mają ten sam znak, ale q ma wartość ujemną, gdy |n| ≥ |d| i n i d mają przeciwne znaki.

Istnieje kilka specjalnych przypadków:

  • Jeśli n wynosi MIN_VALUE , a dzielnik wynosi -1, wówczas występuje przepełnienie liczb całkowitych, a wynikiem jest MIN_VALUE . W tym przypadku nie jest zgłaszany wyjątek.
  • Jeśli d wynosi 0, zgłaszany jest wyjątek „ArithmeticException.

Podział zmiennoprzecinkowy Java ma więcej przypadków krawędzi do rozważenia. Jednak podstawową ideą jest to, że wynik q jest wartością, która jest najbliższa spełnieniu d . q = n .

Dzielenie zmiennoprzecinkowe nigdy nie spowoduje wyjątku. Zamiast tego operacje dzielone przez zero dają wartości INF i NaN; patrz poniżej.

Znaczenie reszty

W przeciwieństwie do C i C ++, operator reszty w Javie działa zarówno z operacjami liczb całkowitych, jak i zmiennoprzecinkowych.

W przypadkach całkowitych wynik a % b jest definiowany jako liczba r taka, że (a / b) * b + r jest równe a , gdzie / , * i + są odpowiednimi operatorami całkowitymi Java. Dotyczy to wszystkich przypadków, z wyjątkiem gdy b wynosi zero. W takim przypadku reszta powoduje ArithmeticException .

Z powyższej definicji wynika, że a % b może być ujemny tylko wtedy, gdy a jest ujemny, i może być dodatni tylko wtedy, gdy a jest dodatni. Co więcej, wartość a % b jest zawsze mniejsza niż wielkość b .

Operacja reszty zmiennoprzecinkowej jest uogólnieniem przypadku na liczbę całkowitą. Wynik a % b jest resztą r jest zdefiniowany przez matematyczną relację r = a - (b ⋅ q) gdzie:

  • q jest liczbą całkowitą,
  • jest ujemny tylko wtedy, gdy a / b jest ujemny, dodatni tylko wtedy, gdy a / b jest dodatni, oraz
  • jego wielkość jest tak duża, jak to możliwe, bez przekraczania wielkości prawdziwego ilorazu matematycznego a i b .

Reszta zmiennoprzecinkowa może wytwarzać wartości INF i NaN w przypadkach krawędziowych, takich jak gdy b wynosi zero; patrz poniżej. To nie spowoduje wyjątku.

Ważna uwaga:

Wynik operacji zmiennoprzecinkowej reszty obliczonej przez % nie jest taki sam, jak wynik operacji reszty zdefiniowanej przez IEEE 754. Resztę IEEE 754 można obliczyć przy użyciu metody bibliotecznej Math.IEEEremainder .

Całkowitą przepełnienie

Java 32 i 64-bitowe wartości całkowite są podpisane i używają dwójkowej komplementacji dwójkowej. Na przykład, zakres liczb odwzorowane jako (32 bitów) int -2 +2 31 do 31 - 1.

Po dodaniu, odjęciu lub wielokrotności dwóch N-bitowych liczb całkowitych (N == 32 lub 64) wynik operacji może być zbyt duży, aby reprezentować jako N-bitową liczbę całkowitą. W takim przypadku operacja powoduje przepełnienie liczb całkowitych , a wynik można obliczyć w następujący sposób:

  • Operacja matematyczna jest wykonywana w celu uzyskania pośredniej reprezentacji dwóch uzupełnień całej liczby. Ta reprezentacja będzie większa niż N bitów.
  • Jako wynik wykorzystuje się dolne 32 lub 64 bity reprezentacji pośredniej.

Należy zauważyć, że przepełnienie liczb całkowitych w żadnym wypadku nie powoduje wyjątków.

Wartości zmiennoprzecinkowe INF i NAN

Java używa reprezentacji zmiennoprzecinkowych IEE 754 dla liczb float i double . Te reprezentacje mają pewne specjalne wartości do reprezentowania wartości, które nie należą do dziedziny liczb rzeczywistych:

  • Wartości „nieskończone” lub INF oznaczają liczby, które są zbyt duże. Wartość +INF oznacza liczby, które są zbyt duże i dodatnie. Wartość -INF oznacza liczby, które są zbyt duże i ujemne.
  • „Nieokreślony” / „nie liczba” lub NaN oznaczają wartości wynikające z bezsensownych operacji.

Wartości INF są tworzone przez operacje zmiennoprzecinkowe, które powodują przepełnienie, lub przez podzielenie przez zero.

Wartości NaN są wytwarzane przez podzielenie zera przez zero lub obliczenie zera reszty zera.

Nieoczekiwanie możliwe jest wykonywanie arytmetyki przy użyciu operandów INF i NaN bez wyzwalania wyjątków. Na przykład:

  • Dodanie + INF i wartości skończonej daje + INF.
  • Dodanie + INF i + INF daje + INF.
  • Dodanie + INF i -INF daje NaN.
  • Dzielenie przez INF daje wartość +0,0 lub -0,0.
  • Wszystkie operacje z jednym lub większą liczbą operandów NaN dają NaN.

Szczegółowe informacje znajdują się w odpowiednich podsekcjach JLS 15 . Zauważ, że jest to w dużej mierze „akademickie”. Dla typowych obliczeń INF lub NaN oznacza, że coś poszło nie tak; np. masz niekompletne lub nieprawidłowe dane wejściowe lub obliczenia zostały niepoprawnie zaprogramowane.

Operatory równości (==,! =)

Operatory == i != Są operatorami binarnymi, które oceniają na true lub false zależności od tego, czy operandy są równe. Operator == daje wartość true jeśli argumenty są równe, a false przeciwnym razie. Operator != Podaje false jeśli argumenty są równe, a w przeciwnym razie true .

Operatory te mogą być używane z operandami pierwotnymi i referencyjnymi, ale zachowanie jest znacznie inne. Według JLS istnieją trzy odrębne zestawy tych operatorów:

  • Operatory logiczne == i != .
  • Operatory numeryczne == i != .
  • Operatory Reference == i != .

Jednak we wszystkich przypadkach typ wyniku operatorów == i != boolean .

Operatory numeryczne == i !=

Gdy jeden (lub oba) argumenty operatora == lub != Jest prymitywnym typem liczbowym ( byte , short , char , int, long , float lub double ), operator jest porównaniem numerycznym. Drugi operand musi być albo pierwotnym typem liczbowym, albo pudełkowym typem liczbowym.

Zachowanie innych operatorów numerycznych jest następujące:

  1. Jeśli jeden z operandów jest typu pudełkowego, jest rozpakowywany.
  2. Jeśli którykolwiek z operandów jest teraz byte , short lub char , jest on awansowany na liczbę int .
  3. Jeśli typy operandów nie są takie same, to operand typu „mniejszego” jest promowany do typu „większego”.
  4. Porównanie przeprowadza się następnie w następujący sposób:
    • Jeśli promowane operandy są int lub long wartości są testowane, aby sprawdzić, czy są identyczne.
    • Jeśli promowane operandy są float lub double to:
      • dwie wersje zera ( +0.0 i -0.0 ) są traktowane jako równe
      • wartość NaN jest traktowana jako nie równa się niczego, i
      • inne wartości są równe, jeśli ich reprezentacje IEEE 754 są identyczne.

Uwaga: trzeba być ostrożnym przy użyciu == i != Porównać pływających wartości punktowe.

Operatory logiczne == i !=

Jeśli oba operandy są boolean , lub jeden jest boolean a drugi jest Boolean , operatory te to operatory logiczne == i != . Zachowanie jest następujące:

  1. Jeśli jeden z operandów ma wartość Boolean , jest rozpakowywany.
  2. Nieopakowane operandy są testowane, a wynik boolowski jest obliczany zgodnie z poniższą tabelą prawdy
ZA b A == B A! = B
fałszywy fałszywy prawdziwe fałszywy
fałszywy prawdziwe fałszywy prawdziwe
prawdziwe fałszywy fałszywy prawdziwe
prawdziwe prawdziwe prawdziwe fałszywy

Istnieją dwa „pułapki”, które sprawiają, że wskazane jest używanie == i != Oszczędnie z wartościami prawdy:

Operatory Reference == i !=

Jeśli oba operandy są odwołaniami do obiektów, operatory == i != Sprawdzają, czy dwa operandy odnoszą się do tego samego obiektu . To często nie to, czego chcesz. Aby sprawdzić, czy dwa obiekty są równe pod względem wartości , należy zamiast tego użyć metody .equals() .

String s1 = "We are equal";
String s2 = new String("We are equal");

s1.equals(s2); // true

// WARNING - don't use == or != with String values
s1 == s2;      // false

Ostrzeżenie: używanie == i != Porównać String wartości jest niepoprawny w większości przypadków; patrz http://www.riptutorial.com/java/example/16290/pitfall--using----to-compare-strings . Podobny problem dotyczy prymitywnych typów opakowań; patrz http://www.riptutorial.com/java/example/8996/pitfall--using----to-compare-primitive-wrappers-objects-such-as-integer .

O obudowach NaN

JLS 15.21.1 stwierdza, co następuje:

Jeśli jednym z operandów jest NaN , wynik == jest false ale wynik != Jest true . Rzeczywiście, test x != x jest true tylko wtedy, gdy wartość x to NaN .

Takie zachowanie jest (dla większości programistów) nieoczekiwane. Jeśli przetestujesz, czy wartość NaN jest równa sobie, odpowiedź brzmi „Nie, to nie jest!”. Innymi słowy, == nie jest zwrotne dla wartości NaN .

Jednak nie jest to „osobliwość” Java, takie zachowanie jest określone w standardach zmiennoprzecinkowych IEEE 754 i przekonasz się, że jest ono implementowane przez większość współczesnych języków programowania. (Aby uzyskać więcej informacji, zobacz http://stackoverflow.com/a/1573715/139985 ... zauważając, że jest to napisane przez kogoś, kto był „w pokoju, gdy podejmowano decyzje”!)

Operatory inkrementacji / dekrementacji (++ / -)

Zmienne mogą być zwiększane lub zmniejszane o 1 za pomocą odpowiednio operatorów ++ i -- .

Gdy operatory ++ i -- podążają za zmiennymi, są one odpowiednio nazywane odpowiednio po zwiększeniu i po zmniejszeniu .

int a = 10;
a++; // a now equals 11
a--; // a now equals 10 again

Gdy operatory ++ i -- poprzedzają zmienne, operacje nazywane są odpowiednio wstępnym wzrostem i wstępnym zmniejszeniem .

int x = 10;
--x; // x now equals 9
++x; // x now equals 10

Jeśli operator poprzedza zmienną, wartość wyrażenia jest wartością zmiennej po jej zwiększeniu lub zmniejszeniu. Jeśli operator podąża za zmienną, wartość wyrażenia jest wartością zmiennej przed jej zwiększeniem lub zmniejszeniem.

int x=10;

System.out.println("x=" + x + " x=" + x++ + " x=" + x); // outputs x=10 x=10 x=11
System.out.println("x=" + x + " x=" + ++x + " x=" + x); // outputs x=11 x=12 x=12
System.out.println("x=" + x + " x=" + x-- + " x=" + x); // outputs x=12 x=12 x=11
System.out.println("x=" + x + " x=" + --x + " x=" + x); // outputs x=11 x=10 x=10

Uważaj, aby nie nadpisywać przyrostów ani dekrecji. Dzieje się tak, jeśli użyjesz operatora post-in / decrement na końcu wyrażenia, które jest ponownie przypisane do samej zmiennej in / decremented. Zwiększenie / zmniejszenie nie będzie miało wpływu. Mimo że zmienna po lewej stronie jest poprawnie zwiększana, jej wartość zostanie natychmiast zastąpiona poprzednio ocenionym wynikiem z prawej strony wyrażenia:

int x = 0; 
x = x++ + 1 + x++;      // x = 0 + 1 + 1 
                        // do not do this - the last increment has no effect (bug!) 
System.out.println(x);  // prints 2 (not 3!) 

Poprawny:

int x = 0;
x = x++ + 1 + x;        // evaluates to x = 0 + 1 + 1
x++;                    // adds 1
System.out.println(x);  // prints 3 

Operator warunkowy (? :)

Składnia

{warunek do oceny} ? {instrukcja-wykonana-na-prawdziwej} : {instrukcja-wykonana-na-fałszywej}

Jak pokazano w składni, operator warunkowy (znany również jako operator trójskładnikowy 1 ) używa ? (znak zapytania) i : (dwukropek), aby umożliwić warunkowe wyrażenie dwóch możliwych wyników. Można go użyć do zastąpienia dłuższych bloków if-else aby zwrócić jedną z dwóch wartości w zależności od warunku.

result = testCondition ? value1 : value2

Jest równa

if (testCondition) { 
    result = value1; 
} else { 
    result = value2; 
}

Można to odczytać jako „Jeśli testCondition jest prawdziwy, ustaw wynik na wartość1; w przeciwnym razie ustaw wynik na wartość 2 ”.

Na przykład:

// get absolute value using conditional operator 
a = -10;
int absValue = a < 0 ? -a : a;
System.out.println("abs = " + absValue); // prints "abs = 10"

Jest równa

// get absolute value using if/else loop
a = -10;
int absValue;
if (a < 0) {
    absValue = -a;
} else {
    absValue = a;
}
System.out.println("abs = " + absValue); // prints "abs = 10"

Wspólne użycie

Możesz użyć operatora warunkowego do przypisań warunkowych (takich jak sprawdzanie wartości NULL).

String x = y != null ? y.toString() : ""; //where y is an object

Ten przykład jest równoważny z:

String x = "";

if (y != null) {
    x = y.toString();
}

Ponieważ operator warunkowy ma drugi najniższy priorytet, ponad operatory przypisania , rzadko występuje potrzeba użycia nawiasów wokół warunku , ale nawiasy są wymagane wokół całej konstrukcji operatora warunkowego w połączeniu z innymi operatorami:

// no parenthesis needed for expressions in the 3 parts
10 <= a && a < 19 ? b * 5 : b * 7

// parenthesis required
7 * (a > 0 ? 2 : 5)

Zagnieżdżanie operatorów warunkowych można również wykonać w trzeciej części, gdzie działa bardziej jak łączenie w łańcuch lub instrukcja switch.

a ? "a is true" :
b ? "a is false, b is true" :
c ? "a and b are false, c is true" :
    "a, b, and c are false"

//Operator precedence can be illustrated with parenthesis:

a ? x : (b ? y : (c ? z : w))

Notatka:

1 - Zarówno specyfikacja języka Java, jak i samouczek Java nazywają operatora ( ? : :) Operatorem warunkowym . Samouczek mówi, że jest on „znany również jako operator trójskładnikowy”, ponieważ jest (obecnie) jedynym trójskładnikowym operatorem zdefiniowanym przez Javę. Terminologia „Operator warunkowy” jest spójna z C i C ++ oraz innymi językami z równoważnym operatorem.

Operatory bitowe i logiczne (~, &, |, ^)

Język Java udostępnia 4 operatorów, którzy wykonują operacje bitowe lub logiczne na operandach całkowitoliczbowych lub boolowskich.

  • Operator dopełniacza ( ~ ) jest operatorem jednoargumentowym, który dokonuje bitowej lub logicznej inwersji bitów jednego operandu; patrz JLS 15.15.5. .
  • Operator AND ( & ) jest operatorem binarnym, który wykonuje bitowe lub logiczne „i” dwóch operandów; patrz JLS 15.22.2. .
  • Operator OR ( | ) jest operatorem binarnym, który wykonuje bitową lub logiczną operację „włącznie” lub dwóch operandów; patrz JLS 15.22.2. .
  • Operator XOR ( ^ ) jest operatorem binarnym, który wykonuje bitowe lub logiczne „wyłączne” lub dwa operandy; patrz JLS 15.22.2. .

Operacje logiczne wykonywane przez te operatory, gdy operandami są logiczne wartości, można podsumować w następujący sposób:

ZA b ~ A A i B. A | b A ^ B
0 0 1 0 0 0
0 1 1 0 1 1
1 0 0 0 1 1
1 1 0 1 1 0

Zauważ, że w przypadku argumentów całkowitych powyższa tabela opisuje, co dzieje się z poszczególnymi bitami. Operatory faktycznie działają na wszystkich 32 lub 64 bitach argumentu lub argumentów równolegle.

Typy argumentów i typy wyników.

Zwykłe konwersje arytmetyczne mają zastosowanie, gdy operandami są liczby całkowite. Typowe przypadki użycia dla operatorów bitowych


Operator ~ służy do odwracania wartości boolowskiej lub zmiany wszystkich bitów w operandzie liczby całkowitej.

Operator & służy do „maskowania” niektórych bitów w operandzie liczby całkowitej. Na przykład:

int word = 0b00101010;
int mask = 0b00000011;   // Mask for masking out all but the bottom 
                         // two bits of a word
int lowBits = word & mask;            // -> 0b00000010
int highBits = word & ~mask;          // -> 0b00101000

The | operator służy do łączenia wartości prawdy dwóch operandów. Na przykład:

int word2 = 0b01011111; 
// Combine the bottom 2 bits of word1 with the top 30 bits of word2
int combined = (word & mask) | (word2 & ~mask);   // -> 0b01011110

Operator ^ służy do przełączania lub „przerzucania” bitów:

int word3 = 0b00101010;
int word4 = word3 ^ mask;             // -> 0b00101001

Aby uzyskać więcej przykładów użycia operatorów bitowych, zobacz Manipulowanie bitami

Instancja operatora

Ten operator sprawdza, czy obiekt należy do określonej klasy / typu interfejsu. operator instanceof jest zapisywany jako:

( Object reference variable ) instanceof  (class/interface type)

Przykład:

public class Test {

   public static void main(String args[]){
      String name = "Buyya";
      // following will return true since name is type of String
      boolean result = name instanceof String;  
      System.out.println( result );
   }
}

Dałoby to następujący wynik:

true

Ten operator nadal zwróci wartość true, jeśli porównywany obiekt jest przypisaniem zgodnym z typem po prawej stronie.

Przykład:

class Vehicle {}

public class Car extends Vehicle {
   public static void main(String args[]){
      Vehicle a = new Car();
      boolean result =  a instanceof Car;
      System.out.println( result );
   }
}

Dałoby to następujący wynik:

true

Operatory przypisania (=, + =, - =, * =, / =,% =, << =, >> =, >>> =, & =, | = i ^ =)

Operand po lewej stronie dla tych operatorów musi być nieokreśloną zmienną lub elementem tablicy. Operand prawej ręki musi być przypisany do operandu lewej ręki. Oznacza to, że albo typy muszą być takie same, lub prawy typ operandu musi być konwertowany na typ lewego operandu poprzez kombinację boksu, rozpakowania lub rozszerzenia. (Aby uzyskać szczegółowe informacje, patrz JLS 5.2 .)

Dokładne znaczenie operatorów „operacja i przypisanie” jest określone w JLS 15.26.2 jako:

Wyrażenie przypisania związku w postaci E1 op= E2 jest równoważne E1 = (T) ((E1) op (E2)) , gdzie T jest rodzajem E1 , z tym że E1 jest oceniane tylko raz.

Zauważ, że istnieje niejawna rzutowanie typu przed ostatecznym przypisaniem.

1. =

Prosty operator przypisania: przypisuje wartość prawego operandu do lewego operandu.

Przykład: c = a + b doda wartość a + b do wartości c i przypisze ją do c

2. +=

Operator „dodaj i przypisz”: dodaje wartość prawego operandu do wartości lewego operandu i przypisuje wynik do lewego operandu. Jeśli operand po lewej stronie ma typ String , to jest to operator „konkatenacji i przypisania”.

Przykład: c += a jest mniej więcej takie samo jak c = c + a

3. -=

Operator „odejmij i przypisz”: odejmuje wartość prawego operandu od wartości lewego operandu i przypisuje wynik do lewego operandu.

Przykład: c -= a jest mniej więcej takie samo jak c = c - a

4. *=

Operator „pomnóż i przypisz”: mnoży wartość argumentu prawej ręki przez wartość argumentu lewej ręki i przypisuje wynik do argumentu lewej ręki. .

Przykład: c *= a jest mniej więcej tym samym co c = c * a

5. /=

Operator „dziel i przypisz”: dzieli wartość prawego operandu przez wartość lewego operandu i przypisuje wynik do lewego operandu.

Przykład: c /*= a jest mniej więcej taki sam jak c = c / a

6 %=

Operator „moduł i przypisz”: oblicza moduł wartości argumentu prawej ręki na podstawie wartości argumentu lewej ręki i przypisuje wynik do argumentu lewej ręki.

Przykład: c %*= a jest mniej więcej takie samo jak c = c % a

7. <<=

Operator „przesuń w lewo i przypisz”.

Przykład: c <<= 2 jest mniej więcej takie samo jak c = c << 2

8. >>=

Operator „arytmetyczne przesunięcie w prawo i przypisanie”.

Przykład: c >>= 2 jest mniej więcej taki sam jak c = c >> 2

9. >>>=

Operator „logiczne przesunięcie w prawo i przypisanie”.

Przykład: c >>>= 2 jest mniej więcej taki sam jak c = c >>> 2

10. &=

Operator „bitowe i i przypisuj”.

Przykład: c &= 2 jest mniej więcej takie samo jak c = c & 2

11. |=

Operator „bitowy lub i przypisuj”.

Przykład: c |= 2 jest mniej więcej taki sam jak c = c | 2

12. ^=

Operator „wyłączny bitowo lub przypisz”.

Przykład: c ^= 2 jest mniej więcej taki sam jak c = c ^ 2

Operatory warunkowe i warunkowe lub Operatory (&& i ||)

Java udostępnia operator warunkowy i warunkowy lub operator, które przyjmują jeden lub dwa operandy typu boolean i dają wynik boolean . To są:

  • && - operator warunkowy AND,

  • || - operatory warunkowe OR. Ocena <left-expr> && <right-expr> jest równoważna z następującym pseudo-kodem:

    {
       boolean L = evaluate(<left-expr>);
       if (L) {
           return evaluate(<right-expr>);
       } else {
           // short-circuit the evaluation of the 2nd operand expression
           return false;
       }
    }
    

Ocena <left-expr> || <right-expr> jest równoważne z następującym pseudokodem:

    {
       boolean L = evaluate(<left-expr>);
       if (!L) {
           return evaluate(<right-expr>);
       } else {
           // short-circuit the evaluation of the 2nd operand expression
           return true;
       }
    }

Jak pokazuje powyższy pseudo-kod, zachowanie operatorów zwarciowych jest równoważne z użyciem instrukcji if / else .

Przykład - użycie && jako osłony w wyrażeniu

Poniższy przykład pokazuje najczęstszy wzorzec użycia dla operatora && . Porównaj te dwie wersje metody, aby sprawdzić, czy podana liczba Integer jest równa zero.

public boolean isZero(Integer value) {
    return value == 0;
}

public boolean isZero(Integer value) {
    return value != null && value == 0;
}

Pierwsza wersja działa w większości przypadków, ale jeśli argumentem value jest null , NullPointerException zostanie NullPointerException .

W drugiej wersji dodaliśmy test „ochronny”. value != null && value == 0 wyrażenie jest oceniane przez wykonanie najpierw value != null Test value != null . Jeśli test null powiedzie się (tzn. Że ma wartość true ), wówczas value == 0 wyrażenie jest oceniane. Jeśli test null zakończy się niepowodzeniem, wówczas ocena value == 0 jest pomijana (zwarcie) i nie otrzymujemy NullPointerException .

Przykład - użycie &&, aby uniknąć kosztownych obliczeń

Poniższy przykład pokazuje, jak można użyć && aby uniknąć względnie kosztownych obliczeń:

public boolean verify(int value, boolean needPrime) {
    return !needPrime | isPrime(value);
}

public boolean verify(int value, boolean needPrime) {
    return !needPrime || isPrime(value);
}

W pierwszej wersji oba operandy | będzie zawsze oceniane, więc (droga) metoda isPrime zostanie wywołana niepotrzebnie. Druga wersja pozwala uniknąć niepotrzebnego połączenia za pomocą || zamiast | .

Operatorzy Shift (<<, >> i >>>)

Język Java zapewnia trzem operatorom wykonywanie bitowego przesunięcia na 32 i 64-bitowe wartości całkowite. Są to wszystkie operatory binarne, przy czym pierwszy operand jest wartością, która ma zostać przesunięta, a drugi operand mówi, jak daleko należy się przesunąć.

  • Operator przesunięcia << lub lewy przesuwa wartość podaną przez pierwszy operand w lewo o liczbę pozycji bitów podaną przez drugi operand. Puste pozycje po prawej stronie są wypełnione zerami.

  • „>>” lub operator przesunięcia arytmetycznego przesuwa wartość podaną przez pierwszy argument operacji w prawo o liczbę pozycji bitów podanych przez drugi argument operacji. Puste pozycje na lewym końcu są wypełniane przez skopiowanie bitu najbardziej w lewo. Ten proces jest znany jako rozszerzenie znaku .

  • „>>>” lub logiczny operator przesunięcia w prawo przesuwa wartość podaną przez pierwszy argument operacji w prawo o liczbę pozycji bitów podanych przez drugi argument operacji. Puste pozycje na lewym końcu są wypełnione zerami.

Uwagi:

  1. Operatory te wymagają wartości int lub long jako pierwszego operandu i generują wartość tego samego typu, co pierwszy operand. (Trzeba będzie użyć rzutowania typu jawnego podczas przypisywania wyniku zmiany do zmiennej byte , short lub char .)

  2. Jeśli używasz operatora shift z pierwszym operandem, który jest byte , char lub short , jest on promowany na int a operacja tworzy int .)

  3. Drugi operand jest zredukowany modulo liczbą bitów operacji, aby podać wielkość przesunięcia. Aby uzyskać więcej informacji na temat koncepcji matematycznej mod , zobacz Przykłady modułu .

  4. Bity, które są przesunięte w lewo lub w prawo przez operację, są odrzucane. (Java nie zapewnia prymitywnego operatora „obracania”).

  5. Operator przesunięcia arytmetycznego jest równoważny dzieląc liczbę (uzupełnienie dwóch) przez potęgę 2.

  6. Lewy operator zmiany jest równoważny mnożąc liczbę (uzupełnienie dwóch) przez potęgę 2.

Poniższa tabela pomoże ci zobaczyć efekty trzech operatorów zmiany. (Liczby wyrażono w notacji binarnej w celu ułatwienia wizualizacji.)

Operand 1 Operand2 << >> >>>
0b0000000000001011 0 0b0000000000001011 0b0000000000001011 0b0000000000001011
0b0000000000001011 1 0b0000000000010110 0b0000000000000101 0b0000000000000101
0b0000000000001011 2) 0b0000000000101100 0b0000000000000010 0b0000000000000010
0b0000000000001011 28 0b1011000000000000 0b0000000000000000 0b0000000000000000
0b0000000000001011 31 0b1000000000000000 0b0000000000000000 0b0000000000000000
0b0000000000001011 32 0b0000000000001011 0b0000000000001011 0b0000000000001011
... ... ... ... ...
0b1000000000001011 0 0b1000000000001011 0b1000000000001011 0b1000000000001011
0b1000000000001011 1 0b0000000000010110 0b1100000000000101 0b0100000000000101
0b1000000000001011 2) 0b0000000000101100 0b1110000000000010 0b00100000000000100
0b1000000000001011 31 0b1000000000000000 0b1111111111111111 0b0000000000000001

Istnieją przykłady użytkowników operatorów zmiany w manipulacji bitami

Operator Lambda (->)

Począwszy od Java 8, operator Lambda ( -> ) jest operatorem używanym do wprowadzenia wyrażenia Lambda. Istnieją dwie typowe składnie, co ilustrują poniższe przykłady:

Java SE 8
  a -> a + 1              // a lambda that adds one to its argument
  a -> { return a + 1; }  // an equivalent lambda using a block.

Wyrażenie lambda definiuje anonimową funkcję, a dokładniej instancję anonimowej klasy, która implementuje funkcjonalny interfejs .

(Ten przykład znajduje się tutaj w celu uzyskania kompletności. Aby uzyskać pełne leczenie, zobacz temat Wyrażenia lambda ).

Operatory relacyjne (<, <=,>,> =)

Operatory < , <= , > i >= są operatorami binarnymi do porównywania typów numerycznych. Znaczenie operatorów jest zgodne z oczekiwaniami. Na przykład, jeśli a i b są zadeklarowane jako jeden z byte , short , char , int , long , float , double lub odpowiadających im pól:

- `a < b` tests if the value of `a` is less than the value of `b`. 
- `a <= b` tests if the value of `a` is less than or equal to the value of `b`. 
- `a > b` tests if the value of `a` is greater than the value of `b`. 
- `a >= b` tests if the value of `a` is greater than or equal to the value of `b`. 

Typ wyniku dla tych operatorów jest boolean we wszystkich przypadkach.

Operatorów relacyjnych można używać do porównywania liczb z różnymi typami. Na przykład:

int i = 1;
long l = 2;
if (i < l) {
    System.out.println("i is smaller");
}

Operatorów relacyjnych można używać, gdy jedna lub obie liczby są instancjami typów liczbowych w ramkach. Na przykład:

Integer i = 1;   // 1 is autoboxed to an Integer
Integer j = 2;   // 2 is autoboxed to an Integer
if (i < j) {
    System.out.println("i is smaller");
}

Dokładne zachowanie podsumowano w następujący sposób:

  1. Jeśli jeden z operandów jest typu pudełkowego, jest rozpakowywany.
  2. Jeśli którykolwiek z operandów jest teraz byte , short lub char , jest on awansowany na liczbę int .
  3. Jeśli typy operandów nie są takie same, to operand typu „mniejszego” jest promowany do typu „większego”.
  4. Porównanie jest wykonywane na wynikowych wartościach int , long , float lub double .

Należy zachować ostrożność przy porównaniach relacyjnych, które obejmują liczby zmiennoprzecinkowe:

  • Wyrażenia, które obliczają liczby zmiennoprzecinkowe, często powodują błędy zaokrąglania, ponieważ reprezentacje zmiennoprzecinkowe komputera mają ograniczoną precyzję.
  • Podczas porównywania liczb całkowitych i zmiennoprzecinkowych konwersja liczb całkowitych na zmiennoprzecinkowe może również prowadzić do błędów zaokrąglania.

Na koniec Java trochę obsługuje użycie operatorów relacyjnych z dowolnymi typami innymi niż te wymienione powyżej. Na przykład nie można używać tych operatorów do porównywania ciągów, tablic liczb i tak dalej.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow