Operatory logiczne w Javie to && (i), || (lub) oraz ! (nie). Łączą i odwracają wartości typu boolean, czyli true albo false, i decydują o tym, którą ścieżką pójdzie program. To z nich zbudowany jest każdy warunek w if, w pętli i w asercji testu automatycznego.
To ósma lekcja kursu Javy dla testerów. Pod filmem znajdziecie pełną wersję tekstową: tabelę prawdy, krótkie spięcie i różnicę między && a &, operator ^, operatory porównania, kolejność wykonywania, prawa De Morgana, komunikaty kompilatora przy typowych błędach i ćwiczenie z rozwiązaniem. Cały kod skompilowaliśmy poleceniem javac --release 25 i uruchomiliśmy, a wyniki wpisaliśmy w komentarzach.
Spis treści
- Czym są operatory logiczne w Javie?
- Jak działają &&, || i !? Tabela prawdy
- Czym różni się && od &? Krótkie spięcie w praktyce
- Do czego służy XOR (^) w Javie?
- Jakie są operatory porównania w Javie?
- W jakiej kolejności Java liczy warunek?
- Prawa De Morgana: jak uprościć zanegowany warunek?
- Operatory logiczne w asercjach testów
- Jakie błędy zgłosi kompilator?
- Ćwiczenie z rozwiązaniem
Czym są operatory logiczne w Javie?
Operatory logiczne w Javie działają wyłącznie na wartościach typu boolean. Nie zamienią liczby na prawdę ani pustego tekstu na fałsz, jak robią to JavaScript czy Python. Według specyfikacji języka Java SE 25 (rozdziały 15.23 i 15.24) operandy && i || muszą być typu boolean albo Boolean, inaczej kod się nie skompiluje. To dobra wiadomość dla Was jako testerów: cała klasa błędów typu „pusty napis potraktowany jak fałsz” w Javie po prostu nie istnieje.
Do grupy operatorów logicznych w szerokim sensie należą trzy rodziny. Operatory warunkowe && i || z krótkim spięciem. Operator negacji !. Oraz operatory logiczne bez krótkiego spięcia, &, | i ^, które specyfikacja w rozdziale 15.22 opisuje razem z operacjami bitowymi. Wynikiem każdego z nich jest znowu boolean, więc można je łączyć w dłuższe warunki.
Operatory logiczne w Javie w liczbach
3
operatory warunkowe i negacja: &&, ||, !
3
operatory logiczne bez krótkiego spięcia: &, |, ^
6
operatorów porównania zwracających boolean
2
prawa De Morgana do upraszczania negacji
450 000+
testów automatycznych napisanych w Quality Island
Źródła: Java Language Specification SE 25, rozdziały 15.20 do 15.24, 2025; dane własne Quality Island 2026.
OperatoryLogiczne.java, Java 25, wynik w komentarzach
public class OperatoryLogiczne {
public static void main(String[] args) {
boolean zalogowany = true;
boolean maUprawnienia = false;
System.out.println(zalogowany && maUprawnienia); // false
System.out.println(zalogowany || maUprawnienia); // true
System.out.println(!maUprawnienia); // true
System.out.println(zalogowany ^ maUprawnienia); // true
int liczbaProduktow = 3;
double kwota = 149.99;
boolean darmowaDostawa = liczbaProduktow >= 3 || kwota > 200;
System.out.println("Darmowa dostawa: " + darmowaDostawa); // Darmowa dostawa: true
}
}
Ostatni przykład to typowa reguła biznesowa: darmowa dostawa przy trzech produktach albo przy kwocie powyżej 200 zł. Taki warunek w aplikacji to od razu zestaw przypadków testowych, o czym więcej przy prawach De Morgana i w ćwiczeniu.
Jak działają &&, || i !? Tabela prawdy
Operator && zwraca true tylko wtedy, gdy obie strony są prawdziwe. Operator || zwraca true, gdy prawdziwa jest choć jedna strona. Negacja ! odwraca wartość. Wszystkie kombinacje dwóch wartości mieszczą się w tabeli prawdy z czterema wierszami i warto mieć ją przed oczami, gdy projektujecie przypadki testowe dla warunku.
Źródło: Java Language Specification SE 25, 2025, rozdziały 15.15.6, 15.22.2, 15.23 i 15.24; wszystkie wiersze sprawdzone uruchomieniem kodu w Javie 25.
Z tabeli wynika praktyczna reguła dla testera. Warunek z dwoma składnikami ma cztery kombinacje wejść, z trzema osiem, z czterema szesnaście. Nie zawsze trzeba sprawdzić wszystkie, ale zawsze warto wiedzieć, które pominęliście. Z naszego doświadczenia przy przeglądach zestawów testów najczęściej brakuje wiersza, w którym oba składniki są fałszywe, bo nikt nie pomyślał, że ten przypadek też ma swoje zachowanie.
Czym różni się && od &? Krótkie spięcie w praktyce
&& i || liczą prawą stronę tylko wtedy, gdy wynik nie jest jeszcze przesądzony. Jeśli lewa strona && jest fałszywa, cała koniunkcja i tak będzie fałszywa, więc Java nie sprawdza prawej. Jeśli lewa strona || jest prawdziwa, wynik jest prawdziwy bez patrzenia dalej. To jest krótkie spięcie (short circuit). Operatory & i | zastosowane do wartości logicznych zawsze liczą obie strony, co według specyfikacji Java SE 25 (rozdział 15.22.2) daje ten sam wynik logiczny, ale inne zachowanie programu.
KrotkieSpiecie.java, wynik w komentarzach
public class KrotkieSpiecie {
static int wywolania = 0;
static boolean sprawdz(boolean wynik) {
wywolania++;
return wynik;
}
public static void main(String[] args) {
String login = null;
// && nie sprawdzi prawej strony, bo lewa jest false
if (login != null && login.length() > 3) {
System.out.println("Login poprawny");
} else {
System.out.println("Brak loginu"); // Brak loginu
}
wywolania = 0;
boolean a = sprawdz(false) && sprawdz(true);
System.out.println(a + ", wywołania: " + wywolania); // false, wywołania: 1
wywolania = 0;
boolean b = sprawdz(false) & sprawdz(true);
System.out.println(b + ", wywołania: " + wywolania); // false, wywołania: 2
wywolania = 0;
boolean c = sprawdz(true) || sprawdz(false);
System.out.println(c + ", wywołania: " + wywolania); // true, wywołania: 1
// & zawsze liczy obie strony, więc tu poleci wyjątek
if (login != null & login.length() > 3) {
System.out.println("Ta linia się nie wykona");
}
}
}
Pierwszy if jest bezpieczny, bo przy login == null wywołanie login.length() w ogóle się nie wykona. Licznik wywołań pokazuje to czarno na białym: przy && metoda sprawdz poszła raz, przy & dwa razy. Ostatni if z pojedynczym & kończy program wyjątkiem:
Konsola, kod skompilowany z informacjami debugowania, jak w IntelliJ IDEA
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "login" is null
at KrotkieSpiecie.main(KrotkieSpiecie.java:32)
Komunikat jest tak dokładny dzięki funkcji Helpful NullPointerExceptions, opisanej w dokumencie OpenJDK JEP 358 i włączonej domyślnie od Javy 15. Jeśli skompilujecie kod bez informacji debugowania, zamiast nazwy zmiennej zobaczycie "<local1>", co sprawdziliśmy na tym samym przykładzie.
W testach krótkie spięcie przydaje się codziennie. Warunek element != null && element.isDisplayed() nie wywróci się na brakującym elemencie, a lista.isEmpty() || lista.get(0).equals("OK") nie rzuci wyjątku na pustej liście. Kolejność ma znaczenie: tańsze i bezpieczniejsze sprawdzenie zawsze po lewej stronie.
Wbrew pozorom
Operatory złożone &= i |= nie mają krótkiego spięcia. Zapis ok &= sprawdzStrone() wywoła metodę zawsze, nawet gdy ok jest już fałszem, bo według specyfikacji Java SE 25 (rozdział 15.26.2) to skrót od ok = ok & sprawdzStrone(), a nie od wersji z &&. Operatorów &&= i ||= Java w ogóle nie ma. Czasem to zaleta: w pętli zbierającej wyniki kilku sprawdzeń chcecie, żeby wykonały się wszystkie.
Do czego służy XOR (^) w Javie?
XOR, czyli alternatywa wykluczająca, zwraca true, gdy dokładnie jedna strona jest prawdziwa. W Javie zapisujecie go znakiem ^. Przydaje się rzadziej niż && i ||, ale gdy wymaganie brzmi „dokładnie jedno z dwóch”, jest najkrótszym poprawnym zapisem. Przykład: formularz, w którym klient podaje telefon albo e-mail, ale nie oba naraz.
Xor.java, wynik w komentarzach
public class Xor {
static boolean poprawnyFormularz(String telefon, String email) {
boolean jestTelefon = telefon != null && !telefon.isBlank();
boolean jestEmail = email != null && !email.isBlank();
// dokładnie jedno pole ma być wypełnione
return jestTelefon ^ jestEmail;
}
public static void main(String[] args) {
System.out.println(poprawnyFormularz("600100200", null)); // true
System.out.println(poprawnyFormularz(null, "anna@example.com")); // true
System.out.println(poprawnyFormularz("600100200", "anna@example.com")); // false
System.out.println(poprawnyFormularz("", " ")); // false
System.out.println(true ^ true); // false
System.out.println(true != true); // false, dla boolean ^ działa jak !=
}
}
Ostatnie dwie linie pokazują ciekawostkę, którą warto znać: dla wartości logicznych a ^ b daje to samo co a != b. W kodzie produkcyjnym wiele zespołów woli wersję z !=, bo jest czytelniejsza dla osób, które nie pamiętają, co oznacza daszek. Dla testera najważniejsze jest co innego: wymaganie „dokładnie jedno” ma cztery przypadki testowe, a nie dwa, i przypadek z oboma wypełnionymi polami jest tym, który najczęściej przecieka na produkcję.
Jakie są operatory porównania w Javie?
Operatorów porównania w Javie jest sześć i każdy zwraca boolean, więc to one najczęściej dostarczają wartości, które potem łączycie operatorami logicznymi. Według specyfikacji Java SE 25 operatory relacyjne opisuje rozdział 15.20, a operatory równości rozdział 15.21.
Źródło: Java Language Specification SE 25, 2025, rozdziały 15.20 i 15.21; Oracle, The Java Tutorials: Equality, Relational, and Conditional Operators.
Porownania.java, wynik w komentarzach
public class Porownania {
public static void main(String[] args) {
int status = 200;
System.out.println(status == 200); // true
System.out.println(status != 404); // true
System.out.println(status >= 200 && status < 300); // true, zakres 2xx
String oczekiwany = "OK";
String zOdpowiedzi = new String("OK"); // jak tekst odczytany z API albo ze strony
System.out.println(oczekiwany == zOdpowiedzi); // false, dwa różne obiekty
System.out.println(oczekiwany.equals(zOdpowiedzi)); // true, ta sama treść
double suma = 0.1 + 0.2;
System.out.println(suma == 0.3); // false
System.out.println(Math.abs(suma - 0.3) < 1e-9); // true
}
}
Dwie pułapki z tego przykładu kosztują testerów najwięcej godzin. Pierwsza: == na obiektach porównuje referencje, a nie treść. Tekst odczytany ze strony albo z odpowiedzi API to zwykle nowy obiekt, więc == zwróci false, mimo że na ekranie widzicie to samo słowo. Do tekstów używajcie equals. Druga: liczby zmiennoprzecinkowe. 0.1 + 0.2 nie jest dokładnie równe 0.3, co wynika z zapisu liczb w standardzie IEEE 754, dlatego kwoty porównujecie z tolerancją albo typem BigDecimal. Więcej o typach znajdziecie w lekcji typy danych w Javie, a o samych operacjach na liczbach w lekcji operatory matematyczne.
Porównywanie tekstów przez == działa czasem, bo dwa identyczne literały w kodzie wskazują ten sam obiekt z puli łańcuchów. Wystarczy, że jedna strona przyjdzie z getText() albo z odpowiedzi API, i ten sam warunek zwróci false. To najgorszy rodzaj błędu: test z tekstem wpisanym na sztywno przechodzi, a po podpięciu prawdziwej strony pada w czwartek o 23:00 na serwerze CI.
W jakiej kolejności Java liczy warunek?
Java liczy warunek według priorytetów, a nie od lewej do prawej. Negacja ! wiąże najmocniej, potem porównania, potem &, ^, |, a na końcu && i ||. Najważniejsza praktyczna konsekwencja: && ma wyższy priorytet niż ||, tak jak mnożenie ma wyższy priorytet niż dodawanie.
Źródło: Oracle, The Java Tutorials: Operators, tabela Operator Precedence; Java Language Specification SE 25, 2025, rozdział 15.
Priorytet.java, wynik w komentarzach
public class Priorytet {
public static void main(String[] args) {
boolean admin = true;
boolean aktywny = false;
boolean zweryfikowany = false;
// && ma wyższy priorytet niż ||, więc to jest: admin || (aktywny && zweryfikowany)
System.out.println(admin || aktywny && zweryfikowany); // true
System.out.println((admin || aktywny) && zweryfikowany); // false
int wiek = 17;
// ! wiąże najmocniej, dlatego porównanie trzeba wziąć w nawias
System.out.println(!(wiek >= 18)); // true
}
}
Nasza rada z przeglądów kodu testów: gdy w jednym warunku mieszacie && i ||, zawsze dopiszcie nawiasy, nawet jeśli priorytet daje ten sam wynik. Nawias kosztuje dwa znaki, a oszczędza kolejnej osobie zastanawiania się, co autor miał na myśli.
Prawa De Morgana: jak uprościć zanegowany warunek?
Prawa De Morgana mówią, jak wnieść negację do środka nawiasu. Negacja koniunkcji to alternatywa negacji, a negacja alternatywy to koniunkcja negacji. Brzmi jak egzamin z logiki, ale w kodzie testów przydaje się co tydzień, bo warunki z ! przed długim nawiasem czyta się źle.
Źródło: prawa De Morgana; równoważność dwóch pierwszych wierszy sprawdzona dla wszystkich 4 kombinacji wartości kodem poniżej.
DeMorgan.java, wynik w komentarzach
public class DeMorgan {
public static void main(String[] args) {
boolean[] wartosci = {true, false};
int zgodne = 0;
for (boolean a : wartosci) {
for (boolean b : wartosci) {
boolean prawo1 = !(a && b) == (!a || !b);
boolean prawo2 = !(a || b) == (!a && !b);
if (prawo1 && prawo2) {
zgodne++;
}
}
}
System.out.println("Zgodne kombinacje: " + zgodne + " z 4"); // Zgodne kombinacje: 4 z 4
String status = "ANULOWANE";
// przed: trudno przeczytać
boolean doWysylki1 = !(status.equals("ANULOWANE") || status.equals("ZWROT"));
// po De Morganie: to samo, czytelniej
boolean doWysylki2 = !status.equals("ANULOWANE") && !status.equals("ZWROT");
System.out.println(doWysylki1 + " " + doWysylki2); // false false
}
}
Ostatni wiersz tabeli to klasyka testów wartości brzegowych. Warunek „poza zakresem” zapisany jako x < min || x > max od razu podpowiada cztery wartości do sprawdzenia: min - 1, min, max i max + 1. Wersja z negacją przed nawiasem ukrywa tę strukturę.
Operatory logiczne w asercjach testów
W asercjach operatory logiczne są wygodne i zdradliwe jednocześnie. assertTrue(a && b) sprawdzi oba warunki, ale gdy test padnie, raport powie tylko „expected: true but was: false”. Nie dowiecie się, który składnik zawiódł. Dokumentacja JUnit opisuje na tę okazję metodę assertAll, która wykonuje wszystkie asercje z grupy i raportuje każdą porażkę osobno.
KoszykTest.java, JUnit 6.1.3, oba testy celowo padają
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
class KoszykTest {
int liczbaProduktow = 2;
String status = "NOWE";
@Test
void jednaAsercjaZOperatorem() {
assertTrue(liczbaProduktow == 3 && status.equals("NOWE"));
}
@Test
void osobneAsercje() {
assertAll("koszyk",
() -> assertEquals(3, liczbaProduktow, "liczba produktów"),
() -> assertEquals("NOWE", status, "status zamówienia"));
}
}
Uruchomiliśmy ten test w JUnit 6.1.3 z konsoli. Oto, co zobaczycie w raporcie dla obu wersji:
Raport JUnit, fragment
jednaAsercjaZOperatorem() expected: <true> but was: <false>
osobneAsercje() koszyk (1 failure)
org.opentest4j.AssertionFailedError: liczba produktów ==> expected: <3> but was: <2>
Różnica jest kolosalna, gdy test pada o trzeciej w nocy w potoku CI i rano macie tylko log. W naszych projektach automatyzacji przyjmujemy prostą zasadę: operator logiczny wolno umieścić w asercji tylko wtedy, gdy cały warunek ma jedną nazwę biznesową, na przykład zamowienie.mozeBycWyslane(). Wtedy warunek trafia do metody o czytelnej nazwie, a asercja sprawdza jej wynik. Więcej o samych asercjach, także w TestNG i pytest, przeczytacie w tekście asercja: co to jest i jak jej używać w testach automatycznych.
Złożone warunki w testach to też częsty powód niestabilności. Strefa QA opisała ten problem razem z innymi pułapkami w tekście o najczęstszych antywzorcach w testowaniu oprogramowania, który warto przeczytać, zanim Wasz zestaw testów urośnie do kilkuset przypadków.
Jakie błędy zgłosi kompilator?
Kompilator Javy pilnuje typów w warunkach bardzo dokładnie, więc większość pomyłek z operatorami logicznymi wychodzi jeszcze przed uruchomieniem programu. Poniżej cztery błędy, które widzimy u kursantów najczęściej, z dosłownym komunikatem javac z JDK 25. Każdy z nich odtworzyliśmy na osobnym pliku.
Źródło: komunikaty skopiowane z kompilatora javac (OpenJDK, opcja --release 25), sprawdzone przez Quality Island 11.10.2026.
Zapis min < x < max z matematyki to ulubiony błąd osób, które przychodzą z Pythona, gdzie taki łańcuch porównań działa. W Javie pierwsze porównanie zwraca boolean, a boolean nie da się porównać z liczbą, stąd komunikat o typach boolean i int.
Ćwiczenie z rozwiązaniem
Zadanie: napiszcie metodę mozeKupicBilet(int wiek, boolean maZgodeRodzica, boolean zablokowany). Bilet może kupić osoba pełnoletnia albo osoba od 13 lat ze zgodą rodzica, ale nigdy konto zablokowane. Zanim zajrzycie do rozwiązania, wypiszcie na kartce przypadki testowe, które sprawdzicie, i porównajcie je z naszą listą.
Cwiczenie.java, rozwiązanie z wynikami
public class Cwiczenie {
static boolean mozeKupicBilet(int wiek, boolean maZgodeRodzica, boolean zablokowany) {
return !zablokowany && (wiek >= 18 || (wiek >= 13 && maZgodeRodzica));
}
public static void main(String[] args) {
System.out.println(mozeKupicBilet(25, false, false)); // true
System.out.println(mozeKupicBilet(15, true, false)); // true
System.out.println(mozeKupicBilet(15, false, false)); // false
System.out.println(mozeKupicBilet(12, true, false)); // false
System.out.println(mozeKupicBilet(30, true, true)); // false
}
}
Na co zwrócić uwagę. Negacja blokady stoi na początku, więc przy zablokowanym koncie reszta warunku się nie liczy. Nawias wokół wiek >= 18 || (...) jest konieczny, bo bez niego && związałby się tylko z pierwszym porównaniem i konto zablokowane z pełnoletnim właścicielem mogłoby kupić bilet. Pięć wywołań w main to minimalny zestaw przypadków: pełnoletni, nastolatek ze zgodą, nastolatek bez zgody, dwunastolatek ze zgodą (wartość brzegowa) i konto zablokowane. Dla pełnego pokrycia brzegów dopiszcie jeszcze wiek 13 i 17 oraz 18 bez zgody.
Ścieżka po tej lekcji
Następny krok
Instrukcja warunkowa if: warunki z tej lekcji sterują w niej przebiegiem programu.
Potem
Pętle for i while: warunek logiczny decyduje, kiedy pętla się kończy.
W testach
Asercje i wartości brzegowe: każdy złożony warunek to zestaw przypadków testowych.
Jeśli wolicie przerobić podstawy z trenerem, z ćwiczeniami sprawdzanymi na bieżąco, zobaczcie szkolenie wprowadzenie do programowania od podstaw w Javie. Osobom, które znają już składnię i chcą pisać testy, polecamy programowanie w Javie dla testerów, a zespołom, które potrzebują gotowego frameworka zamiast kursu, automatyzację testów UI w wykonaniu naszego zespołu. Trenerzy Quality Island mają minimum 5 lat doświadczenia w projektach, a przez nasze szkolenia przeszło już ponad 2 500 specjalistów.
Co zabrać z tego artykułu
01Operatory logiczne w Javie działają tylko na boolean: && (i), || (lub), ! (nie). Java nie zamienia liczb ani tekstów na prawdę i fałsz.
02&& i || mają krótkie spięcie. Bezpieczne sprawdzenie, na przykład != null, stawiajcie po lewej stronie.
03& i | na wartościach logicznych liczą obie strony. Stąd NullPointerException w kodzie, który wygląda poprawnie.
04&& ma wyższy priorytet niż ||. Przy mieszaniu obu zawsze dopisujcie nawiasy.
05W asercjach zamiast assertTrue(a && b) używajcie assertAll albo osobnych asercji, żeby raport mówił, co zawiodło.
Chcecie przejść od operatorów do pierwszych testów automatycznych pod okiem trenera? Na szkoleniu piszecie kod od pierwszego dnia, na naszej aplikacji demo.
Powiązane na blogu Quality Island
- Wprowadzenie do języka Java i spis lekcji kursu
- Instrukcja warunkowa if w Javie
- Instrukcja switch w Javie
- Pętle w Javie: for, while, do while i for-each
- Asercja: co to jest i jak używać asercji w testach automatycznych
Pełna lista źródeł
- Oracle, The Java Language Specification, Java SE 25 Edition, rozdział 15: Expressions, 2025. Rozdziały 15.15.6 (negacja), 15.20 i 15.21 (porównania), 15.22.2 (&, ^, | na wartościach logicznych), 15.23 i 15.24 (&& i ||), 15.26.2 (przypisania złożone).
- Oracle, The Java Tutorials: Operators, tabela priorytetów, stan na 10.10.2026.
- Oracle, The Java Tutorials: Equality, Relational, and Conditional Operators, stan na 10.10.2026.
- Oracle, dev.java: Using Operators in Your Programs, 2026.
- OpenJDK, JEP 358: Helpful NullPointerExceptions, 2019, domyślnie włączone od Javy 15.
- JUnit, Assertions, dokumentacja JUnit, 2026. Stąd assertAll i grupowanie asercji.
- Wikipedia, De Morgan’s laws, stan na 10.10.2026.
- Dane własne Quality Island, 2026: ponad 450 000 napisanych testów automatycznych, ponad 2 500 przeszkolonych specjalistów, trenerzy z minimum 5 latami doświadczenia w projektach.