Kurs Java

Operatory

Appa Notka. Rozdział został rozbudowany o dodatkowe treści, które znajdują się w części zatytułowanej Operatory - Materiały dodatkowe. Przedstawiamy tam operator instanceof, operator warunkowy (ternary) oraz operatory bitowe wraz z przesunięciami.
Operatory są używane do wykonywania operacji na zmiennych i wartościach. Wartość jest nazywana operandem, podczas gdy operacja (wykonywana między dwoma operandami) jest definiowana przez operator.
Operand    Operator   Operand
   1          +         23
Zastosowanie. Operatory są dosyć często wykorzystywane razem z typami prostymi w ramach operacji matematycznych (takich jak dodawanie, mnożenie wartości...), ale często są też wykorzystywane do innych zadań. Pozwalają na przykład przypisać wartość do zmiennej (bardzo często wykonywane), albo uczestniczą w porównaniach wartości. Istnieją także operatory o zupełnie innym obszarze działania i te opisujemy na końcu rozdziału w materiałach dodatkowych.

Rodzaje operatorów

  • Operatory arytmetyczne - działają na podanych argumentach reprezentujących wartości liczbowe, w wyniku zwracając również wyznaczoną wartość liczbową. Najprościej mówiąc realizują podstawowe operacje arytmetyki:
    + - * / %
    
    W ramach przykładu pokażemy operator dodawania, należący do grupy operatorów arytmetycznych. W dowolnym programie możemy dodawać do siebie bezpośrednie wartości:
    int itemValue = 100 + 50;        // 150 (100 + 50)
    
    albo zmienną i wartość:
    int itemCurrentValue = itemValue + 250;      // 400 (150 + 250)
    
    albo zmienną i zmienną:
    int itemNextValue = itemCurrentValue + itemValue;     // 550 (400 + 150)
    
    Analogicznie będziemy wykonywać operacje na innych operatorach arytmetycznych.
  • Operatory przypisania - umożliwiają operację nadania, wpisania do określonej zmiennej nowej wartości:
    =  +=  -=  *=  /=  %=
    
    Zobaczmy teraz następujący przykład:
    int itemValue = 10;        
    
    Od tego momentu zmienna itemValue będzie miała w programie wartość 10. Na tak zainicowanej zmiennej możemy wykonać operację przypisania po dodaniu. Innymi słowy stosując ten operator jednocześnie pobieramy wartość zmiennej, następnie dodajemy ją do tej wartości, a na końcu przypisujemy wynik z powrotem do tej samej zmiennej:
    itemValue += 5;        
    
    Teraz zmienna itemValue będzie miała wartość 15. Podobnie działają pozostałe operatory przypisania, z tą różnicą, że zamiast dodawania wykonywana jest alternatywna operacja (np. mnożenie, dzielenie, procent itp.)
  • Operatory porównania - porównują argumenty równania i zwracają logiczną wartość, bazującą na sprawdzeniu czy wartość jest prawdziwa (true). Argumenty są wartościami liczbowymi, łańcuchowymi, logicznymi lub obiektowymi. Do operatorów porównania należą działania:
    ==  !=  <  >  <=  >=
    
    Najczęściej używamy tych operatorów razem z instrukcją warunkową np. if, o której piszemy w osobnym rozdziale:
    if (itemValue < maxValue) {
        ...
    }
    
    W tym przykładzie porównujemy wartość zmiennej itemValue do zmiennej maxValue. Oczekujemy, że wartość ta będzie mniejsza od wartości maksymalnej. Tylko wtedy zostanie wykonany kod w bloku instrukcji if.
  • Operatory logiczne - działają na argumentach reprezentujących wartości logiczne, w wyniku zwracając również wartość logiczną:
    &&  ||  !
    
    Operatory te są wręcz stworzone do tego by używać ich razem z instrukcją warunkową:
    if (itemVisible && itemEnabled) {
        ...
    }
    
    W przykładzie sprawdzamy czy obie zmienne zwracają wartość logiczną PRAWDA (true). Oczekujemy, że itemVisible zwaraca true oraz, że itemEnabled również zwraca true.

    Operator && tłumaczymy jako "i". Operator || oznacza "lub". Natomiast operator ! to zaprzeczenie całego warunku, na przykład:
    if !(itemVisible && itemEnabled) {
        ...
    }
    
    Powyższy zapis oznacza, że jeśli obie zmienne zwrócą true, to używając zaprzeczenia odwracamy ten wynik porównania i wtedy całość zwraca false.

Przykład w programie

Teraz zobaczmy jak typy proste wyglądają po wpięciu ich do naszego programu z rozdziału Hello JavAPPa:
public class Start {

    public static void main(String[] args) {
    
        int itemValue = 100 + 50;
        System.out.println(itemValue);
        
        int itemCurrentValue = itemValue + 250;
        System.out.println(itemCurrentValue);
        
        int itemNextValue = itemCurrentValue + itemValue;
        System.out.println(itemNextValue);
        
        // Przypisanie nowej wartości do zmiennej
        itemValue = 10;
        System.out.println(itemValue);
        
        // Dodanie nowej wartości do zmiennej
        // i przypisanie wyniku do tej samej zmiennej
        itemValue += 5;
        System.out.println(itemValue);
        
        // Pomnożenie wartości zmiennej
        // i przypisanie wyniku do tej samej zmiennej
        itemValue *= 3;
        System.out.println(itemValue);
        
        if (itemValue >= 10) {
            System.out.println("Zmienna jest większa od 10.");
        } 
    }
}
[Film bez dźwięku] [720p]

Operatory - Materiały dodatkowe

W tej sekcji przedstawiamy dodatkowe informacje oraz wprowadzamy kolejne przykłady. Warto zaznajomić się z tym materiałem, aby poszerzyć zasób swojej wiedzy. Powodzenia!
Appa Notka. Operator instanceof wymaga znajomości pojęć takich jak klasa, interfejs oraz dziedziczenie, dlatego, jeśli nie masz jeszcze wiedzy z tego obszaru, polecamy najpierw zapoznać się z rozdziałami kursu z części obiektowej, która zaczyna się rozdziałem Klasy i obiekty z życia wzięte.

Kolejne opisywane tutaj operatory, a więc operator warunkowy oraz operatory bitowe nie wymagają wiedzy z zakresu obiektowości.

Operator instanceof

Operator ten jest często wykorzystywany, choć w zasadzie powinniśmy tak pisać kod, aby korzystać z niego jak najmniej. To dosyć przewrotne stwierdzenie, ale jest w nim dużo racji, ponieważ z jednej strony operator ten jest nieodzowną częścią języka i czasem jego użycie jest wręcz nieuniknione, a z drugiej używany w nadmiarze świadczy o nie najlepiej zaprojektowanym kodzie.

Kiedy zatem używamy słowa kluczowego instanceof? Zawsze wtedy, gdy chcemy sprawdzić, czy dany obiekt jest oczekiwanego przez nas typu. Na przykład chcąc sprawdzić, czy obiekt jest typu String, użyjemy operatora w ten sposób:

public class Start {

    public static void main(String[] args) {

        String someText = "Great string!";
        Integer someNumber = 0;
        checkSomeObject(someText);
        checkSomeObject(someNumber);
    }

    static void checkSomeObject(Object object) {
        if(object instanceof String) {
            String someText = (String) object;
            System.out.println("Some text: " + someText);
        }
        else {
            System.out.println("Other type");
        }
    }
}
Wynik wykonania kodu:
Java instanceof
W zależności od tego, czy do metody przekazujemy obiekt typu String, czy innego typu (Integer), nasz kod zostanie obsłużony. W przypadku gdy przekazujemy do metody tekst, wykona się jego wydruk na konsolę, a gdy przekazujemy liczbę (obiekt typu Integer), wydrukuje się "Other type". Dzieje się tak, ponieważ operator porównuje obiekt do określonego typu i zwraca wartość true, jeśli obiekt jest danego typu lub false, gdy obiekt jest innego typu.

Jak to będzie wyglądać w bardziej zaawansowanym przykładzie? Załóżmy, że mamy metodę, która przyjmuje jako parametr obiekt klasy Item. My jednak wiemy, że tak naprawdę dostajemy tam zawsze obiekt jednej z klas dziedziczących, powiedzmy - MovieItem lub SongItem. Interesuje nas wykonanie kodu metody tylko dla przypadku gdy otrzymamy obiekt typu MovieItem. W takiej sytuacji możemy wykorzystać opisywany tutaj operator:
void printItemInfo(Item item) {
    
    if(item instanceof MovieItem) {
        // Wykonaj kod tylko w przypadku gdy obiekt jest typu MovieItem
    }
    
    // Wykonaj kod dla każdego typu, a więc również gdy do metody wpadnie obiekt typu SongItem
}
Na koniec warto dodać, że już niedługo w Javie pojawi się finalne rozwiązanie pozwalające na połączenie sprawdzenia typu z jego deklaracją i nazwą zmiennej. Obecnie musimy wykonywać rzutowanie. Kiedy się pojawi taka opcja? Nie wiadomo. Może już w Javie 16. Zobaczymy. Aktualnie (czyli w Java 15) jest ona dostępna w trybie preview.
if (obj instanceof String someText) {
    System.out.println(someText);
}   

Operator warunkowy

Kolejnym operatorem jest operator warunkowy zwany z angielskiego ternary. Warto zapamiętać tę nazwę w obu językach, ponieważ podczas rozmowy kwalifikacyjnej, czy też w nowej pracy, można być o to zapytanym na różny sposób (nazwa angielska tego operatora jest dosyć popularna).

Konstrukcja operatora wygląda mało atrakcyjnie:
warunek ? warunek-spełniony : warunek-niespełniony
Dlatego od razu zobaczmy przykład:
int someValue = 7;
System.out.println(someValue == 7 ?  "prawda" :  "fałsz");
Ten mało błyskotliwy kawałek kodu dobrze pokazuje działanie operatora. Najpierw przypisujemy wartość 7 do zmiennej typu int, a następnie wykonujemy wydruk tekstu na konsoli. To, co będzie wydrukowane, zależy od wyniku działania operatora ternary. Najpierw przed znakiem zapytania wprowadzamy warunek, który ma być przetestowany. W przypadku gdy warunek zwróci wartość true, wykonuje się kod zaraz po znaku zapytania. W przypadku gdy warunek zwróci false (co jak wiemy, w tym przykładzie akurat jest niemożliwe) wówczas wykona się kod po dwukropku. Innymi słowy, w przypadku true do metody println zostanie przekazany tekst "prawda", a w przeciwnym wypadku tekst "fałsz".

Warto wkleić powyższy kawałek kodu do programu, by zobaczyć, że wydrukowane zostanie słowo "prawda". W ramach ćwiczenia polecamy również zmienić wartość zmiennej someValue na inną wartość, na przykład 5. Wtedy warunek zwróci false i na konsoli zobaczymy wydruk "fałsz".

Operatory bitowe

Wielu z początkujących programistów nie lubi operatorów bitowych, ponieważ na pierwszy rzut oka wydają się one być skomplikowane. Prawda jest jednak taka, że to nie operatory są problemem. One jedynie bazują na logice matematycznej, nad którą nie zastanawiamy się na co dzień. Mowa tutaj o liczbach binarnych i operacjach, jakie możemy na nich wykonywać. Dlatego warto najpierw zaznajomić się z takimi pojęciami matematycznymi jak:

koniunkcja (AND), alternatywa (OR), negacja (~), alternatywa wykluczająca (XOR).

W przypadku operacji na bitach (odpowiednia kombinacja 0 i 1 tworzących liczbę) pojęcia te są realizowane w następujący sposób:
public static void main(String[] args) {

    int x = 5; // w systemie binarnym: 0101
    int y = 7; // w systemie binarnym: 0111
    
    // Koniunkcja. Porównaj kolejne pozycje w dwóch ciągach bitowych. 
    // Jeśli co najmniej jedna z nich zawiera 0 wówczas wynikiem będzie 0. 
    // Jeśli obie zawierają jedynkę wynikiem będzie 1. 
    // 0101 & 0111 = 0101 = 5    
    System.out.println("x & y = " + (x & y));
    
    // Alternatywa. Porównaj kolejne pozycje w dwóch ciągach bitowych.
    // Jeśli co najmniej jedna z nich zawiera 1 wówczas wynikiem będzie 1. 
    // Jeśli obie zawierają zero wynikiem będzie 0.
    // Dla dwóch jedynek otrzymamy 1. 
    // 0101 | 0111 = 0111 = 7
    System.out.println("x | y = " + (x | y));
   
    // Alternatywa wykluczająca. Porównaj kolejne pozycje w dwóch ciągach bitowych.
    // Jeśli obie zawierają jedynkę wynikiem będzie 0!
    // Jeśli tylko jedna z nich zawiera 1 wówczas wynikiem będzie 1.     
    // Alternatywa wykluczająca: 0101 ^ 0111 = 0010 = 2
    System.out.println("x ^ y = " + (x ^ y));
    
    // Negacja. Na każdej pozycji zamieniamy 1 z 0 a 0 z 1.
    // ~0101 = 1010 = -6
    System.out.println("~x = " + ~x);
}
OperatorAlternative

Przesunięcie bitowe

Operatory realizujące przesunięcie bitowe również nie są ulubioną częścią nauki, choć tak naprawdę same w sobie są dosyć proste do zrozumienia. W Javie rozróżniamy operatory: Signed Right shift (>>), Left shift (<<) oraz Unsigned right shift (>>>). Wszystkie służą do przesuwania bitów w określoną stronę w liczbie przedstawionej w systemie binarnym i wypełniania miejsc po opuszczonych pozycjach z prawej lub lewej strony liczby.
Appa Notka. Zanim przejdziemy do omówienia przykładu z przesunięciami bitowymi, niezwykle ważne jest zapoznanie się z podstawową regułą obsługi liczb binarnych w Javie. W języku tym używa się dopełnienia do dwóch (Two's Complement). Na przykład dla liczb 3 i -3 binarne formaty określamy w ten sposób:

Format binarny dla 3 (zapisany na 8 bitach):   0000 0011
Format binarny dla -3 (zapisany na 8 bitach): 1111 1101

Dlaczego -3 wygląda akurat tak? Właśnie dlatego, że używamy tutaj dopełnienia do dwóch:

0000 0011  <---  format binary liczby 3
1111 1100  <---  odwrócenie bitów (0 na 1 i 1 na 0)
1111 1101  <---  dodanie 1
public class Start {
	
    public static void main(String[] args) {
        
        int posValue = 5; 
        int negValue = -19; 	   
        
        System.out.println("posValue: " + format32bit(posValue));
        System.out.println("negValue: " + format32bit(negValue));	    	    
        System.out.println();
        
        System.out.println("=================================================================================");
        System.out.println("\"Przesuń bity o jedną pozycję w prawo wstawiając 0 lub 1 w opuszczonej");
        System.out.println("pozycji po lewej stronie. Dla liczby dodatniej jest to 0, a dla ujemnej 1\"");
        System.out.println("=================================================================================");
        System.out.println("posValue    = " + format32bit(posValue));
        System.out.println("posValue>>1 = " + format32bit(posValue>>1));     
        System.out.println("negValue    = " + format32bit(negValue));
        System.out.println("negValue>>1 = " + format32bit(negValue>>1));	    
        System.out.println("posValueDecimal    = " + binaryStringToDecimal(format32bit(posValue)));
        System.out.println("posValueDecimal>>1 = " + binaryStringToDecimal(format32bit(posValue>>1)));     
        System.out.println("negValueDecimal    = " + binaryStringToDecimal(format32bit(negValue)));
        System.out.println("negValueDecimal>>1 = " + binaryStringToDecimal(format32bit(negValue>>1)));	    
        System.out.println();
        
        System.out.println("=================================================================================");
        System.out.println("Left shift operator <<");
        System.out.println("\"Przesuń bity o jedną pozycję w lewo wstawiając 0 w opuszczonej pozycji\"");
        System.out.println("=================================================================================");
        System.out.println("posValue    = " + format32bit(posValue));
        System.out.println("posValue<<1 = " + format32bit(posValue<<1));
        System.out.println("negValue    = " + format32bit(negValue));
        System.out.println("negValue<<1 = " + format32bit(negValue<<1));
        System.out.println("posValueDecimal    = " + binaryStringToDecimal(format32bit(posValue)));
        System.out.println("posValueDecimal<<1 = " + binaryStringToDecimal(format32bit(posValue<<1)));
        System.out.println("negValueDecimal    = " + binaryStringToDecimal(format32bit(negValue)));
        System.out.println("negValueDecimal<<1 = " + binaryStringToDecimal(format32bit(negValue<<1)));		    
        System.out.println();
        
        System.out.println("=================================================================================");
        System.out.println("Unsigned Right shift operator >>>");
        System.out.println("\"Przesuń bity o jedną pozycję w prawo wstawiając 0 w opuszczonej pozycji\"");
        System.out.println("=================================================================================");
        System.out.println("posValue     = " + format32bit(posValue));
        System.out.println("posValue>>>1 = " + format32bit(posValue>>>1));
        System.out.println("negValue     = " + format32bit(negValue));
        System.out.println("negValue>>>1 = " + format32bit(negValue>>>1));	
        System.out.println("posValueDecimal     = " + binaryStringToDecimal(format32bit(posValue)));
        System.out.println("posValueDecimal>>>1 = " + binaryStringToDecimal(format32bit(posValue>>>1)));
        System.out.println("negValueDecimal     = " + binaryStringToDecimal(format32bit(negValue)));
        System.out.println("negValueDecimal>>>1 = " + binaryStringToDecimal(format32bit(negValue>>>1)));	    
    }
    
    static String format32bit(int value) {
    	return String.format("%32s", 
    			Integer.toBinaryString(value)).replaceAll(" ", "0");
    }
    
    static int binaryStringToDecimal(String binaryValue) {
    	return (int) Long.parseLong(binaryValue, 2);
    }
}
Tak to wygląda na wydruku z konsoli:
BitMovements
Linki
https://www.w3schools.com/java/java_operators.asp
https://docs.oracle.com/javase/tutorial/java/nutsandbolts/operators.html
Two's Complement
Masz pytanie dotyczące tego rozdziału? Zadaj je nam!
Masz pytanie dotyczące prezentowanego materiału?
Coś jest dla Ciebie niejasne i Twoje wątpliwości przeszkadzają Ci w pełnym zrozumieniu treści?
Napisz do nas maila, a my chętnie znajdziemy odpowiednie rozwiązanie.
Najciekawsze pytania wraz z odpowiedziami będziemy publikować pod rozdziałem.
Nie czekaj. Naucz się programować jeszcze lepiej.
kursjava@javappa.com

Stale się rozwijamy, a więc bądź na bieżąco!
Na ten adres będziemy przesyłać informacje o ważniejszych aktualizacjach, a także o nowych materiałach pojawiających się na stronie.
Polub nas na Facebooku:
Nasi partnerzy: stackshare
Javappa to również profesjonalne usługi programistyczne oparte o technologie JAVA. Jeśli chesz nawiązać z nami kontakt w celu uzyskania doradztwa bądź stworzenia aplikacji webowej powinieneś poznać nasze doświadczenia.
Kliknij O nas .


Pozycjonowanie stron: Grupa TENSE