Operatory matematyczne w Javie to pięć symboli działań arytmetycznych: dodawanie +, odejmowanie -, mnożenie *, dzielenie / i reszta z dzielenia %, uzupełnione o inkrementację ++, dekrementację -- i operatory przypisania złożonego, takie jak +=. Wyglądają jak w szkole, ale działają według reguł typów danych: 17 / 5 to w Javie 3, a nie 3,4, a dzielenie przez zero raz rzuca wyjątek, a raz daje nieskończoność, zależnie od typu.
To siódma lekcja kursu Javy dla testerów. Opiera się na poprzedniej, o typach danych w Javie, bo wynik każdego działania zależy od typów, na których je wykonujecie. Następna lekcja dotyczy operatorów logicznych. Pod filmem znajdziecie kod, który skompilowaliśmy i uruchomiliśmy 11.10.2026 na OpenJDK 25.0.2 z opcją --release 25, wraz z wynikami, komunikatami kompilatora i testem JUnit z przykładami z koszyka sklepu.
Spis treści
- Jakie operatory matematyczne ma Java?
- Jak działa dzielenie całkowite i reszta z dzielenia?
- Czym różni się i++ od ++i?
- Jaki jest priorytet operatorów matematycznych?
- Jak działają operatory przypisania złożonego?
- Co daje klasa Math i gdzie zaskakuje?
- Co się dzieje przy dzieleniu przez zero w int i double?
- Gdzie operatory matematyczne wychodzą w testach automatycznych?
- Jakie błędy kompilatora zobaczycie przy operatorach?
- Ćwiczenie: jak zamienić sekundy na godziny, minuty i sekundy?
- Co dalej po lekcji o operatorach?
Jakie operatory matematyczne ma Java?
Java ma pięć operatorów arytmetycznych dwuargumentowych. Działają na wszystkich typach liczbowych: int, long, double i pozostałych, a operator + ma dodatkowo drugie zadanie, czyli łączenie napisów. Przed wykonaniem działania Java sprowadza oba argumenty do wspólnego typu: jeśli którykolwiek jest double, wynik też jest double; jeśli oba są typu int albo mniejszego, wynik jest int.
// 1. Pięć operatorów arytmetycznych
int a = 17, b = 5;
System.out.println(a + b); // 22
System.out.println(a - b); // 12
System.out.println(a * b); // 85
System.out.println(a / b); // 3, dzielenie całkowite
System.out.println(a % b); // 2, reszta z dzielenia
System.out.println(17.0 / 5); // 3.4
Komentarze po prawej pokazują wyniki z naszego uruchomienia. Jedyna linia, która daje ułamek, to ostatnia, bo literał 17.0 jest typu double. Gdy dzielicie dwie zmienne typu int, a potrzebujecie ułamka, wystarczy zrzutować jedną z nich: (double) suma / liczba.
Jak działa dzielenie całkowite i reszta z dzielenia?
Według specyfikacji języka Java (JLS, rozdział 15.17.2, Java SE 25, 2025) dzielenie całkowite zaokrągla w stronę zera, czyli po prostu obcina część ułamkową. Dla liczb dodatnich oznacza to zaokrąglenie w dół, ale dla ujemnych w górę: -7 / 2 daje -3, a nie -4. Reszta z dzielenia ma według JLS (rozdział 15.17.3) zawsze znak dzielnej, czyli liczby po lewej stronie.
// 2. Ujemne liczby i reszta
System.out.println(-7 / 2); // -3
System.out.println(-7 % 3); // -1
System.out.println(Math.floorMod(-7, 3)); // 2
Różnica między % a Math.floorMod() wychodzi przy liczbach ujemnych. Jeśli liczycie dzień tygodnia albo indeks w liście cyklicznej i może Wam wyjść liczba ujemna, -7 % 3 da -1, czyli indeks, którego nie ma, a Math.floorMod(-7, 3) da 2. Do sprawdzania parzystości piszcie x % 2 != 0, a nie x % 2 == 1, bo dla liczb ujemnych nieparzystych reszta wynosi -1.
Czym różni się i++ od ++i?
Operator ++ zwiększa zmienną o 1, a -- zmniejsza o 1. Każdy ma dwie formy. Postinkrementacja i++ najpierw zwraca starą wartość, a potem zwiększa zmienną. Preinkrementacja ++i najpierw zwiększa, a potem zwraca nową wartość. Gdy instrukcja stoi samodzielnie, jak licznik++;, obie formy działają identycznie; różnica pojawia się dopiero wtedy, gdy wynik od razu przypisujecie albo przekazujecie dalej.
// 3. Inkrementacja: przed i po
int i = 5;
int x = i++; // x = 5, i = 6
int y = ++i; // i = 7, y = 7
System.out.println(x + " " + y + " " + i);
5 7 7
Najczęściej spotkacie i++ w pętli for, którą omawia lekcja o pętlach w Javie. W kodzie testów unikajcie łączenia inkrementacji z innymi działaniami w jednej linii, np. wynik = i++ + ++i;. Kompilator to przyjmie, Java policzy to deterministycznie od lewej do prawej, ale osoba czytająca test straci pięć minut, a osoba debugująca dwadzieścia.
Jaki jest priorytet operatorów matematycznych?
Mnożenie, dzielenie i reszta z dzielenia mają wyższy priorytet niż dodawanie i odejmowanie, dokładnie jak w matematyce. Operatory o tym samym priorytecie wykonują się od lewej do prawej. Nawiasy zmieniają kolejność i warto ich używać także tam, gdzie nie są konieczne, jeśli poprawiają czytelność.
// 4. Priorytet i nawiasy
System.out.println(2 + 3 * 4); // 14
System.out.println((2 + 3) * 4); // 20
System.out.println("Suma: " + 2 + 3); // Suma: 23
System.out.println("Suma: " + (2 + 3)); // Suma: 5
Dwie ostatnie linie to klasyczna pułapka w komunikatach asercji i logach. Operator + działa od lewej do prawej, więc najpierw napis „Suma: ” łączy się z 2, a potem powstały napis z 3. Wynik to „Suma: 23”. Nawias wymusza najpierw dodawanie liczb. Test, który raportuje „Oczekiwano 23 pozycji” przy koszyku z pięcioma, potrafi wywołać niepotrzebny alarm na całym kanale zespołu.
Jak działają operatory przypisania złożonego?
Operatory przypisania złożonego skracają zapis: x += 5 znaczy prawie to samo co x = x + 5. Słowo „prawie” jest tu ważne. Według JLS (rozdział 15.26.2) wyrażenie E1 op= E2 jest równoważne E1 = (T) ((E1) op (E2)), gdzie T to typ zmiennej po lewej. Operator złożony dokłada więc niewidoczne rzutowanie, a ono potrafi po cichu zgubić dane.
// 5. Przypisanie złożone ukrywa rzutowanie
byte proby = 120;
proby += 10; // kompiluje się, wynik -126
System.out.println(proby);
int punkty = 10;
punkty *= 2.5; // 25, część ułamkowa obcięta
System.out.println(punkty);
-126
25
Bajt może przechować najwyżej 127, więc 120 + 10 przepełnia się do -126 bez żadnego ostrzeżenia. Ten sam zapis bez operatora złożonego, proby = proby + 10;, w ogóle by się nie skompilował, co pokazuje sekcja o błędach. Przy 10 punktach mnożenie przez 2,5 daje akurat 25, ale przy 11 punktach wyjdzie 27 zamiast 27,5, i znów bez słowa ze strony kompilatora.
Co daje klasa Math i gdzie zaskakuje?
Klasa java.lang.Math zbiera funkcje matematyczne, których nie da się zapisać operatorem: potęgi, pierwiastki, zaokrąglenia, wartość bezwzględną, minimum i maksimum. Nie trzeba jej importować, bo pakiet java.lang jest dostępny zawsze. Kilka metod ma jednak zachowanie, które zaskakuje w asercjach.
// 6. Math
System.out.println(Math.pow(2, 10)); // 1024.0, zawsze double
System.out.println(Math.round(2.5) + " " + Math.round(-2.5)); // 3 -2
System.out.println(Math.abs(Integer.MIN_VALUE)); // nadal ujemne
System.out.println(Math.max(3, 9) + " " + Math.sqrt(16));
1024.0
3 -2
-2147483648
9 4.0
Przy kwotach Math.round() to zły pomysł, bo działa na double, który nie przechowuje dokładnie ułamków dziesiętnych. Kwoty zaokrąglajcie metodą setScale(2, RoundingMode.HALF_UP) klasy BigDecimal, którą pokazaliśmy w lekcji o typach danych i której użyjemy w teście niżej.
Co się dzieje przy dzieleniu przez zero w int i double?
To jedna z niewielu sytuacji, w których Java zachowuje się zupełnie inaczej zależnie od typu. Według JLS (rozdział 15.17.2) dzielenie całkowite przez zero rzuca ArithmeticException, a dzielenie zmiennoprzecinkowe według standardu IEEE 754 daje nieskończoność albo NaN, czyli „nie liczbę”, bez żadnego wyjątku.
// 7. Dzielenie przez zero
System.out.println(10.0 / 0); // Infinity
System.out.println(0.0 / 0); // NaN
double nan = 0.0 / 0;
System.out.println(nan == nan); // false
System.out.println(Double.isNaN(nan)); // true
try {
System.out.println(10 / 0);
} catch (ArithmeticException e) {
System.out.println("int: " + e.getMessage());
}
Infinity
NaN
false
true
int: / by zero
Kompilator przepuszcza 10 / 0
Linia int wynik = 10 / 0; kompiluje się bez błędu i bez ostrzeżenia, mimo że oba argumenty są stałymi i kompilator mógłby policzyć wynik od razu. Wyjątek java.lang.ArithmeticException: / by zero pojawia się dopiero w czasie działania programu. Druga osobliwość: NaN nie jest równe niczemu, nawet samemu sobie, więc nan == nan zwraca false. Sprawdzajcie je metodą Double.isNaN(), a w JUnit assertEquals(Double.NaN, wynik) zadziała, bo porównuje przez Double.equals(), które traktuje NaN jak równe.
Dla testera wniosek jest praktyczny. Jeśli aplikacja liczy średni czas odpowiedzi jako sumę podzieloną przez liczbę żądań, przypadek z zerową liczbą żądań da w kodzie na int wyjątek i błąd 500, a w kodzie na double napis „NaN ms” albo „Infinity” w raporcie. Zero w mianowniku to jedna z pierwszych wartości granicznych, które warto sprawdzić w każdym polu, które coś przez coś dzieli. Tak samo widzimy w projektach testowych: puste koszyki, raporty bez żądań i listy bez wyników to przypadki, które zespołom najczęściej umykają, bo dane testowe zwykle są „pełne”. Dopiszcie je do Waszych zestawów jako osobne przypadki, a oszczędzicie sobie i użytkownikom niespodzianek.
Gdzie operatory matematyczne wychodzą w testach automatycznych?
Operatory matematyczne w testach automatycznych rzadko służą do obliczeń dla samej przyjemności. Służą do wyliczenia wartości oczekiwanej: liczby stron listy wyników, ceny po rabacie, sumy koszyka, liczby wierszy w tabeli. Poniższa klasa i test JUnit pokazują dwa takie przypadki. Liczbę stron liczy wzór (wyniki + naStronie - 1) / naStronie, który zaokrągla dzielenie całkowite w górę bez użycia double.
package pl.qi;
import java.math.BigDecimal;
import java.math.RoundingMode;
public final class Koszyk {
private Koszyk() {
}
/** Liczba stron listy wyników przy danej liczbie pozycji na stronie. */
public static int liczbaStron(int wyniki, int naStronie) {
return (wyniki + naStronie - 1) / naStronie;
}
/** Cena po rabacie procentowym, zaokrąglona do groszy. */
public static BigDecimal poRabacie(BigDecimal cena, int procent) {
BigDecimal mnoznik = BigDecimal.valueOf(100 - procent).divide(BigDecimal.valueOf(100));
return cena.multiply(mnoznik).setScale(2, RoundingMode.HALF_UP);
}
}
package pl.qi;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.math.BigDecimal;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.api.Test;
class KoszykTest {
@ParameterizedTest
@CsvSource({"0, 0", "1, 1", "20, 1", "21, 2", "100, 5", "101, 6"})
void liczbaStronNaGranicach(int wyniki, int oczekiwane) {
assertEquals(oczekiwane, Koszyk.liczbaStron(wyniki, 20));
}
@Test
void rabat15ProcentOd199i99() {
assertEquals(new BigDecimal("169.99"), Koszyk.poRabacie(new BigDecimal("199.99"), 15));
}
@Test
void zeroPozycjiNaStronieToBladKonfiguracji() {
assertThrows(ArithmeticException.class, () -> Koszyk.liczbaStron(10, 0));
}
}
KoszykTest ✔
├─ liczbaStronNaGranicach(int, int) ✔ (6 przypadków: 0, 1, 20, 21, 100, 101)
├─ rabat15ProcentOd199i99() ✔
└─ zeroPozycjiNaStronieToBladKonfiguracji() ✔
8 tests successful, 0 failed
Test parametryzowany sprawdza dokładnie wartości graniczne: 20 wyników mieści się na jednej stronie, 21 potrzebuje drugiej. To te same granice, które sprawdzilibyście ręcznie w ramach analizy wartości brzegowych, tylko zapisane raz i uruchamiane przy każdym buildzie. Test uruchomiliśmy w JUnit 6.1.3; adnotacje @ParameterizedTest i @CsvSource pochodzą z modułu junit-jupiter-params.
Wzór ma jedno ograniczenie, które wraca do przepełnienia z lekcji o typach danych. Dla liczby wyników bliskiej Integer.MAX_VALUE suma wyniki + naStronie - 1 przekracza zakres int i wynik wychodzi ujemny: (Integer.MAX_VALUE + 20 - 1) / 20 daje -107374181. Od Javy 18 bezpieczniej użyć Math.ceilDiv(wyniki, naStronie), która zaokrągla w górę bez przepełnienia i dla tych samych danych zwraca 107374183, a przy zerze w mianowniku też rzuca ArithmeticException, więc test z zerem dalej przechodzi. Sprawdziliśmy oba wyniki w JDK 25.
W testach UI wynik takiego wzoru porównujecie z tym, co widać na stronie. W Selenium liczbę wierszy daje driver.findElements(By.cssSelector("table tbody tr")).size(), w Playwright for Java page.locator("table tbody tr").count(); obie metody zwracają int, więc asercja assertEquals(oczekiwane, liczbaWierszy) działa bez żadnej konwersji. Cenę ze strony najpierw zamieniacie na BigDecimal, jak w lekcji o typach. Jeśli dopiero wybieracie narzędzie do takich testów, przeczytajcie na portalu Strefa QA porównanie Selenium, Cypress i Playwright w 2026 roku.
liczcie ją w teście tym samym wzorem, którego wymaga specyfikacja, a nie kopiujcie z aplikacji; inaczej test potwierdzi błąd zamiast go znaleźć.
zero, jeden, wartość równa limitowi i o jeden większa: przy dzieleniu i modulo to tam ukrywa się większość błędów.
liczby całkowite porównujcie wprost, double z tolerancją, kwoty jako BigDecimal po setScale(2).
Jakie błędy kompilatora zobaczycie przy operatorach?
Jeśli Wasz kod się nie kompiluje, a komunikat mówi o „lossy conversion”, niemal zawsze chodzi o typ wyniku działania. Komunikaty poniżej wywołaliśmy celowo w javac 25 z angielskimi komunikatami. Wszystkie wynikają z tej samej reguły z lekcji o typach: działanie na byte, short i int daje int, a na long daje long, i przypisanie wyniku do węższego typu wymaga jawnego rzutowania.
Ćwiczenie: jak zamienić sekundy na godziny, minuty i sekundy?
Zadanie: raport z testów wydajności podaje czas trwania przebiegu w sekundach, np. 3725. Napiszcie program, który zamieni go na zapis „1 h 2 min 5 s”, używając wyłącznie operatorów / i % na typie int. Spróbujcie, zanim zajrzycie niżej.
Rozwiązanie
public class Czas {
public static void main(String[] args) {
int sekundy = 3725;
int godziny = sekundy / 3600;
int minuty = sekundy % 3600 / 60;
int reszta = sekundy % 60;
System.out.println(godziny + " h " + minuty + " min " + reszta + " s");
}
}
1 h 2 min 5 s
Dzielenie całkowite przez 3600 daje pełne godziny. Reszta z dzielenia przez 3600 to sekundy ponad pełne godziny, a jej dzielenie przez 60 daje minuty; operatory % i / mają ten sam priorytet, więc liczą się od lewej do prawej. Reszta z dzielenia przez 60 to pozostałe sekundy. Sprawdźcie program dla wartości granicznych 0, 59, 60, 3599 i 3600; jeśli wszystkie dają sensowny wynik, rozwiązanie jest gotowe. W kodzie produkcyjnym to samo zrobi Duration.ofSeconds(3725) z metodami toHoursPart(), toMinutesPart() i toSecondsPart().
Co dalej po lekcji o operatorach?
Po operatorach matematycznych przychodzą operatory logiczne i porównania, a potem instrukcja warunkowa if, czyli moment, w którym program zaczyna podejmować decyzje. W Quality Island te same podstawy przerabiamy na szkoleniu Programowanie w języku Java dla testerów oprogramowania (2 dni, 2 212 zł netto według karty szkolenia, odczyt 10.10.2026), od razu na przykładach z testów. Osobom zaczynającym od zera polecamy Wprowadzenie do programowania od podstaw w Javie, a po podstawach automatyzację testów z Playwright w Javie.
Co zabrać z tego artykułu
01Java ma pięć operatorów arytmetycznych: + - * / %; wynik ma typ szerszego argumentu, a int / int to zawsze int.
02Dzielenie całkowite obcina w stronę zera (-7 / 2 = -3), a reszta ma znak dzielnej; przy cyklach używajcie Math.floorMod().
03i++ zwraca starą wartość, ++i nową; "Suma: " + 2 + 3 daje „Suma: 23”.
04Operatory +=, *= i pozostałe ukrywają rzutowanie (JLS 15.26.2) i mogą po cichu przepełnić byte albo obciąć ułamek.
0510 / 0 na int rzuca ArithmeticException, na double daje Infinity albo NaN; zero w mianowniku to obowiązkowa wartość graniczna.
Źródło: JLS Java SE 25, rozdziały 15.17 i 15.26.2; dokumentacja klasy Math; uruchomienie Quality Island na OpenJDK 25.0.2 i JUnit 6.1.3, 11.10.2026.
Chcecie liczyć wartości oczekiwane w testach bez niespodzianek z typami i zaokrągleniami? Na szkoleniu Java dla testerów ćwiczycie to na przykładach z automatyzacji.
Powiązane na blogu Quality Island
- Typy danych w Javie, lekcja 6 kursu
- Operatory logiczne, lekcja 8 kursu
- Pętle w Javie: for, while, do while i for-each
- Asercje w testach automatycznych: Selenium, JUnit, TestNG
- Java dla testerów automatyzujących: plan nauki na 8 tygodni
Pełna lista źródeł
- Oracle, The Java Language Specification, Java SE 25, rozdział 15 (15.17 Multiplicative Operators, 15.26.2 Compound Assignment Operators), 2025.
- Oracle, Math, dokumentacja API Java SE 25, 2025.
- Oracle, Integer, dokumentacja API Java SE 25, 2025.
- Oracle, The Java Tutorials: Operators, odczyt 2026.
- David Goldberg, What Every Computer Scientist Should Know About Floating-Point Arithmetic, ACM Computing Surveys 1991, przedruk Oracle.
- JUnit, JUnit User Guide (Parameterized Tests), odczyt 2026.
- Selenium, Information about web elements, dokumentacja 2026.
- Playwright, Locator, Playwright for Java (count), dokumentacja 2026.
- Quality Island, uruchomienie przykładów z tej lekcji na OpenJDK 25.0.2 (opcja
--release 25) i JUnit 6.1.3, 11.10.2026.