Basics Flashcards
HashMap
HashMap to struktura danych w języku Java, która implementuje interfejs Map i umożliwia przechowywanie danych w formie par klucz-wartość. Jest często używana do tworzenia kolekcji, w której można szybko znajdować wartości na podstawie ich kluczy. Klucze w HashMap są unikalne, co oznacza, że nie może istnieć więcej niż jedna para z tym samym kluczem.
Oto główne cechy HashMap:
Klucz-Wartość: HashMap przechowuje dane w formie par klucz-wartość. Klucz jest unikalny w obrębie HashMap, co oznacza, że każda para klucz-wartość ma unikalny klucz.
Szybki dostęp: HashMap umożliwia bardzo szybki dostęp do wartości na podstawie klucza. Dzięki temu jest wydajnym sposobem przechowywania i pobierania danych.
Brak gwarancji kolejności: Elementy w HashMap nie są przechowywane w określonej kolejności. W przeciwnym razie, kolejność elementów może być niestabilna i może zmieniać się w zależności od implementacji i historii operacji.
Null-klucze i null-wartości: HashMap zezwala na przechowywanie wartości null oraz pojedynczego klucza null. Oznacza to, że możesz przechowywać wartości, które nie są zdefiniowane, ale nie można przechowywać wielu wartości z kluczem null.
Niestabilność rozmiaru: Rozmiar HashMap może się dynamicznie zmieniać w zależności od ilości danych przechowywanych w kolekcji.
Wielowątkowość: HashMap nie jest w pełni bezpieczna w kontekście wielowątkowym. W przypadku współbieżnego dostępu do HashMap może być konieczne stosowanie dodatkowych mechanizmów synchronizacji lub rozważenie użycia synchronizowanych wersji Hashtable lub ConcurrentHashMap.
Przykład użycia HashMap w Javie:
java
Copy code
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
// Tworzenie i inicjalizacja HashMap
Map<String, Integer> hashMap = new HashMap<>();
// Dodawanie par klucz-wartość hashMap.put("Jan", 25); hashMap.put("Anna", 30); hashMap.put("Marek", 22); // Pobieranie wartości na podstawie klucza int age = hashMap.get("Anna"); System.out.println("Wiek Anny: " + age); } } HashMap jest często używana do przechowywania danych, takich jak konfiguracje, cache, indeksy i wiele innych, gdzie dostęp do danych na podstawie klucza jest istotny.
HashMap zasada działania
Oto bardziej szczegółowe wyjaśnienie działania HashMap:
Inicjalizacja: Kiedy tworzysz nową instancję HashMap, tworzona jest wewnętrzna tablica o określonym początkowym rozmiarze. Ta tablica będzie służyć do przechowywania par klucz-wartość.
Funkcja mieszająca (hashing): Każdy klucz jest przekształcany w unikalny skrócony hasz (hash code) za pomocą specjalnej funkcji mieszającej. Ta funkcja zamienia długi ciąg znaków, jakim jest klucz, na krótszy ciąg liczbowy (skrót). Skrócony hasz jest używany jako indeks do przechowywania wartości w tablicy.
Zamiana hasza w indeks: Skrócony hasz jest przekształcany w indeks wewnętrznej tablicy, zwykle przez wykorzystanie operacji reszty z dzielenia przez liczbę dostępnych indeksów. Na przykład, skrócony hasz może być przekształcony w indeks w zakresie od 0 do (rozmiar tablicy - 1).
Dodawanie danych: Aby dodać parę klucz-wartość do HashMap, obliczany jest skrócony hasz klucza, a następnie przeliczany na indeks w tablicy. Jeśli wskazany indeks jest wolny (tj. nie ma jeszcze wartości), para klucz-wartość jest zapisywana pod tym indeksem. Jeśli występuje kolizja (czyli inne pary mają ten sam indeks), HashMap musi obsłużyć ją, np. umieszczając nową parę w specjalnej strukturze danych wewnątrz tego indeksu.
Pobieranie danych: Aby pobrać wartość na podstawie klucza, HashMap oblicza skrócony hasz klucza i przelicza go na indeks w tablicy. Następnie przeszukuje tablicę, aby znaleźć odpowiednią parę klucz-wartość pod wskazanym indeksem. W przypadku kolizji, HashMap musi sprawdzić, która para klucz-wartość jest właściwą odpowiedzią.
Rozmiar tablicy: HashMap ma zmienne rozmiary tablicy. Jeśli liczba przechowywanych elementów przekracza pewien próg (nazywany “ładunkiem”), HashMap może zdecydować się na zwiększenie rozmiaru tablicy, aby zmniejszyć gęstość elementów na indeksach. To pomaga utrzymać wydajność.
Null-klucze i null-wartości: HashMap pozwala na przechowywanie kluczy i wartości null. Jednak istnieje ograniczenie, że w danej mapie może istnieć tylko jedna para z kluczem null. Można przechowywać wiele wartości z kluczem null.
Kolizje: W przypadku, gdy dwie różne pary klucz-wartość mają ten sam indeks w tablicy, HashMap używa specjalnych struktur danych wewnętrznych, takich jak lista lub drzewo, aby przechowywać te pary w jednym “bucket” (koszu). Rozwiązywanie kolizji polega na iteracji lub przeszukiwaniu tych struktur danych wewnątrz indeksu.
HashMap oferuje bardzo szybki dostęp do danych, ponieważ przeszukiwanie tablicy jest operacją o stałym czasie (O(1)), o ile nie ma nadmiernego zapełnienia (co może prowadzić do spadku wydajności). Jest to bardzo użyteczna struktura danych do przechowywania i pobierania danych na podstawie kluczy w aplikacjach Java.
HashMap budowa wewnętrzna
Wewnętrzna struktura HashMap w języku Java jest złożona i opiera się na tablicy z tzw. “bucketami”, które zawierają pary klucz-wartość. Oto podstawowa budowa wewnętrza HashMap:
Tablica (Array): HashMap wykorzystuje wewnętrzną tablicę do przechowywania “bucketów”, które zawierają pary klucz-wartość. Tablica jest dynamicznie dostosowywana w zależności od ilości przechowywanych elementów i może być powiększana lub zmniejszana w miarę potrzeb.
Bucket: Każdy element tablicy jest referencją do “bucketu”, który może zawierać jedną lub wiele par klucz-wartość. W przypadku, gdy wiele różnych kluczy ma ten sam indeks w tablicy (kolizja), HashMap używa struktury danych wewnątrz “bucketu” do przechowywania tych par.
Lista lub Drzewo: Dla rozwiązywania kolizji HashMap może wykorzystywać listę lub drzewo wewnątrz “bucketu”. W przypadku, gdy kilka par klucz-wartość ma ten sam indeks w tablicy, są one umieszczane wewnątrz “bucketu”. W takim przypadku HashMap może stosować listę lub drzewo wewnątrz “bucketu” do przechowywania i rozwiązywania konfliktów.
Pary klucz-wartość: Wewnątrz “bucketu” znajdują się pary klucz-wartość. Każda para składa się z klucza i odpowiadającej mu wartości. HashMap wykorzystuje skrócony hasz klucza, aby efektywnie odnajdywać odpowiednią parę w “bucketach”.
Ogólnie rzecz biorąc, HashMap tworzy tablicę z pewnym początkowym rozmiarem, a każda komórka tablicy zawiera referencję do “bucketu”. Wewnątrz “bucketu” są przechowywane pary klucz-wartość, a w przypadku kolizji, listy lub drzewa służą do przechowywania wielu par.
Dynamiczne dostosowywanie rozmiaru tablicy i rozwiązywanie konfliktów w “bucketach” pozwalają na efektywne przechowywanie i pobieranie danych w HashMap. Dzięki temu HashMap jest jednym z najczęściej używanych rodzajów map w Javie.
Linked List
Lista jednokierunkowa (ang. singly linked list) to struktura danych, która służy do przechowywania kolekcji elementów, zwanych węzłami, w których każdy węzeł zawiera dane oraz wskaźnik (ang. pointer) do następnego węzła w liście. Działa to na zasadzie łańcucha, gdzie każdy element wskazuje na kolejny, tworząc sekwencję.
Ogólna zasada działania listy jednokierunkowej jest następująca:
Węzły: Każdy węzeł w liście zawiera dwie rzeczy:
Dane: To mogą być dowolne informacje, które chcemy przechowywać w liście, na przykład liczby całkowite, łańcuchy znaków, obiekty itp.
Wskaźnik do następnego węzła: Wskaźnik ten wskazuje na kolejny węzeł w liście. Ostatni węzeł w liście wskazuje na null lub None, co oznacza koniec listy.
Struktura listy: Lista jednokierunkowa zazwyczaj ma jeden wskaźnik, który wskazuje na głowę listy (ang. head). Głowa to pierwszy węzeł w liście. Aby uzyskać dostęp do innych węzłów, musisz przeszukać listę, przechodząc od jednego węzła do drugiego, korzystając z ich wskaźników.
Operacje na liście jednokierunkowej:
Dodawanie elementu: Możesz dodać nowy element do listy, tworząc nowy węzeł i ustawiając wskaźnik poprzedniego węzła na ten nowy węzeł.
Usuwanie elementu: Możesz usunąć węzeł, aktualizując wskaźniki poprzedniego węzła tak, aby omijały usunięty węzeł.
Wyszukiwanie elementu: Możesz przeszukiwać listę, porównując dane węzłów z danymi, których szukasz.
Modyfikowanie elementu: Możesz zmieniać dane w węzłach, gdy znajdziesz węzeł, który chcesz zaktualizować.
Zalety i wady:
Zalety:
Dynamiczny rozmiar: Możesz dodawać i usuwać elementy z listy w czasie rzeczywistym, bez potrzeby alokacji stałej ilości pamięci.
Efektywne wstawianie i usuwanie: Dodawanie i usuwanie elementów z początku listy jest efektywne, o ile masz dostęp do głowy listy.
Wady:
Wolne przeszukiwanie: Przeszukiwanie elementu w liście jednokierunkowej może być wolne, ponieważ musisz przeglądać listę w porządku, zaczynając od głowy.
Brak dostępu wstecznego: Nie masz dostępu do poprzednich węzłów bez rekursji lub innych technik.
Listy jednokierunkowe są często wykorzystywane w programowaniu, szczególnie w językach takich jak C, C++, Java, Python itp. Są używane tam, gdzie dynamiczny rozmiar i szybkie wstawianie/usuwanie elementów są ważne, a dostęp do elementów wstecz nie jest wymagany.
Array List
ArrayList to struktura danych w językach programowania, taka jak Java, C#, Python (w ramach bibliotek, np. list w Pythonie), która działa podobnie do tradycyjnej tablicy (array), ale ma elastyczny rozmiar i wiele wbudowanych operacji, które ułatwiają zarządzanie danymi. ArrayList jest często używany, gdy potrzebujesz kolekcji elementów o zmiennej długości, gdzie dostęp do elementów odbywa się na podstawie indeksu.
Oto ogólna zasada działania ArrayList:
Elastyczny rozmiar: ArrayList umożliwia dynamiczne zmienianie rozmiaru kolekcji. Oznacza to, że możesz dodawać i usuwać elementy z listy bez konieczności określania jej rozmiaru na etapie tworzenia.
Przechowywanie danych: ArrayList przechowuje elementy w tablicy o zmiennej długości. Na początku ma pewną początkową pojemność, ale kiedy lista się zapełnia, automatycznie powiększa swoją pojemność, aby pomieścić więcej elementów.
Operacje na ArrayList:
Dodawanie elementów: Możesz dodać nowy element do ArrayList przy użyciu metody add(). Jeśli tablica jest zapełniona, zostanie automatycznie powiększona.
Usuwanie elementów: Możesz usunąć element z ArrayList za pomocą metody remove() lub removeAt().
Dostęp do elementów: Możesz uzyskiwać dostęp do elementów w ArrayList poprzez indeksowanie, np. get(index).
Wyszukiwanie elementów: Możesz przeszukiwać ArrayList w celu znalezienia określonego elementu, używając np. metody contains().
Iteracja: Możesz iterować po elementach w ArrayList, używając pętli lub iteratorów.
Zalety i wady:
Zalety:
Elastyczny rozmiar: ArrayList automatycznie zarządza rozmiarem, co ułatwia dodawanie i usuwanie elementów.
Skuteczne przeszukiwanie: ArrayList umożliwia dostęp do elementów poprzez indeks, co oznacza, że przeszukiwanie jest szybkie.
Dobre wydajności w przypadku dostępu do elementów: Dostęp do elementów w ArrayList jest szybki.
Wady:
Dla dużych kolekcji: Rozszerzanie ArrayList może być kosztowne w przypadku dużych kolekcji, ponieważ wymaga realokacji pamięci.
Nieefektywne wstawianie i usuwanie na początku: Wstawianie lub usuwanie elementów na początku ArrayList może być kosztowne ze względu na przesunięcie innych elementów.
ArrayList jest powszechnie używany w programowaniu do przechowywania i zarządzania kolekcjami danych o zmiennym rozmiarze, w szczególności w językach, które nie mają wbudowanych tablic o zmiennej długości, takich jak Java czy C#.
Typy danych w javie
ava oferuje różne typy danych, które można podzielić na kilka kategorii. Poniżej przedstawiam najważniejsze typy danych dostępne w Javie:
Typy prymitywne (primitive types):
int: Przechowuje liczby całkowite.
byte: Przechowuje małe liczby całkowite.
short: Przechowuje krótkie liczby całkowite.
long: Przechowuje długie liczby całkowite.
float: Przechowuje liczby zmiennoprzecinkowe (z ograniczoną precyzją).
double: Przechowuje liczby zmiennoprzecinkowe (z podwójną precyzją).
char: Przechowuje pojedyncze znaki Unicode.
boolean: Przechowuje wartości logiczne true lub false.
Typy referencyjne (reference types):
String: Przechowuje łańcuchy znaków.
Klasy: Programiści mogą tworzyć własne klasy zdefiniowane przez użytkownika.
Interfejsy: Definiują abstrakcyjne metody, które implementują różne klasy.
Enumeracje (enum): Reprezentują zestaw stałych wartości.
Tablice (arrays): Przechowują sekwencje elementów o tym samym typie danych.
Typy zdefiniowane przez użytkownika (user-defined types):
Klasa: Programiści mogą tworzyć własne klasy, definiując własne typy danych.
Interfejs: Definiują zestaw metod, które inne klasy mogą implementować.
Enumeracje (enum): Reprezentują zestaw stałych wartości.
Typy generyczne (generic types):
Programiści mogą tworzyć ogólne klasy i interfejsy, które mogą obsługiwać różne typy danych.
Typy złożone (complex types):
Klasa: Obejmuje dane i metody.
Interfejs: Definiuje zestaw metod, które inne klasy implementują.
Enumeracje (enum): Reprezentują zestaw stałych wartości.
Tablice: Przechowują sekwencje elementów o tym samym typie danych.
Typy bazowe (base types):
Byte, Short, Integer, Long: Klasa opakowująca dla typów prymitywnych.
Float, Double: Klasa opakowująca dla typów zmiennoprzecinkowych.
Character: Klasa opakowująca dla typu char.
Boolean: Klasa opakowująca dla typu boolean.
W Javie, typy prymitywne są bardziej wydajne w użyciu niż typy referencyjne, ale nie mogą przechowywać wartości null. Typy referencyjne, takie jak String czy klasy, mogą przechowywać null i są bardziej wszechstronne. Programiści mogą również tworzyć swoje własne typy danych, tworząc klasy i interfejsy zdefiniowane przez użytkownika.
Przeciążanie i nadpisywanie metod
Przeciążanie (overloading) i nadpisywanie (overriding) to dwa różne mechanizmy w języku Java, które pozwalają na definiowanie różnych zachowań dla metod o tych samych nazwach w różnych klasach lub w tej samej klasie. Oto ich krótka charakteryzacja oraz przykłady:
Przeciążanie (overloading):
Przeciążanie metod w Javie polega na definiowaniu wielu wersji tej samej metody w danej klasie, ale z różnymi zestawami parametrów (typami lub ilością parametrów). Wszystkie te wersje muszą różnić się od siebie pod względem parametrów.
Przykład przeciążania:
java
Copy code
public class Calculator {
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) { return a + b; } public String add(String a, String b) { return a + b; } } W powyższym przykładzie mamy trzy wersje metody add, z których każda przyjmuje różne typy danych jako parametry. Dzięki przeciążaniu, możemy używać add do dodawania liczb całkowitych, zmiennoprzecinkowych lub łańcuchów znaków.
Nadpisywanie (overriding):
Nadpisywanie metod w Javie występuje, gdy klasa podrzędna dostarcza własną implementację metody, która jest już zdefiniowana w klasie nadrzędnej (lub interfejsie). Metoda podrzędna musi mieć ten sam nazwę, ten sam typ zwracany i ten sam zestaw parametrów co metoda nadrzędna.
Przykład nadpisywania:
java
Copy code
class Animal {
public void makeSound() {
System.out.println(“Dźwięk zwierzęcia”);
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println(“Szczekanie psa”);
}
}
W powyższym przykładzie metoda makeSound w klasie Dog nadpisuje metodę makeSound z klasy Animal. Dzięki temu, gdy wywołamy makeSound na obiekcie Dog, zostanie wykonana implementacja z klasy Dog, a nie z klasy Animal.
Przeciążanie pozwala na różne wersje tej samej metody w ramach jednej klasy, natomiast nadpisywanie umożliwia modyfikację zachowania dziedziczonej metody w klasie podrzędnej. Obie te techniki są ważne w programowaniu obiektowym i pozwalają na elastyczność i reużywalność kodu.
Różnica między == a equals?
W języku Java == i .equals() to dwie różne operacje używane do porównywania obiektów. Oto główne różnice między nimi:
== (operator porównywania referencji):
== służy do porównywania referencji do obiektów, a nie samych obiektów.
Porównuje, czy dwie referencje wskazują na ten sam obszar pamięci, czyli na ten sam obiekt.
Zwraca true, jeśli referencje wskazują na ten sam obiekt, a false, jeśli wskazują na różne obiekty.
.equals() (metoda porównywania zawartości):
.equals() jest metodą zdefiniowaną w klasie java.lang.Object, którą dziedziczy każda klasa w Javie.
Domyślna implementacja metody .equals() w klasie Object działa tak samo jak ==, tj. porównuje referencje do obiektów.
Niemniej jednak, wiele klas w Javie przesłania tę metodę, aby porównać zawartość obiektów, a nie tylko referencje.
Przykład:
java
Copy code
String str1 = new String(“abc”);
String str2 = new String(“abc”);
String str3 = str1;
boolean result1 = (str1 == str2); // Porównuje referencje, zwraca false
boolean result2 = (str1 == str3); // Porównuje referencje, zwraca true
boolean result3 = str1.equals(str2); // Porównuje zawartość (treść) obiektów, zwraca true
System.out.println(result1); // false
System.out.println(result2); // true
System.out.println(result3); // true
W powyższym przykładzie, == porównuje referencje str1 i str2, co zwraca false, ponieważ są to różne obiekty. == porównuje referencje str1 i str3, co zwraca true, ponieważ obie te referencje wskazują na ten sam obiekt. .equals() porównuje zawartość obiektów str1 i str2, co zwraca true, ponieważ oba obiekty zawierają ten sam łańcuch znaków “abc”.
Co to jest deklaracja, inicjalizacja i przypisanie w kontekście zmiennych w Javie?
Deklaracja, inicjalizacja i przypisanie to trzy ważne kroki związane z zarządzaniem zmiennymi w języku Java:
Deklaracja:
Deklaracja to pierwszy krok w tworzeniu zmiennej.
W deklaracji określasz typ zmiennej i jej nazwę.
Deklaracja nie przydziela pamięci ani nie przypisuje wartości zmiennej; po prostu informuje kompilator o istnieniu zmiennej o określonym typie i nazwie.
Przykład deklaracji:
java
Copy code
int x;
String name;
Inicjalizacja:
Inicjalizacja to proces nadawania zmiennej początkowej wartości.
Po zadeklarowaniu zmiennej możesz przypisać jej wartość przy pomocy operatora przypisania =.
Inicjalizacja jest ważna, ponieważ niezainicjalizowana zmienna w Javie ma wartość domyślną (zero, null, itp.), co może prowadzić do błędów w działaniu programu.
Przykład inicjalizacji:
java
Copy code
int x = 10;
String name = “John”;
Przypisanie:
Przypisanie jest procesem zmiany wartości zmiennej po jej zainicjowaniu.
Możesz wielokrotnie przypisywać nowe wartości zmiennej w trakcie działania programu.
Przypisanie odbywa się również przy użyciu operatora przypisania =.
Przykład przypisania:
java
Copy code
x = 20; // Zmiana wartości zmiennej x na 20
name = “Alice”; // Zmiana wartości zmiennej name na “Alice”
Podsumowując, deklaracja to proces informowania kompilatora o istnieniu zmiennej, inicjalizacja to nadawanie zmiennej początkowej wartości podczas jej deklaracji lub później, a przypisanie to zmiana wartości zmiennej po jej zainicjowaniu. Właściwe zarządzanie zmiennymi jest kluczowe w programowaniu, aby uniknąć błędów i osiągnąć oczekiwane rezultaty w programach.
Różnica między klasą a obiektem
W języku Java, klasa i obiekt to dwa fundamentalne pojęcia związane z programowaniem obiektowym. Oto główne różnice między nimi:
Klasa:
Klasa to szablon lub projekt, który definiuje, jakie właściwości (pola) i zachowania (metody) powinny mieć obiekty utworzone na jej podstawie.
Klasa jest abstrakcyjnym opisem typu obiektu, opisuje, jakie cechy i zachowania mogą posiadać obiekty tej klasy.
Klasa jest jednym z fundamentów programowania obiektowego i służy do tworzenia nowych typów danych.
Obiekt:
Obiekt to konkretna instancja klasy. Oznacza to, że obiekt jest rzeczywistą reprezentacją klasy i ma określone wartości pól i stan.
Każdy obiekt jest tworzony na podstawie klasy i dziedziczy od niej właściwości i zachowania.
Obiekty są używane do przechowywania danych i wykonywania operacji zdefiniowanych w klasie.
Każdy obiekt jest niezależny od innych obiektów tej samej klasy i ma swoje własne dane i stan.
Przykład:
java
Copy code
class Person { // Klasa Person
String name; // Pole klasy
int age; // Pole klasy
void introduce() { // Metoda klasy System.out.println("Nazywam się " + name + " i mam " + age + " lat."); } }
public class Main {
public static void main(String[] args) {
// Tworzenie dwóch obiektów klasy Person
Person person1 = new Person(); // Obiekt 1
Person person2 = new Person(); // Obiekt 2
// Inicjalizacja pól obiektów person1.name = "John"; person1.age = 30; person2.name = "Alice"; person2.age = 25; // Wywoływanie metody obiektów person1.introduce(); person2.introduce(); } } W powyższym przykładzie Person to klasa, która definiuje właściwości (pole name i pole age) oraz zachowanie (metodę introduce). person1 i person2 to dwa obiekty utworzone na podstawie tej klasy, które posiadają różne wartości pól i zachowują się niezależnie od siebie. Obiekt to konkretna reprezentacja klasy, a klasa to abstrakcyjny szablon, na podstawie którego tworzone są obiekty.
Co to jest constructor i do czego służy?
Konstruktor w języku Java to specjalna metoda wewnątrz klasy, której głównym zadaniem jest inicjalizacja obiektu tej klasy. Konstruktor ma kilka ważnych cez, w tym:
Inicjalizacja obiektu: Konstruktor jest używany do ustawienia początkowych wartości pól (zmiennych) obiektu. Dzięki konstruktorowi można zapewnić, że obiekt będzie mieć określony stan na początku swojego istnienia.
Wywoływany automatycznie: Konstruktor jest wywoływany automatycznie w momencie tworzenia nowego obiektu danej klasy. W momencie tworzenia obiektu przy użyciu słowa kluczowego new, Java automatycznie wybiera odpowiedni konstruktor do inicjalizacji obiektu.
Może być przeciążany: Klasa może mieć wiele konstruktorów o różnych zestawach parametrów, co nazywa się przeciążaniem konstruktorów. Dzięki temu programista ma możliwość tworzenia obiektów z różnymi początkowymi ustawieniami.
Nie zwraca wartości: Konstruktor nie ma zadeklarowanego typu zwracanego (nawet nie jest to void). Jest to jedna z różnic między konstruktorem a zwykłą metodą.
Przykład:
java
Copy code
public class Person {
String name;
int age;
// Konstruktor bezargumentowy (domyślny) public Person() { name = "John"; age = 30; } // Konstruktor z argumentami public Person(String name, int age) { this.name = name; this.age = age; } public void introduce() { System.out.println("Nazywam się " + name + " i mam " + age + " lat."); } public static void main(String[] args) { // Tworzenie obiektów klasy Person za pomocą konstruktorów Person person1 = new Person(); Person person2 = new Person("Alice", 25); person1.introduce(); // Wywołanie metody introduce() person2.introduce(); } } W powyższym przykładzie mamy dwie wersje konstruktora: jeden bezargumentowy (domyślny) i drugi przyjmujący argumenty. Konstruktory inicjalizują pola obiektów klasy Person na podstawie przekazanych wartości lub wartości domyślnych. Konstruktory umożliwiają tworzenie obiektów z określonymi danymi na początku ich istnienia.
Jak działa mechanizm garbage collection w Javie?
Mechanizm garbage collection (GC) w języku Java jest częścią systemu zarządzania pamięcią i jest odpowiedzialny za automatyczne usuwanie nieużywanych obiektów z pamięci w celu zwolnienia zasobów. Działanie garbage collection w Javie można opisać w kilku krokach:
Śledzenie referencji: GC śledzi wszystkie referencje do obiektów w programie. Referencje to wskaźniki do obiektów i wskazują, które obiekty są aktualnie używane przez program.
Oznaczenie nieużywanych obiektów: W regularnych odstępach czasu lub w odpowiednich momentach GC analizuje wszystkie dostępne referencje i oznacza te obiekty, do których nie można dotrzeć z aktualnych referencji. Te oznaczone obiekty są uważane za nieużywane i do usunięcia.
Usuwanie nieużywanych obiektów: Po oznaczeniu nieużywanych obiektów, GC usuwa je z pamięci. Proces ten nazywa się “zbieraniem śmieci” (garbage collection). Usunięcie nieużywanych obiektów zwalnia pamięć i przywraca ją do użytku.
Mechanizm GC w Javie zapewnia kilka zalet:
Automatyczne zarządzanie pamięcią: Programista nie musi ręcznie zwalniać pamięci po nieużywanych obiektach, co pomaga uniknąć wycieków pamięci (memory leaks).
Unikanie błędów: Usunięcie nieużywanych obiektów pozwala uniknąć błędów związanych z dostępem do pamięci, takich jak odwołania do pamięci, które zostały już zwolnione.
Wydajność: Mechanizm GC działa w sposób zoptymalizowany, co pozwala na efektywne zarządzanie pamięcią i minimalizuje wpływ na wydajność programu.
Należy jednak pamiętać, że mechanizm GC nie jest pozbawiony wad. Może wpływać na wydajność programu w niektórych sytuacjach, zwłaszcza w aplikacjach wymagających niskiego opóźnienia (low-latency) lub dużych ilości danych. Dlatego istnieją różne algorytmy GC, które można dostosować do konkretnych potrzeb aplikacji.
Warto również zaznaczyć, że programiści w Javie mają pewien wpływ na zachowanie GC poprzez zarządzanie referencjami, takimi jak referencje słabe (weak references) i referencje miękkie (soft references), oraz poprzez konfigurację parametrów GC w maszynie wirtualnej Javy (JVM).
Różnice między equals i hashcode
equals i hashCode to dwie metody w języku Java, które są związane z porównywaniem i hashowaniem obiektów. Oto różnice między nimi:
equals:
equals jest metodą używaną do porównywania dwóch obiektów pod kątem ich zawartości, to znaczy, czy są one “równe” z punktu widzenia ich wartości.
Możesz przesłonić metodę equals w swojej klasie, aby zdefiniować własną logikę porównywania obiektów na podstawie ich atrybutów.
Domyślna implementacja equals w klasie Object porównuje jedynie referencje do obiektów (czy są to te same obiekty w pamięci).
Jeśli przesłaniasz metodę equals, powinieneś również przesłonić metodę hashCode, aby zachować spójność - dwa obiekty, które są równe pod względem equals, powinny mieć ten sam hash code.
hashCode:
hashCode to metoda używana do generowania unikalnej liczby całkowitej, która służy jako “skrót” obiektu.
Hash code jest wykorzystywany w kolekcjach, takich jak HashSet i HashMap, aby efektywnie przechowywać i wyszukiwać obiekty.
Dobrej jakości implementacja hashCode powinna minimalizować kolizje (sytuacje, w których dwa różne obiekty mają ten sam hash code), co przyczynia się do wydajności kolekcji.
Wartość hash code jest obliczana na podstawie atrybutów obiektu, ale jest zwykle stała dla danego obiektu podczas jego życia.
Podsumowując, equals służy do porównywania zawartości obiektów, podczas gdy hashCode służy do efektywnego przechowywania i indeksowania obiektów w kolekcjach. Oba te zagadnienia są istotne w kontekście pracy z kolekcjami i porównywania obiektów w języku Java.
Różnica między FetchType a CascadeType?
Różnica między FetchType a CascadeType dotyczy dwóch różnych aspektów mapowania obiektowo-relacyjnego w kontekście technologii ORM (Object-Relational Mapping), takich jak Hibernate czy Java Persistence API (JPA). Oto omówienie tych dwóch pojęć:
FetchType:
FetchType odnosi się do sposobu, w jaki dane związane z daną encją są pobierane z bazy danych w momencie odwołania się do tych danych.
Domyślnie, dla pól, które reprezentują relacje @ManyToOne, @OneToOne lub @OneToMany, wartość FetchType to EAGER. Oznacza to, że dane te są automatycznie pobierane z bazy danych razem z obiektem nadrzędnym.
Jeśli ustawisz FetchType na LAZY, dane te zostaną pobrane dopiero wtedy, gdy zostaniesz odwołasz się do powiązanego obiektu lub kolekcji. To pozwala na leniwe ładowanie i może poprawić wydajność, szczególnie gdy masz wiele relacji i nie zawsze potrzebujesz pobierać wszystkie dane.
CascadeType:
CascadeType odnosi się do propagacji operacji z obiektu nadrzędnego na związane obiekty w kontekście cyklu życia obiektów.
Na przykład, ustawienie CascadeType.PERSIST oznacza, że operacja persist() (czyli zapis nowego obiektu) na obiekcie nadrzędnym spowoduje, że ta operacja zostanie również wywołana na wszystkich związanych obiektach, które są oznaczone jako CascadeType.PERSIST.
CascadeType pomaga w zarządzaniu cyklem życia obiektów w kontekście ORM. Ułatwia to zapisywanie, usuwanie i aktualizowanie obiektów w spójny sposób.
Podsumowując, FetchType dotyczy sposobu pobierania danych z bazy danych w momencie dostępu do związanych obiektów, podczas gdy CascadeType dotyczy propagacji operacji na obiekcie nadrzędnym na jego związane obiekty w kontekście cyklu życia obiektów. Oba te mechanizmy są ważne w kontekście technologii ORM i wpływają na wydajność oraz zachowanie operacji na danych.
Jak działa Hashmapa
HashMapa to struktura danych w języku Java, która implementuje interfejs Map i jest używana do przechowywania danych w formie par klucz-wartość. Działa na zasadzie tablicy skrótów (hash table) i zapewnia efektywne przeszukiwanie, wstawianie i usuwanie elementów. Oto jak dokładnie działa HashMapa:
Tworzenie HashMapy: Można utworzyć nową HashMapę, podając typ klucza i wartości, na przykórej chcemy ją zastosować. Na przykład:
java
Copy code
Map<String, Integer> hashMap = new HashMap<>();
Wstawianie elementów: Aby wstawić element do HashMapy, używamy metody put(key, value), gdzie key jest kluczem, a value jest wartością. Klucz musi być unikalny w obrębie HashMapy. Przykład:
java
Copy code
hashMap.put(“apple”, 5);
hashMap.put(“banana”, 3);
Pobieranie elementów: Możemy pobrać wartość na podstawie klucza za pomocą metody get(key). Jeśli klucz istnieje, zostanie zwrócona jego wartość. Przykład:
java
Copy code
int appleCount = hashMap.get(“apple”); // Zwróci 5
Usunięcie elementu: Aby usunąć element, używamy metody remove(key). Przykład:
java
Copy code
hashMap.remove(“apple”);
Iterowanie po HashMapie: Możemy iterować po elementach HashMapy za pomocą pętli for-each lub innych metod dostępnych w Java. Na przykład:
java
Copy code
for (Map.Entry<String, Integer> entry : hashMap.entrySet()) {
String key = entry.getKey();
int value = entry.getValue();
System.out.println(key + “: “ + value);
}
Podstawowa zasada działania: HashMapa przechowuje elementy w tablicy skrótów, gdzie indeksy w tablicy są obliczane na podstawie skrótu (hash code) klucza. Wartości są przechowywane pod odpowiednimi indeksami. W przypadku kolizji, tj. gdy dwa różne klucze mają ten sam skrót, używana jest lista lub drzewo do przechowywania elementów.
Wybór odpowiedniego rozmiaru: Rozmiar wewnętrzej tablicy skrótów jest ważny dla wydajności HashMapy. Zbyt mała tablica może prowadzić do kolizji, a zbyt duża tablica do straty pamięci. W praktyce zazwyczaj nie jest to problem, ponieważ HashMapa dostosowuje swój rozmiar automatycznie w miarę dodawania i usuwania elementów.
HashMapa jest jedną z najczęściej używanych struktur danych w języku Java do szybkiego wyszukiwania i przechowywania danych w formie par klucz-wartość. Jeśli potrzebujesz trzymać elementy w określonej kolejności, zamiast HashMapy możesz użyć LinkedHashMap lub TreeMap, które utrzymują porządek elementów.
Optional w springu
Optional w Springu to typ pochodzący z języka Java, który jest często wykorzystywany w kontekście programowania reaktywnego i obsługi błędów. Spring Framework obsługuje Optional w różnych częściach swojej infrastruktury, a także zachęca do jego stosowania w niektórych przypadkach.
Oto kilka zastosowań Optional w Springu:
Obsługa wyników metod serwisów: W serwisach Springa często zwracane są wyniki operacji, na przykład rezultat wyszukiwania w bazie danych. Można użyć Optional do opakowania wyniku, aby określić, że wynik może być pusty. Przykład:
java
Copy code
public Optional<User> findUserById(Long userId) {
// Logika wyszukiwania użytkownika
}
W przypadku braku użytkownika, możesz zwrócić Optional.empty(), a w przypadku znalezienia użytkownika - Optional.of(user).</User>
Obsługa wyników kontrolerów: Kontrolery Spring MVC lub Spring WebFlux mogą zwracać Optional w wyniku swoich akcji. Dzięki temu klient może łatwo sprawdzić, czy zasób istnieje. Przykład w kontrolerze Spring MVC:
java
Copy code
@GetMapping(“/user/{id}”)
public ResponseEntity<Optional<User>> getUserById(@PathVariable Long id) {
Optional<User> user = userService.findUserById(id);
return ResponseEntity.ok(user);
}
Manipulacje wartościami opcjonalnymi: Spring Framework dostarcza szereg narzędzi do pracy z Optional, takie jak ifPresent, orElse, orElseGet, itp., które pozwalają na wygodne operacje na wartościach opcjonalnych. Na przykład:</User></User>
java
Copy code
Optional<User> user = userService.findUserById(id);
user.ifPresent(u -> {
// Wykonywanie operacji na obiekcie użytkownika, jeśli istnieje
});
Obsługa błędów w strumieniach reaktywnych: W programowaniu reaktywnym, Optional jest używane do obsługi przypadków, gdy reaktywny strumień może nie zawierać wartości. Możesz użyć operatora defaultIfEmpty w Mono lub Flux, aby określić wartość domyślną, jeśli strumień jest pusty. Przykład w Spring WebFlux:</User>
java
Copy code
userService.findUserById(id)
.defaultIfEmpty(User.getDefaultUser())
.subscribe(user -> {
// Obsługa użytkownika lub domyślnego użytkownika
});
Optional w Springu jest często używane jako narzędzie do obsługi opcjonalnych wartości lub braku wyników w bardziej elegancki sposób niż sprawdzanie wartości na null. Pomaga to unikać błędów NPE (NullPointerException) i zwiększa czytelność kodu. Jednak zawsze warto dobrze przemyśleć, gdzie Optional jest odpowiednie, a gdzie lepiej stosować tradycyjne warunki if, w zależności od konkretnego przypadku użycia.
Controller a RestController
Czym różni się Controller od Rest ControlleraW środowisku programowania w języku Java, w ramach frameworka Spring, Controller i RestController to dwie różne adnotacje używane w tworzeniu aplikacji internetowych. Oto główne różnice między nimi:
Controller:
@Controller jest adnotacją używaną do oznaczania klas, które pełnią rolę kontrolerów w aplikacji webowej.
Kontrolery oznaczone adnotacją @Controller obsługują żądania HTTP i generują odpowiedzi HTML, co oznacza, że są używane w tradycyjnych aplikacjach internetowych opartych na modelu widok-kontroler (MVC - Model-View-Controller).
Standardowe metody w kontrolerach mogą zwracać widoki HTML, które są generowane na serwerze i przesyłane do klienta, co umożliwia renderowanie treści na stronie internetowej.
RestController:
@RestController to specjalizowana wersja kontrolera w Springu, która jest używana do tworzenia tzw. RESTful API (API, które działa w oparciu o standardy REST - Representational State Transfer).
Kontrolery oznaczone adnotacją @RestController obsługują żądania HTTP i generują odpowiedzi w formacie JSON lub XML. Odpowiedzi te są zwykle przetwarzane przez aplikacje klienckie, takie jak aplikacje internetowe, aplikacje mobilne itp.
Standardowe metody w kontrolerach @RestController zazwyczaj zwracają dane bezpośrednio, a te dane są serializowane do formatu JSON lub XML i przesyłane jako treść odpowiedzi HTTP.
Podsumowując, główną różnicą między Controller a RestController jest sposób generowania odpowiedzi. @Controller generuje odpowiedzi HTML, które są przeznaczone do wyświetlenia w przeglądarkach internetowych, podczas gdy @RestController generuje odpowiedzi w formacie danych (zwykle JSON lub XML), które są przeznaczone do komunikacji między aplikacjami. Wybór między tymi dwiema adnotacjami zależy od rodzaju aplikacji, którą tworzysz i jakie rodzaje odpowiedzi musisz obsłużyć.