Wykład 2
Java oferuje zestaw typów podstawowych (nazywanych też typami prostymi), które są używane do reprezentacji prostych wartości, takich jak liczby całkowite, liczby zmiennoprzecinkowe, znaki czy wartości logiczne.
byte
: 8-bitowa wartość liczby całkowitej; zakres od \(-128\) do \(127\).short
: 16-bitowa wartość liczby całkowitej; zakres od \(-32,768\) do \(32,767\).int
: 32-bitowa wartość liczby całkowitej; zakres od \(-2^{31}\) do \(2^{31}-1\).long
: 64-bitowa wartość liczby całkowitej; zakres od \(-2^{63}\) do \(2^{63}-1\).float
: 32-bitowa wartość liczby zmiennoprzecinkowej (jednokrotnej precyzji). Zaleca się używanie sufiksu F
lub f
przy inicjalizacji wartości stałych.double
: 64-bitowa wartość liczby zmiennoprzecinkowej (podwójnej precyzji). Jest to domyślny typ dla wartości zmiennoprzecinkowych w Javie.char
: Reprezentuje pojedynczy znak w standardzie Unicode i zajmuje 16 bitów. Znaki są zamykane w pojedynczych cudzysłowach, np. 'A'
.boolean
: Reprezentuje wartość prawda/fałsz. Może przyjmować tylko jedną z dwóch wartości: true
lub false
.Uwagi:
boolean
) ma odpowiadającą mu klasę opakowującą w pakiecie java.lang
(np. Integer
dla int
, Double
dla double
itp.). Klasy te są używane, gdy potrzebujemy reprezentować typ podstawowy jako obiekt, oraz oferują wiele pomocniczych metod do pracy z danym typem.0
dla typów liczbowych, false
dla boolean
), ale wartości te są ustawiane domyślnie tylko dla zmiennych klasowych lub instancyjnych, nie dla lokalnych zmiennych wewnątrz metod.public class Main {
public static void main(String[] args) {
int localVar; // zmienna lokalna niezainicjalizowana
// Poniższa linia spowoduje błąd kompilacji, ponieważ próbujemy użyć zmiennej,
// która nie została zainicjalizowana
// System.out.println(localVar);
localVar = 10; // inicjalizacja zmiennej lokalnej
System.out.println(localVar); // teraz jest w porządku, ponieważ zmienna
// została zainicjalizowana
}
}
W języku Java operacje wyjścia na konsolę odnoszą się głównie do wydruków tekstowych prezentowanych użytkownikowi na standardowym wyjściu (czyli zazwyczaj konsoli terminala).
System.out.print() i System.out.println():
System.out.print("Tekst")
: Drukuje podany tekst na konsoli bez przechodzenia do nowej linii.System.out.println("Tekst")
: Drukuje podany tekst na konsoli i przechodzi do nowej linii.Przykład:
Wyjście:
Witaj, świecie!
System.out.printf(): Pozwala na formatowane wyjście. Umożliwia wstawianie zmiennych w określone miejsca w tekście oraz kontrolowanie sposobu ich prezentacji.
Przykład:
double cena = 12.5;
int ilosc = 5;
System.out.printf("Cena: %.2f, Ilość: %d, Łącznie: %.2f", cena, ilosc, cena * ilosc);
Wyjście:
Cena: 12.50, Ilość: 5, Łącznie: 62.50
Metoda System.out.printf()
w Javie oraz funkcja printf()
w języku C są do siebie podobne pod względem ogólnego przeznaczenia i użycia, ponieważ obie służą do formatowanego wydruku napisów i są inspirowane tym samym konceptem. Niemniej jednak, istnieje kilka różnic w używaniu tych funkcji/metod, zarówno pod względem składni, jak i funkcjonalności, które mogą wpłynąć na to, jak są używane w obu językach.
Formatowanie: dokumentacja.
Znacznik %n
oznacza koniec linii w Javie.
Klasa Scanner
jest często używana do wczytywania danych wejściowych z konsoli, ponieważ jest łatwa w użyciu i oferuje szeroki zakres funkcji do wczytywania różnych typów danych.
Inicjalizacja: Aby użyć klasy Scanner
, najpierw musisz ją zaimportować z pakietu java.util
, a następnie utworzyć jej obiekt, używając System.in
jako źródła wejściowego.
Wczytywanie danych:
Wczytywanie całego wiersza tekstu:
Wczytywanie pojedynczego słowa:
Wczytywanie danych liczbowych:
Wczytywanie wartości logicznych:
Zamykanie skanera: Po zakończeniu wczytywania danych z konsoli warto zamknąć obiekt Scanner
, aby zwolnić zasoby.
Więcej info: w dokumentacji.
Jeśli nie przypiszesz wartości początkowej do zmiennej podczas deklaracji, dla zmiennych lokalnych musisz to zrobić przed ich pierwszym użyciem. W przeciwnym razie kompilator zgłosi błąd. Zmienne instancji (pola klasy) otrzymują domyślne wartości.
var
Słowo kluczowe var
zostało wprowadzone w Javie 10 w celu poprawy czytelności kodu poprzez lokalne inferencje typów dla deklaracji zmiennych. Umożliwia to deklarowanie zmiennych bez konieczności jawnego podawania ich typu, o ile można jednoznacznie wywnioskować typ zmiennej na podstawie przypisywanej jej wartości.
Lokalne inferencje typów: var
może być używane tylko do deklaracji lokalnych zmiennych (wewnątrz metod, bloków, pętli itp.), inicjalizowanych zmiennych dla instrukcji for
oraz zmiennych lokalnych w try-with-resources.
Nie można używać z wartościami null
: Ponieważ var
opiera się na inferencji typów, nie można zadeklarować zmiennej jako var
i jednocześnie przypisać jej wartości null
, ponieważ kompilator nie będzie mógł wywnioskować typu.
var
nie jest dozwolone dla deklaracji pól klasy, sygnatur metod ani jako typ zwracany.Czytelność kodu: Używanie var
może poprawić czytelność, zwłaszcza gdy typ zmiennej jest oczywisty z kontekstu lub kiedy typ właściwy jest długi i skomplikowany (np. typy generyczne). Jednak w innych sytuacjach jawnie podane typy mogą być bardziej czytelne. Należy stosować var
z rozwagą.
Nie można używać dla zmiennych bez inicjalizacji: Deklaracja zmiennej z użyciem var
musi odbywać się jednocześnie z jej inicjalizacją.
Przykład:
Deklaracja: Używając modyfikatora final
podczas deklarowania zmiennej, sprawiamy, że staje się ona stałą. Oznacza to, że jej wartość nie może zostać zmieniona po pierwszym przypisaniu.
Inicjalizacja: Stała zmienna musi być zainicjowana w momencie jej deklaracji lub w konstruktorze (w przypadku zmiennych instancji).
Nazewnictwo: Konwencją jest nazywanie stałych zmiennych używając wielkich liter i podkreślników, aby łatwo je rozpoznać w kodzie, choć to nie jest wymogiem języka.
enum
Podstawy podobnie jak w C. Inne elementy będą omówione później.
+
)-
)*
)/
)%
)++
)--
)+=
, -=
, *=
, /=
, %=
)Ważne uwagi:
double
czy float
) mogą prowadzić do błędów zaokrągleń, co jest typowe dla arytmetyki zmiennoprzecinkowej.Stałe:
Funkcje:
Math.abs(x): Zwraca wartość bezwzględną argumentu.
Math.ceil(x): Zwraca najmniejszą liczbę całkowitą większą lub równą argumentowi.
Math.floor(x): Zwraca największą liczbę całkowitą mniejszą lub równą argumentowi.
Math.round(x): Zaokrągla liczbę zmiennoprzecinkową do najbliższej liczby całkowitej. Istnieją wersje dla float
i double
.
Math.sqrt(x): Zwraca pierwiastek kwadratowy z argumentu.
Math.cbrt(x): Zwraca pierwiastek sześcienny z argumentu.
Math.pow(a, b): Podnosi liczbę a
do potęgi b
.
Math.max(a, b): Zwraca większą z dwóch wartości.
Math.min(a, b): Zwraca mniejszą z dwóch wartości.
Math.sin(x), Math.cos(x), Math.tan(x): Zwracają wartości funkcji trygonometrycznych sinus, cosinus i tangens odpowiednio.
Math.asin(x), Math.acos(x), Math.atan(x): Zwracają arcus sinus, arcus cosinus i arcus tangens odpowiednio.
Math.exp(x): Zwraca wartość e
podniesioną do potęgi x
.
Math.log(x): Zwraca logarytm naturalny liczby x
.
Math.log10(x): Zwraca logarytm o podstawie 10 liczby x
.
Math.random(): Zwraca losową liczbę zmiennoprzecinkową z zakresu [0, 1).
==
)!=
)>
)<
)>=
)<=
)&&
)||
)!
)true
lub false
.boolean
).&&
i ||
są operatorami skróconej ewaluacji. Oznacza to, że drugi argument nie jest oceniany, jeśli pierwszy argument determinuje wynik.Zasięg blokowy jest zdefiniowany przez nawiasy klamrowe { }
. Zmienne zadeklarowane wewnątrz tych nawiasów są dostępne tylko w tym konkretnym bloku kodu.
Zasięg wewnątrz metody:
Zasięg w blokach sterujących:
Zasięg w pętlach:
Instrukcja if
:
Instrukcja if-else
:
Instrukcja if-else if-else
:
Instrukcja switch
:
Pętla for
:
Pętla for-each
(zwana też “enhanced for”):
Pętla while
:
Pętla do-while
:
varargs
(variable-length argument) to konstrukcja, która pozwala metodzie przyjmować dowolną liczbę argumentów tego samego typu, traktując je jako tablicę. Umożliwia to definiowanie metod, które mogą być wywoływane z różnymi ilościami argumentów.
Definiowanie metody z varargs:
Aby zdefiniować metodę z varargs, użyj trzech kropek (...
) przed typem argumentu. Na przykład:
Wywoływanie metody z varargs:
Możesz wywołać metodę z varargs, przekazując dowolną liczbę argumentów (lub nawet brak argumentów) tego typu:
Uwagi dotyczące używania varargs:
Jeden varargs na metodę: W definicji metody możesz mieć tylko jeden argument varargs, i musi on być na końcu listy argumentów.
Traktuj varargs jako tablicę: Wewnątrz metody argument varargs jest traktowany jako tablica. W powyższym przykładzie numbers
jest traktowane jako tablica int[]
.
Ostrożnie z przeciążaniem: Przeciążanie metod z varargs może prowadzić do niejednoznaczności i błędów w czasie kompilacji. Dlatego warto być ostrożnym i upewnić się, że przeciążone wersje metody są łatwo rozróżnialne.
Przesyłanie tablicy do metody z varargs: Możesz również przekazać gotową tablicę do metody z varargs:
Przeciążanie metod (ang. method overloading) to technika w Javie, która pozwala na definiowanie wielu metod o tej samej nazwie w tej samej klasie lub klasie pochodnej, ale z różnymi listami parametrów. Metody te mogą różnić się liczbą, typem lub kolejnością parametrów.
Przykład:
Reguły przeciążania:
public
, private
itp.) również nie wpływają na przeciążenie metody.Klasa Random
w Javie służy do generowania strumieni pseudolosowych liczb. Jest ona częścią pakietu java.util
.
Oto kilka najczęściej używanych metod klasy Random
:
nextInt(): Zwraca losową liczbę całkowitą (typu int
).
nextInt()
: zwraca dowolną wartość int
.nextInt(int bound)
: zwraca wartość od 0 (włącznie) do podanej wartości (wyłącznie).nextLong(): Zwraca losową liczbę całkowitą typu long
.
nextDouble(): Zwraca losową liczbę zmiennoprzecinkową typu double
z zakresu od 0.0 (włącznie) do 1.0 (wyłącznie).
nextFloat(): Zwraca losową liczbę zmiennoprzecinkową typu float
z zakresu od 0.0 (włącznie) do 1.0 (wyłącznie).
nextBoolean(): Zwraca losową wartość boolean
, czyli true
lub false
.
nextBytes(byte[] bytes): Wypełnia podaną tablicę bajtów losowymi bajtami.
nextGaussian(): zwraca losową liczbę zmiennoprzecinkową typu double
wygenerowaną według standardowego rozkładu normalnego (rozkładu Gaussa)o średniej równej 0 i odchyleniu standardowym równym 1.
Random
, będą one generować te same sekwencje liczb. To jest przydatne, gdy chcesz uzyskać powtarzalne wyniki dla testów czy symulacji.Tablice jednowymiarowe to struktury danych, które przechowują wiele wartości tego samego typu w jednym obiekcie.
Deklaracja: Aby zadeklarować tablicę, używa się typu danych, jaki ma być przechowywany, a następnie nawiasów kwadratowych. Na przykład, aby zadeklarować tablicę liczb całkowitych, używa się:
Inicjalizacja: Można zainicjować tablicę w kilka sposobów: - Przy pomocy operatora new
oraz określenia rozmiaru tablicy:
Uwaga: Wersja z C też działa, ale nie jest dobrą praktyką:
Dostęp do elementów: Aby uzyskać dostęp do elementu tablicy, używa się indeksu (numerowane od 0) w nawiasach kwadratowych:
Modyfikacja elementów: Można modyfikować zawartość tablicy, odnosząc się do konkretnego indeksu:
Długość tablicy: Aby uzyskać długość tablicy, używa się atrybutu length
:
Ograniczenia:
Przypisanie:
W Javie możesz przypisać jedną tablicę do innej zmiennej tablicowej, ale ważne jest zrozumienie, co się wtedy dzieje.
Gdy przypisujesz tablicę do innej zmiennej tablicowej, nie tworzysz nowej kopii tej tablicy. Zamiast tego tworzysz drugą referencję do tej samej tablicy w pamięci. Oznacza to, że obie zmienne wskazują na tę samą tablicę, a zmiany dokonane za pomocą jednej zmiennej będą widoczne przy użyciu drugiej zmiennej.
W przykładzie, po przypisaniu tablica1
do tablica2
, obie zmienne wskazują na tę samą tablicę. Dlatego zmiana wartości w tablica2
wpływa również na tablica1
.
Jeśli chcesz mieć dwie różne kopie tej samej tablicy, musisz skopiować zawartość jednej tablicy do drugiej, na przykład za pomocą pętli lub używając metody System.arraycopy()
:
int[] tablica1 = {1, 2, 3};
int[] tablica2 = new int[tablica1.length];
System.arraycopy(tablica1, 0, tablica2, 0, tablica1.length);
Teraz tablica2
jest osobną kopią tablica1
, a zmiany w jednej z nich nie wpłyną na drugą.
Przekazywanie tablic do metody:
W Javie do obsługi tablic dostępna jest klasa java.util.Arrays
, która dostarcza szereg przydatnych metod. Oto niektóre z nich:
sort
: Sortuje elementy tablicy. Może być używane do sortowania całej tablicy lub tylko jej części.
binarySearch
: Wyszukuje określony element w posortowanej tablicy, używając wyszukiwania binarnego.
equals
: Sprawdza, czy dwie tablice są równe pod względem zawartości.
fill
: Ustala wszystkie elementy tablicy na określoną wartość.
copyOf
: Kopiuje określoną liczbę elementów tablicy źródłowej do nowej tablicy.
copyOfRange
: Kopiuje zakres elementów z tablicy źródłowej do nowej tablicy.
deepEquals
: Sprawdza, czy dwie tablice wielowymiarowe są równe pod względem zawartości.
deepHashCode
: Oblicza kod hash dla tablicy, biorąc pod uwagę wszystkie elementy w tablicach wielowymiarowych.
deepToString
: Konwertuje tablicę wielowymiarową na łańcuch reprezentujący jej zawartość (w sposób głęboki).
hashCode
: Oblicza hash dla tablicy.
stream
: Dostarcza sekwencyjny strumień zawierający elementy tablicy, co pozwala na używanie operacji strumieniowych (od Javy 8).
setAll
: Ustala wartości wszystkich elementów tablicy, używając dostarczonej funkcji generującej.
parallelSetAll
: Podobnie jak setAll
, ale używa wielowątkowości do jednoczesnego ustalania wartości dla wielu elementów.
parallelSort
: Sortuje tablicę używając wielowątkowości.
mismatch
: Znajduje i zwraca indeks pierwszego niezgodnego elementu między dwiema tablicami (od Javy 9).
toString
: Konwertuje tablicę wielowymiarową na łańcuch reprezentujący jej zawartość (w sposób płytki).
import java.util.Arrays;
public class SortowanieNierosnace {
public static void main(String[] args) {
int[] tablica = {34, 12, 5, 78, 2, 10, 8};
Arrays.sort(tablica);
for (int i = 0; i < tablica.length / 2; i++) {
int temp = tablica[i];
tablica[i] = tablica[tablica.length - 1 - i];
tablica[tablica.length - 1 - i] = temp;
}
System.out.println(Arrays.toString(tablica));
}
}
import java.util.Arrays;
public class PorownanieTablic {
public static void main(String[] args) {
int[] tablica1 = {1, 2, 3, 4, 5};
int[] tablica2 = {1, 2, 3, 4, 5};
int[] tablica3 = {5, 4, 3, 2, 1};
boolean czyRowne1i2 = Arrays.equals(tablica1, tablica2); // true
boolean czyRowne1i3 = Arrays.equals(tablica1, tablica3); // false
System.out.println("Czy tablica1 jest równa tablica2? " + czyRowne1i2);
System.out.println("Czy tablica1 jest równa tablica3? " + czyRowne1i3);
}
}
ArrayList
ArrayList
to jedna z najczęściej używanych implementacji interfejsu List
. Jest to dynamicznie rozszerzalna tablica, która może zmieniać swój rozmiar w miarę dodawania i usuwania elementów. Wewnętrznie ArrayList
używa tablicy do przechowywania elementów.
Własności:
Wewnętrzna struktura danych: Jak wspomniano wcześniej, ArrayList
wewnętrznie używa tablicy do przechowywania elementów. Gdy lista staje się pełna i potrzeba dodać kolejny element, Java tworzy większą tablicę i kopiuje elementy starej tablicy do nowej.
Rozmiar vs. Pojemność:
ArrayList
może zmieniać swój rozmiar w miarę potrzeb, co czyni ją bardziej elastyczną niż zwykłe tablice.null
.LinkedList
z powodu dodatkowej pojemności rezerwowej.Bezpieczeństwo wątkowe: Standardowa implementacja ArrayList
nie jest synchronizowana, co oznacza, że nie jest bezpieczna do użytku w środowiskach wielowątkowych bez odpowiedniej synchronizacji. Jeśli potrzebujesz wersji synchronizowanej, możesz użyć Collections.synchronizedList()
.
Pamięć: Jeśli znasz ostateczny rozmiar listy, warto zainicjalizować ArrayList
z odpowiednią pojemnością początkową, aby uniknąć wielokrotnego rozszerzania wewnętrznej tablicy.
Okej, przyjrzyjmy się niektórym z najczęściej używanych metod i właściwości klasy ArrayList
w Javie:
add(E element)
:
add(int index, E element)
:
get(int index)
:
remove(int index)
:
remove(Object o)
:
size()
:
isEmpty()
:
clear()
:
contains(Object o)
:
indexOf(Object o)
:
set(int index, E element)
:
toArray()
:
Klasy takie jak ArrayList
są generycznymi klasami kontenerowymi, które przechowują obiekty, a nie typy proste. Dlatego nie możemy bezpośrednio użyć int
lub double
jako typu elementu dla ArrayList
.
Aby rozwiązać ten problem, Java dostarcza klasy opakowujące (wrapper classes) dla wszystkich typów prostych. Dla int
mamy Integer
, dla double
mamy Double
itd.
Dzięki autoboxingowi (automatyczne konwersje między typami prostymi a ich klasami opakowującymi) korzystanie z tych klas opakowujących jest stosunkowo proste i wygodne.
Dla int
:
Dla double
:
Inicjalizacja:
Niezmienność (Immutability):
String
są niezmienne. Oznacza to, że raz utworzony łańcuch znaków nie może być zmieniony. Wszelkie operacje modyfikujące zawartość łańcucha (np. dodawanie, usuwanie znaków) skutkują stworzeniem nowego obiektu String
.Konkatenacja:
+
:Porównywanie:
equals()
zamiast operatora ==
:Pamięć:
new
.Zmienne łańcuchowe:
StringBuilder
i StringBuffer
. Są one szczególnie przydatne w sytuacjach, gdzie zachodzi wiele modyfikacji łańcucha, ponieważ operują one w miejscu (in-place) i są zwykle szybsze niż tworzenie wielu obiektów String
.null
Wartość:
""
): To faktyczny obiekt klasy String
, który ma wartość, ale ta wartość jest pusta. Inaczej mówiąc, jest to łańcuch, który nie zawiera żadnych znaków.null
: To specjalna wartość, która oznacza, że zmienna nie wskazuje na żaden obiekt. Dla zmiennej typu String
, jeśli jest ona ustawiona na null
, nie wskazuje ona na żaden łańcuch (pusty czy inny).Długość:
""
): Jego długość wynosi 0. Można to sprawdzić używając metody length()
: "".length()
zwróci 0.null
: Zmienna o wartości null
nie posiada metod ani atrybutów. Próba wywołania metody, np. null.length()
, spowoduje wyjątek NullPointerException
.Operacje:
""
): Możesz wykonywać na nim różne operacje, takie jak konkatenacja czy wywoływanie innych metod klasy String
.null
: Nie możesz wykonywać na nim żadnych operacji. Każda próba dostępu do metody lub atrybutu na zmiennej o wartości null
spowoduje NullPointerException
.Porównywanie:
Możesz porównać zarówno łańcuch pusty, jak i null
z innymi łańcuchami. Ale musisz być ostrożny z null
, ponieważ:
String s1 = "";
String s2 = null;
System.out.println(s1.equals("")); // true
System.out.println(s2.equals(null)); // Wyjątek: NullPointerException
Zastosowania:
""
): Często używany do inicjowania łańcuchów bez konkretnej wartości lub do wskazania, że łańcuch powinien być “czysty” lub “bez wartości”, ale nadal istnieje.null
: Wskazuje, że zmienna nie odnosi się do żadnego obiektu. Jest używane w wielu przypadkach, np. gdy wartość nie jest jeszcze znana lub nie została ustawiona.length()
:
Zwraca liczbę znaków w łańcuchu.
charAt(int index)
:
Zwraca znak na określonej pozycji w łańcuchu.
substring(int beginIndex, int endIndex)
:
Zwraca nowy łańcuch zawierający znaki z oryginalnego łańcucha od beginIndex
(włącznie) do endIndex
(wyłącznie).
indexOf(String str)
i lastIndexOf(String str)
:
Zwracają indeks pierwszego/ostatniego wystąpienia podciągu w łańcuchu. Jeśli podciąg nie jest znaleziony, zwraca -1.
equals(Object obj)
:
Porównuje zawartość tego łańcucha z zawartością innego obiektu. Zwraca true
, jeśli są równe.
equalsIgnoreCase(String anotherString)
:
Porównuje łańcuchy bez uwzględniania wielkości liter.
startsWith(String prefix)
i endsWith(String suffix)
:
Sprawdzają, czy łańcuch zaczyna się lub kończy danym ciągiem znaków.
replace(char oldChar, char newChar)
lub replace(CharSequence target, CharSequence replacement)
:
Zwraca nowy łańcuch, w którym wszystkie wystąpienia oldChar
lub target
są zastąpione przez newChar
lub replacement
.
trim()
:
Zwraca kopię łańcucha z usuniętymi białymi znakami na początku i końcu.
toLowerCase()
i toUpperCase()
:
Zmieniają wielkość liter w łańcuchu.
split(String regex)
:
Dzieli łańcuch według podanego wyrażenia regularnego.
isEmpty()
:
Sprawdza, czy łańcuch jest pusty (długość wynosi 0).
valueOf()
(statyczna metoda):
Konwertuje różne typy danych (np. int, char) na łańcuchy.
String
:
String
są niemutowalne, co oznacza, że po ich utworzeniu nie można ich zmienić. Każda operacja, która wydaje się modyfikować łańcuch (np. konkatenacja), faktycznie tworzy nowy obiekt String
.StringBuilder
:
StringBuilder
są mutowalne. Można dodawać, usuwać i modyfikować zawartość obiektu bez tworzenia nowych obiektów.String
w operacjach modyfikujących, szczególnie w intensywnych operacjach, takich jak budowanie łańcuchów w pętlach.StringBuilder
jest zwykle lepszym wyborem niż StringBuffer
.StringBuffer
:
StringBuilder
, obiekty StringBuffer
są mutowalne.StringBuilder
, ale operacje są synchronizowane.StringBuffer
może być odpowiednim wyborem.