pytania rekrutacyjne Flashcards

1
Q

Equals i hashcode

A

equals() i hashCode() to dwie metody, które są związane z implementacją równości obiektów w języku Java. Oto krótka wyjaśnienie obu metod:

equals():
Metoda equals() jest używana do porównywania dwóch obiektów pod kątem ich treści (wartości).
Domyślna implementacja metody equals() w klasie Object porównuje referencje do obiektów, co oznacza, że porównuje, czy oba obiekty są dokładnie tym samym obiektem w pamięci.
W praktyce często konieczne jest dostosowanie tej metody w własnych klasach, aby porównywać obiekty na podstawie ich zawartości, a nie referencji.
Implementacja equals() powinna spełniać pewne warunki, takie jak: być refleksyjna, symetryczna, przechodzić test równości z obiektem null, i innych.
Przykład dostosowanej implementacji equals():

java
Copy code
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
// Porównanie pól obiektów
// …

return true; } hashCode(): Metoda hashCode() zwraca wartość liczbową, która reprezentuje "skróconą" wersję obiektu. Ta wartość liczbowo identyfikuje obiekt i jest wykorzystywana w mechanizmach haszujących, takich jak tablice mieszające (hash tables). Implementacja hashCode() powinna być zgodna z implementacją equals(), tj. obiekty, które są równe według equals(), powinny mieć również takie same wartości hashCode(). Domyślna implementacja w klasie Object zwraca różne wartości dla różnych obiektów, nawet jeśli są one równe według equals(). Przykład dostosowanej implementacji hashCode():

java
Copy code
@Override
public int hashCode() {
int result = 17;
result = 31 * result + field1.hashCode();
result = 31 * result + field2.hashCode();
// Dodaj inne pola, jeśli istnieją
return result;
}
Wartości 17 i 31 są wybranymi liczbami pierwszymi, które pomagają w uzyskaniu rozróżnialnych wartości hashCode() dla różnych kombinacji pól. Działanie tych liczb przyczynia się do lepszej dystrybucji wartości haszy.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

Co to jest tablica haszująca

A

Tablica haszująca (ang. hash table) to struktura danych, która umożliwia efektywne przechowywanie i wyszukiwanie elementów w oparciu o klucze. Tablica haszująca wykorzystuje funkcję haszującą do przekształcenia klucza w indeks tablicy, gdzie wartość związana z danym kluczem jest przechowywana. Ta technika pozwala na szybkie odnajdywanie elementów przy użyciu klucza.

Podstawowe operacje na tablicy haszującej to dodawanie (wstawianie), usuwanie i wyszukiwanie elementu. Klucz jest przekształcany za pomocą funkcji haszującej, która zwraca indeks w tablicy. W przypadku kolizji, tj. sytuacji, gdy dwa różne klucze mają tę samą wartość hasza i prowadziłyby do zajęcia tego samego indeksu, stosuje się różne metody rozwiązywania konfliktów. Popularne techniki to:

Separate Chaining (łańcuchy oddzielone): Każdy indeks tablicy zawiera listę (łańcuch) elementów, które mają ten sam wartość hasza. W przypadku kolizji, nowy element jest po prostu dodawany do odpowiedniej listy.

Open Addressing (otwarte adresowanie): Jeśli nastąpi kolizja, algorytm poszukuje następnego dostępnego indeksu w tablicy, aż znajdzie wolne miejsce. Różne metody otwartego adresowania obejmują liniowe przeszukiwanie, kwadratowe przeszukiwanie czy podwójne haszowanie.

Ważne jest, aby funkcja haszująca była dobrze zdefiniowana i zapewniała jak najmniejszą liczbę kolizji, aby zachować efektywność tablicy haszującej. Optymalny wybór funkcji haszującej zależy od charakterystyki danych, jakie są przechowywane w tablicy. Tablice haszujące są szeroko stosowane w praktyce do implementacji różnych struktur danych, takich jak słowniki, zbiory czy cache.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

Relacja wiele do wielu na przykładzie agent ubezpieczenie klient

A

Order:
Relacja z Agent: Wiele do jednego (Many-to-One). Wiele zamówień może być przypisanych do jednego agenta.
Relacja z Ubezpieczenie: Wiele do jednego (Many-to-One). Wiele zamówień może być przypisanych do jednego ubezpieczenia.
Relacja z Client: Wiele do jednego (Many-to-One). Wiele zamówień może być przypisanych do jednego klienta.
Relacja z OrderLine: Jeden do wielu (One-to-Many). Jedno zamówienie może mieć wiele linii zamówienia.

OrderLine:
Relacja z Order: Wiele do jednego (Many-to-One). Wiele linii zamówienia może być przypisanych do jednego zamówienia.
Relacja z Agent: Wiele do jednego (Many-to-One). Wiele linii zamówienia może być przypisanych do jednego agenta.
Relacja z Ubezpieczenie: Wiele do jednego (Many-to-One). Wiele linii zamówienia może być przypisanych do jednego ubezpieczenia.
Relacja z Client: Wiele do jednego (Many-to-One). Wiele linii zamówienia może być przypisanych do jednego klienta

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

Joiny

A

SQL umożliwia stosowanie różnych rodzajów joinów, aby łączyć dane z różnych tabel w zapytaniach. Poniżej znajdziesz przykłady różnych joinów z wykorzystaniem dwóch tabel: Orders i OrderDetails.

INNER JOIN:
Zwraca tylko wiersze, które mają odpowiadające wartości w obu tabelach.
sql
Copy code
SELECT Orders.OrderID, Customers.CustomerName
FROM Orders
INNER JOIN Customers ON Orders.CustomerID = Customers.CustomerID;
LEFT JOIN (lub LEFT OUTER JOIN):
Zwraca wszystkie wiersze z lewej tabeli (Orders) i pasujące wiersze z prawej tabeli (Customers).
sql
Copy code
SELECT Orders.OrderID, Customers.CustomerName
FROM Orders
LEFT JOIN Customers ON Orders.CustomerID = Customers.CustomerID;
RIGHT JOIN (lub RIGHT OUTER JOIN):
Zwraca wszystkie wiersze z prawej tabeli (Customers) i pasujące wiersze z lewej tabeli (Orders).
sql
Copy code
SELECT Orders.OrderID, Customers.CustomerName
FROM Orders
RIGHT JOIN Customers ON Orders.CustomerID = Customers.CustomerID;
FULL JOIN (lub FULL OUTER JOIN):
Zwraca wiersze, gdy istnieje pasujące wartości w jednej z tabel.
sql
Copy code
SELECT Orders.OrderID, Customers.CustomerName
FROM Orders
FULL JOIN Customers ON Orders.CustomerID = Customers.CustomerID;
CROSS JOIN:
Tworzy iloczyn kartezjański dwóch tabel, zwracając wszystkie możliwe kombinacje wierszy.
sql
Copy code
SELECT Orders.OrderID, Customers.CustomerName
FROM Orders
CROSS JOIN Customers;
SELF JOIN:
Tworzy połączenie między dwiema instancjami tej samej tabeli.
sql
Copy code
SELECT a.OrderID, b.OrderID
FROM Orders a, Orders b
WHERE a.CustomerID = b.CustomerID AND a.OrderID <> b.OrderID;
Te przykłady używają tabeli Orders i Customers, ale możesz dostosować je do swoich konkretnych tabel i relacji. Warto również pamiętać, że efektywność i zrozumienie zapytań zależą od konkretnych wymagań i struktury danych w danej bazie.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

group by

A

Grupa GROUP BY w SQL jest używana do grupowania wyników zapytania według jednego lub więcej kryteriów. Poniżej znajdziesz przykłady zastosowania GROUP BY na przykładowej tabeli Orders z polami CustomerID i TotalAmount.

Podstawowe użycie GROUP BY:
Poniższe zapytanie grupuje zamówienia według identyfikatora klienta (CustomerID) i oblicza sumę łącznej kwoty (TotalAmount) dla każdego klienta.
sql
Copy code
SELECT CustomerID, SUM(TotalAmount) as TotalSpent
FROM Orders
GROUP BY CustomerID;
Zastosowanie funkcji agregującej z GROUP BY:
W tym przypadku używamy funkcji COUNT() do zliczenia liczby zamówień dla każdego klienta.
sql
Copy code
SELECT CustomerID, COUNT(OrderID) as OrderCount
FROM Orders
GROUP BY CustomerID;
Grupowanie według wielu kolumn:
Możemy grupować wyniki według kilku kolumn jednocześnie.
sql
Copy code
SELECT CustomerID, ProductCategory, AVG(ProductPrice) as AvgPrice
FROM Orders
GROUP BY CustomerID, ProductCategory;
Grupowanie po wynikach funkcji agregujących:
Możemy również grupować po wynikach funkcji agregujących.
sql
Copy code
SELECT COUNT(OrderID) as OrderCount, CustomerID
FROM Orders
GROUP BY CustomerID
HAVING COUNT(OrderID) > 5; – Ograniczenie wyników za pomocą HAVING
Zastosowanie GROUP_CONCAT():
Funkcja GROUP_CONCAT() może być używana do konkatenacji wartości w obrębie grupy.
sql
Copy code
SELECT CustomerID, GROUP_CONCAT(ProductName SEPARATOR ‘, ‘) as PurchasedProducts
FROM Orders
GROUP BY CustomerID;
Grupowanie według wyrażeń:
Możemy także grupować wyniki według wyników wyrażeń.
sql
Copy code
SELECT CASE WHEN TotalAmount > 1000 THEN ‘High Spender’ ELSE ‘Regular’ END as SpendingCategory, COUNT(CustomerID) as CustomerCount
FROM Orders
GROUP BY SpendingCategory;
Powyższe przykłady ilustrują różne przypadki użycia GROUP BY w zapytaniach SQL. Warto zauważyć, że grupowanie często używane jest z funkcjami agregującymi, takimi jak SUM(), COUNT(), AVG(), itp.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

1 level cache 2 nd level cache

A

W kontekście Spring oraz technologii ORM (Object-Relational Mapping) takich jak Hibernate, pojawiły się dwa poziomy pamięci podręcznej (cache), które są wykorzystywane w celu zwiększenia wydajności operacji dostępu do bazy danych. Są to:

First Level Cache (Pamięć podręczna pierwszego poziomu):
Jest to cache, który jest specyficzny dla sesji (transakcji) i istnieje tylko w jej zakresie.
Każda sesja Hibernate ma swój własny cache pierwszego poziomu.
Kiedy aplikacja pobiera obiekt z bazy danych, Hibernate zapisuje go w pamięci podręcznej pierwszego poziomu, a następnie przy kolejnym żądaniu pobrania tego samego obiektu w ramach tej samej sesji, Hibernate sprawdza najpierw cache pierwszego poziomu zamiast wysyłać zapytanie do bazy danych.
Cache pierwszego poziomu jest efektywny w trakcie pojedynczej sesji, ale nie przechodzi poza jej granice.
Przykład użycia cache pierwszego poziomu w Spring i Hibernate:

java
Copy code
// Przy pobieraniu obiektu, Hibernate zapisuje go w cache pierwszego poziomu
MyEntity entity = entityManager.find(MyEntity.class, entityId);

// Przy kolejnym pobieraniu tego samego obiektu w ramach tej samej sesji, Hibernate korzysta z cache pierwszego poziomu
MyEntity cachedEntity = entityManager.find(MyEntity.class, entityId);
Second Level Cache (Pamięć podręczna drugiego poziomu):
Jest to cache współdzielony pomiędzy różnymi sesjami i transakcjami.
Przechowuje obiekty oraz dane z bazy danych w pamięci aplikacji, aby unikać częstego korzystania z bazy danych i poprawić ogólną wydajność.
Cache drugiego poziomu jest bardziej globalny, ponieważ przechodzi poza zakres pojedynczej sesji i może być używany przez różne części aplikacji.
Popularnym narzędziem do zarządzania pamięcią podręczną drugiego poziomu w Hibernate jest Ehcache.
Przykład konfiguracji cache drugiego poziomu w pliku persistence.xml:

xml
Copy code

<property></property>

<property></property>

Warto zauważyć, że korzystanie z pamięci podręcznej, zarówno pierwszego, jak i drugiego poziomu, wymaga starannego zarządzania, aby uniknąć problemów związanych z niezgodnością danych w przypadku modyfikacji w bazie danych z poziomu innych aplikacji lub procesów.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

n+1

A

Problem N+1 jest sytuacją, która często występuje w kontekście relacji jeden do wielu w bazie danych. Główna koncepcja tego problemu polega na tym, że podczas pobierania danych z relacji jeden do wielu, dla każdego głównego obiektu jest wykonywane dodatkowe zapytanie, aby pobrać powiązane obiekty. Ostateczna liczba zapytań jest równa N+1, gdzie N to liczba głównych obiektów.

Przykład:
Rozważmy dwie encje: Author (Autor) i Book (Książka), gdzie jeden autor może napisać wiele książek (relacja jeden do wielu).

Problem N+1:
sql
Copy code
– Zapytanie 1: Pobranie wszystkich autorów
SELECT * FROM Author;

– Dla każdego autora wykonuje się dodatkowe zapytanie:
– Zapytanie 2: Pobranie książek dla autora o ID 1
SELECT * FROM Book WHERE author_id = 1;
– Zapytanie 3: Pobranie książek dla autora o ID 2
SELECT * FROM Book WHERE author_id = 2;
– …
W rezultacie otrzymujemy N+1 zapytań, co może prowadzić do znacznego obciążenia bazy danych, zwłaszcza gdy mamy dużą ilość danych.

Rozwiązanie:
Fetch Join:

Użyj JOIN FETCH w zapytaniu JPQL lub HQL, aby załadować powiązane obiekty w jednym zapytaniu.
sql
Copy code
SELECT a FROM Author a JOIN FETCH a.books;
Batch Loading:

Użyj mechanizmów ładowania danych wsadowych, takich jak @BatchSize w Hibernate, aby zoptymalizować sposób, w jaki dane są pobierane z bazy danych.
java
Copy code
@OneToMany(mappedBy = “author”)
@BatchSize(size = 10) // Przykładowa wartość
private List<Book> books;
Eager Loading:</Book>

Ustaw relację jeden do wielu jako FetchType.EAGER, jeśli istnieje pewność, że zawsze chcemy pobierać powiązane obiekty wraz z głównym obiektem.
java
Copy code
@OneToMany(mappedBy = “author”, fetch = FetchType.EAGER)
private List<Book> books;
Zastosowanie jednego z tych rozwiązań pozwala uniknąć nadmiernego obciążenia bazy danych poprzez minimalizację liczby zapytań wykonywanych podczas pobierania danych z relacji jeden do wielu.</Book>

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

lazy loading loading exception LAZY EAGER

A

Rozwiązanie problemu dotyczącego błędów związanych z leniwym (lazy) i chciwym (eager) ładowaniem zwykle zależy od konkretnego kontekstu i struktury aplikacji. Poniżej przedstawiam kilka wskazówek i potencjalnych rozwiązań.

Wyjątek LazyInitializationException:
Oznaczanie Leniwych Relacji:

Upewnij się, że relacje, które są leniwie ładowane (FetchType.LAZY), są oznaczone jako transakcyjne, a dostęp do nich odbywa się w ramach aktywnej sesji Hibernate.
java
Copy code
@OneToMany(mappedBy = “author”, fetch = FetchType.LAZY)
private List<Book> books;
Praca w Kontekście Transakcji:</Book>

Upewnij się, że dostęp do leniwie ładowanych relacji odbywa się wewnątrz otwartej transakcji. W Springu można to zrealizować przy użyciu adnotacji @Transactional.
java
Copy code
@Transactional
public Author getAuthorWithBooks(Long authorId) {
Author author = authorRepository.findById(authorId).orElse(null);
// Relacja books zostanie leniwie załadowana wewnątrz tej transakcji.
return author;
}
Wyjątek Hibernate:
Błąd org.hibernate.LazyInitializationException:

Jeśli otrzymujesz błąd LazyInitializationException przy próbie dostępu do leniwie ładowanej relacji poza sesją Hibernate, możesz rozważyć kilka rozwiązań.
java
Copy code
@OneToMany(mappedBy = “author”, fetch = FetchType.LAZY)
private List<Book> books;
a. OpenSessionInView (OSIV):</Book>

W przypadku aplikacji webowej możesz rozważyć użycie wzorca Open Session In View (OSIV), który utrzymuje sesję otwartą przez całe życie żądania HTTP.
b. Eager Loading lub Fetch Join:

W niektórych przypadkach rozważ zastosowanie chciwego ładowania (FetchType.EAGER) lub Fetch Join, aby zminimalizować ryzyko błędów LazyInitializationException.
java
Copy code
@OneToMany(mappedBy = “author”, fetch = FetchType.EAGER)
private List<Book> books;
Używanie FetchType.LAZY z Uważnością:</Book>

Używaj FetchType.LAZY z pełną świadomością. Upewnij się, że relacje są leniwie ładowane tylko tam, gdzie jest to uzasadnione, a dostęp do nich odbywa się w odpowiednich kontekstach transakcyjnych.
Zastosowanie FetchType.EAGER:
Ostrzeżenie dotyczące FetchType.EAGER:

Używanie FetchType.EAGER dla wszystkich relacji może prowadzić do tzw. “problemu N+1”, gdzie pobierane są wszystkie powiązane obiekty nawet w przypadku prostych operacji.
java
Copy code
@OneToMany(mappedBy = “author”, fetch = FetchType.EAGER)
private List<Book> books;
Rozważ zastosowanie FetchType.LAZY dla większości relacji, a jedynie w przypadkach, gdzie pełne chciwe ładowanie jest uzasadnione, używaj FetchType.EAGER.
Podsumowanie:
Ostateczny wybór między FetchType.LAZY a FetchType.EAGER zależy od wymagań aplikacji, skomplikowania modelu danych i konkretnego przypadku użycia. Dobre zrozumienie leniwego ładowania, kontekstu transakcji i odpowiednie zarządzanie sesją Hibernate są kluczowe dla uniknięcia błędów i zoptymalizowanego dostępu do danych.</Book>

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

@Transactional

A

@Transactional to adnotacja w Springu, która informuje kontener Springa o tym, że metoda powinna być transakcyjna. Adnotacja ta może być stosowana do metody lub klasy, co oznacza, że wszystkie metody w danej klasie będą transakcyjne, jeśli tylko ta klasa zostanie użyta jako komponent Springa (na przykład poprzez wstrzykiwanie zależności).

Kluczową cechą @Transactional jest zarządzanie transakcjami w kontekście aplikacji. Pozwala na definiowanie granic transakcji, propagację, izolację i obsługę błędów transakcyjnych.

Najważniejsze atrybuty adnotacji @Transactional:
propagation:

Określa, jak ma się zachować metoda, gdy jest wywoływana w kontekście istniejącej transakcji. Na przykład: Propagation.REQUIRED, Propagation.REQUIRES_NEW, itp.
isolation:

Określa poziom izolacji transakcji. Definiuje stopień, w jakim jedna transakcja może “widzieć” zmiany dokonane przez inne transakcje. Na przykład: Isolation.READ_COMMITTED, Isolation.SERIALIZABLE, itp.
readOnly:

Określa, czy transakcja powinna być oznaczona jako tylko do odczytu (true/false). Oznaczenie transakcji jako tylko do odczytu może umożliwić pewne optymalizacje w zarządzaniu bazą danych.
timeout:

Określa czas, po którym transakcja powinna zostać automatycznie zakończona, jeśli nie została zakończona wcześniej.
rollbackFor:

Określa wyjątki, które powinny prowadzić do automatycznego wycofania transakcji.
noRollbackFor:

Określa wyjątki, które nie powinny prowadzić do automatycznego wycofania transakcji.
Przykład użycia adnotacji @Transactional:
java
Copy code
@Service
public class MyService {

@Autowired
private MyRepository myRepository;

@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)
public void performTransactionalOperation() {
    // Logika biznesowa

    myRepository.save(entity1);

    // Inna operacja
    myRepository.save(entity2);
} } W tym przykładzie, metoda performTransactionalOperation() jest oznaczona adnotacją @Transactional z określonymi atrybutami. Wszystkie operacje wykonywane w ramach tej metody są objęte jedną transakcją. Jeśli wystąpi błąd, cała transakcja zostanie automatycznie wycofana (rollback). Adnotacja ta jest szczególnie przydatna w środowiskach, gdzie korzysta się z baz danych i wymaga zarządzania transakcjami w sposób spójny i niezawodny.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

@preDestroy

A

@PreDestroy to adnotacja w Springu używana do oznaczania metody, która powinna zostać wywołana przed zniszczeniem obiektu zarządzanego przez kontener Springa. Jest to często wykorzystywane do wykonania operacji czyszczenia, zwalniania zasobów czy zamykania połączeń przed zakończeniem cyklu życia danego beana. Poniżej znajdziesz prosty przykład:

java
Copy code
import javax.annotation.PreDestroy;

import org.springframework.stereotype.Component;

@Component
public class MyDatabaseConnection {

// Inne pola, metody itp.

@PreDestroy
public void closeConnection() {
    System.out.println("Closing database connection...");
    // Logika zamykania połączenia z bazą danych
} } W tym przykładzie, klasa MyDatabaseConnection jest oznaczona adnotacją @Component, co oznacza, że jest zarządzana przez kontener Springa. Metoda closeConnection z adnotacją @PreDestroy zostanie wywołana przed zniszczeniem beana. Wewnątrz tej metody można umieścić wszelkie operacje związane z zamykaniem połączenia z bazą danych lub innymi działaniami przy zamykaniu beana.

Kiedy kontener Springa zauważa, że bean ma być zniszczony (na przykład w wyniku zamykania aplikacji), automatycznie wywołuje metody oznaczone adnotacją @PreDestroy na tym beanie. To zapewnia kontrolowane i porządkowe zamykanie zasobów przed zniszczeniem beana

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

@PostConstruct

A

@PostConstruct to adnotacja w Springu, która jest używana do oznaczania metody, która powinna być wykonana po zakończeniu procesu konstrukcji bean’a, ale przed udostępnieniem go do klienta. Metoda oznaczona adnotacją @PostConstruct zostanie wykonana zaraz po tym, jak obiekt zostanie skonstruowany, ale przed tym, jak zostanie użyty w aplikacji. Oto prosty przykład:

java
Copy code
import javax.annotation.PostConstruct;

import org.springframework.stereotype.Component;

@Component
public class MyService {

@PostConstruct
public void init() {
    System.out.println("Initializing MyService...");
    // Logika inicjalizacyjna
}

// Inne metody, pola itp. } W tym przykładzie, klasa MyService jest oznaczona adnotacją @Component, co oznacza, że jest zarządzana przez kontener Springa. Metoda init z adnotacją @PostConstruct zostanie automatycznie wywołana po zakończeniu konstrukcji obiektu, co pozwala na wykonanie inicjalizacyjnych operacji.

Główne zastosowania @PostConstruct obejmują:

Inicjalizację zasobów, takich jak połączenia z bazą danych czy pliki konfiguracyjne.
Inicjalizację stanu obiektu przed użyciem w aplikacji.
Wykonanie operacji, które muszą być wykonane w momencie inicjalizacji, ale po konstrukcji obiektu.
Warto zauważyć, że metoda oznaczona @PostConstruct powinna być bezparametrowa i nie powinna zwracać wartości. Jeśli używasz Java 9 lub nowszej, możesz również zastąpić javax.annotation.PostConstruct przez javax.annotation.processing.Processing z pakietu java.annotation.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

Scope beanów

A

W kontekście Springa, “scope” określa zakres istnienia beana i kontroluje, jak długo bean pozostaje aktywny oraz jakie ma właściwości w kontekście cyklu życia aplikacji. Spring oferuje różne zakresy (scope) dla beana, a wybór odpowiedniego zależy od wymagań aplikacji. Poniżej przedstawiam najważniejsze zakresy beana w Springu:

  1. Singleton (Domyślny):
    Zakres (@Scope): @Scope(“singleton”)
    Opis:
    Domyślny zakres beana w Springu.
    Oznacza, że tylko jedna instancja beana jest tworzona dla każdego kontekstu aplikacji.
    Singleton jest jednym z najczęściej używanych zakresów i jest odpowiedni dla wielu przypadków użycia.
  2. Prototype:
    Zakres (@Scope): @Scope(“prototype”)
    Opis:
    Oznacza, że dla każdego żądania utworzenia beana zostaje stworzona nowa instancja.
    Każda instancja bean jest niezależna od innych, co jest przydatne w przypadku, gdy chcemy uniknąć współdzielenia stanu między różnymi częściami aplikacji.
  3. Request:
    Zakres (@Scope): @Scope(“request”)
    Opis:
    W kontekście aplikacji webowej, oznacza, że dla każdego żądania HTTP jest tworzona nowa instancja bean.
    Po zakończeniu żądania, instancja bean jest usuwana.
  4. Session:
    Zakres (@Scope): @Scope(“session”)
    Opis:
    W kontekście aplikacji webowej, oznacza, że dla każdej sesji HTTP (sesji użytkownika) jest tworzona nowa instancja bean.
    Instancja jest usuwana po zakończeniu sesji.
  5. Global Session:
    Zakres (@Scope): @Scope(“globalSession”)
    Opis:
    Podobne do session, ale stosowane w kontekście portletów w środowisku portletowym.
  6. Application:
    Zakres (@Scope): @Scope(“application”)
    Opis:
    W kontekście aplikacji webowej, oznacza, że dla całej aplikacji jest tworzona jedna instancja bean.
    Instancja jest usuwana po zakończeniu aplikacji.
  7. WebSocket:
    Zakres (@Scope): @Scope(“websocket”)
    Opis:
    Dotyczy kontekstu WebSocket w aplikacjach webowych.
  8. Custom:
    Zakres (@Scope): @Scope(“customScopeName”)
    Opis:
    Możliwość zdefiniowania własnego zakresu dla specyficznych wymagań aplikacji.
    Przykład użycia:
    java
    Copy code
    import org.springframework.context.annotation.Scope;
    import org.springframework.stereotype.Component;

@Component
@Scope(“prototype”)
public class MyPrototypeBean {
// …
}
W powyższym przykładzie, MyPrototypeBean będzie miał zakres prototype, co oznacza, że dla każdego żądania utworzenia nowej instancji beana będzie tworzona nowa kopia.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

Cykl życia beana

A

Cykl życia beana w Springu obejmuje szereg kroków, od momentu utworzenia do zniszczenia, a każdy krok jest zarządzany przez kontener Springa. Poniżej przedstawiam podstawowy cykl życia beana w Springu:

  1. Utworzenie Beana (Instantiation):
    Moment, w którym Spring tworzy nową instancję beana. W przypadku Singletona dzieje się to tylko raz, podczas startu kontenera. W przypadku Prototypu, nowa instancja jest tworzona za każdym razem, gdy bean jest żądany.
  2. Ustawienie Właściwości (Populating Properties):
    Spring ustawia właściwości (zależności) beana, używając konstruktorów, setterów lub pól (w zależności od konfiguracji).
  3. Wywołanie Metody @PostConstruct lub Interfejsu InitializingBean:
    Jeśli bean ma metody oznaczone adnotacją @PostConstruct lub implementuje interfejs InitializingBean, te metody są wywoływane tuż po ustawieniu właściwości i przed udostępnieniem beana.
  4. Użycie Beana przez Aplikację (In Use):
    W tym momencie bean jest dostępny dla innych komponentów w aplikacji i może być używany.
  5. Zakończenie Używania Beana (Disposal):
    W przypadku singletonów, bean jest przechowywany przez cały okres życia kontenera. W przypadku prototypów, kiedy bean nie jest już używany, może być zniszczony.
  6. Wywołanie Metody @PreDestroy lub Interfejsu DisposableBean:
    Jeśli bean ma metody oznaczone adnotacją @PreDestroy lub implementuje interfejs DisposableBean, te metody są wywoływane przed zniszczeniem beana.
    Diagram Cyklu Życia Beana w Springu:
    sql
    Copy code
    +——————-+
    | Utworzenie Beana |
    +——–|———-+
    |
    v
    +——————-+
    | Ustawienie |
    | Właściwości |
    +——–|———-+
    |
    v
    +———————|——————+
    | @PostConstruct / v |
    | InitializingBean +——————+
    +———————+
    |
    v
    +——————-+
    | Użycie Beana |
    +——–|———-+
    |
    v
    +——————-+
    | Zakończenie |
    | Używania Beana |
    +——–|———-+
    |
    v
    +———————|——————+
    | @PreDestroy / v |
    | DisposableBean +——————+
    +———————+
    Przykład:
    java
    Copy code
    import javax.annotation.PostConstruct;
    import javax.annotation.PreDestroy;

import org.springframework.stereotype.Component;

@Component
public class MyBean {

@PostConstruct
public void postConstruct() {
    System.out.println("Bean is being constructed...");
    // Logika inicjalizacyjna
}

// Inne metody, pola itp.

@PreDestroy
public void preDestroy() {
    System.out.println("Bean is being destroyed...");
    // Logika przed zniszczeniem beana
} } W tym przykładzie, postConstruct jest wywoływane po utworzeniu beana, a preDestroy przed zniszczeniem. Ta metoda zarządzania cyklem życia jest bardzo przydatna do wykonywania działań przed inicjalizacją i przed zniszczeniem obiektu zarządzanego przez kontener Springa.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

bean

A

W kontekście Springa, termin “bean” odnosi się do obiektów, które są zarządzane przez kontener Springa. Beany w Springu są komponentami, które są tworzone, konfigurowane i zarządzane przez kontener. Są one używane do reprezentowania różnych części aplikacji, takich jak usługi, repozytoria, kontrolery, czy też obiekty dostępu do danych. Beany są podstawowym elementem wstrzykiwania zależności i zarządzania cyklem życia komponentów w Springu. Poniżej przedstawiam kilka przykładów, aby lepiej zrozumieć, co to jest bean w Springu:

Przykład 1: Prosty Bean
java
Copy code
import org.springframework.stereotype.Component;

@Component
public class MyBean {
// Logika beana
}
W tym przykładzie, klasa MyBean jest oznaczona adnotacją @Component, co oznacza, że jest to bean zarządzany przez kontener Springa.

Przykład 2: Bean z Zależnościami
java
Copy code
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class MyService {

private MyRepository myRepository;

@Autowired
public MyService(MyRepository myRepository) {
    this.myRepository = myRepository;
}

// Logika usługi } W tym przykładzie, klasa MyService posiada zależność MyRepository, a adnotacja @Autowired oznacza, że Spring automatycznie wstrzyknie odpowiednią instancję MyRepository podczas tworzenia beana MyService.

Przykład 3: Konfiguracja XML Bean
xml
Copy code
<!-- plik applicationContext.xml -->

<beans>

<bean></bean>
</beans>

W tym przykładzie, beana są definiowane i konfigurowane w pliku XML. myBean jest instancją klasy MyBean.

Przykład 4: Bean z Cyklem Życia
java
Copy code
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.stereotype.Component;

@Component
public class LifecycleBean {

@PostConstruct
public void init() {
    // Logika inicjalizacyjna
}

// Logika beana

@PreDestroy
public void destroy() {
    // Logika przed zniszczeniem beana
} } W tym przykładzie, klasa LifecycleBean ma metody oznaczone adnotacjami @PostConstruct i @PreDestroy, co umożliwia wykonanie pewnych operacji przed inicjalizacją beana oraz przed jego zniszczeniem.

Beany w Springu są wszechstronnymi komponentami, które można skonfigurować, wstrzykiwać zależności, zarządzać ich cyklem życia i używać w różnych obszarach aplikacji.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

orElse a orElseGet

A

orElse i orElseGet to metody dostępne w interfejsie Optional w języku Java, które pozwalają obsługiwać sytuacje, gdy opcjonalna wartość jest pusta (null). Oto główne różnice między nimi:

  1. orElse:
    Sygnatura:

java
Copy code
T orElse(T other)
Działa:

Zwraca wartość opcjonalną, jeśli istnieje, lub podaną wartość domyślną, jeśli opcjonalna wartość jest pusta.
Wywoływane Zawsze:

Metoda orElse zawsze wywołuje przekazaną wartość, niezależnie od tego, czy opcjonalna wartość jest obecna czy pusta.
Przykład:

java
Copy code
Optional<String> optionalValue = Optional.ofNullable(someValue);
String result = optionalValue.orElse("defaultValue");
2. orElseGet:
Sygnatura:</String>

java
Copy code
T orElseGet(Supplier<? extends T> supplier)
Działa:

Zwraca wartość opcjonalną, jeśli istnieje, lub wywołuje dostawcę (funkcję) i zwraca wynik tej funkcji jako wartość domyślną, jeśli opcjonalna wartość jest pusta.
Wywoływane Tylko, Gdy Potrzebne:

Metoda orElseGet wywołuje dostawcę tylko wtedy, gdy opcjonalna wartość jest pusta, co może być bardziej efektywne, jeśli obliczenia domyślnej wartości są kosztowne.
Przykład:

java
Copy code
Optional<String> optionalValue = Optional.ofNullable(someValue);
String result = optionalValue.orElseGet(() -> calculateDefaultValue());
Kiedy Wybrać Którąś Z Metod?
orElse:</String>

Użyj, gdy wartość domyślna jest prostą wartością lub nie wymaga obliczeń.
Metoda ta jest zawsze wywoływana, nawet jeśli opcjonalna wartość jest obecna.
orElseGet:

Użyj, gdy wartość domyślna wymaga obliczeń lub jest wynikiem operacji.
Dostawca (lambda lub referencja do metody) zostanie wywołany tylko wtedy, gdy opcjonalna wartość jest pusta.
W zależności od kontekstu i specyfiki sytuacji, wybór między orElse a orElseGet zależy od preferencji i efektywności obliczeń domyślnej wartości. Jeśli wartość domyślna jest stała lub obliczenia są niewielkie, obie metody mogą być używane zamiennie. Jednakże, gdy wartość domyślna jest wynikiem złożonych obliczeń, orElseGet może być bardziej efektywne.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

Optional Optional to klasa wprowadzona w języku Java 8 w pakiecie java.util, która służy do obsługi potencjalnie pustych (null) wartości. Optional zapewnia bardziej bezpieczne i ekspresywne podejście do obsługi sytuacji, w których wartość może być obecna lub też nie. Pozwala to unikać potencjalnych błędów związanych z dostępem do null oraz ułatwia programowanie defensywne.

Oto kilka kluczowych cech i sposobów użycia Optional:

  1. Tworzenie Optional:
    Z of (gdy wartość nie może być null):

java
Copy code
Optional<String> optionalValue = Optional.of("Hello, World!");
Z ofNullable (gdy wartość może być null):</String>

java
Copy code
String value = // …
Optional<String> optionalValue = Optional.ofNullable(value);
Pusty Optional:</String>

java
Copy code
Optional<String> emptyOptional = Optional.empty();
2. Sprawdzanie Obecności Wartości:
isPresent - Sprawdzanie czy wartość jest obecna:
java
Copy code
if (optionalValue.isPresent()) {
// Wartość jest obecna
String result = optionalValue.get();
} else {
// Wartość jest pusta
}
3. Dostęp do Wartości:
get - Pobieranie Wartości (uwaga: unikaj bezpośredniego get):</String>

java
Copy code
String result = optionalValue.get(); // Unikaj tego, sprawdź obecność przed użyciem
orElse - Dostarczanie Wartości Domyślnej:

java
Copy code
String result = optionalValue.orElse(“Default Value”);
orElseGet - Dostarczanie Wartości Domyślnej za pomocą Dostawcy (Supplier):

java
Copy code
String result = optionalValue.orElseGet(() -> calculateDefaultValue());
4. Operacje na Wartości:
map - Wykonywanie Operacji na Wartości:

java
Copy code
Optional<String> upperCaseValue = optionalValue.map(String::toUpperCase);
filter - Filtracja Wartości:</String>

java
Copy code
Optional<String> filteredValue = optionalValue.filter(s -> s.startsWith("H"));
5. Obsługa Pustych Wartości:
orElseThrow - Rzucanie Wyjątku dla Pustego Optional:</String>

java
Copy code
String result = optionalValue.orElseThrow(() -> new RuntimeException(“Value not present”));
ifPresent - Wykonywanie Operacji, Jeśli Wartość Jest Obecna:

java
Copy code
optionalValue.ifPresent(val -> System.out.println(“Value is present: “ + val));
Podsumowanie:
Optional w języku Java jest przydatnym narzędziem do lepszego zarządzania potencjalnie pustymi wartościami. Jest często używany w metodach zwracających wartości, które mogą być nullem, a także w sytuacjach, gdzie wymagane jest bezpieczne operowanie na wartościach. Należy jednak unikać nadużywania Optional i stosować go tam, gdzie faktycznie ma sens, a nie w każdej sytuacji.

A
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
17
Q

Stream i operacje na streamach

A

Stream w języku Java to sekwencyjny i potencjalnie nieograniczony zestaw elementów, który można przetwarzać w oparty na funkcyjnych operacjach sposób. Streamy pozwalają na wygodne i efektywne przetwarzanie danych, stosując różne operacje takie jak filtrowanie, mapowanie, sortowanie czy redukcję.

Oto kilka kluczowych cech i operacji związanych ze Stream:

  1. Tworzenie Strumieni:
    Z Kolekcji:

java
Copy code
List<String> myList = Arrays.asList("a1", "a2", "b1", "c2", "c1");
Stream<String> streamFromList = myList.stream();
Z Tablicy:</String></String>

java
Copy code
String[] myArray = {“a1”, “a2”, “b1”, “c2”, “c1”};
Stream<String> streamFromArray = Arrays.stream(myArray);
Z Elementów:</String>

java
Copy code
Stream<String> streamOfElements = Stream.of("a1", "a2", "b1", "c2", "c1");
Z Funkcji Generatora:</String>

java
Copy code
Stream<String> generatedStream = Stream.generate(() -> "generated").limit(5);
2. Operacje Pośrednie:
filter - Filtracja:</String>

java
Copy code
Stream<String> filteredStream = stream.filter(s -> s.startsWith("a"));
map - Mapowanie:</String>

java
Copy code
Stream<String> uppercasedStream = stream.map(String::toUpperCase);
sorted - Sortowanie:</String>

java
Copy code
Stream<String> sortedStream = stream.sorted();
distinct - Usunięcie Duplikatów:</String>

java
Copy code
Stream<String> distinctStream = stream.distinct();
3. Operacje Końcowe:
forEach - Iteracja po Elementach:</String>

java
Copy code
stream.forEach(System.out::println);
collect - Zbieranie Wyników do Kolekcji:

java
Copy code
List<String> collectedList = stream.collect(Collectors.toList());
toArray - Zbieranie do Tablicy:</String>

java
Copy code
String[] array = stream.toArray(String[]::new);
reduce - Redukcja:

java
Copy code
Optional<String> concatenated = stream.reduce((s1, s2) -> s1 + s2);
4. Operacje na Liczbach:
sum, average, count, max, min - Operacje na Liczbach:
java
Copy code
int sum = intStream.sum();
OptionalDouble average = doubleStream.average();
long count = longStream.count();
OptionalInt max = intStream.max();
OptionalInt min = intStream.min();
5. Strumienie Nieskończone:
iterate - Generowanie Nieskończonego Strumienia:</String>

java
Copy code
Stream.iterate(0, n -> n + 1).limit(10).forEach(System.out::println);
generate - Generowanie Nieskończonego Strumienia z Funkcji Generatora:

java
Copy code
Stream.generate(() -> “generated”).limit(5).forEach(System.out::println);
6. Operacje na Strumieniach z Obiektami:
allMatch, anyMatch, noneMatch - Sprawdzanie Warunków:

java
Copy code
boolean allMatch = stream.allMatch(s -> s.startsWith(“a”));
boolean anyMatch = stream.anyMatch(s -> s.startsWith(“a”));
boolean noneMatch = stream.noneMatch(s -> s.startsWith(“z”));
findFirst, findAny - Znajdowanie Pierwszego lub Dowolnego Elementu:

java
Copy code
Optional<String> firstElement = stream.findFirst();
Optional<String> anyElement = stream.findAny();
groupBy - Grupowanie Elementów:</String></String>

java
Copy code
Map<Integer, List<String>> groupedByLength = stream.collect(Collectors.groupingBy(String::length));
Podsumowanie:
Strumienie (Stream) w Javie dostarczają eleganckie i funkcyjne narzędzie do przetwarzania kolekcji danych. Umożliwiają bardziej deklaratywne i czytelne podejście do operacji na danych, co przyczynia się do bardziej ekspresywnego i czytelnego kodu. Strumienie są integralną częścią programowania funkcyjnego w Javie i pozwalają na bardziej wydajne oraz zwięzłe manipulowanie danymi.</String>

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
18
Q

Heap stack - sterta stos

A

Heap (sterta) i Stack (stos) to dwa rodzaje pamięci używane w trakcie wykonywania programów komputerowych. Oto krótkie omówienie każdego z tych rodzajów pamięci:

  1. Heap Memory (Pamięć Sterta):
    Charakterystyka:

Jest obszarem pamięci komputera, gdzie dynamicznie alokowane są obiekty i dane, które mają dłuższy czas życia niż lokalne zmienne przechowywane na stosie.
Obiekty w pamięci sterty są zarządzane ręcznie lub automatycznie przez mechanizmy takie jak Garbage Collector.
Użycie:

Wykorzystywana jest głównie do przechowywania obiektów o zmiennej wielkości i długo żyjących danych, takich jak instancje klas, tablice, itp.
Przykłady:

Obiekty stworzone za pomocą operatora new w Javie.
Dynamicznie alokowane struktury danych, takie jak listy, mapy, itp.
Żywotność:

Obiekty w pamięci sterty pozostają “żywe” tak długo, jak są dostępne z programu i nie zostaną one ręcznie zwolnione.
2. Stack Memory (Pamięć Stos):
Charakterystyka:

Jest to obszar pamięci, w którym przechowywane są lokalne zmienne oraz dane związane z bieżącymi wywołaniami funkcji.
Stos jest stosunkowo niewielki, ale operuje bardzo szybko, ponieważ dostęp do danych na stosie jest prostszy i szybszy.
Użycie:

Przechowuje lokalne zmienne oraz dane związane z bieżącymi wywołaniami funkcji.
Każde wywołanie funkcji powoduje utworzenie nowego “ramienia” na stosie z lokalnymi zmiennymi dla tej funkcji.
Przykłady:

Wszystkie lokalne zmienne w trakcie wykonywania funkcji.
Adresy powrotu z funkcji.
Wartości przekazywane do funkcji jako argumenty.
Żywotność:

Zmienne na stosie mają krótki czas życia. Są tworzone przy wejściu do funkcji i usuwane przy jej opuszczeniu.
Podsumowanie:
Heap i Stack to dwa główne obszary pamięci używane w trakcie działania programów.
Heap jest używany do przechowywania dynamicznie alokowanych obiektów o dłuższym czasie życia.
Stack jest używany do przechowywania lokalnych zmiennych i danych związanych z bieżącymi wywołaniami funkcji.
Zarządzanie pamięcią na stosie jest zazwyczaj bardziej efektywne i szybsze niż na stercie, ale stos ma ograniczoną wielkość.
Heap umożliwia dynamiczne alokowanie i zwalnianie pamięci, co może prowadzić do fragmentacji pamięci.
W językach programowania takich jak Java, zarządzanie pamięcią na stercie jest często zautomatyzowane, a programiści rzadko muszą się martwić o ręczne zwalnianie pamięci.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
19
Q

System.gc()

A

System.gc() to wywołanie metody w języku Java, które służy do sugestii dla systemu o konieczności wykonania operacji zbierania śmieci (Garbage Collection). Jednak, ważne jest zaznaczenie, że wywołanie System.gc() to jedynie sugestia, a nie bezpośrednie polecenie do natychmiastowego rozpoczęcia procesu zbierania śmieci. Ostateczna decyzja o wykonaniu zbierania śmieci pozostaje w gestii JVM (Java Virtual Machine).

Garbage Collector (Zbieracz Śmieci):
Garbage Collector (GC) to mechanizm wewnątrz JVM, który odpowiada za automatyczne zarządzanie pamięcią, zwalnianie niepotrzebnych obiektów (śmieci) i przywracanie dostępnego miejsca w pamięci. Główne zadania Garbage Collectora to:

Identyfikacja Śmieci:

Rozpoznawanie obiektów, które nie są już dostępne dla programu i mogą być bezpiecznie usunięte.
Zbieranie Śmieci:

Usuwanie obiektów, które zostały zidentyfikowane jako nieosiągalne, zwolnienie ich pamięci i przywrócenie jej do puli dostępnej dla nowych obiektów.
Zapobieganie Wyciekom Pamięci:

Zapobieganie wyciekom pamięci poprzez automatyczne usuwanie obiektów, które nie są już używane, ale nie zostały ręcznie zwolnione przez programistę.
Działanie Garbage Collectora:
Generational Garbage Collection:

Większość współczesnych Garbage Collectorów, w tym ten używany w JVM, opiera się na podejściu generacyjnym. Działa ono na zasadzie podziału obszaru pamięci na dwie główne części: młodą generację (Young Generation) i starą generację (Old Generation).
Kroki Procesu Zbierania Śmieci:

Młoda Generacja:

Nowo utworzone obiekty są umieszczane w młodej generacji.
W trakcie zbierania śmieci w młodej generacji, usuwane są obiekty, które są już nieosiągalne.
Pozostałe obiekty przechodzą do starej generacji.
Stara Generacja:

Obiekty, które przetrwały kilka cykli zbierania śmieci w młodej generacji, są przenoszone do starej generacji.
W starej generacji odbywa się bardziej kosztowne zbieranie śmieci, ponieważ tu znajdują się obiekty o dłuższym czasie życia.
Różnice W Efektywności:

Procesy zbierania śmieci mogą być wywoływane automatycznie w odpowiednich momentach przez JVM. Wpływ System.gc() na wywołanie Garbage Collectora zależy od implementacji JVM i konfiguracji systemu.
System.gc():
Sugestia do GC:

System.gc() służy do wywołania Garbage Collectora, ale nie gwarantuje natychmiastowego wykonania operacji zbierania śmieci.
Jest to jedynie sugestia dla JVM, a decyzja o realizacji tej sugestii zależy od wielu czynników, takich jak obciążenie systemu, strategia Garbage Collectora, itp.
Użycie z Ostrożnością:

Zazwyczaj nie jest zalecane ręczne wywoływanie System.gc(), ponieważ współczesne Garbage Collectory są zdolne do efektywnego zarządzania pamięcią bez ingerencji programisty.
Może to prowadzić do nadmiernego obciążenia systemu i wpływać na wydajność aplikacji.
W skrócie, System.gc() to sugestia dla JVM, a nie bezpośrednie polecenie do natychmiastowego rozpoczęcia procesu zbierania śmieci. Jeśli program jest odpowiednio napisany, Garbage Collector powinien efektywnie zarządzać pamięcią bez potrzeby ręcznego interweniowania.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
20
Q

Garbage collector

A

Garbage Collector (GC) to mechanizm w językach programowania, który jest odpowiedzialny za automatyczne zarządzanie pamięcią, usuwanie obiektów, które nie są już używane przez program, i zwalnianie zasobów, które były im przypisane. GC eliminuje konieczność ręcznego zwalniania pamięci przez programistów, co pomaga w uniknięciu wycieków pamięci i związanych z nimi problemów.

Jak działa Garbage Collector?
Identyfikacja Nieosiągalnych Obiektów:

GC identyfikuje obiekty, które nie są już dostępne z programu, a zatem nie mają referencji do nich.
Oznaczanie Obiektów:

GC oznacza obiekty uznane za nieosiągalne, stosując różne algorytmy, takie jak algorytm znacznikowy (mark-and-sweep) lub śledzenie od korzenia (tracing from roots).
Usuwanie Obiektów:

Zidentyfikowane i oznaczone jako nieosiągalne obiekty są usuwane z pamięci. Mogą one być albo zwolnione od razu, albo przeniesione do specjalnej sterty, gdzie zostaną zwolnione w późniejszym czasie.
Zwalnianie Pamięci:

GC zwalnia pamięć zajmowaną przez usunięte obiekty. W przypadku algorytmu znacznikowego, obszar pamięci, na którym znajdują się usunięte obiekty, może ulec fragmentacji.
Rodzaje Garbage Collector w Javie (Java Virtual Machine - JVM):
Serial Garbage Collector:

Wykorzystuje algorytm mark-and-sweep.
Przeznaczony głównie do prostych aplikacji, testów i małych serwerów.
Parallel Garbage Collector (Parallel GC):

Równoległa wersja mark-and-sweep.
Stosowany w środowiskach, w których ważna jest wydajność dla wielu rdzeni CPU.
Concurrent Mark-Sweep (CMS) Collector:

Oznacza i sprząta obiekty równolegle z działaniem aplikacji.
Bardziej odpowiedni dla aplikacji, w których ważna jest niska latencja.
G1 (Garbage First) Collector:

Zastosowanie zaawansowanych algorytmów, takich jak garbage-first heap.
Bardziej efektywny w obszarze dużej pamięci i niskiej latencji.
ZGC (Z Garbage Collector):

Projektowany z myślą o niskiej latencji i dużych obszarach pamięci.
Introdukuje algorytmy kompresji i oczyszczania.
Dlaczego Garbage Collection jest Ważny?
Unikanie Wycieków Pamięci:

Zapobiega wyciekom pamięci poprzez usuwanie obiektów, które nie są już używane.
Usprawnianie Programowania:

Eliminuje potrzebę ręcznego zwalniania pamięci przez programistów, co ułatwia pisanie bezpiecznego i wydajnego kodu.
Optymalizacja Pamięci:

Pomaga w optymalizacji wykorzystania pamięci poprzez zwalnianie zasobów, gdy są one już niepotrzebne.
Redukcja Ryzyka Błędów:

Zmniejsza ryzyko błędów związanych z zarządzaniem pamięcią, takich jak wycieki pamięci czy dostęp do zwolnionej pamięci.
Warto pamiętać, że choć Garbage Collector automatyzuje zarządzanie pamięcią, to nie zwalnia programistów od odpowiedzialności za pisanie efektywnego i dobrze zoptymalizowanego kodu. Zrozumienie, jak działa GC, może być przydatne przy optymalizacji i unikaniu potencjalnych problemów z wydajnością.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
21
Q

Try catch finally

A

Kod, który dostarczyłeś, sugeruje użycie bloku try-with-resources wraz z blokami catch i finally. Przedstawmy, jak działa każda z tych części:

  1. try-with-resources:
    java
    Copy code
    try (ResourceType resource = new ResourceType()) {
    // Kod, który korzysta z zasobu (resource)
    } catch (ExceptionType e) {
    // Obsługa wyjątku (jeśli wystąpił)
    } finally {
    // Kod, który zostanie wykonany zawsze, niezależnie od tego, czy wyjątek wystąpił czy nie
    }
    try-with-resources jest mechanizmem wprowadzonym w języku Java 7, który ułatwia zarządzanie zasobami, takimi jak strumienie, pliki, połączenia do baz danych itp.
    W bloku try-with-resources, zasoby są otwierane w nawiasach okrągłych. Java automatycznie zajmuje się zamykaniem tych zasobów po zakończeniu bloku try.
  2. catch:
    Blok catch jest używany do obsługi wyjątków, które mogą zostać zgłoszone w trakcie wykonania bloku try.
    Jeśli w bloku try wystąpi wyjątek odpowiadający typowi określonemu w bloku catch, to ten blok zostanie wykonany.
  3. finally:
    Blok finally jest opcjonalny i zawiera kod, który zostanie wykonany zawsze, niezależnie od tego, czy wystąpił wyjątek, czy nie.
    Jest to miejsce do umieszczenia kodu, który musi zostać wykonany, niezależnie od tego, czy wystąpił wyjątek, czy nie.
    Przykładowe Użycie:
    java
    Copy code
    import java.io.BufferedReader;
    import java.io.FileReader;
    import java.io.IOException;

public class Example {
public static void main(String[] args) {
try (BufferedReader reader = new BufferedReader(new FileReader(“example.txt”))) {
String line = reader.readLine();
// Kod, który korzysta z zasobu (reader)
} catch (IOException e) {
// Obsługa wyjątku IOException
e.printStackTrace();
} finally {
// Kod, który zostanie wykonany zawsze
}
}
}

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
22
Q

Out of Memory Error

A

OutOfMemoryError to rodzaj wyjątku, który jest zgłaszany, gdy program osiąga limit dostępnej pamięci i nie jest w stanie zaalokować więcej miejsca. Ten błąd wskazuje, że program zużył całą dostępną pamięć i nie może już zaalokować dodatkowej przestrzeni, co prowadzi do awarii aplikacji.

Przyczyny OutOfMemoryError:
Wycieki Pamięci (Memory Leaks): Gdy obiekty nie są odpowiednio usuwane przez Garbage Collector, mogą pozostawać w pamięci, co z czasem prowadzi do wycieku pamięci.

Niewystarczająca Pamięć:

Brak dostępnej pamięci na stercie (Heap) dla nowych obiektów.
Brak dostępnej pamięci na stosie podczas rekurencyjnych wywołań funkcji.
Zbyt Duże Obiekty:

Próba utworzenia bardzo dużego obiektu, który przekracza dostępną pamięć.
Obsługa OutOfMemoryError:
Zidentyfikuj Przyczynę:

Spróbuj zidentyfikować, dlaczego doszło do wyczerpania pamięci. Czy to w wyniku wycieku pamięci, czy może próby utworzenia zbyt dużego obiektu?
Monitoruj Pamięć:

Używaj narzędzi do monitorowania zużycia pamięci, takich jak VisualVM, jconsole lub narzędzia dostarczone przez środowisko uruchomieniowe JVM.
Optymalizuj Kod:

Spróbuj zoptymalizować kod w celu zmniejszenia zużycia pamięci, zwłaszcza jeśli wyciek pamięci jest wynikiem błędu programistycznego.
Zwiększ Dostępną Pamięć:

W przypadku aplikacji Java można rozważyć zwiększenie dostępnej pamięci za pomocą parametrów uruchomieniowych JVM, takich jak -Xmx (maksymalna dostępna pamięć dla sterty).
java
Copy code
java -Xmx512m MyApp
Popraw Zarządzanie Pamięcią:
Zapewnij, że zarządzanie pamięcią w aplikacji jest poprawnie zaimplementowane, a obiekty są zwalniane, gdy nie są już potrzebne.
System.gc():
System.gc() to metoda w języku Java, która wywołuje Garbage Collector w celu próby zwolnienia nieużywanej pamięci i usunięcia niepotrzebnych obiektów. Jednakże, nie ma gwarancji, że Garbage Collector zostanie natychmiast uruchomiony po wywołaniu tej metody. Użycie System.gc() nie jest zalecane, ponieważ JVM (Java Virtual Machine) zazwyczaj samodzielnie zarządza procesem usuwania nieużywanych obiektów.

Blok try-catch-finally:
Konstrukcja try-catch-finally jest używana do obsługi wyjątków w języku Java. Struktura ta pozwala na wykonanie pewnych działań w bloku finally niezależnie od tego, czy wystąpił wyjątek, czy nie.

java
Copy code
try {
// Kod, który może wywołać wyjątek
} catch (Exception e) {
// Obsługa wyjątku
} finally {
// Kod, który zostanie wykonany niezależnie od tego, czy wystąpił wyjątek, czy nie
}
Blok finally jest opcjonalny, ale jeśli jest obecny, kod w nim zawarty zostanie wykonany zawsze, niezależnie od tego, czy w bloku try wystąpił wyjątek czy nie. Jest to przydatne, na przykład, do zwolnienia zasobów, które muszą być zwolnione, niezależnie od tego, czy doszło do wyjątku.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
23
Q

Exceptions

A

W Javie, zarówno błędy (errors), jak i wyjątki (exceptions) są rodzajami obiektów, które dziedziczą z klasy Throwable. Oba te typy mają pewne różnice w kontekście ich przeznaczenia i obsługi. Oto krótkie omówienie różnic między błędami a wyjątkami w Javie:

Błędy (Errors):
Charakterystyka:

Błędy w Javie reprezentują sytuacje, które są poza kontrolą programisty i zazwyczaj wskazują na poważne problemy w środowisku wykonawczym JVM.
Reprezentują błędy, które są trudne lub niemożliwe do naprawy podczas działania programu.
Przykłady Błędów:

OutOfMemoryError: Występuje, gdy brakuje pamięci.
StackOverflowError: Występuje, gdy stos wywołań jest przepełniony.
LinkageError: Występuje, gdy występuje problem z linkowaniem klas.
Obsługa Błędów:

Błędy zazwyczaj nie są obsługiwane w kodzie programu, ponieważ wskazują na poważne błędy systemowe.
Nie jest zalecane próbowanie obsługić lub naprawić błędy, ponieważ ich wystąpienie wskazuje na poważne problemy.
Wyjątki (Exceptions):
Charakterystyka:

Wyjątki w Javie reprezentują sytuacje, które można obsłużyć w kodzie programu.
Dziedziczą z klasy Exception i mogą wystąpić w wyniku błędów wykonawczych, które mogą być naprawione.
Przykłady Wyjątków:

NullPointerException: Występuje, gdy program próbuje odwołać się do obiektu, którego wartość jest null.
ArithmeticException: Występuje, gdy wykonujemy nieprawidłowe operacje matematyczne, np. dzielenie przez zero.
FileNotFoundException: Występuje, gdy próbujemy otworzyć plik, który nie istnieje.
Obsługa Wyjątków:

Wyjątki mogą być obsługiwane za pomocą bloków try-catch, gdzie kod w bloku catch wykonuje się, jeśli wyjątek zostanie zgłoszony.
Obsługa wyjątków umożliwia programowi reakcję na nieprzewidziane sytuacje i podjęcie działań naprawczych.
Piramida Wyjątków:
W programowaniu obiektowym, często mówi się o “piramidzie wyjątków”, co oznacza, że istnieje hierarchia klas wyjątków. Na szczycie piramidy znajduje się klasa bazowa Throwable, a potem są klasy Error i Exception. Klasy Exception dzielą się na checked (kontrolowane) i unchecked (niekontrolowane) wyjątki. Unchecked wyjątki dziedziczą z klasy RuntimeException.

plaintext
Copy code
Throwable
|– Error
|– Exception
|– RuntimeException (Unchecked)
|– Checked Exceptions
Podsumowując:

Błędy wskazują na poważne problemy w środowisku wykonawczym i zazwyczaj nie są obsługiwane.
Wyjątki reprezentują sytuacje, które można obsłużyć, i są podzielone na wyjątki kontrolowane i niekontrolowane.
Obsługa wyjątków odbywa się za pomocą bloków try-catch, a ich brak może prowadzić do przerwania działania programu.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
24
Q

Hierarchia wyjątków

A

Hierarchia wyjątków w języku Java jest zorganizowana w sposób hierarchiczny, a najważniejszą klasą bazową dla wszystkich wyjątków jest klasa Throwable. Poniżej przedstawiam uproszczoną hierarchię wyjątków w języku Java:

plaintext
Copy code
Throwable
|– Error
| |– AssertionError
| |– LinkageError
| |– VirtualMachineError
| |– OutOfMemoryError
| |– StackOverflowError
|– Exception
|– RuntimeException
| |– ArithmeticException
| |– ArrayIndexOutOfBoundsException
| |– ClassCastException
| |– ConcurrentModificationException
| |– IllegalArgumentException
| |– IllegalStateException
| |– NoSuchElementException
| |– NullPointerException
| |– IndexOutOfBoundsException
| |– ArrayIndexOutOfBoundsException
| |– StringIndexOutOfBoundsException
|– IOException
| |– FileNotFoundException
| |– IOException
| |– EOFException
| |– SocketException
|– SQLException
Throwable: Klasa bazowa dla wszystkich wyjątków i błędów w języku Java.

Error: Reprezentuje poważne błędy, z którymi programista zazwyczaj nie powinien próbować sobie radzić, np. OutOfMemoryError, StackOverflowError.

Exception: Jest to klasa bazowa dla większości wyjątków. Działa jako kategoria ogólna dla różnych wyjątków, które nie są błędami systemowymi.

RuntimeException: Reprezentuje wyjątki, które są niekontrolowane (unchecked). Programista nie jest zobowiązany do ich obsługi. Obejmuje wiele popularnych wyjątków, takich jak NullPointerException, ArithmeticException, itp.

IOException: Reprezentuje wyjątki związane z operacjami wejścia/wyjścia, np. FileNotFoundException, EOFException, itp.

SQLException: Reprezentuje wyjątki związane z bazą danych SQL.

Powyższa hierarchia to tylko fragment pełnej hierarchii wyjątków w języku Java. Programista ma również możliwość tworzenia własnych klas wyjątków, które dziedziczą z Exception lub RuntimeException, zależnie od tego, czy chce, aby były kontrolowane czy niekontrolowane. Hierarchia ta jest bardzo przydatna podczas obsługi wyjątków w kodzie, ponieważ pozwala na bardziej precyzyjne przechwytywanie i obsługę różnych rodzajów błędów i wyjątków.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
25
Q

checked unchecked

A

W języku Java wyjątki można podzielić na dwie główne kategorie: kontrolowane (checked) i niekontrolowane (unchecked). Podział ten wprowadza dwie różne gałęzie w hierarchii klas wyjątków: Exception dla wyjątków kontrolowanych i RuntimeException dla wyjątków niekontrolowanych. Oto krótkie omówienie obu rodzajów wyjątków:

  1. Wyjątki Kontrolowane (Checked Exceptions):
    Charakterystyka:

Są to wyjątki, które muszą być obsługiwane przez programistę w kodzie programu.
Dziedziczą bezpośrednio z klasy Exception (lub z klas dziedziczących z Exception).
Przykłady Wyjątków Kontrolowanych:

IOException: np. FileNotFoundException, EOFException.
SQLException: związane z operacjami na bazie danych.
ClassNotFoundException: gdy klasa nie może zostać znaleziona podczas ładowania.
Obsługa:

Wyjątki kontrolowane muszą być obsługiwane za pomocą bloku try-catch lub deklarowane w sygnaturze metody za pomocą klauzuli throws.
java
Copy code
try {
// Kod, który może zgłosić wyjątek kontrolowany
} catch (IOException e) {
// Obsługa wyjątku
}
2. Wyjątki Niekontrolowane (Unchecked Exceptions):
Charakterystyka:

Są to wyjątki, które nie muszą być obsługiwane lub deklarowane przez programistę.
Dziedziczą z klasy RuntimeException (lub z klas dziedziczących z RuntimeException).
Przykłady Wyjątków Niekontrolowanych:

NullPointerException: próba odwołania się do obiektu, który ma wartość null.
ArithmeticException: błąd arytmetyczny, np. dzielenie przez zero.
ArrayIndexOutOfBoundsException: próba dostępu do tablicy z nieprawidłowym indeksem.
Obsługa:

Nie jest wymagane, aby wyjątki niekontrolowane były obsługiwane za pomocą bloku try-catch lub deklarowane w sygnaturze metody. Jednakże, mogą być obsługiwane, jeśli programista tego chce.
java
Copy code
try {
// Kod, który może zgłosić wyjątek niekontrolowany
} catch (NullPointerException e) {
// Obsługa wyjątku
}
Podsumowanie:
Wyjątki kontrolowane to te, które wymagają interwencji programisty i muszą być obsługiwane lub deklarowane.
Wyjątki niekontrolowane to te, które mogą wystąpić podczas działania programu, ale programista nie jest zobowiązany do ich obsługi.
W praktyce, wyjątki niekontrolowane są zazwyczaj błędami programistycznymi, takimi jak błędy logiki biznesowej, podczas gdy wyjątki kontrolowane są związane z sytuacjami, które mogą wystąpić w wyniku zewnętrznych czynników, takich jak operacje wejścia/wyjścia czy obsługa baz danych.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
26
Q

klasa abstrakcyjna a interfejs

A

Klasy abstrakcyjne i interfejsy są dwoma konceptami w języku Java, które umożliwiają tworzenie abstrakcji i definiowanie kontraktów dla klas implementujących. Oto krótka charakteryzacja obu tych koncepcji:

Klasa Abstrakcyjna:
Słowo Kluczowe:

Używa się słowa kluczowego abstract przed definicją klasy.
Konstruktory:

Może mieć konstruktory, które mogą być wywoływane przez podklasy.
Pola:

Może zawierać pola (zmienne instancji).
Metody:

Może zawierać zarówno metody abstrakcyjne (bez implementacji) jak i metody z implementacją.
Metody abstrakcyjne są oznaczane słowem kluczowym abstract.
Dziedziczenie:

Może rozszerzać jedną klasę (tylko jedno dziedziczenie).
Klasy Potomne:

Klasy potomne (dziedziczące) muszą dostarczyć implementację dla wszystkich metod abstrakcyjnych.
java
Copy code
abstract class Animal {
private String name;

public Animal(String name) {
    this.name = name;
}

abstract void makeSound();

void eat() {
    System.out.println(name + " is eating.");
} } Interfejs: Słowo Kluczowe:

Używa się słowa kluczowego interface przed definicją interfejsu.
Konstruktory:

Nie może zawierać konstruktorów. Interfejsy definiują jedynie kontrakty, a nie stan.
Pola:

Może zawierać tylko stałe (zmienne oznaczone jako final i static).
Metody:

Metody w interfejsie są domyślnie abstrakcyjne, nie posiadają implementacji.
Od Javy 8, interfejsy mogą również zawierać metody z domyślną implementacją oraz statyczne metody.
Dziedziczenie:

Może dziedziczyć wiele interfejsów (wielokrotne dziedziczenie).
Klasy Implementujące:

Klasy implementujące interfejs muszą dostarczyć implementację wszystkich metod zadeklarowanych w interfejsie.
java
Copy code
interface Vehicle {
void start();

void stop(); } Kiedy Wybrać Klasę Abstrakcyjną lub Interfejs: Klasa Abstrakcyjna:

Jeśli chcesz dostarczyć pewną wspólną implementację dla klas dziedziczących.
Jeśli planujesz dodawać więcej funkcji w przyszłości do klasy bazowej.
Gdy potrzebujesz konstruktorów w klasie bazowej.
Interfejs:

Jeśli klasa potrzebuje dziedziczyć z kilku źródeł (wielokrotne dziedziczenie).
Jeśli chcesz stworzyć kontrakt bez dostarczania implementacji (szczególnie w przypadku interfejsów w programowaniu zorientowanym obiektowo).
Gdy chcesz zdefiniować metody z domyślną implementacją bez konieczności dostarczania implementacji w klasach implementujących (od Javy 8).

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
27
Q

Array list a linked list

A

ArrayList i LinkedList to dwie popularne implementacje interfejsu List w języku Java, które różnią się strukturą danych i zasadami działania. Oto krótka charakteryzacja obu struktur:

ArrayList:
Implementacja:

Bazuje na tablicy dynamicznej.
Dostęp do Elementów:

Dostęp do elementów za pomocą indeksów.
Operacje odczytu są efektywne, ponieważ tablica umożliwia bezpośredni dostęp do elementów.
Dodawanie Elementów:

Dodawanie na końcu listy jest szybkie i efektywne.
Dodawanie lub usuwanie elementów w środku listy może być kosztowne, ponieważ wymaga przesunięcia elementów w tablicy.
Pamięć:

Zużywa mniej pamięci, ponieważ nie wymaga dodatkowej przestrzeni na wskaźniki.
Iteracja:

Iteracja jest szybka za pomocą pętli for i indeksów.
java
Copy code
List<String> arrayList = new ArrayList<>();
arrayList.add("One");
arrayList.add("Two");
arrayList.add("Three");
LinkedList:
Implementacja:</String>

Bazuje na liście dwukierunkowej.
Dostęp do Elementów:

Dostęp do elementów odbywa się poprzez przeglądanie listy od początku do końca lub od końca do początku.
Operacje odczytu mogą być wolniejsze w porównaniu do ArrayList.
Dodawanie Elementów:

Dodawanie i usuwanie elementów w środku listy jest szybkie, ponieważ nie wymaga przesunięcia elementów.
Dodawanie na końcu listy może być wolniejsze niż w przypadku ArrayList.
Pamięć:

Zużywa więcej pamięci, ponieważ każdy element wymaga dodatkowego miejsca na przechowywanie wskaźników do poprzedniego i następnego elementu.
Iteracja:

Iteracja odbywa się za pomocą iteratora i operacji przesuwania wskaźników.
java
Copy code
List<String> linkedList = new LinkedList<>();
linkedList.add("One");
linkedList.add("Two");
linkedList.add("Three");
Kiedy Wybrać ArrayList a kiedy LinkedList:
ArrayList:</String>

Dostęp do elementów poprzez indeksy.
W przypadku częstych operacji odczytu.
Gdy lista jest rzadko modyfikowana (dodawanie/usuwanie elementów).
LinkedList:

Dla częstych operacji dodawania i usuwania elementów, zwłaszcza w środku listy.
Gdy wymagane jest wielokrotne przeszukiwanie listy od początku do końca lub od końca do początku.
W przypadku operacji wymagających przesunięcia elementów, LinkedList jest bardziej efektywny.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
28
Q

Listy, sety, mapy

A

Listy, sety i mapy to trzy różne rodzaje kolekcji w języku Java, dostępne w ramach frameworku kolekcji (java.util), które oferują różne funkcje do przechowywania i manipulowania danymi. Oto krótkie omówienie każdej z tych kolekcji:

  1. Listy:
    Reprezentacja:

List to kolekcja, która przechowuje elementy w określonej kolejności.
Każdy element jest przypisany do indeksu, co umożliwia dostęp do elementów za pomocą indeksów.
Implementacje:

ArrayList: Implementacja oparta na tablicy, co oznacza, że dostęp do elementów jest szybki, ale operacje dodawania/usuwania w środku listy mogą być kosztowne.
LinkedList: Implementacja oparta na liście dwukierunkowej, co umożliwia szybkie dodawanie/usuwanie elementów, ale dostęp do elementów jest wolniejszy niż w przypadku ArrayList.
Przykład:

java
Copy code
List<String> arrayList = new ArrayList<>();
arrayList.add("Element 1");
arrayList.add("Element 2");</String>

List<String> linkedList = new LinkedList<>();
linkedList.add("Element 1");
linkedList.add("Element 2");
2. Sety:
Reprezentacja:</String>

Set to kolekcja, która przechowuje unikalne elementy, eliminując duplikaty.
Implementacje:

HashSet: Implementacja, która nie gwarantuje zachowania kolejności elementów.
LinkedHashSet: Implementacja, która utrzymuje kolejność elementów zgodnie z kolejnością ich dodania.
TreeSet: Implementacja, która przechowuje elementy w posortowanej kolejności.
Przykład:

java
Copy code
Set<String> hashSet = new HashSet<>();
hashSet.add("Element 1");
hashSet.add("Element 2");</String>

Set<String> treeSet = new TreeSet<>();
treeSet.add("Element 1");
treeSet.add("Element 2");
3. Mapy:
Reprezentacja:</String>

Map to kolekcja, która przechowuje pary klucz-wartość.
Każdy klucz w mapie jest unikalny, a wartości są przypisane do odpowiadających im kluczy.
Implementacje:

HashMap: Implementacja, która nie gwarantuje zachowania kolejności par klucz-wartość.
LinkedHashMap: Implementacja, która utrzymuje kolejność par klucz-wartość zgodnie z kolejnością ich dodania.
TreeMap: Implementacja, która przechowuje pary klucz-wartość w posortowanej kolejności względem kluczy.
Przykład:

java
Copy code
Map<String, Integer> hashMap = new HashMap<>();
hashMap.put(“Klucz 1”, 1);
hashMap.put(“Klucz 2”, 2);

Map<String, Integer> treeMap = new TreeMap<>();
treeMap.put(“Klucz 1”, 1);
treeMap.put(“Klucz 2”, 2);
Podsumowując, wybór między ArrayList a LinkedList, HashSet, LinkedHashSet, TreeSet oraz różnymi implementacjami map (HashMap, LinkedHashMap, TreeMap) zależy od konkretnych wymagań dotyczących dostępu, dodawania, usuwania elementów i zachowania kolejności w danej sytuacji.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
29
Q

D Dependency Inversion solid

A

Dependency Inversion to jeden z pięciu zasad S.O.L.I.D., który został zaproponowany przez Roberta C. Martina. Ta zasada dotyczy organizacji relacji między komponentami w systemie i jest kluczowym elementem projektowania obiektowego. Dependency Inversion jest skoncentrowany na tym, aby zależności między klasami były odwrócone w stosunku do tradycyjnego podejścia. Oto główne założenia zasady Dependency Inversion:

Główne Założenia:
Wysokopoziomowe moduły nie powinny zależeć od niskopoziomowych modułów. Obydwa powinny zależeć od abstrakcji.

W tradycyjnym podejściu często widzimy, że klasy wysokopoziomowe zależą od klas niskopoziomowych. Dependency Inversion sugeruje, aby oba rodzaje modułów zależały od abstrakcji.
Abstrakcje nie powinny zależeć od szczegółów. Szczegóły powinny zależeć od abstrakcji.

Oznacza to, że interfejsy i klasy abstrakcyjne, które definiują abstrakcje, nie powinny zależeć od implementacji. Implementacje powinny zależeć od abstrakcji.
Przykład:
Załóżmy, że mamy system do zarządzania pojazdami, gdzie mamy Car (samochód) jako niskopoziomowy moduł i Driver (kierowca) jako wysokopoziomowy moduł. W tradycyjnym podejściu Driver zależałby bezpośrednio od Car:

java
Copy code
class Car {
void start() {
// implementacja
}
}

class Driver {
Car car;

Driver() {
    this.car = new Car();
}

void drive() {
    car.start();
    // kierowca jedzie
} } W podejściu Dependency Inversion użylibyśmy abstrakcji, aby odwrócić te zależności. Moglibyśmy stworzyć interfejs Vehicle jako abstrakcję dla pojazdów:

java
Copy code
interface Vehicle {
void start();
}

class Car implements Vehicle {
@Override
public void start() {
// implementacja startu samochodu
}
}

class Driver {
Vehicle vehicle;

Driver(Vehicle vehicle) {
    this.vehicle = vehicle;
}

void drive() {
    vehicle.start();
    // kierowca jedzie
} } Teraz Driver zależy od abstrakcji Vehicle, a nie bezpośrednio od konkretnej implementacji Car. Możemy łatwo rozszerzyć system o nowe rodzaje pojazdów, które implementują interfejs Vehicle, nie zmieniając kodu w klasie Driver. To umożliwia elastyczność i łatwość rozbudowy systemu.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
30
Q

I interface segregation

A

Interface Segregation Principle (ISP) to jedna z pięciu zasad S.O.L.I.D., wprowadzonych przez Roberta C. Martina, mających na celu poprawę projektowania obiektowego. ISP koncentruje się na tym, aby interfejsy były zorganizowane w taki sposób, aby klienci korzystający z interfejsów nie byli zmuszeni implementować metod, których nie potrzebują. Zasada ta przeciwdziała tworzeniu “grubych” interfejsów, które wymagają implementacji wielu metod, nawet jeśli nie są one używane przez wszystkich klientów. Oto główne założenia Interface Segregation Principle:

Główne Założenia:
Duże interfejsy są złe:

Interfejsy nie powinny być “grube” i zawierać zbyt wielu metod. Jeśli klient nie potrzebuje wszystkich metod z interfejsu, nie powinien być zmuszany do ich implementacji.
Rozbijanie na mniejsze interfejsy:

Lepiej jest tworzyć kilka mniejszych interfejsów, z których każdy zawiera zestaw metod skorelowanych tematycznie. Klient może implementować tylko te interfejsy, które są dla niego istotne.
Unikanie redundancji:

Unikaj sytuacji, w których jedna klasa musi implementować wiele metod, z których wiele nie jest używanych w danym kontekście.
Przykład:
Załóżmy, że mamy interfejs Worker, który zawiera metody związane zarówno z pracą biurową, jak i pracą na budowie:

java
Copy code
interface Worker {
void doOfficeWork();
void doConstructionWork();
}
Problem pojawia się, gdy mamy dwie różne klasy klientów: OfficeWorker i ConstructionWorker. Każda z tych klas implementuje interfejs, ale często korzystają tylko z jednej grupy metod. To prowadzi do niepotrzebnej implementacji i redundancji.

Lepsze podejście to rozbicie interfejsu na dwa mniejsze:

java
Copy code
interface OfficeWorker {
void doOfficeWork();
}

interface ConstructionWorker {
void doConstructionWork();
}
Teraz klasy klientów implementują tylko te interfejsy, które są dla nich istotne, eliminując tym samym niepotrzebną redundancję i skomplikowanie implementacji.

Korzyści Interface Segregation Principle:
Elastyczność: Pozwala na elastyczność w implementacji, ponieważ klienci implementują tylko te metody, których potrzebują.
Unikanie redundancji: Eliminuje konieczność implementacji niepotrzebnych metod.
Łatwiejsze utrzymanie: Skomplikowane interfejsy są trudniejsze do utrzymania, a rozbicie ich na mniejsze części ułatwia zarządzanie kodem.
Interface Segregation Principle pomaga projektantom i programistom tworzyć bardziej modularne, zrozumiałe i elastyczne systemy.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
31
Q

L liskov substitution priniciple solid

A

Zasada podstawiania Liskov (ang. Liskov Substitution Principle - LSP) to jedna z pięciu zasad S.O.L.I.D., które zostały zaproponowane przez Barbarę Liskov. Zasada ta odnosi się do dziedziczenia klas w programowaniu obiektowym i zakłada, że obiekty powinny być w stanie być zastąpione (podstawione) obiektami swoich klas pochodnych bez zmiany poprawności programu. Sformułowanie zasady LSP brzmi:

“Jeśli S jest podtypem T, to obiekt typu T może być zastąpiony obiektem typu S bez zmiany poprawności programu.”

W praktyce oznacza to, że każdy obiekt klasy potomnej powinien być w stanie zastąpić obiekt klasy bazowej, nie powodując żadnych nieoczekiwanych błędów ani zmian w działaniu programu. W skrócie, jeśli klasa B dziedziczy po klasie A, to obiekt typu A może być zastąpiony obiektem typu B.

Przykład:

java
Copy code
class Rectangle {
protected int width;
protected int height;

public void setWidth(int width) {
    this.width = width;
}

public void setHeight(int height) {
    this.height = height;
}

public int calculateArea() {
    return width * height;
} }

class Square extends Rectangle {
@Override
public void setWidth(int width) {
this.width = width;
this.height = width;
}

@Override
public void setHeight(int height) {
    this.width = height;
    this.height = height;
} } W powyższym przykładzie Square dziedziczy po Rectangle. Mimo że z punktu widzenia dziedziczenia jest to poprawne, z punktu widzenia zasady LSP może prowadzić do problemów. Na przykład:

java
Copy code
void processRectangle(Rectangle rectangle) {
rectangle.setWidth(5);
rectangle.setHeight(10);
int area = rectangle.calculateArea();
System.out.println(“Area: “ + area);
}

// Użycie zasady LSP
Rectangle rectangle = new Rectangle();
processRectangle(rectangle); // Poprawne

Square square = new Square();
processRectangle(square); // Niespodziewany wynik, naruszona zasada LSP
W przypadku Square modyfikacja jednego z wymiarów wpływa na drugi, co prowadzi do niespodziewanych wyników. Właśnie to narusza zasadę Liskov Substitution Principle, ponieważ obiekt Square nie zachowuje się tak, jak oczekuje się od ogólnego obiektu Rectangle. Optymalne rozwiązanie tego problemu wymagałoby przemyślenia projektu klas, aby poprawnie zastosować zasadę LSP.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
32
Q

O open-closed priniciple

A

Zasada otwarte/zamknięte (Open/Closed Principle, OCP) to jedna z zasad projektowania obiektowego, wprowadzona przez Bertranda Meyera, która jest często skrócona do zdania: “Otwarte na rozszerzenia, zamknięte na modyfikacje”. Zasada ta jest jedną z pięciu zasad SOLID i skupia się na tym, aby klasa była dostosowana do rozszerzania, ale jednocześnie zamknięta na zmiany w swoim istniejącym kodzie.

Główne założenia zasady otwarte/zamknięte:

Otwarte na Rozszerzenia (Open for Extension):

Klasa powinna być zaprojektowana w taki sposób, aby można było ją rozszerzać bez modyfikacji jej istniejącego kodu.
Zamknięte na Modyfikacje (Closed for Modification):

Istniejący kod klasy powinien być zamknięty na zmiany, aby unikać wprowadzania niepotrzebnych modyfikacji, które mogą wpłynąć na stabilność i funkcjonalność istniejącego systemu.
Przykładowy Scenariusz:

Załóżmy, że mamy klasę Shape z metodą area(), która oblicza pole powierzchni dla różnych kształtów. Zastosowanie zasady OCP oznacza, że powinniśmy być w stanie dodawać nowe kształty, nie modyfikując kodu istniejącej klasy Shape.
java
Copy code
// Przykład przed zastosowaniem OCP
class Shape {
String type;

double area() {
    if (type.equals("Circle")) {
        // obliczenia pola powierzchni dla okręgu
    } else if (type.equals("Rectangle")) {
        // obliczenia pola powierzchni dla prostokąta
    }
    // inne przypadki
} } java Copy code // Zastosowanie OCP interface Shape {
double area(); }

class Circle implements Shape {
double radius;

@Override
public double area() {
    // obliczenia pola powierzchni dla okręgu
} }

class Rectangle implements Shape {
double width;
double height;

@Override
public double area() {
    // obliczenia pola powierzchni dla prostokąta
} } W powyższym przykładzie zastosowaliśmy interfejs Shape, który jest otwarty na rozszerzenia poprzez implementowanie go w różnych klasach reprezentujących różne kształty, takie jak Circle i Rectangle. Dzięki temu możemy dodawać nowe kształty, jednocześnie nie modyfikując kodu istniejącej klasy Shape. To jest zgodne z zasadą otwarte/zamknięte.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
33
Q

S single responsibility principle

A

Zasada pojedynczej odpowiedzialności (Single Responsibility Principle, SRP) to jedna z zasad SOLID, której głównym celem jest zachęcanie do projektowania klas o jednej, klarownej odpowiedzialności. SRP mówi, że klasa powinna mieć tylko jeden powód do zmiany, co oznacza, że ​​powinna mieć tylko jedno zadanie lub odpowiedzialność.

Główne założenia zasady pojedynczej odpowiedzialności:

Jedna Odpowiedzialność:

Klasa powinna mieć tylko jedną odpowiedzialność, tj. jedno konkretne zadanie do wykonania.
Unikanie Zmiany z Kilku Powodów:

Kiedy klasa ma tylko jedną odpowiedzialność, zmiany w systemie, które wpływają na tę odpowiedzialność, nie powinny wymagać modyfikacji klasy z innych powodów.
Wysoki Poziom Koherenji:

Klasa powinna być kohernetna, czyli wszystkie jej metody i pola powinny być ze sobą powiązane w ramach jednej konkretnej odpowiedzialności.
Przykład:

Załóżmy, że mamy klasę Report, która ma generować raport i jednocześnie zapisywać go do pliku. Narusza to zasadę pojedynczej odpowiedzialności, ponieważ klasa ma dwa powody do zmiany: zmiana formatu raportu i zmiana sposobu zapisu do pliku.
java
Copy code
// Naruszające SRP
class Report {
void generateReport() {
// generowanie raportu
}

void saveToFile() {
    // zapisywanie raportu do pliku
} } W zgodzie z SRP możemy rozdzielić te dwie odpowiedzialności do dwóch różnych klas:

java
Copy code
// Zgodne z SRP
class ReportGenerator {
void generateReport() {
// generowanie raportu
}
}

class FileSaver {
void saveToFile() {
// zapisywanie raportu do pliku
}
}
W ten sposób, jeśli zmieni się sposób generowania raportu, nie będzie to miało wpływu na klasę FileSaver, a zmiana sposobu zapisu do pliku nie wpłynie na klasę ReportGenerator. To zwiększa elastyczność i utrzymywalność kodu. Zasada pojedynczej odpowiedzialności pomaga unikać sytuacji, w których klasa staje się “zbyt rozbuchana” i trudna do zrozumienia, ponieważ próbuje wykonywać zbyt wiele różnych zadań.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
34
Q

Singleton

A

Wzorzec Singleton jest jednym z wzorców projektowych, które wprowadzają jedyny punkt instancji dla danej klasy, zapewniając, że klasa ta ma tylko jedną instancję i dostarcza globalny punkt dostępu do niej. Wzorzec Singleton jest szczególnie przydatny, gdy chcemy upewnić się, że tylko jedna instancja klasy istnieje w systemie i dostęp do niej jest możliwy z dowolnego miejsca.

Podstawowe cechy wzorca Singleton:

Konstruktor prywatny:

Singleton posiada prywatny konstruktor, co oznacza, że nie jest możliwe utworzenie instancji tej klasy z zewnątrz.
Statyczna metoda dostępu:

Singleton dostarcza statyczną metodę, która zwraca jedyną instancję obiektu (najczęściej nazywaną getInstance).
Pola prywatne:

Często instancja singletona jest przechowywana w prywatnym statycznym polu w klasie.
Leniwa inicjalizacja (Lazy Initialization):

Instancja singletona jest inicjalizowana dopiero w momencie pierwszego wywołania metody getInstance, co nazywane jest leniwą inicjalizacją.
Poniżej przedstawiam prosty przykład implementacji wzorca Singleton w języku Java:

java
Copy code
public class Singleton {
// Prywatne statyczne pole przechowujące jedyną instancję klasy
private static Singleton instance;

// Prywatny konstruktor, aby uniemożliwić tworzenie instancji z zewnątrz
private Singleton() {
    // Inicjalizacja instancji (może zawierać inne operacje)
}

// Statyczna metoda dostępu do instancji Singletona
public static Singleton getInstance() {
    if (instance == null) {
        // Leniwa inicjalizacja, jeśli instancja jeszcze nie istnieje
        instance = new Singleton();
    }
    return instance;
} } Warto zauważyć, że ten przykład używa leniwej inicjalizacji, co oznacza, że instancja zostanie utworzona dopiero wtedy, gdy zostanie wywołana metoda getInstance. Istnieje także inne podejście, tzw. inicjalizacja eager, w której instancja jest tworzona od razu podczas ładowania klasy. Wybór pomiędzy leniwą a eager inicjalizacją zależy od wymagań danego przypadku użycia.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
35
Q

Wzorzec Factory

A

Wzorzec Factory (Fabryka) to wzorzec projektowy z kategorii creational, który zajmuje się procesem tworzenia obiektów. Celem tego wzorca jest dostarczenie interfejsu do tworzenia obiektów w klasie bazowej, jednocześnie pozostawiając podklasy odpowiedzialne za wybór typu tworzonego obiektu.

Główne elementy wzorca Factory to:

Produkt (Product):

Interfejs lub klasa abstrakcyjna definiująca obiekt, który ma być utworzony przez konkretne fabryki.
Konkretne Produkty (Concrete Products):

Klasy implementujące interfejs lub dziedziczące po klasie abstrakcyjnej produktu, reprezentujące rzeczywiste obiekty, które fabryka będzie tworzyć.
Fabryka (Factory):

Interfejs lub klasa abstrakcyjna definiująca metodę (lub zestaw metod), które są odpowiedzialne za tworzenie obiektów (produktów).
Może istnieć wiele różnych fabryk, każda tworząca różne rodzaje produktów.
Konkretne Fabryki (Concrete Factories):

Klasy implementujące interfejs lub dziedziczące po klasie abstrakcyjnej fabryki, dostarczające konkretną implementację tworzenia produktów.
Przykład:
Załóżmy, że mamy hierarchię produktów, takich jak Button (przycisk) i Checkbox (pole wyboru), a także chcemy stworzyć różne rodzaje fabryk, takie jak WindowsFactory i MacOSFactory, które będą tworzyć konkretne produkty dla systemów Windows i macOS.

java
Copy code
// Interfejs Produktu
interface Button {
void render();
}

// Konkretne Produkty
class WindowsButton implements Button {
@Override
public void render() {
System.out.println(“Render Windows Button”);
}
}

class MacOSButton implements Button {
@Override
public void render() {
System.out.println(“Render MacOS Button”);
}
}

// Interfejs Fabryki
interface GUIFactory {
Button createButton();
}

// Konkretne Fabryki
class WindowsFactory implements GUIFactory {
@Override
public Button createButton() {
return new WindowsButton();
}
}

class MacOSFactory implements GUIFactory {
@Override
public Button createButton() {
return new MacOSButton();
}
}
Teraz możemy użyć fabryk do tworzenia produktów w zależności od wymagań systemowych:

java
Copy code
public class Client {
public static void main(String[] args) {
// Wybór fabryki w zależności od systemu
GUIFactory factory;
if (isWindows()) {
factory = new WindowsFactory();
} else {
factory = new MacOSFactory();
}

    // Użycie fabryki do utworzenia produktu
    Button button = factory.createButton();
    button.render();
}

private static boolean isWindows() {
    // Sprawdzenie warunku dla systemu Windows
    return true;
} } W ten sposób wzorzec Factory pozwala na elastyczne tworzenie obiektów różnych rodzajów, przy jednoczesnym uniezależnieniu kodu klienta od konkretnej implementacji produktów.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
36
Q

Wzorzec builder

A

Wzorzec Builder to wzorzec kreacyjny, który pozwala na konstruowanie złożonych obiektów krok po kroku. Głównym celem wzorca Builder jest dostarczenie elastycznego interfejsu do tworzenia różnych konfiguracji złożonych obiektów, pozwalając klientom na konstruowanie obiektów z różnymi właściwościami bez konieczności bezpośredniego operowania na ich wewnętrznych detalach.

Główne składniki wzorca Builder to:

Produkt (Product):

Reprezentuje złożony obiekt, który ma być zbudowany. Wzorzec Builder jest używany do konstrukcji różnych wariantów tego produktu.
Interfejs Builder:

Zawiera deklaracje metod do konstruowania poszczególnych części produktu.
Konkretny Builder:

Implementuje interfejs Builder i dostarcza konkretne implementacje metod do budowy poszczególnych części produktu.
Dyrektor (Director):

Zarządza procesem konstruowania obiektu za pomocą interfejsu Builder. Może definiować różne kroki procesu konstrukcji i korzystać z różnych konkretnych builderów.
Klient (Client):

Korzysta z dyrektora i/lub bezpośrednio z builderów do konstrukcji różnych wariantów produktu.
Poniżej przedstawiam prosty przykład implementacji wzorca Builder w języku Java. Rozważmy budowę obiektu Computer z różnymi właściwościami, takimi jak procesor, pamięć RAM, dysk twardy itp.

java
Copy code
// Produkt
class Computer {
private String processor;
private int ram;
private int storage;

public Computer(String processor, int ram, int storage) {
    this.processor = processor;
    this.ram = ram;
    this.storage = storage;
}

// Gettery
// ... }

// Interfejs Builder
interface ComputerBuilder {
void buildProcessor(String processor);
void buildRAM(int ram);
void buildStorage(int storage);
Computer getResult();
}

// Konkretny Builder
class DesktopBuilder implements ComputerBuilder {
private Computer computer;

public DesktopBuilder() {
    this.computer = new Computer("Standard Processor", 4, 500);
}

@Override
public void buildProcessor(String processor) {
    computer = new Computer(processor, computer.ram, computer.storage);
}

@Override
public void buildRAM(int ram) {
    computer = new Computer(computer.processor, ram, computer.storage);
}

@Override
public void buildStorage(int storage) {
    computer = new Computer(computer.processor, computer.ram, storage);
}

@Override
public Computer getResult() {
    return computer;
} }

// Dyrektor
class ComputerDirector {
private ComputerBuilder builder;

public ComputerDirector(ComputerBuilder builder) {
    this.builder = builder;
}

public void construct() {
    builder.buildProcessor("High-Performance Processor");
    builder.buildRAM(16);
    builder.buildStorage(1000);
} }

// Klient
public class Client {
public static void main(String[] args) {
ComputerBuilder desktopBuilder = new DesktopBuilder();
ComputerDirector director = new ComputerDirector(desktopBuilder);

    director.construct();
    Computer desktop = desktopBuilder.getResult();

    System.out.println("Desktop Configuration:");
    System.out.println("Processor: " + desktop.getProcessor());
    System.out.println("RAM: " + desktop.getRAM() + "GB");
    System.out.println("Storage: " + desktop.getStorage() + "GB");
} } W tym przykładzie ComputerDirector zarządza procesem konstruowania obiektu Computer za pomocą interfejsu ComputerBuilder. DesktopBuilder dostarcza konkretne implementacje metod do budowy poszczególnych części Computer. Klient (Client) używa dyrektora i/lub bezpośrednio builderów do konstrukcji różnych wariantów Computer.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
37
Q

Wzorzec Dekorator

A

Wzorzec dekorator (Decorator Pattern) to wzorzec strukturalny, który umożliwia dodawanie nowych funkcji do istniejących obiektów poprzez umieszczanie ich wewnątrz specjalnych dekoratorów. Ten wzorzec jest często używany do elastycznego rozszerzania funkcjonalności obiektów bez konieczności modyfikacji ich kodu źródłowego.

Główne komponenty wzorca dekoratora to:

Komponent (Component):

Definiuje interfejs dla obiektów, do których możemy dodawać nowe funkcje.
Konkretny Komponent (Concrete Component):

Implementuje interfejs komponentu i dostarcza podstawową funkcjonalność.
Dekorator (Decorator):

Abstrakcyjna klasa lub interfejs, który definiuje interfejs dla dekoratorów. Zawiera odniesienie do komponentu.
Konkretny Dekorator (Concrete Decorator):

Implementuje dekorator i rozszerza funkcjonalność komponentu podstawowego. Przechowuje odniesienie do dekorowanego komponentu.
Poniżej przedstawiam prosty przykład implementacji wzorca dekoratora w języku Java. Rozważmy kawiarnię, gdzie możemy zamawiać różne rodzaje kaw z różnymi dodatkami.

java
Copy code
// Komponent
interface Coffee {
double cost();
String description();
}

// Konkretny Komponent
class SimpleCoffee implements Coffee {
@Override
public double cost() {
return 2.0; // Cena podstawowa
}

@Override
public String description() {
    return "Simple Coffee";
} }

// Dekorator
abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;

public CoffeeDecorator(Coffee decoratedCoffee) {
    this.decoratedCoffee = decoratedCoffee;
}

@Override
public double cost() {
    return decoratedCoffee.cost();
}

@Override
public String description() {
    return decoratedCoffee.description();
} }

// Konkretny Dekorator 1
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee decoratedCoffee) {
super(decoratedCoffee);
}

@Override
public double cost() {
    return super.cost() + 0.5; // Cena za mleko
}

@Override
public String description() {
    return super.description() + " with Milk";
} }

// Konkretny Dekorator 2
class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee decoratedCoffee) {
super(decoratedCoffee);
}

@Override
public double cost() {
    return super.cost() + 0.2; // Cena za cukier
}

@Override
public String description() {
    return super.description() + " with Sugar";
} }

// Klient
public class Client {
public static void main(String[] args) {
Coffee simpleCoffee = new SimpleCoffee();
System.out.println(“Simple Coffee: “ + simpleCoffee.cost() + “ - “ + simpleCoffee.description());

    Coffee milkCoffee = new MilkDecorator(simpleCoffee);
    System.out.println("Coffee with Milk: " + milkCoffee.cost() + " - " + milkCoffee.description());

    Coffee sugarMilkCoffee = new SugarDecorator(milkCoffee);
    System.out.println("Coffee with Sugar and Milk: " + sugarMilkCoffee.cost() + " - " + sugarMilkCoffee.description());
} } W tym przykładzie SimpleCoffee to konkretny komponent, CoffeeDecorator to abstrakcyjny dekorator, a MilkDecorator i SugarDecorator to konkretne dekoratory. Klient może dowolnie komponować różne rodzaje kaw z różnymi dodatkami, jednocześnie trzymając się interfejsu Coffee.
38
Q

Wzorzec Fasada

A

Wzorzec fasady (Facade Pattern) to wzorzec projektowy strukturalny, który dostarcza jednolite interfejsy do zestawu interfejsów w podsystemie. Fasada definiuje wyższopoziomowy interfejs, który upraszcza użycie podsystemu, eliminując konieczność bezpośredniego kontaktu klienta z wieloma klasami w podsystemie.

Główne cele wzorca fasady to:

Ułatwienie korzystania z systemu:

Fasada dostarcza prosty, jednolity interfejs, który ukrywa skomplikowaną strukturę podsystemu.
Odseparowanie klienta od szczegółów implementacyjnych:

Klient nie musi znać wewnętrznej struktury podsystemu ani operować na wielu jego klasach.
Zapewnienie elastyczności:

Możliwość zmian w podsystemie bez wpływu na kod klienta.
Promowanie jednolitego interfejsu:

Ułatwia utrzymanie jednolitego interfejsu dla podsystemu, nawet jeśli składa się on z wielu klas.
Poniżej przedstawiam prosty przykład implementacji wzorca fasady w języku Java. Załóżmy, że mamy skomplikowany system komputerowy z wieloma komponentami, a fasada ComputerFacade upraszcza proces włączania i wyłączania komputera.

java
Copy code
// Podsystem - Komponent 1
class CPU {
public void start() {
System.out.println(“CPU started”);
}

public void shutdown() {
    System.out.println("CPU shutdown");
} }

// Podsystem - Komponent 2
class Memory {
public void load() {
System.out.println(“Memory loaded”);
}

public void unload() {
    System.out.println("Memory unloaded");
} }

// Podsystem - Komponent 3
class HardDrive {
public void read() {
System.out.println(“Hard Drive read”);
}

public void write() {
    System.out.println("Hard Drive write");
} }

// Fasada
class ComputerFacade {
private CPU cpu;
private Memory memory;
private HardDrive hardDrive;

public ComputerFacade() {
    this.cpu = new CPU();
    this.memory = new Memory();
    this.hardDrive = new HardDrive();
}

public void start() {
    cpu.start();
    memory.load();
    hardDrive.read();
    System.out.println("Computer started");
}

public void shutdown() {
    cpu.shutdown();
    memory.unload();
    hardDrive.write();
    System.out.println("Computer shutdown");
} }

// Klient
public class Client {
public static void main(String[] args) {
ComputerFacade computer = new ComputerFacade();

    // Włączanie komputera
    computer.start();

    // Wyłączanie komputera
    computer.shutdown();
} } W tym przykładzie ComputerFacade dostarcza jednolity interfejs dla włączania i wyłączania komputera, jednocześnie ukrywając szczegóły implementacyjne podsystemu składającego się z CPU, Memory i HardDrive. Klient korzysta tylko z fasady, co ułatwia korzystanie z systemu komputerowego.
39
Q

Wzorzec obserwator

A

Wzorzec obserwator (Observer Pattern) to wzorzec projektowy behawioralny, który definiuje zależność jeden-do-wielu między obiektami, tak że gdy jeden obiekt zmienia stan, wszyscy zależni obserwatorzy są informowani i automatycznie aktualizują się. Wzorzec ten jest szeroko stosowany w projektowaniu systemów, gdzie zmiana stanu jednego obiektu wymaga reakcji lub synchronizacji z innymi obiektami.

Główne komponenty wzorca obserwatora to:

Podmiot (Subject):

Reprezentuje obiekt, który utrzymuje listę obserwatorów i zarządza nimi. W przypadku zmiany stanu, powiadamia obserwatorów.
Obserwator (Observer):

Definiuje interfejs, który obiekty obserwujące muszą zaimplementować w celu otrzymywania powiadomień od podmiotu.
Konkretny Obserwator (Concrete Observer):

Implementuje interfejs obserwatora i przechowuje referencję do obiektu podmiotu. Otrzymuje powiadomienia i reaguje na zmiany stanu.
Konkretny Podmiot (Concrete Subject):

Implementuje interfejs podmiotu. Przechowuje stan i powiadamia obserwatorów o zmianach w stanie.
Poniżej znajduje się prosty przykład implementacji wzorca obserwator w języku Java. Rozważmy system pogodowy, gdzie obiekty obserwujące (np. wyświetlacze) reagują na zmiany w stanie pogody.

java
Copy code
import java.util.ArrayList;
import java.util.List;

// Interfejs obserwatora
interface Observer {
void update(float temperature, float humidity, float pressure);
}

// Interfejs podmiotu
interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}

// Konkretny Podmiot
class WeatherStation implements Subject {
private List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;</Observer>

public WeatherStation() {
    this.observers = new ArrayList<>();
}

@Override
public void registerObserver(Observer observer) {
    observers.add(observer);
}

@Override
public void removeObserver(Observer observer) {
    observers.remove(observer);
}

@Override
public void notifyObservers() {
    for (Observer observer : observers) {
        observer.update(temperature, humidity, pressure);
    }
}

public void setMeasurements(float temperature, float humidity, float pressure) {
    this.temperature = temperature;
    this.humidity = humidity;
    this.pressure = pressure;
    notifyObservers();
} }

// Konkretny Obserwator
class Display implements Observer {
private float temperature;
private float humidity;

@Override
public void update(float temperature, float humidity, float pressure) {
    this.temperature = temperature;
    this.humidity = humidity;
    display();
}

public void display() {
    System.out.println("Current conditions: " + temperature + "C degrees and " + humidity + "% humidity");
} }

// Klient
public class Client {
public static void main(String[] args) {
WeatherStation weatherStation = new WeatherStation();

    Display display1 = new Display();
    Display display2 = new Display();

    weatherStation.registerObserver(display1);
    weatherStation.registerObserver(display2);

    // Symulacja zmiany stanu pogody
    weatherStation.setMeasurements(25, 60, 1010);
    weatherStation.setMeasurements(27, 55, 1005);
} } W tym przykładzie WeatherStation to konkretny podmiot, a Display to konkretny obserwator. Klient rejestruje obserwatorów na podmiocie (weatherStation) i następnie symuluje zmiany stanu pogody. Po każdej zmianie stanu, obserwatorzy są informowani i reagują odpowiednio.
40
Q

Wzorzec Wizytator

A

Wzorzec Visitor to wzorzec projektowy behawioralny, który umożliwia zdefiniowanie nowych operacji na strukturze obiektów, nie modyfikując tych obiektów. Pozwala odseparować algorytmy od struktury obiektowej, co sprawia, że jest szczególnie przydatny, gdy mamy do czynienia z hierarchią klas o stałej strukturze, a chcemy dodać nowe operacje, które zależą od tych klas.

Główne komponenty wzorca Visitor to:

Element (Element):

Definiuje interfejs, który muszą implementować wszystkie elementy struktury obiektowej. Wzorzec Visitor operuje na obiektach tego interfejsu.
Konkretny Element (Concrete Element):

Implementuje interfejs Element. To są obiekty, dla których chcemy zdefiniować nowe operacje.
Wizytator (Visitor):

Definiuje interfejs, który zawiera metody visit dla każdego konkretnego elementu w strukturze obiektowej. Te metody są odpowiedzialne za wykonanie konkretnej operacji na elemencie.
Konkretny Wizytator (Concrete Visitor):

Implementuje interfejs Wizytatora. Dla każdej metody visit odpowiada za wykonanie konkretnej operacji na danym konkretnym elemencie.
Struktura (Structure):

Reprezentuje strukturę obiektową, która składa się z elementów.
Przyjrzyjmy się prostemu przykładowi, w którym mamy strukturę zwierząt, a wizytator AnimalVisitor umożliwia zdefiniowanie różnych operacji dla różnych rodzajów zwierząt.

java
Copy code
// Element
interface Animal {
void accept(AnimalVisitor visitor);
}

// Konkretny Element
class Lion implements Animal {
@Override
public void accept(AnimalVisitor visitor) {
visitor.visitLion(this);
}
}

// Konkretny Element
class Elephant implements Animal {
@Override
public void accept(AnimalVisitor visitor) {
visitor.visitElephant(this);
}
}

// Wizytator
interface AnimalVisitor {
void visitLion(Lion lion);
void visitElephant(Elephant elephant);
}

// Konkretny Wizytator
class AnimalSoundVisitor implements AnimalVisitor {
@Override
public void visitLion(Lion lion) {
System.out.println(“Lion roars”);
}

@Override
public void visitElephant(Elephant elephant) {
    System.out.println("Elephant trumpets");
} }

// Struktura
class Zoo {
private List<Animal> animals;</Animal>

public Zoo() {
    this.animals = new ArrayList<>();
}

public void addAnimal(Animal animal) {
    animals.add(animal);
}

public void makeAnimalSounds(AnimalVisitor visitor) {
    for (Animal animal : animals) {
        animal.accept(visitor);
    }
} }

// Klient
public class Client {
public static void main(String[] args) {
Zoo zoo = new Zoo();
zoo.addAnimal(new Lion());
zoo.addAnimal(new Elephant());

    AnimalVisitor animalSoundVisitor = new AnimalSoundVisitor();
    zoo.makeAnimalSounds(animalSoundVisitor);
} } W tym przykładzie Animal to interfejs Element, a Lion i Elephant to konkretne elementy. AnimalVisitor to interfejs Wizytatora, a AnimalSoundVisitor to konkretny wizytator, który definiuje operacje dla różnych rodzajów zwierząt. Zoo reprezentuje strukturę obiektową, a klient może używać wizytatora do wykonania operacji na wszystkich elementach w strukturze. W ten sposób wzorzec Visitor pozwala na dodawanie nowych operacji, nie zmieniając struktury obiektowej.
40
Q

Wzorzec MVC

A

Wzorzec architektoniczny MVC (Model-View-Controller) jest jednym z najbardziej popularnych wzorców stosowanych w projektowaniu oprogramowania. MVC separuje aplikację na trzy główne komponenty, aby ułatwić zarządzanie kodem, utrzymanie i rozwijanie aplikacji. Oto krótkie omówienie każdego z tych komponentów:

Model (Model):

Reprezentuje dane i logikę biznesową aplikacji. Model jest odpowiedzialny za przechowywanie, przetwarzanie i zarządzanie danymi. Nie komunikuje się bezpośrednio z widokiem ani kontrolerem. Jeśli dane w modelu ulegają zmianie, informuje obiekty obserwujące (na przykład kontrolery), aby zaktualizować widok.
Widok (View):

Odpowiada za prezentację danych użytkownikowi i interakcję z użytkownikiem. Nie przechowuje danych ani nie zawiera logiki biznesowej. Widok obserwuje model i reaguje na jego zmiany, prezentując aktualne dane użytkownikowi. Wzorzec ten pozwala na wielokrotne użycie różnych widoków dla tego samego modelu.
Kontroler (Controller):

Zarządza interakcjami użytkownika i obsługuje żądania użytkownika. Kontroler odbiera wejścia od użytkownika, przetwarza je i aktualizuje model oraz widok na podstawie tych działań. Kontroler jest mostem między modelem a widokiem, utrzymując logiczną separację między nimi.
Wzorzec MVC pomaga w utrzymaniu modularności, ponieważ każdy z komponentów ma określone zadania i odpowiedzialności. Pozwala to na łatwiejsze dodawanie, usuwanie i modyfikowanie funkcji w aplikacji. Dodatkowo, dzięki zastosowaniu obserwatorów, model może powiadamiać widoki o zmianach, co umożliwia automatyczne aktualizacje interfejsu użytkownika bez bezpośredniego zaangażowania kontrolera.

Przykładowa implementacja wzorca MVC w języku Java może wyglądać następująco:

java
Copy code
// Model
class UserModel {
private String name;

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
    // Powiadom obserwatorów (kontrolery lub widoki) o zmianie
    notifyObservers();
}

// Implementacja obserwatorów (patrz wzorzec Obserwator)
private List<UserObserver> observers = new ArrayList<>();

public void addObserver(UserObserver observer) {
    observers.add(observer);
}

public void removeObserver(UserObserver observer) {
    observers.remove(observer);
}

private void notifyObservers() {
    for (UserObserver observer : observers) {
        observer.update(this);
    }
} }

// Widok
interface UserView {
void displayUserInfo(String name);
}

// Kontroler
class UserController implements UserObserver {
private UserModel userModel;
private UserView userView;

public UserController(UserModel userModel, UserView userView) {
    this.userModel = userModel;
    this.userView = userView;

    // Kontroler rejestruje się jako obserwator modelu
    userModel.addObserver(this);
}

public void setUserName(String name) {
    userModel.setName(name);
}

@Override
public void update(UserModel userModel) {
    // Aktualizuj widok na podstawie zmian w modelu
    userView.displayUserInfo(userModel.getName());
} }

// Obserwator
interface UserObserver {
void update(UserModel userModel);
}

// Widok konkretnej implementacji
class ConsoleUserView implements UserView {
@Override
public void displayUserInfo(String name) {
System.out.println(“User Info: “ + name);
}
}
W tym przykładzie UserModel reprezentuje model, UserView to widok, a UserController jest kontrolerem. UserObserver to interfejs obserwatora, a ConsoleUserView to konkretna implementacja widoku. Dzięki zastosowaniu wzorca MVC, zmiana nazwy użytkownika w kontrolerze (UserController) automatycznie spowoduje aktualizację widoku (ConsoleUserView).

40
Q

Dependency Inection

A

Dependency Injection (DI) to wzorzec projektowy, który pomaga zorganizować zależności między komponentami w systemie. Głównym celem Dependency Injection jest odwrócenie kontroli (Inversion of Control, IoC) – umożliwienie zależnościom komponentu dostarczania się z zewnątrz, co poprawia elastyczność, testowalność i konserwację kodu.

W Dependency Injection, zamiast tego, aby obiekt samodzielnie tworzył swoje zależności, te zależności są mu dostarczane z zewnątrz. Istnieje kilka sposobów realizacji Dependency Injection, a poniżej przedstawiam główne rodzaje:

Konstruktor (Constructor Injection):

Polega na przekazywaniu zależności przez konstruktor obiektu. W tym przypadku, zależności są przekazywane podczas tworzenia instancji klasy.
java
Copy code
public class Car {
private Engine engine;

public Car(Engine engine) {
    this.engine = engine;
} } Setter (Setter Injection):

Zależności są przekazywane za pomocą setterów klasy. Pozwala to na elastyczne ustawianie zależności po utworzeniu obiektu.
java
Copy code
public class Car {
private Engine engine;

public void setEngine(Engine engine) {
    this.engine = engine;
} } Metoda (Method Injection):

Zależności są przekazywane poprzez metody obiektu. W tym przypadku, zamiast ustawiać zależności w konstruktorze lub setterach, przekazuje się je bezpośrednio do metody, która ich potrzebuje.
java
Copy code
public class Car {
public void start(Engine engine) {
// logika rozruchu z użyciem silnika
}
}
Interfejs (Interface Injection):

Ten rodzaj Dependency Injection jest mniej popularny niż pozostałe i polega na implementacji interfejsu, który definiuje metody do ustawiania zależności. Nie jest często stosowany, ponieważ wprowadza konieczność implementacji zbędnych metod w klasach, co może prowadzić do zanieczyszczenia interfejsu.
java
Copy code
public interface EngineSetter {
void setEngine(Engine engine);
}

public class Car implements EngineSetter {
private Engine engine;

@Override
public void setEngine(Engine engine) {
    this.engine = engine;
} } W zależności od kontekstu i preferencji, można stosować różne rodzaje Dependency Injection. W praktyce, Konstruktor Injection jest najczęściej używany, ponieważ zapewnia niezmienną konfigurację obiektu od momentu jego utworzenia, co ułatwia testowanie, a także pomaga uniknąć stanu połowicznego (half-initialized state). Ostateczny wybór zależy od specyfiki projektu i wymagań.
40
Q

Dobry a zły kod

A

Dobry kod:

Czytelność (Readability):

Kod powinien być czytelny i zrozumiały dla innych programistów. Zastosowanie znaczących nazw zmiennych i funkcji, stosowanie konwencji nazewniczych, odpowiednie formatowanie i składnia przyczyniają się do zwiększenia czytelności kodu.
Modularność:

Kod powinien być podzielony na małe, logiczne moduły (funkcje, klasy, moduły), co ułatwia zrozumienie i utrzymanie. Dobre podziały pomagają również w ponownym użyciu kodu.
Efektywność:

Kod powinien być efektywny pod względem zasobów, takich jak czas procesora, pamięć itp. Unikanie złożonych i nieefektywnych algorytmów oraz optymalizacja kluczowych fragmentów kodu to cechy dobrego kodu.
Testowalność:

Kod powinien być łatwy do testowania. To oznacza, że powinno być możliwe napisanie testów jednostkowych, integracyjnych i innych testów, które potwierdzają poprawność kodu.
Dokumentacja:

Kod powinien zawierać odpowiednią dokumentację, taką jak komentarze, aby inni programiści mogli zrozumieć, jak kod działa. Jednak najlepszym sposobem dokumentacji jest samoczynna dokumentacja poprzez zrozumiały i czytelny kod.
Zły kod:

Zawiłość:

Zbyt skomplikowany i zawiły kod utrudnia jego zrozumienie i utrzymanie. Nadmiernie zagnieżdżone struktury warunkowe, zbyt długie funkcje czy klasy mogą być oznaką zawiłości.
Duplikacja kodu:

Powtarzający się kod jest trudny do utrzymania i prowadzi do problemów w przypadku wprowadzania zmian. Dlatego unikaj duplikacji kodu i staraj się stosować zasadę DRY (Don’t Repeat Yourself).
Złe nazwy zmiennych i funkcji:

Nazwy zmiennych i funkcji powinny być znaczące i opisowe. Zbyt krótkie lub niejasne nazwy mogą wprowadzić w błąd innych programistów.
Brak dokumentacji:

Brak dokumentacji sprawia, że trudno jest zrozumieć, jak kod działa. Nawet jeśli kod jest zrozumiały sam w sobie, warto dostarczyć dodatkową dokumentację w postaci komentarzy czy specyfikacji.
Nieskładny format kodu:

Nieskładny format kodu, z brakiem wcięć czy niestosowaniem konwencji, może znacznie utrudnić zrozumienie kodu.
Warto pamiętać, że ocena jakości kodu jest subiektywna, a dobre praktyki programistyczne mogą się różnić w zależności od kontekstu i technologii. Istotne jest, aby dbać o czytelność, utrzymanie, testowalność i efektywność kodu, dostosowując się jednocześnie do wymagań projektu oraz współpracując z innymi członkami zespołu.

41
Q

Piramida testów

A

Piramida testów to koncepcja, która przedstawia hierarchię różnych rodzajów testów w aplikacji. W tej hierarchii testy są rozmieszczone w formie piramidy, przy czym na dole znajdują się testy jednostkowe, a na szczycie testy end-to-end (E2E) lub testy akceptacyjne. Piramida ta reprezentuje optymalny rozkład testów dla efektywnego testowania oprogramowania. Poniżej przedstawiam szczegóły poszczególnych poziomów piramidy:

Testy Jednostkowe (Unit Tests):

Stanowią fundament piramidy. Testy jednostkowe sprawdzają pojedyncze komponenty (najczęściej metody) oprogramowania. Są napisane przez programistów i sprawdzają, czy poszczególne jednostki kodu działają poprawnie. Testy jednostkowe są szybkie do wykonania i dostarczają informacji zwrotnej na temat poprawności poszczególnych komponentów.
Testy Integracyjne (Integration Tests):

Weryfikują współpracę między różnymi komponentami lub modułami. Celem testów integracyjnych jest sprawdzenie, czy te komponenty współpracują ze sobą zgodnie z oczekiwaniami. Testy integracyjne pomagają upewnić się, że poszczególne jednostki są w stanie współpracować poprawnie jako całość.
Testy Akceptacyjne (Acceptance Tests):

Zwane również testami funkcjonalnymi lub testami użytkownika. Sprawdzają, czy cała aplikacja działa zgodnie z oczekiwaniami klienta. Testy akceptacyjne skupiają się na funkcjonalnościach dostarczanych klientowi, a nie na szczegółach implementacyjnych.
Testy End-to-End (E2E Tests):

Obejmują całą aplikację i symulują rzeczywiste scenariusze użytkowania. Testy E2E sprawdzają, czy poszczególne komponenty współpracują poprawnie, a także czy cała aplikacja działa zgodnie z oczekiwaniami użytkownika. Są najbardziej kosztowne pod względem czasu i zasobów, ale jednocześnie są najbliższe rzeczywistemu środowisku produkcyjnemu.
Idea piramidy testów zakłada, że większość testów powinna skupiać się na niższych poziomach (jednostkowe, integracyjne), a testy wyższego poziomu (akceptacyjne, E2E) powinny być ograniczone do niezbędnego minimum. Dzieje się tak, ponieważ testy jednostkowe i integracyjne są tańsze do napisania, szybsze w wykonaniu, łatwiejsze do utrzymania i dostarczają bardziej szczegółowych informacji zwrotnych na temat błędów.

Piramida testów pomaga w utrzymaniu równowagi między efektywnym pokryciem testami a minimalnym nakładem czasowym i zasobowym. W praktyce warto dostosować strukturę testów do konkretnego projektu i jego wymagań, ale ogólna idea piramidy testów jest często stosowana jako dobra praktyka w testowaniu oprogramowania.

42
Q

Wiążanie autowiring beanów

A

W kontekście frameworka Spring, wiązanie (binding) beanów dotyczy procesu łączenia komponentów i zarządzania ich cyklem życia w kontenerze Spring. Istnieją różne rodzaje wiązania beanów w Spring, z których najważniejsze to:

Wiązanie Statyczne (Static Binding):

Bean jest wiązany do kontenera Spring podczas konfiguracji (na etapie kompilacji). To oznacza, że informacje o beanie są dostępne przed uruchomieniem aplikacji. Wiązanie statyczne odbywa się na etapie konfiguracji kontekstu aplikacji, a konfiguracja beanów zwykle znajduje się w plikach XML lub klasach konfiguracyjnych.
Przykład konfiguracji w pliku XML:

xml
Copy code

<beans>
<bean></bean>
</beans>

Wiązanie Dynamiczne (Dynamic Binding):

Bean jest wiązany do kontenera Spring podczas uruchamiania aplikacji. Dynamiczne wiązanie umożliwia elastyczne zarządzanie beanami i ich konfiguracją. Może być realizowane za pomocą adnotacji, konfiguracji Java lub innych mechanizmów dostępnych w Spring.
Przykład wiązania dynamicznego za pomocą adnotacji:

java
Copy code
@Component
public class MyBean {
// …
}
Wiązanie przez Konstruktor (Constructor Binding):

Bean jest wiązany przez konstruktor. Kontener Spring wykorzystuje konstruktor do wstrzykiwania zależności. To jest często stosowane podejście, zwłaszcza w przypadku preferencji DI (Dependency Injection).
Przykład wiązania przez konstruktor w konfiguracji Java:

java
Copy code
@Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
return new MyBean(); // wstrzykiwanie przez konstruktor
}
}
Wiązanie przez Właściwości (Property Binding):

Bean jest wiązany przez właściwości (setter). Kontener Spring wstrzykuje zależności poprzez ustawianie wartości właściwości za pomocą setterów. Jest to jedno z najczęstszych podejść w DI.
Przykład wiązania przez właściwości w konfiguracji XML:

xml
Copy code

<beans>
<bean>
<property></property>
<property></property>
</bean>
</beans>

Wiązanie przez Metody (Method Binding):

Bean jest wiązany poprzez metody inicjalizacyjne lub destrukcyjne (init i destroy). Metody te są wywoływane podczas inicjalizacji i zakończenia cyklu życia beana.
Przykład wiązania przez metody w konfiguracji Java:

java
Copy code
@Bean(initMethod = “init”, destroyMethod = “destroy”)
public MyBean myBean() {
return new MyBean();
}
Wiązanie beanów w Spring umożliwia elastyczne zarządzanie zależnościami i cyklem życia komponentów. Wybór konkretnego rodzaju wiązania zależy od preferencji programisty oraz struktury i wymagań projektu. Większość nowoczesnych projektów w Spring korzysta z dynamicznego wiązania przy użyciu adnotacji, co ułatwia konfigurację i utrzymanie kodu.

43
Q

Glowne moduly w springu

A

Spring Framework to rozbudowany framework do tworzenia aplikacji w języku Java, a składa się z wielu modułów, z których każdy zajmuje się konkretnym obszarem funkcjonalności. Poniżej przedstawiam główne moduły w Springu:

Spring Core Container:

Spring Beans: Zapewnia mechanizmy zarządzania ziarnami (beanami), wstrzykiwania zależności (Dependency Injection), cyklu życia ziaren i konfiguracji aplikacji.
Spring Core: Zapewnia podstawowe funkcje frameworka, takie jak obsługa zdarzeń, zarządzanie własnością, obsługa wyjątków, konteksty aplikacji itp.
Spring AOP (Aspect-Oriented Programming):

Umożliwia programowanie aspektowe, co pozwala na oddzielenie funkcjonalności niezwiązanej z głównym kodem biznesowym (np. logging, transakcje, bezpieczeństwo). Wprowadza pojęcie aspektów, które są modularnymi jednostkami kodu.
Spring Data Access / JDBC:

Spring JDBC: Zapewnia uproszczone API do obsługi operacji bazodanowych, eliminując konieczność zarządzania połączeniem i zamykania zasobów ręcznie.
Spring ORM (Object-Relational Mapping): Umożliwia integrację z różnymi frameworkami ORM, takimi jak Hibernate, JPA, czy MyBatis, aby ułatwić pracę z bazami danych relacyjnymi.
Spring Transaction Management:

Zapewnia obsługę transakcji w aplikacji. Spring oferuje zarówno deklaratywną konfigurację transakcji za pomocą adnotacji, jak i programową obsługę transakcji.
Spring Model-View-Controller (MVC):

Implementuje wzorzec projektowy MVC w warstwie prezentacji. Umożliwia budowanie aplikacji internetowych opartych na wzorcu architektonicznym Model-View-Controller.
Spring Security:

Zapewnia funkcje zabezpieczeń, takie jak uwierzytelnianie, autoryzacja i zarządzanie dostępem w aplikacjach Spring. Jest używany do zabezpieczania zasobów przed nieautoryzowanym dostępem.
Spring REST:

Umożliwia rozwijanie aplikacji opartych na architekturze RESTful. Spring MVC oferuje wsparcie dla budowania interfejsów API HTTP (RESTful API).
Spring Batch:

Zapewnia obsługę przetwarzania wsadowego w aplikacjach. Jest używany do zarządzania i przetwarzania dużych ilości danych w regularnych, cyklicznych jobach.
Spring Integration:

Umożliwia integrację systemów poprzez wzorce integracyjne. Oferuje mechanizmy do implementacji przepływów danych między różnymi systemami.
Spring Test:

Dostarcza wsparcie dla testowania aplikacji Spring, zarówno jednostkowego, jak i integracyjnego. Oferuje narzędzia do testowania komponentów Spring, włączając w to wstrzykiwanie zależności.
Spring Boot:

Choć to nie jest tradycyjny moduł, Spring Boot zasługuje na uwagę. Jest to projekt nadbudowujący na Springu, który dostarcza narzędzi i konwencji umożliwiających szybkie i łatwe uruchamianie aplikacji bez konieczności skomplikowanej konfiguracji.
Te moduły stanowią jedynie fragment pełnej listy projektów w ramach rodziny Spring. Wybór konkretnych modułów zależy od potrzeb projektu i funkcjonalności, które są wymagane. Spring Framework jest bardzo elastyczny i umożliwia programistom korzystanie z tych modułów zgodnie z potrzebami aplikacji.

44
Q

Polimorfizm

A

Polimorfizm to jedno z podstawowych pojęć programowania obiektowego, które pozwala na traktowanie obiektów różnych klas w sposób jednolity. Pojęcie to jest związane z ideą multipleksowania wielu typów obiektów poprzez jednolite interfejsy i hierarchie klas.

Istnieją dwa główne rodzaje polimorfizmu: polimorfizm kompilacyjny (compile-time polymorphism) i polimorfizm czasu wykonania (runtime polymorphism).

Polimorfizm Kompilacyjny (Compile-Time Polymorphism):

Nazywany również polimorfizmem statycznym, występuje na etapie kompilacji kodu. Do tego rodzaju polimorfizmu należą przeciążanie funkcji (method overloading) i przeciążanie operatorów (operator overloading).

Przeciążanie funkcji (Method Overloading):
W ramach jednej klasy możemy zdefiniować wiele funkcji o tej samej nazwie, ale o różnych parametrach. Kompilator na podstawie przekazywanych argumentów wybiera odpowiednią wersję funkcji.

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;
} } Przeciążanie operatorów (Operator Overloading): Pozwala na zdefiniowanie własnych działań dla operatorów, takich jak +, -, itp., w kontekście własnych klas.

java
Copy code
public class Complex {
private double real;
private double imag;

public Complex(double real, double imag) {
    this.real = real;
    this.imag = imag;
}

public Complex add(Complex other) {
    return new Complex(this.real + other.real, this.imag + other.imag);
} } Polimorfizm Czasu Wykonania (Runtime Polymorphism):

Nazywany również polimorfizmem dynamicznym, występuje w trakcie działania programu. Jego głównym narzędziem są klasy abstrakcyjne, interfejsy i mechanizmy dziedziczenia.

Dziedziczenie i Przesłanianie Metod (Inheritance and Method Overriding):
Klasy potomne mogą nadpisywać (przesłaniać) metody swoich klas bazowych. W trakcie wykonywania programu, wywołanie metody zależy od rzeczywistego typu obiektu.

java
Copy code
public class Animal {
public void makeSound() {
System.out.println(“Animal makes a sound”);
}
}

public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println(“Dog barks”);
}
}

public class Main {
public static void main(String[] args) {
Animal animal = new Dog();
animal.makeSound(); // Wywoła metodę makeSound z klasy Dog
}
}
Interfejsy:
Interfejsy pozwalają na zdefiniowanie wspólnego zestawu metod, które różne klasy implementują. W ten sposób, różne klasy mogą być traktowane jednolicie poprzez interfejs, co umożliwia polimorfizm.

java
Copy code
public interface Shape {
void draw();
}

public class Circle implements Shape {
@Override
public void draw() {
System.out.println(“Drawing a circle”);
}
}

public class Square implements Shape {
@Override
public void draw() {
System.out.println(“Drawing a square”);
}
}
Polimorfizm pozwala na elastyczne korzystanie z różnych typów obiektów w czasie działania programu, co przyczynia się do zwiększenia elastyczności i zrozumiałości kodu. Wspólny interfejs i hierarchia klas umożliwiają traktowanie różnych obiektów w sposób jednolity, co jest jednym z kluczowych założeń programowania obiektowego.

45
Q

Dziedziczenie

A

Dziedziczenie jest jednym z fundamentalnych konceptów programowania obiektowego, pozwalającym na tworzenie hierarchii klas, w których klasy potomne (dziedziczące) mogą korzystać z cech i zachowań klas bazowych (nadrzędnych). Dziedziczenie wprowadza ponowne użycie kodu oraz umożliwia strukturyzację i hierarchizację kodu.

Poniżej omówię kluczowe aspekty dziedziczenia:

Klasa Bazowa (Nadrzędna) i Klasa Potomna (Dziedzicząca):

Klasa bazowa, zwana również klasą nadrzędną lub rodzicem, to klasa, która dostarcza pewne właściwości i metody. Klasa potomna, zwana również klasą dziedziczącą lub dzieckiem, dziedziczy te właściwości i metody od klasy bazowej.
java
Copy code
public class Animal {
void eat() {
System.out.println(“Animal is eating”);
}
}

public class Dog extends Animal {
void bark() {
System.out.println(“Dog is barking”);
}
}
Rozszerzenie Funkcjonalności (Rozszerzanie Klas):

Dziedziczenie umożliwia rozbudowywanie funkcjonalności klas poprzez dodawanie nowych metod i właściwości w klasach potomnych. Klasa potomna może wprowadzać nowe elementy, jednocześnie dziedzicząc funkcje klasy bazowej.
java
Copy code
public class Cat extends Animal {
void meow() {
System.out.println(“Cat is meowing”);
}
}
Overriding (Przesłanianie Metod):

Klasa potomna może przesłaniać (nadpisywać) metody z klasy bazowej. Jeśli klasa potomna dostarcza własną implementację danej metody, to ta implementacja jest wywoływana zamiast metody z klasy bazowej.
java
Copy code
public class Bird extends Animal {
@Override
void eat() {
System.out.println(“Bird is pecking”);
}
}
Klasy Abstrakcyjne:

Klasy abstrakcyjne to klasy, które nie mogą być instancjonowane, ale dostarczają pewne abstrakcyjne metody, które muszą być zaimplementowane przez klasy potomne. Są one używane, gdy chcemy narzucić pewne wymagania dla klas dziedziczących.
java
Copy code
public abstract class Shape {
abstract void draw();
}

public class Circle extends Shape {
@Override
void draw() {
System.out.println(“Drawing a circle”);
}
}
Interfejsy:

Interfejsy to kontrakty, które definiują zestaw metod, które klasa implementująca interfejs musi dostarczyć. W przeciwieństwie do klas abstrakcyjnych, interfejsy mogą być implementowane przez wiele klas, co umożliwia wielodziedziczenie.
java
Copy code
public interface Printable {
void print();
}

public class Document implements Printable {
@Override
public void print() {
System.out.println(“Printing document”);
}
}
Dziedziczenie umożliwia ponowne użycie kodu, zwiększa czytelność i ułatwia zarządzanie kodem. Jednak nadmierne korzystanie z dziedziczenia może prowadzić do nadmiernego sprzężenia (coupling) między klasami oraz problemów z hierarchią klas. W związku z tym, zaleca się zawsze rozważanie alternatywnych podejść, takich jak kompozycja czy interfejsy, w zależności od konkretnego kontekstu projektowego.

46
Q

Hermetyzacja enkapsulacja

A

Hermetyzacja (encapsulation) i enkapsulacja (encapsulation) to dwa pojęcia związane z programowaniem obiektowym, które pomagają w organizacji i zarządzaniu kodem poprzez ukrywanie implementacji detali wewnątrz klas. Oto ich omówienie:

Hermetyzacja (Encapsulation):

Hermetyzacja to proces ukrywania wewnętrznych detali implementacyjnych obiektu i udostępnianie jedynie tych elementów, które są niezbędne do korzystania z danego obiektu. W praktyce oznacza to, że pola klasy są prywatne, a dostęp do nich oraz ich modyfikacja odbywa się za pomocą publicznych metod zwanych getterami i setterami.
Przykład w języku Java:

java
Copy code
public class Car {
private String model;
private int year;

// Metody getter i setter umożliwiają dostęp do pól klasy
public String getModel() {
    return model;
}

public void setModel(String model) {
    this.model = model;
}

public int getYear() {
    return year;
}

public void setYear(int year) {
    if (year > 0) {
        this.year = year;
    }
} } Dzięki hermetyzacji, zmiany wewnętrznej implementacji klasy (np. zmiana typu pola) nie wpływają na inne części kodu, które korzystają z tej klasy.

Enkapsulacja (Encapsulation):

Enkapsulacja to szersze pojęcie, które odnosi się do ukrywania implementacji i grupowania jej w jednostki zwane klasami. Obejmuje hermetyzację, ale także inne aspekty organizacji kodu, takie jak grupowanie powiązanych funkcji w jednej klasie, zastosowanie interfejsów, dziedziczenie, itp.
Enkapsulacja pomaga w tworzeniu klarownego interfejsu dla użytkowników klasy, a jednocześnie ukrywa przed nimi wewnętrzne szczegóły implementacyjne. Zabezpiecza także przed niekontrolowanym dostępem i modyfikacją danych.

Przykład enkapsulacji:

java
Copy code
public class BankAccount {
private String accountNumber;
private double balance;

// Konstruktor, getter i setter dla accountNumber
// Getter i setter dla balance

public void deposit(double amount) {
    if (amount > 0) {
        balance += amount;
    }
}

public void withdraw(double amount) {
    if (amount > 0 && balance >= amount) {
        balance -= amount;
    }
} } W tym przykładzie, klasa BankAccount ukrywa implementację swoich pól i dostarcza interfejs w postaci metod deposit i withdraw do manipulowania stanem konta. To jest przykład enkapsulacji, gdzie szczegóły implementacyjne są ukrywane, a jednostki logiczne są dobrze zorganizowane.

Hermetyzacja i enkapsulacja są kluczowymi koncepcjami programowania obiektowego, które pomagają w tworzeniu trwałego, elastycznego i bezpiecznego kodu. Dzięki nim, zmiany wewnątrz jednej klasy nie wpływają bezpośrednio na pozostałe części systemu.

47
Q

Modyfikatory dostępu

A

W językach programowania, modyfikatory są słowami kluczowymi używanymi do kontrolowania dostępu, zakresu i zachowania klas, metod, zmiennych czy innych elementów kodu. Poniżej omówię najczęściej używane modyfikatory w języku Java, ale podobne koncepcje istnieją w innych językach programowania.

Modyfikatory Dostępu:

Kontrolują, które inne klasy mają dostęp do danego elementu.

public: Oznacza, że element jest dostępny dla wszystkich klas, zarówno w obrębie pakietu, jak i z zewnątrz pakietu.

java
Copy code
public class Example {
public int publicField;
public void publicMethod() {
// …
}
}
protected: Oznacza, że element jest dostępny w obrębie tej samej klasy, w obrębie pakietu oraz dla klas potomnych.

java
Copy code
class Example {
protected int protectedField;
protected void protectedMethod() {
// …
}
}
default (bez modyfikatora): Oznacza, że element jest dostępny tylko w obrębie tego samego pakietu (package-private).

java
Copy code
class Example {
int defaultField;
void defaultMethod() {
// …
}
}
private: Oznacza, że element jest dostępny tylko w obrębie tej samej klasy.

java
Copy code
class Example {
private int privateField;
private void privateMethod() {
// …
}
}
Modyfikatory Zakresu:

Kontrolują widoczność danego elementu w określonym zakresie, np. w obrębie metody, bloku kodu, klasy.

final: Oznacza, że element jest niezmienialny lub nieprzeszukiwalny, w zależności od kontekstu.

java
Copy code
final int constantValue = 10;
static: Oznacza, że element jest współdzielony między wszystkimi instancjami danej klasy, a nie należy do konkretnego obiektu.

java
Copy code
class Example {
static int staticField;
static void staticMethod() {
// …
}
}
abstract: Oznacza, że element jest deklarowany, ale nie ma implementacji i musi być zaimplementowany w klasie dziedziczącej.

java
Copy code
abstract class AbstractExample {
abstract void abstractMethod();
}
synchronized: Oznacza, że dostęp do danego bloku kodu jest synchronizowany, co może pomóc w unikaniu problemów z wielowątkowością.

java
Copy code
class Example {
synchronized void synchronizedMethod() {
// …
}
}
Modyfikatory Specjalne:

transient: Oznacza, że pole klasy nie jest uwzględniane podczas procesu serializacji.

java
Copy code
class Example implements Serializable {
transient int transientField;
}
volatile: Oznacza, że pole klasy może być modyfikowane przez wiele wątków, a dostęp do niego jest zawsze aktualny.

java
Copy code
class Example {
volatile int volatileField;
}
strictfp: Oznacza, że metoda lub klasa korzystają z restrykcyjnych zasad zmiennoprzecinkowej, co ma zagwarantować takie same wyniki na różnych platformach.

java
Copy code
strictfp class Example {
strictfp void strictfpMethod() {
// …
}
}
Modyfikatory są istotnym narzędziem w programowaniu obiektowym, które pomagają w tworzeniu bezpiecznego i elastycznego kodu. Odpowiedni wybór modyfikatorów zależy od konkretnego kontekstu i wymagań projektu.

48
Q

Super()

A

super() to wywołanie konstruktora klasy nadrzędnej (superklasy) wewnątrz konstruktora klasy podrzędnej (podklasy) w języku Java. Służy to do inicjalizacji pól i wykonania kodu związanej klasy nadrzędnej przed rozpoczęciem inicjalizacji pól i kodu klasy podrzędnej.

Główne zastosowanie super() można znaleźć w przypadku, gdy klasa podrzędna rozszerza klasę nadrzędną, a konstruktor klasy podrzędnej wymaga wykonania pewnych operacji zdefiniowanych w konstruktorze klasy nadrzędnej.

Przykładowa sytuacja:

java
Copy code
class Animal {
String name;

Animal(String name) {
    this.name = name;
}

void makeSound() {
    System.out.println("Animal makes a sound");
} }

class Dog extends Animal {
String breed;

Dog(String name, String breed) {
    // Wywołanie konstruktora klasy nadrzędnej (Animal)
    super(name);

    // Inicjalizacja pól klasy podrzędnej
    this.breed = breed;
}

@Override
void makeSound() {
    System.out.println("Dog barks");
}

void printDetails() {
    System.out.println("Name: " + name);
    System.out.println("Breed: " + breed);
} }

public class Main {
public static void main(String[] args) {
// Tworzenie obiektu klasy Dog
Dog myDog = new Dog(“Buddy”, “Labrador”);

    // Wywołanie metody z klasy nadrzędnej
    myDog.makeSound();

    // Wywołanie metody z klasy podrzędnej
    myDog.printDetails();
} } W tym przykładzie konstruktor klasy Dog wywołuje konstruktor klasy Animal przy użyciu super(name), co umożliwia przekazanie nazwy zwierzęcia do klasy nadrzędnej. Dzięki temu można zainicjować pola klasy nadrzędnej przed inicjalizacją pól klasy podrzędnej.
49
Q

final

A

Słowo kluczowe final w języku Java może być używane w różnych kontekstach, a jego zastosowanie zależy od rodzaju elementu, do którego się odnosi. Poniżej omówię kilka zastosowań final w różnych kontekstach:

Dla Zmiennych:

Gdy final jest stosowane do zmiennej, oznacza to, że raz zainicjowana, jej wartość nie może być zmieniona.
java
Copy code
public class Example {
final int constantValue = 10;

void modifyValue() {
    // Błąd kompilacji - wartość nie może być zmieniona
    // constantValue = 20;
} } Dla Metod:

Gdy final jest stosowane do metody w klasie, oznacza to, że metoda nie może być nadpisana w klasach potomnych.
java
Copy code
public class Parent {
final void finalMethod() {
// implementacja metody
}
}

public class Child extends Parent {
// Błąd kompilacji - nie można nadpisać finalMethod
// @Override
// void finalMethod() {
// // nowa implementacja
// }
}
Dla Klas:

Gdy final jest stosowane do klasy, oznacza to, że klasa nie może mieć klas potomnych (nie może być dziedziczona).
java
Copy code
final class FinalClass {
// implementacja klasy
}

// Błąd kompilacji - nie można dziedziczyć po FinalClass
// class Subclass extends FinalClass {
// // implementacja
// }
Dla Argumentów Metod:

Gdy final jest stosowane do argumentu metody, oznacza to, że wartość tego argumentu nie może być zmieniana w trakcie wykonywania metody.
java
Copy code
public class Example {
void methodWithFinalArgument(final int value) {
// Błąd kompilacji - nie można zmienić wartości argumentu
// value = 20;
}
}
Dla Referencji do Obiektu:

Gdy final jest stosowane do referencji do obiektu, oznacza to, że referencja ta nie może zostać zmieniona, ale obiekt, do którego referencja wskazuje, może być modyfikowany.
java
Copy code
public class Example {
final int[] array = {1, 2, 3};

void modifyArray() {
    // To jest dozwolone - można modyfikować zawartość tablicy
    array[0] = 10;

    // Błąd kompilacji - nie można zmienić referencji array
    // array = new int[]{4, 5, 6};
} } Ogólnie rzecz biorąc, final jest używane do określenia, że dany element nie może być modyfikowany po jego zainicjowaniu lub definiowaniu. Ma to zastosowanie zarówno do zmiennych, metod, klas, argumentów metody, jak i referencji do obiektów. Stosując final, programista sygnalizuje swoje intencje dotyczące niemodyfikowalności danego elementu, co może poprawić czytelność i zrozumiałość kodu.
50
Q

Static

A

Słowo kluczowe static w języku Java ma kilka zastosowań, które nadają różnym elementom programu specjalne właściwości. Poniżej omówię główne zastosowania static:

Dla Zmiennych (Pól Klasowych):

Gdy static jest stosowane do zmiennej (pola klasy), oznacza to, że zmienna ta jest współdzielona między wszystkimi instancjami danej klasy. Każda instancja klasy ma dostęp do tej samej kopii zmiennej.
java
Copy code
public class Example {
static int staticVariable = 10;

public static void main(String[] args) {
    Example obj1 = new Example();
    Example obj2 = new Example();

    System.out.println(obj1.staticVariable); // 10
    System.out.println(obj2.staticVariable); // 10

    obj1.staticVariable = 20;

    System.out.println(obj1.staticVariable); // 20
    System.out.println(obj2.staticVariable); // 20
} } Dla Metod Klasowych (Metody Statyczne):

Gdy static jest stosowane do metody, oznacza to, że metoda ta należy do klasy, a nie do konkretnego obiektu. Może być wywoływana bez konieczności tworzenia instancji klasy.
java
Copy code
public class Example {
static void staticMethod() {
System.out.println(“This is a static method.”);
}

public static void main(String[] args) {
    Example.staticMethod(); // Wywołanie metody statycznej bez tworzenia instancji
} } Blok Inicjalizacyjny (Statyczny Blok Inicjalizacyjny):

Bloki inicjalizacyjne są używane do inicjalizacji zmiennych lub wykonania pewnych czynności podczas tworzenia instancji klasy lub przy ładowaniu klasy. Blok inicjalizacyjny oznaczony jako static jest wykonywany tylko raz, gdy klasa jest ładowana.
java
Copy code
public class Example {
static {
System.out.println(“Static block is executed.”);
}

public static void main(String[] args) {
    // Klasa Example zostanie załadowana, co spowoduje wykonanie bloku statycznego
} } Dla Zmiennych Lokalnych w Metodach Statycznych:

Gdy metoda jest statyczna, to zmienne lokalne w tej metodzie również muszą być statyczne.
java
Copy code
public class Example {
static int staticVariable = 10;

static void staticMethod() {
    int localVariable = 5; // Błąd kompilacji - zmienna lokalna w metodzie statycznej musi być statyczna
} } Dla Klas Wewnętrznych:

Jeśli klasa wewnętrzna jest statyczna, oznacza to, że nie musi być powiązana z instancją klasy zewnętrznej i może być używana niezależnie.
java
Copy code
public class OuterClass {
static class StaticInnerClass {
// …
}
}
Użycie static może zmieniać kontekst i zachowanie danego elementu w programie. Jest to szczególnie przydatne w sytuacjach, gdy chcemy współdzielić pewne zasoby między różnymi instancjami klasy, lub gdy mamy do czynienia z funkcjami ogólnymi dla klasy, a nie związane z konkretną instancją.

51
Q

Przeciązanie metod overloading

A

Przeciążanie metod (method overloading) w języku programowania polega na definiowaniu wielu metod o tej samej nazwie w obrębie danej klasy, ale o różnych zestawach parametrów. Kluczowym elementem przeciążania metod jest różnica w liczbie lub typie parametrów. Wartością zwracaną, nazwa metody lub typ zwracany nie są brane pod uwagę przy przeciążaniu metod.

Oto kilka przykładów przeciążania metod:

Różne Typy Parametrów:

java
Copy code
public class Calculator {
// Przeciążenie metody add dla różnych typów parametrów
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;
} } Różna Liczba Parametrów:

java
Copy code
public class Printer {
// Przeciążenie metody print dla różnej liczby parametrów
public void print(String message) {
System.out.println(message);
}

public void print(String message, int copies) {
    for (int i = 0; i < copies; i++) {
        System.out.println(message);
    }
} } Przeciążanie Metod z Dziedziczeniem: Przeciążanie metod może również obejmować dziedziczenie. Metody w klasie potomnej mogą być przeciążane w odniesieniu do metod z klasy bazowej.

java
Copy code
public class Animal {
public void makeSound() {
System.out.println(“Animal makes a sound”);
}
}

public class Dog extends Animal {
// Przeciążanie metody makeSound
@Override
public void makeSound() {
System.out.println(“Dog barks”);
}

// Przeciążanie metody makeSound z różnymi parametrami
public void makeSound(int volume) {
    System.out.println("Dog barks with volume: " + volume);
} } Podczas wywoływania przeciążonej metody, kompilator decyduje, która wersja metody zostanie użyta, na podstawie ilości i typów przekazywanych argumentów. Overloading pozwala na stworzenie bardziej elastycznego interfejsu API, gdzie różne wersje metody mogą obsługiwać różne przypadki użycia, co przyczynia się do czytelności i zwięzłości kodu.
52
Q

Przesłanianie metod overriding

A

Przesłanianie metod (method overriding) w języku programowania obiektowego oznacza dostarczenie nowej implementacji już istniejącej metody w klasie potomnej, która jest dziedziczona po klasie bazowej. Przesłanianie metod pozwala na dostosowanie zachowania metody w klasie potomnej do własnych potrzeb, przy jednoczesnym zachowaniu jej sygnatury.

Oto podstawowe cechy przesłaniania metod:

Zakres Przesłaniania:

Metoda w klasie potomnej musi mieć taką samą sygnaturę (nazwę, typy parametrów, typ zwracany) jak metoda w klasie bazowej.
Override Annotation:

W języku Java używa się adnotacji @Override, aby jawnie oznaczyć, że metoda w klasie potomnej jest przesłanianiem metody z klasy bazowej. Jest to zalecane, ale nieobowiązkowe.
java
Copy code
class Animal {
void makeSound() {
System.out.println(“Animal makes a sound”);
}
}

class Dog extends Animal {
@Override
void makeSound() {
System.out.println(“Dog barks”);
}
}
Typ Zwracany:

Typ zwracany metody w klasie potomnej może być identyczny lub być typem pochodnym od typu zwracanego metody w klasie bazowej.
java
Copy code
class Animal {
Animal reproduce() {
// implementacja reprodukcji zwierzęcia
return new Animal();
}
}

class Dog extends Animal {
@Override
Dog reproduce() {
// implementacja reprodukcji psa
return new Dog();
}
}
Zastosowanie w Hierarchii Dziedziczenia:

Przesłanianie metod jest często stosowane w hierarchii dziedziczenia, gdzie klasy potomne dostarczają specjalizowane wersje metod zdefiniowanych w klasie bazowej.
java
Copy code
class Shape {
void draw() {
System.out.println(“Drawing a shape”);
}
}

class Circle extends Shape {
@Override
void draw() {
System.out.println(“Drawing a circle”);
}
}

class Square extends Shape {
@Override
void draw() {
System.out.println(“Drawing a square”);
}
}
Wywoływanie Metody Bazowej:

W przypadku przesłaniania metod, klasy potomne mogą używać słowa kluczowego super do wywołania wersji metody z klasy bazowej.
java
Copy code
class Animal {
void makeSound() {
System.out.println(“Animal makes a sound”);
}
}

class Dog extends Animal {
@Override
void makeSound() {
super.makeSound(); // Wywołanie metody makeSound z klasy bazowej
System.out.println(“Dog barks”);
}
}
Przesłanianie metod jest ważnym mechanizmem w programowaniu obiektowym, który umożliwia tworzenie hierarchii dziedziczenia z elastycznym zachowaniem dla poszczególnych klas. Dzięki temu, można dostosować działanie klasy potomnej do specyficznych wymagań, jednocześnie korzystając z ogólnego interfejsu zdefiniowanego w klasie bazowej.

53
Q

String metody

A

Klasa String w języku Java oferuje wiele przydatnych metod i funkcji do manipulacji i operacji na ciągach znaków. Oto kilka popularnych metod dostępnych w klasie String:

Tworzenie Stringa:

String str = “Hello, World!”;
String str = new String(“Hello, World!”);
Długość Stringa:

int length = str.length();
Pobieranie Znaku na Konkretnym Indeksie:

char ch = str.charAt(index);
Porównywanie Stringów:

boolean isEqual = str1.equals(str2);
boolean isEqualIgnoreCase = str1.equalsIgnoreCase(str2);
Porównywanie z Instrukcją Warunkową:

int result = str1.compareTo(str2);
int resultIgnoreCase = str1.compareToIgnoreCase(str2);
Sprawdzanie Czy Zaczyna Się / Kończy na danym Ciągu:

boolean startsWith = str.startsWith(“Hello”);
boolean endsWith = str.endsWith(“World!”);
Pobieranie Podciągu:

String substring = str.substring(startIndex, endIndex);
Zamiana na Małe / Duże Litery:

String lowercase = str.toLowerCase();
String uppercase = str.toUpperCase();
Usuwanie Białych Znaków:

String trimmed = str.trim();
Podmiana Znaków:

String replaced = str.replace(oldChar, newChar);
String replacedStr = str.replaceAll(regex, replacement);
Dzielenie Stringa na Podciągi:

String[] parts = str.split(delimiter);
Sprawdzanie Czy String Zawiera Inny String:

boolean contains = str.contains(“Hello”);
Konwersja na Tablicę Znaków:

char[] charArray = str.toCharArray();
Konwersja Stringa na Liczbę:

int intValue = Integer.parseInt(str);
double doubleValue = Double.parseDouble(str);
Formatowanie Stringa:

String formatted = String.format(“Hello, %s!”, name);
Wyszukiwanie Indeksu danego Znaku / Ciągu:

int indexOfChar = str.indexOf(‘o’);
int indexOfStr = str.indexOf(“World”);
Odwrotność Stringa:

String reversed = new StringBuilder(str).reverse().toString();
Te to tylko przykładowe metody dostępne w klasie String. Klasa ta oferuje również inne funkcje, a dokładna dokumentacja znajduje się w oficjalnej dokumentacji języka Java.

54
Q

Porównywanie Stringów

A

Porównywanie stringów w języku Java może być realizowane na różne sposoby, w zależności od tego, czy chcemy porównać zawartość stringów, czy referencje do obiektów. Poniżej omówię kilka metod porównywania stringów:

Porównywanie Zawartości:

equals(): Metoda equals() porównuje zawartość dwóch stringów i zwraca wartość logiczną true, jeśli są one identyczne, a false, jeśli różnią się chociażby jednym znakiem.

java
Copy code
String str1 = “Hello”;
String str2 = “Hello”;

boolean isEqual = str1.equals(str2); // true
equalsIgnoreCase(): Metoda equalsIgnoreCase() działa podobnie do equals(), ale ignoruje wielkość liter.

java
Copy code
String str1 = “Hello”;
String str2 = “hello”;

boolean isEqualIgnoreCase = str1.equalsIgnoreCase(str2); // true
Porównywanie z Instrukcją Warunkową:

compareTo(): Metoda compareTo() porównuje dwa stringi leksykograficznie. Zwraca liczbę całkowitą większą niż zero, jeśli pierwszy string jest leksykograficznie większy, mniejszą niż zero, jeśli jest mniejszy, i zero, jeśli są równe.

java
Copy code
String str1 = “Apple”;
String str2 = “Banana”;

int result = str1.compareTo(str2); // Ujemna liczba
compareToIgnoreCase(): Podobnie jak compareTo(), ale ignoruje wielkość liter.

java
Copy code
String str1 = “apple”;
String str2 = “Banana”;

int resultIgnoreCase = str1.compareToIgnoreCase(str2); // Zero
Porównywanie Referencji:

Operator ==: Porównywanie referencji. Sprawdza, czy obie referencje wskazują na ten sam obiekt w pamięci.

java
Copy code
String str1 = “Hello”;
String str2 = “Hello”;

boolean areSameReference = (str1 == str2); // true
W przypadku porównywania zawartości stringów, zwykle używa się equals() lub equalsIgnoreCase(), ponieważ te metody uwzględniają faktyczną równość znaków, a nie tylko referencji. Porównywanie referencji za pomocą == może prowadzić do błędnych wyników w przypadku stringów utworzonych przy użyciu operatora new.

Pamiętaj, że prawidłowe porównywanie stringów zależy od kontekstu i tego, czy chcesz sprawdzić równość zawartości czy referencji. W większości przypadków preferowane jest użycie equals() do porównywania zawartości stringów.

55
Q

Niemutowalność

A

Niemutowalność (immutability) odnosi się do cechy obiektu, którego stan nie może być zmieniany po jego utworzeniu. W kontekście języka Java, niemutowalność często dotyczy klas, które są tworzone jako obiekty niemodyfikowalne, czyli ich wartości nie mogą być zmieniane po utworzeniu instancji.

String w języku Java jest przykładem niemutowalnej klasy. Oznacza to, że raz utworzony obiekt typu String nie może być zmieniany. Każda operacja, która wydaje się modyfikować łańcuch znaków, w rzeczywistości tworzy nowy obiekt. Oto kilka aspektów niemutowalności w kontekście Stringów:

Tworzenie Nowego Obiektu przy Zmianie:

java
Copy code
String original = “Hello”;
String modified = original + “, World!”;
// original pozostaje niezmienione, a modified wskazuje na nowy obiekt
Metody Nie Modyfikujące Oryginalnego Stringa:

java
Copy code
String original = “Hello”;
String upperCase = original.toUpperCase();
// original pozostaje niezmienione, a upperCase wskazuje na nowy obiekt
StringBuilder vs. StringBuffer:

W przeciwieństwie do Stringa, klasy StringBuilder i StringBuffer w Javie są mutowalne, co oznacza, że ich zawartość może być zmieniana bez konieczności tworzenia nowych obiektów. W praktyce są bardziej wydajne, gdy trzeba wykonywać wiele operacji zmian na łańcuchu znaków.
java
Copy code
StringBuilder mutable = new StringBuilder(“Hello”);
mutable.append(“, World!”);
// mutable jest nadal tym samym obiektem, ale jego zawartość została zmieniona
Niemutowalność Stringów ma kilka korzyści, takich jak:

Bezpieczeństwo Wątkowe: Ponieważ obiekty niemodyfikowalne są bezpieczne wątkowo, można je bezpiecznie udostępniać między wieloma wątkami bez obawy o konflikty z dostępem.
Porównywalność: Łatwiejsze porównywanie i używanie w strukturach danych, ponieważ niezmienione obiekty są stałe.
Warto również pamiętać, że niemutowalność nie oznacza, że obiekty niemodyfikowalne są bardziej kosztowne pod względem zasobów. W przypadku Stringów, Java wprowadza optymalizacje, takie jak pula literałów, co sprawia, że manipulacje na Stringach są wydajne. Jednak przy tworzeniu niemodyfikowalnych klas własnych, ważne jest, aby odpowiednio zarządzać efektywnością.

56
Q

Typy proste java

A

W języku Java istnieje osiem podstawowych typów danych, znanych również jako typy proste (primitive types). Oto lista tych typów:

byte:

Rozmiar: 8 bitów
Zakres: -128 do 127
short:

Rozmiar: 16 bitów
Zakres: -32,768 do 32,767
int:

Rozmiar: 32 bity
Zakres: -2^31 do 2^31 - 1
long:

Rozmiar: 64 bity
Zakres: -2^63 do 2^63 - 1
float:

Rozmiar: 32 bity
Zakres: Zmiennoprzecinkowe, około 7 cyfr znaczących
double:

Rozmiar: 64 bity
Zakres: Zmiennoprzecinkowe, około 15 cyfr znaczących
char:

Rozmiar: 16 bitów
Zakres: Znak Unicode, 0 do 65,535
boolean:

Zakres: true lub false
Przykłady deklaracji i inicjalizacji zmiennych typów prostych:

java
Copy code
byte age = 25;
short temperature = -10;
int count = 1000;
long population = 1234567890L; // L oznacza, że liczba jest typu long
float price = 99.99f; // f oznacza, że liczba jest typu float
double pi = 3.141592653589793;
char initial = ‘A’;
boolean isJavaFun = true;
Wartości przypisane do zmiennych typów prostych przechowywane są bezpośrednio w pamięci, co odróżnia je od typów obiektowych, które przechowują referencje do obiektów. Typy proste są efektywne pod względem zużycia pamięci i szybkości, ponieważ są reprezentowane bezpośrednio jako bity w pamięci komputera.

57
Q

typy złożone java

A

Typy Złożone (Reference Types):
Typy złożone są to typy, które przechowują referencje do obiektów w pamięci, a nie same wartości. Są bardziej złożone i umożliwiają przechowywanie bardziej skomplikowanych danych.

Klasy:

Tworzone przez programistę, reprezentują obiekty i umożliwiają tworzenie niestandardowych struktur danych.
java
Copy code
class Person {
String name;
int age;
}

Person person = new Person();
person.name = “John”;
person.age = 25;
Tablice:

Struktury danych przechowujące elementy jednego typu.
java
Copy code
int[] numbers = {1, 2, 3, 4, 5};
Interfejsy:

Definiują zestaw metod, które klasa musi zaimplementować.
java
Copy code
interface Printable {
void print();
}
Enumy:

Typy wyliczeniowe, które pozwalają zdefiniować zestaw stałych.
java
Copy code
enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
Inne Typy Złożone:

Inne złożone typy danych, takie jak String (choć technicznie jest to klasa), ArrayList, HashMap itp.
java
Copy code
String text = “Hello, World!”;
Typy proste są bardziej efektywne pod względem pamięci i szybkości, ponieważ przechowują wartości bezpośrednio, podczas gdy typy złożone są bardziej elastyczne i umożliwiają bardziej zaawansowane operacje na danych.

58
Q

Kolekcje

A

W języku Java kolekcje są strukturami danych, które służą do przechowywania i manipulowania grupami obiektów. Kolekcje dostarczają gotowe implementacje różnych struktur danych, takich jak listy, zbiory i mapy, co ułatwia programistom pracę z danymi w aplikacjach. Poniżej znajduje się krótkie omówienie najważniejszych interfejsów i klas kolekcji w języku Java.

Interfejsy Kolekcji:
List (java.util.List):

Interfejs reprezentujący uporządkowaną kolekcję elementów, gdzie każdy element ma swój indeks.
Implementacje: ArrayList, LinkedList, Vector.
java
Copy code
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Python");
list.add("C++");
Set (java.util.Set):</String>

Interfejs reprezentujący kolekcję unikalnych elementów, bez zachowania porządku.
Implementacje: HashSet, LinkedHashSet, TreeSet.
java
Copy code
Set<Integer> set = new HashSet<>();
set.add(1);
set.add(2);
set.add(3);
Queue (java.util.Queue):</Integer>

Interfejs reprezentujący kolejkę, gdzie elementy są dodawane na końcu i usuwane z początku (FIFO - First-In-First-Out).
Implementacje: LinkedList, PriorityQueue.
java
Copy code
Queue<String> queue = new LinkedList<>();
queue.offer("First");
queue.offer("Second");
queue.poll(); // Zdejmuje pierwszy element
Map (java.util.Map):</String>

Interfejs reprezentujący kolekcję par klucz-wartość, gdzie klucze są unikalne.
Implementacje: HashMap, LinkedHashMap, TreeMap.
java
Copy code
Map<String, Integer> map = new HashMap<>();
map.put(“One”, 1);
map.put(“Two”, 2);
map.put(“Three”, 3);
Klasy Kolekcji:
ArrayList (java.util.ArrayList):

Implementacja listy opartej na tablicy dynamicznej.
java
Copy code
List<String> arrayList = new ArrayList<>();
LinkedList (java.util.LinkedList):</String>

Implementacja listy opartej na podwójnie wiązanej liście.
java
Copy code
List<String> linkedList = new LinkedList<>();
HashSet (java.util.HashSet):</String>

Implementacja zbioru oparta na funkcji haszującej.
java
Copy code
Set<String> hashSet = new HashSet<>();
HashMap (java.util.HashMap):</String>

Implementacja mapy oparta na funkcji haszującej.
java
Copy code
Map<String, Integer> hashMap = new HashMap<>();
TreeSet (java.util.TreeSet):

Implementacja zbioru oparta na drzewie (uporządkowana).
java
Copy code
Set<String> treeSet = new TreeSet<>();
TreeMap (java.util.TreeMap):</String>

Implementacja mapy oparta na drzewie (uporządkowana).
java
Copy code
Map<String, Integer> treeMap = new TreeMap<>();
Kolekcje w Javie umożliwiają programistom efektywne zarządzanie danymi, operacje na danych, a także są kluczowym elementem wielu algorytmów i struktur danych w programowaniu.

59
Q

Klasa wrapper

A

Klasy wrapper w języku Java są klasami, które zapewniają obudowę (wrapper) wokół typów prymitywnych, przekształcając je w obiekty. Ponieważ typy prymitywne nie są obiektami, korzystanie z klas wrapper umożliwia korzystanie z nich w kontekście, który wymaga obiektów. Klasy wrapper są często używane w obszarach, gdzie wymagane są obiekty, takie jak kolekcje, generyki, operacje na napisach, a także w programowaniu ogólniejszym, gdzie wymagane są obiekty, a nie typy prymitywne.

Poniżej przedstawione są klasy wrapper dla podstawowych typów prymitywnych:

Integer (java.lang.Integer):

Obudowuje typ prymitywny int.
java
Copy code
Integer intObject = 42;
int intValue = intObject.intValue(); // Konwersja na int
Double (java.lang.Double):

Obudowuje typ prymitywny double.
java
Copy code
Double doubleObject = 3.14;
double doubleValue = doubleObject.doubleValue(); // Konwersja na double
Character (java.lang.Character):

Obudowuje typ prymitywny char.
java
Copy code
Character charObject = ‘A’;
char charValue = charObject.charValue(); // Konwersja na char
Boolean (java.lang.Boolean):

Obudowuje typ prymitywny boolean.
java
Copy code
Boolean booleanObject = true;
boolean booleanValue = booleanObject.booleanValue(); // Konwersja na boolean
Byte (java.lang.Byte):

Obudowuje typ prymitywny byte.
java
Copy code
Byte byteObject = 8;
byte byteValue = byteObject.byteValue(); // Konwersja na byte
Short (java.lang.Short):

Obudowuje typ prymitywny short.
java
Copy code
Short shortObject = 16;
short shortValue = shortObject.shortValue(); // Konwersja na short
Long (java.lang.Long):

Obudowuje typ prymitywny long.
java
Copy code
Long longObject = 1000L;
long longValue = longObject.longValue(); // Konwersja na long
Klasy wrapper dostarczają również różne metody i operacje pomocnicze, takie jak parsowanie z napisu (valueOf), konwersje między różnymi typami prymitywnymi, a także statyczne metody ułatwiające pracę z danymi prymitywnymi. Klasy te są używane w wielu kontekstach, w których wymagane są obiekty, a nie typy prymitywne.

60
Q

Boxing unboxing

A

Boxing i unboxing to procesy konwersji między typami prymitywnymi a ich odpowiednimi klasami wrapper.

Boxing:

Boxing to proces, w którym typ prymitywny jest konwertowany na odpowiadającą mu klasę wrapper.
Java automatycznie wykonuje boxing w wielu sytuacjach, kiedy typ prymitywny jest używany w kontekście, który wymaga obiektu. Na przykład, kiedy dodajesz typ prymitywny do kolekcji, przekazujesz go jako argument do metody przyjmującej obiekt itp.
java
Copy code
int primitiveInt = 42;
Integer wrappedInt = primitiveInt; // Boxing automatyczny
Unboxing:

Unboxing to proces, w którym obiekt klasy wrapper jest konwertowany z powrotem na odpowiadający mu typ prymitywny.
Java automatycznie wykonuje unboxing w wielu sytuacjach, kiedy obiekt klasy wrapper jest używany w kontekście, który wymaga typu prymitywnego.
java
Copy code
Integer wrappedInt = 42;
int primitiveInt = wrappedInt; // Unboxing automatyczny
Automatyczne boxing i unboxing są dostępne od Java 5, co znacznie ułatwia pracę z typami prymitywnymi w kontekstach, które wymagają obiektów, takich jak kolekcje czy generyki. Jednak należy pamiętać, że te operacje mogą wpływać na wydajność w niektórych sytuacjach, więc warto zwracać uwagę na ich używanie, szczególnie w kodzie wymagającym dużej wydajności.

61
Q

złożonośc obliczeniowa

A

Złożoność obliczeniowa to koncepcja analizy algorytmów, która ocenia, jak szybko rośnie liczba operacji wykonywanych przez algorytm w miarę wzrostu rozmiaru danych wejściowych. Jest to miara, która pozwala oszacować, jak efektywny jest algorytm pod względem zużycia zasobów obliczeniowych, takich jak czas i pamięć.

Rodzaje Złożoności Obliczeniowej:
Złożoność Czasowa (Time Complexity):

Oznacza, ile czasu potrzebuje algorytm do zakończenia wykonania w zależności od rozmiaru danych wejściowych.
Zwykle wyrażana jest jako funkcja rozmiaru danych wejściowych, np. O(n), gdzie n to rozmiar danych.
Złożoność Pamięciowa (Space Complexity):

Oznacza, ile pamięci jest potrzebne do wykonania algorytmu w zależności od rozmiaru danych wejściowych.
Podobnie jak złożoność czasowa, wyrażana jest jako funkcja rozmiaru danych wejściowych.
Oznaczenia Złożoności:
O-Notacja (Notacja Duże O):

Reprezentuje górne ograniczenie na wzrost liczby operacji w algorytmie w zależności od rozmiaru danych wejściowych.
Na przykład, O(n) oznacza, że czas lub pamięć algorytmu rośnie liniowo wraz z rozmiarem danych.
Omega-Notacja (Notacja Duże Omega):

Reprezentuje dolne ograniczenie na wzrost liczby operacji w algorytmie w zależności od rozmiaru danych wejściowych.
Na przykład, Ω(n) oznacza, że czas lub pamięć algorytmu rośnie co najmniej liniowo wraz z rozmiarem danych.
Theta-Notacja (Notacja Duże Theta):

Reprezentuje dokładne oszacowanie wzrostu liczby operacji w algorytmie w zależności od rozmiaru danych wejściowych.
Na przykład, Θ(n) oznacza, że czas lub pamięć algorytmu rośnie dokładnie liniowo wraz z rozmiarem danych.
Przykłady Złożoności Obliczeniowej:
O(1) - Stała Złożoność:

Złożoność stała, niezależna od rozmiaru danych.
O(log n) - Logarytmiczna Złożoność:

Przykładem jest algorytm dziel i zwyciężaj, taki jak binary search.
O(n) - Liniowa Złożoność:

Liczba operacji rośnie liniowo wraz z rozmiarem danych.
O(n log n) - Złożoność liniowo-logarytmiczna:

Przykładem jest algorytm sortowania szybkiego (QuickSort) czy sortowania przez scalanie (MergeSort).
O(n^2) - Kwadratowa Złożoność:

Liczba operacji rośnie kwadratowo wraz z rozmiarem danych.
O(2^n) - Wykładnicza Złożoność:

Przykładem jest algorytm rozwiązujący problem komiwojażera za pomocą brute-force.
Analiza złożoności obliczeniowej jest kluczowym aspektem projektowania i analizy algorytmów, umożliwiając programistom i inżynierom optymalizację ich kodu pod kątem efektywności czasowej i pamięciowej.

62
Q

Kompozycja

A

Kompozycja w programowaniu obiektowym to technika, w której jednen obiekt zawiera w sobie inne obiekty, a relacja między nimi jest tak silna, że jeden obiekt nie może istnieć bez drugiego. Innymi słowy, obiekt A “komponuje” obiekt B, co oznacza, że obiekt A składa się z obiektu B. Kompozycja jest jednym z fundamentów relacji między obiektami i jest często stosowana w celu budowania złożonych struktur danych lub funkcjonalności.

Kluczowe Aspekty Kompozycji:
Silna Relacja:

W kompozycji obiekt A zawiera obiekt B, a obiekt B jest integralną częścią obiektu A. Relacja jest silna, co oznacza, że obiekt B nie istnieje niezależnie od obiektu A.
Jednorodność:

Obiekt B, będący częścią obiektu A, jest zazwyczaj jednorodny w kontekście obiektu A. Oznacza to, że obiekt B ma znaczenie tylko w kontekście swojego zawierającego obiektu A.
Składowanie:

Obiekt B jest składowany wewnątrz obiektu A, co oznacza, że jest bezpośrednio zawarty jako pole lub element wewnątrz obiektu A.
Zycie:

Czas życia obiektu B jest ściśle powiązany z czasem życia obiektu A. Jeśli obiekt A zostanie zniszczony, to zazwyczaj również obiekt B zostanie zniszczony.
Przykład Kompozycji:
java
Copy code
class Engine {
// implementacja silnika
}

class Car {
private Engine engine; // Kompozycja - samochód zawiera silnik

public Car() {
    this.engine = new Engine();
}

public void start() {
    engine.start();
    // logika rozruchu samochodu
} } W tym przykładzie klasa Car składa się z obiektu Engine, a obiekt Engine jest integralną częścią klasy Car. W konstruktorze klasy Car tworzony jest obiekt Engine, co podkreśla silną relację między nimi. Gdy obiekt Car zostanie zniszczony, również obiekt Engine zostanie zniszczony.

Kompozycja jest często stosowana w celu zbudowania hierarchii obiektów, tworzenia złożonych struktur danych lub modelowania relacji między różnymi elementami systemu. Pomaga to w projektowaniu kodu, który jest modularny, elastyczny i łatwy do zrozumienia.

63
Q

Tech join

A

Fetch Join to mechanizm w języku JPQL (Java Persistence Query Language) używany w kontekście JPA (Java Persistence API), który umożliwia jednoczesne pobranie kilku powiązanych encji w jednym zapytaniu. Zapobiega to zjawisku N+1, gdzie dla każdego wyniku z głównego zapytania pobierane są dodatkowe dane z powiązanych encji, co może prowadzić do zbędnego obciążenia bazy danych.

Przyjrzyjmy się przykładowi z użyciem Fetch Join w kontekście relacji Many-to-One:

java
Copy code
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

// Inne pola Order...

@ManyToOne(fetch = FetchType.LAZY)
private Customer customer; } java Copy code @Entity public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

// Inne pola Customer... } Załóżmy, że chcemy pobrać wszystkie zamówienia wraz z informacjami o klientach, ale chcemy uniknąć zjawiska N+1. Wykorzystamy Fetch Join w zapytaniu JPQL:

java
Copy code
@Entity
public class Order {
// …

@ManyToOne(fetch = FetchType.LAZY)
private Customer customer; } java Copy code @Entity public class Customer {
// ... } java Copy code String jpql = "SELECT o FROM Order o JOIN FETCH o.customer"; List<Order> orders = entityManager.createQuery(jpql, Order.class).getResultList(); W tym przykładzie, dzięki użyciu JOIN FETCH o.customer, pobieramy jednocześnie wszystkie zamówienia i ich powiązanych klientów w jednym zapytaniu. Fetch Join umożliwia zredukowanie liczby zapytań SQL wykonywanych przez JPA, co z kolei poprawia wydajność systemu.

Warto jednak zauważyć, że Fetch Join może prowadzić do pobierania większej ilości danych niż jest rzeczywiście potrzebne, zwłaszcza gdy istnieją duże związki Many-to-One lub One-to-Many. Dlatego zawsze warto ostrożnie dobierać strategię pobierania (Eager/Lazy) i zastanowić się, czy Fetch Join jest rzeczywiście potrzebny w danym przypadku.

64
Q

Wątki sposoby tworzenia

A

Wątki w języku Java umożliwiają jednoczesne wykonywanie wielu zadań, co może poprawić wydajność programów, zwłaszcza w przypadku operacji wejścia/wyjścia lub równoległego przetwarzania danych. Istnieje kilka sposobów tworzenia wątków w Javie, z których najczęściej używane to dziedziczenie z klasy Thread i implementacja interfejsu Runnable. Poniżej przedstawiam oba podejścia:

  1. Dziedziczenie z klasy Thread:
    java
    Copy code
    class MyThread extends Thread {
    public void run() {
    // Kod wykonywany w nowym wątku
    for (int i = 0; i < 5; i++) {
    System.out.println(Thread.currentThread().getId() + “ Value “ + i);
    }
    }
    }

public class Main {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start(); // Rozpoczynamy nowy wątek

    MyThread t2 = new MyThread();
    t2.start(); // Rozpoczynamy kolejny wątek
} } 2. Implementacja interfejsu Runnable: java Copy code class MyRunnable implements Runnable {
public void run() {
    // Kod wykonywany w nowym wątku
    for (int i = 0; i < 5; i++) {
        System.out.println(Thread.currentThread().getId() + " Value " + i);
    }
} }

public class Main {
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable());
t1.start(); // Rozpoczynamy nowy wątek

    Thread t2 = new Thread(new MyRunnable());
    t2.start(); // Rozpoczynamy kolejny wątek
} } Oba te podejścia pozwalają na stworzenie i uruchomienie nowego wątku. Dziedziczenie z klasy Thread jest proste, ale może ograniczać dziedziczenie w innych klasach. Implementacja interfejsu Runnable pozwala na większą elastyczność, ponieważ można dziedziczyć z innych klas jednocześnie.

Od wersji Javy 5 wprowadzono także ExecutorService i ThreadPoolExecutor jako bardziej zaawansowane i elastyczne narzędzia do zarządzania wątkami. Pozwalają one na łatwe zarządzanie pulą wątków, zarządzanie cyklem życia wątków oraz dostarczają bardziej rozbudowane możliwości planowania zadań do wykonania

65
Q

Cykl życia wątku

A

Cykl życia wątku w języku Java obejmuje kilka etapów, od utworzenia wątku do zakończenia jego działania. Poniżej przedstawiam podstawowy cykl życia wątku:

Nowy (New):

W tym etapie wątek jest utworzony, ale jeszcze nie został uruchomiony. Możemy go utworzyć, dziedzicząc z klasy Thread lub implementując interfejs Runnable, ale dopóki nie wywołamy metody start(), wątek pozostaje w stanie “Nowy”.
Uruchomiony (Runnable):

Po wywołaniu metody start(), wątek przechodzi do stanu “Uruchomiony”. W tym stanie wątek jest gotowy do wykonania, ale jeszcze nie jest aktywny. Planista systemowy decyduje, kiedy wątek będzie faktycznie działał.
Gotowy do Przerwania (Blocked):

Wątek może wejść w stan “Gotowy do Przerwania”, gdy czeka na zasób, taki jak dostęp do monitora, semafora, lub inny zasób, który jest obecnie zajęty przez inny wątek. W stanie tym wątek nie wykonuje żadnych operacji.
Zatrzymany (Dead):

Wątek kończy swoje działanie i przechodzi do stanu “Zatrzymany” po zakończeniu metody run(). Wątek jest uznawany za “zmarły” i nie może być ponownie uruchomiony.
Powyższe stany są podstawowymi etapami cyklu życia wątku. Ważne jest zrozumienie tych stanów w kontekście wielowątkowości, aby unikać problemów z synchronizacją i kontrolą dostępu do współdzielonych zasobów. Zarządzanie cyklem życia wątku jest kluczowym aspektem efektywnego programowania wielowątkowego.

66
Q

Race condition

A

Race condition (warunki wyścigowe) to sytuacja, w której działanie programu zależy od tego, w jakiej kolejności są wykonywane operacje przez różne wątki. Warunki wyścigowe mogą prowadzić do nieprzewidywalnych rezultatów, błędów i niepoprawnego działania programu, gdy dwa lub więcej wątków próbuje jednocześnie modyfikować współdzielone dane.

Typowe warunki wyścigowe zachodzą, gdy:

Współdzielone Dane:

Wartości przechowywane w pamięci, do których mają dostęp różne wątki, są zmieniane przez co najmniej jeden wątek.
Operacje Nieatomowe:

Operacje, które wydają się jednoczesne, w rzeczywistości są złożone z kilku kroków, a te kroki mogą być przerywane przez inne wątki, co prowadzi do niejednoznacznych rezultatów.
Brak Synchronizacji:

Brak odpowiednich mechanizmów synchronizacji (np. bloków synchronized, semaforów) między wątkami, co prowadzi do niebezpiecznej konkurencji o dostęp do zasobów.
Przykład warunku wyścigowego w Javie:

java
Copy code
public class RaceConditionExample {
private static int counter = 0;

public static void main(String[] args) {
    Runnable incrementTask = () -> {
        for (int i = 0; i < 10000; i++) {
            counter++;
        }
    };

    Thread thread1 = new Thread(incrementTask);
    Thread thread2 = new Thread(incrementTask);

    thread1.start();
    thread2.start();

    try {
        thread1.join();
        thread2.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println("Final Counter Value: " + counter);
} } W tym przykładzie dwa wątki (thread1 i thread2) inkrementują wspólny licznik counter o 10 000 razy każdy. Wynik ostateczny może być różny przy każdym uruchomieniu programu ze względu na warunki wyścigowe, które zachodzą, gdy oba wątki jednocześnie próbują modyfikować współdzieloną zmienną counter. Aby uniknąć race condition, konieczne jest zastosowanie mechanizmów synchronizacji lub atomowych operacji.
67
Q

Case w sql

A

W języku SQL, instrukcja CASE jest używana do wykonywania warunkowych operacji w zapytaniach. Instrukcja CASE pozwala na tworzenie warunków, które określają, którą wartość zwrócić w wyniku zapytania w zależności od spełnienia określonych warunków. Jest to szczególnie przydatne, gdy chcemy wykonać różne akcje na podstawie warunków logicznych w jednym zapytaniu SQL. Poniżej przedstawiam prosty przykład użycia instrukcji CASE:

sql
Copy code
SELECT
employee_id,
first_name,
last_name,
salary,
CASE
WHEN salary >= 50000 THEN ‘High Salary’
WHEN salary >= 30000 AND salary < 50000 THEN ‘Medium Salary’
ELSE ‘Low Salary’
END AS salary_category
FROM
employees;
W tym przykładzie, mamy tabelę pracowników (employees), a instrukcja CASE jest używana do utworzenia nowej kolumny o nazwie salary_category. Ta kolumna zawiera kategorie wynagrodzenia w zależności od wartości kolumny salary. Jeśli wynagrodzenie (salary) jest większe lub równe 50000, kategoria to “High Salary”. Jeśli wynagrodzenie jest większe lub równe 30000 i mniejsze niż 50000, kategoria to “Medium Salary”. W przeciwnym razie kategoria to “Low Salary”.

Składnia ogólna instrukcji CASE wygląda następująco:

sql
Copy code
CASE
WHEN condition1 THEN result1
WHEN condition2 THEN result2

ELSE result_default
END
Możesz użyć wielu warunków WHEN z odpowiadającymi im wynikami dla danego warunku. Warto również zauważyć, że instrukcję CASE można używać w różnych kontekstach, na przykład w sekcji SELECT, WHERE, ORDER BY itp., aby dostosować wyniki zapytania do określonych warunków.

68
Q

Insert into select

A

W języku SQL, zapytanie INSERT INTO SELECT służy do kopiowania danych z jednej tabeli do drugiej. Pozwala na wstawienie danych, które spełniają warunki wybranego zapytania SELECT, do nowej lub istniejącej tabeli.

Poniżej przedstawiam przykłady różnych scenariuszy użycia INSERT INTO SELECT:

  1. Kopiowanie wszystkich danych z jednej tabeli do drugiej:
    sql
    Copy code
    INSERT INTO tabela_docelowa
    SELECT * FROM tabela_zrodlowa;
  2. Wybieranie określonych kolumn:
    sql
    Copy code
    INSERT INTO tabela_docelowa (kolumna1, kolumna2, kolumna3)
    SELECT kolumna1, kolumna2, kolumna3 FROM tabela_zrodlowa;
  3. Dodawanie warunków do zapytania SELECT:
    sql
    Copy code
    INSERT INTO tabela_docelowa (kolumna1, kolumna2)
    SELECT kolumna1, kolumna2 FROM tabela_zrodlowa
    WHERE warunek = ‘wartosc’;
  4. Użycie zapytania JOIN:
    sql
    Copy code
    INSERT INTO tabela_docelowa (kolumna1, kolumna2)
    SELECT t1.kolumna1, t2.kolumna2
    FROM tabela1 t1
    JOIN tabela2 t2 ON t1.id = t2.t1_id;
    Warto pamiętać, że struktura kolumn w tabeli_docelowej musi odpowiadać strukturze zapytania SELECT. Ważne jest również, aby zadbać o unikalność klucza głównego (jeśli istnieje), aby uniknąć konfliktów przy wstawianiu danych.

Przy użyciu INSERT INTO SELECT można skutecznie kopiować, filtrować i transformować dane między tabelami w bazie danych SQL.

69
Q

Sql any all

A

W języku SQL, ANY i ALL to operatory porównywania, które mogą być używane w zapytaniach w połączeniu z podzapytaniami. Oto ich podstawowe zastosowania:

Operator ANY:
Operator ANY porównuje wartość z jednym lub więcej wyników z podzapytania i zwraca wartość TRUE, jeśli przynajmniej jedna z tych wartości spełnia warunek.

Przykład z użyciem ANY:

sql
Copy code
SELECT column_name
FROM table_name
WHERE column_name operator ANY (subquery);
Przykład:
sql
Copy code
– Znajdź pracowników, których pensja jest większa niż pensja jakiegokolwiek pracownika z działu IT.
SELECT employee_name
FROM employees
WHERE salary > ANY (SELECT salary FROM employees WHERE department = ‘IT’);
Operator ALL:
Operator ALL porównuje wartość ze wszystkimi wynikami z podzapytania i zwraca wartość TRUE, jeśli warunek jest spełniony we wszystkich przypadkach.

Przykład z użyciem ALL:

sql
Copy code
SELECT column_name
FROM table_name
WHERE column_name operator ALL (subquery);
Przykład:
sql
Copy code
– Znajdź pracowników, których pensja jest większa niż pensja wszystkich pracowników z działu IT.
SELECT employee_name
FROM employees
WHERE salary > ALL (SELECT salary FROM employees WHERE department = ‘IT’);
W obu przypadkach operatorów ANY i ALL można używać w różnych kontekstach, a warunki z nimi związane mogą obejmować różne operatory porównywania, takie jak =, <, >, itp. Ważne jest zrozumienie, jakie wartości są porównywane i jakie wyniki zwracane są przez podzapytania.

70
Q

Sql exists

A

Operator EXISTS w języku SQL służy do sprawdzania istnienia wyników w podzapytaniu. Operator ten zwraca wartość logiczną TRUE, jeśli podzapytanie zwraca jakiekolwiek wyniki, w przeciwnym razie zwraca FALSE. Jest często używany w warunkach klauzuli WHERE lub w połączeniu z innymi warunkami.

Poniżej przedstawiam podstawowy schemat użycia operatora EXISTS:

sql
Copy code
SELECT column_name(s)
FROM table_name
WHERE EXISTS (subquery);
Przykład:
Załóżmy, że mamy dwie tabele, orders i customers, i chcemy znaleźć wszystkich klientów, którzy dokonali przynajmniej jednego zamówienia.

sql
Copy code
– Znajdź wszystkich klientów, którzy dokonali przynajmniej jednego zamówienia.
SELECT customer_name
FROM customers
WHERE EXISTS (SELECT * FROM orders WHERE orders.customer_id = customers.customer_id);
W tym przykładzie, EXISTS jest używane do sprawdzenia, czy istnieją jakiekolwiek rekordy w tabeli orders, które mają identyfikator klienta zgodny z identyfikatorem klienta w tabeli customers. Jeśli takie rekordy istnieją, to warunek EXISTS zwróci TRUE, a klient zostanie uwzględniony w wynikach.

Operator EXISTS jest przydatny w sytuacjach, gdzie chcemy sprawdzić, czy jakiekolwiek wyniki są obecne w podzapytaniu, bez konieczności pobierania tych wyników.

71
Q

SQl having

A

Klauzula HAVING w języku SQL jest używana do filtrowania wyników grupowanych według warunków określonych po wykonaniu klauzuli GROUP BY. HAVING działa podobnie do klauzuli WHERE, ale jest stosowana do grup wynikowych, a nie do poszczególnych wierszy.

Poniżej przedstawiam ogólny schemat użycia klauzuli HAVING:

sql
Copy code
SELECT column1, column2, aggregate_function(column3)
FROM table_name
WHERE condition
GROUP BY column1, column2
HAVING condition;
Przykład:
Zakładamy tabelę orders zawierającą zamówienia i chcemy znaleźć sumy wartości zamówień dla klientów, ale tylko dla tych klientów, których łączna wartość zamówień przekracza określoną kwotę.

sql
Copy code
SELECT customer_id, SUM(order_value) as total_order_value
FROM orders
GROUP BY customer_id
HAVING SUM(order_value) > 1000;
W tym przykładzie HAVING jest używane do filtrowania wyników grupowanych. Tylko te grupy (klienci), których łączna wartość zamówień przekracza 1000, będą uwzględnione w wynikach.

Klauzula HAVING jest przydatna w przypadku, gdy chcemy nałożyć warunki na wyniki grupowane, co nie jest możliwe do wykonania za pomocą klauzuli WHERE. Warto jednak pamiętać, że HAVING jest stosowane po GROUP BY, a WHERE przed GROUP BY.

72
Q

SQL group by

A

Klauzula GROUP BY w języku SQL jest używana do grupowania wyników zapytania według wartości jednej lub więcej kolumn. Jest często używana w połączeniu z agregatnymi funkcjami, takimi jak SUM, COUNT, AVG, itp., które działają na grupach danych.

Ogólna składnia zapytania z klauzulą GROUP BY wygląda następująco:

sql
Copy code
SELECT column1, column2, aggregate_function(column3)
FROM table_name
WHERE condition
GROUP BY column1, column2;
column1, column2, …: Kolumny, według których chcemy grupować wyniki.
aggregate_function(column3): Funkcje agregujące, które są stosowane do wartości w określonych grupach.
condition: Warunki dodatkowe, które można zastosować przed grupowaniem.
Przykład:
Zakładamy tabelę orders zawierającą informacje o zamówieniach i chcemy znaleźć łączną wartość zamówień dla każdego klienta.

sql
Copy code
SELECT customer_id, SUM(order_value) as total_order_value
FROM orders
GROUP BY customer_id;
W tym przykładzie, GROUP BY customer_id powoduje, że wyniki są grupowane według wartości w kolumnie customer_id. Funkcja SUM(order_value) jest stosowana do każdej grupy, co daje łączną wartość zamówień dla każdego klienta.

Klauzula GROUP BY jest używana do organizacji danych w logiczne grupy i pozwala na wykonywanie agregacji na tych grupach.

73
Q

SQL union all

A

Operator UNION ALL w języku SQL jest używany do łączenia wyników dwóch lub więcej zapytań SQL w jedną tabelę wynikową. Operator ten łączy wyniki zapytań w taki sposób, że wszystkie rekordy z obu zapytań są uwzględniane, nawet jeśli istnieją duplikaty.

Ogólna składnia zapytania z użyciem UNION ALL wygląda następująco:

sql
Copy code
SELECT column1, column2, …
FROM table1
WHERE condition1

UNION ALL

SELECT column1, column2, …
FROM table2
WHERE condition2;
column1, column2, …: Kolumny, które chcemy wybrać z wyników zapytania.
table1, table2, …: Tabele, z których chcemy wybrać dane.
condition1, condition2, …: Warunki, które mogą być stosowane do filtracji wyników w poszczególnych zapytaniach.
Przykład:
Załóżmy, że mamy dwie tabele employees1 i employees2, a chcemy połączyć wyniki zapytań, aby uzyskać pełną listę pracowników.

sql
Copy code
– Łączenie wyników z dwóch zapytań za pomocą UNION ALL
SELECT employee_id, employee_name FROM employees1
UNION ALL
SELECT employee_id, employee_name FROM employees2;
Wynikiem tego zapytania będzie tabela zawierająca wszystkich pracowników z obu tabel, uwzględniając ewentualne duplikaty.

Warto zauważyć, że UNION ALL zachowuje duplikaty, a UNION (bez ALL) usuwa duplikaty, zwracając tylko unikalne rekordy. Jeśli chcesz uwzględnić tylko unikalne rekordy, użyj po prostu UNION. Jeśli zależy ci na tym, aby zachować duplikaty, użyj UNION ALL.

74
Q

SQL union

A

Operator UNION w języku SQL jest używany do łączenia wyników dwóch lub więcej zapytań SQL w jedną tabelę wynikową. Jednak w przeciwieństwie do UNION ALL, operator UNION usuwa duplikaty, zwracając tylko unikalne rekordy.

Ogólna składnia zapytania z użyciem UNION wygląda następująco:

sql
Copy code
SELECT column1, column2, …
FROM table1
WHERE condition1

UNION

SELECT column1, column2, …
FROM table2
WHERE condition2;
column1, column2, …: Kolumny, które chcemy wybrać z wyników zapytania.
table1, table2, …: Tabele, z których chcemy wybrać dane.
condition1, condition2, …: Warunki, które mogą być stosowane do filtracji wyników w poszczególnych zapytaniach.
Przykład:
Załóżmy, że mamy dwie tabele employees1 i employees2, a chcemy połączyć wyniki zapytań, aby uzyskać pełną, unikalną listę pracowników.

sql
Copy code
– Łączenie wyników z dwóch zapytań za pomocą UNION
SELECT employee_id, employee_name FROM employees1
UNION
SELECT employee_id, employee_name FROM employees2;
Wynikiem tego zapytania będzie tabela zawierająca tylko unikalnych pracowników z obu tabel, eliminując ewentualne duplikaty.

Warto zauważyć, że UNION jest bardziej zasobooszczędne niż UNION ALL, ponieważ nie musi zachowywać duplikatów. Jednak jeśli zależy ci na tym, aby zachować duplikaty, użyj UNION ALL.

75
Q

Jak działa Hashmapa

A

HashMapa w języku Java to struktura danych używana do przechowywania i organizowania danych w formie par klucz-wartość. HashMapa implementuje interfejs Map i jest często używana do szybkiego dostępu do danych. Działa na zasadzie tablicy mieszającej (hash table), co umożliwia efektywne dodawanie, usuwanie i wyszukiwanie elementów.

Poniżej znajdziesz podstawowe zasady działania HashMapy:

Klucz i Wartość:

W HashMapie każdy element przechowuje parę klucz-wartość. Klucz jest unikalny w obrębie mapy, a wartość jest powiązana z danym kluczem.
Funkcja Skrótu (Hash Function):

HashMapa używa funkcji skrótu (hash function), która przekształca klucz na indeks tablicy. Ta funkcja ma za zadanie równomiernie rozkładać klucze w tablicy mieszającej.
Tablica Haszująca (Hash Table):

HashMapa składa się z tablicy mieszającej (hash table), która jest strukturą danych przechowującą pary klucz-wartość. Indeks w tablicy obliczany jest na podstawie funkcji skrótu.
Kolizje:

Kolizje zachodzą, gdy dwie różne wartości mają przypisane ten sam indeks w tablicy mieszającej. W przypadku kolizji, HashMapa stosuje różne techniki, takie jak rozwiązanie kolizji przez łańcuchy (linked lists) lub drzewa czerwono-czarne (red-black trees) w nowszych wersjach Javy.
Pojemność i Faktor Ładowania:

HashMapa ma określoną początkową pojemność (initial capacity), która oznacza początkowy rozmiar tablicy mieszającej. Faktor ładowania (load factor) określa, jak bardzo mapa ma być zapełniona przed automatycznym zwiększeniem rozmiaru tablicy mieszającej.
Operacje O(1):

W przypadku poprawnie zaimplementowanej funkcji skrótu i minimalnych kolizji, operacje takie jak dodawanie, usuwanie i wyszukiwanie elementów w HashMapie mają złożoność czasową O(1).
Przykład użycia HashMapy w Javie:
java
Copy code
import java.util.HashMap;
import java.util.Map;

public class Main {
public static void main(String[] args) {
// Tworzenie HashMapy
Map<String, Integer> hashMap = new HashMap<>();

    // Dodawanie elementów
    hashMap.put("Klucz1", 10);
    hashMap.put("Klucz2", 20);
    hashMap.put("Klucz3", 30);

    // Pobieranie wartości
    int wartość = hashMap.get("Klucz2");
    System.out.println("Wartość dla Klucz2: " + wartość);

    // Iteracja po elementach
    for (Map.Entry<String, Integer> entry : hashMap.entrySet()) {
        System.out.println("Klucz: " + entry.getKey() + ", Wartość: " + entry.getValue());
    }
} } HashMapa jest użyteczna, gdy potrzebujesz efektywnego dostępu do danych poprzez klucz. Jednak należy pamiętać, że kolejność elementów w HashMapie nie jest gwarantowana. Jeśli potrzebujesz zachować kolejność dodawania, możesz użyć klasy LinkedHashMap.
76
Q

Queue

A

Queue (Kolejka) w języku Java to interfejs, który reprezentuje kolejkę FIFO (First-In-First-Out), czyli kolejkę, w której elementy są usuwane w kolejności, w jakiej zostały dodane. Queue jest często używane w sytuacjach, gdzie konieczne jest przetwarzanie elementów w kolejności, w jakiej zostały dodane.

W Javie istnieje kilka implementacji interfejsu Queue. Jedną z popularnych implementacji jest LinkedList, ale istnieją także inne, takie jak PriorityQueue, ArrayDeque, itp.

Podstawowe metody interfejsu Queue:
add(element) / offer(element):

Dodaje element do końca kolejki. Metoda add jest używana w przypadku, gdy kolejka ma określony limit pojemności i nie ma miejsca dla nowego elementu, zostanie rzucony wyjątek. Metoda offer zwróci false w przypadku niepowodzenia dodania elementu.
remove() / poll():

Usuwa i zwraca element z początku kolejki. Metoda remove rzuca wyjątek, jeśli kolejka jest pusta, a metoda poll zwraca null w takim przypadku.
element() / peek():

Zwraca element z początku kolejki bez usuwania go. Metoda element rzuca wyjątek, jeśli kolejka jest pusta, a metoda peek zwraca null w takim przypadku.
Przykład użycia Queue w Javie:
java
Copy code
import java.util.LinkedList;
import java.util.Queue;

public class Main {
public static void main(String[] args) {
// Tworzenie kolejki
Queue<String> queue = new LinkedList<>();</String>

    // Dodawanie elementów
    queue.add("Element1");
    queue.add("Element2");
    queue.add("Element3");

    // Pobieranie i usuwanie elementów
    String firstElement = queue.poll();
    System.out.println("Pobrany element: " + firstElement);

    // Sprawdzanie elementu na początku kolejki
    String peekedElement = queue.peek();
    System.out.println("Element na początku kolejki: " + peekedElement);

    // Iteracja po kolejce
    System.out.println("Elementy kolejki:");
    for (String element : queue) {
        System.out.println(element);
    }
} } W tym przykładzie używamy implementacji LinkedList jako Queue. Kolejność elementów jest zachowana, a operacje dodawania i usuwania elementów są efektywne. Warto pamiętać, że jeśli zależy ci na zachowaniu pewnych właściwości, takich jak sortowanie czy dostęp według priorytetu, możesz wybrać inną implementację Queue.
77
Q

Comparartor

A

Interfejs Comparator w języku Java jest używany do zdefiniowania niestandardowego porządku elementów, które nie posiadają naturalnego porządku lub w sytuacjach, gdy chcemy sortować obiekty w inny sposób niż domyślny. Comparator jest często używany w połączeniu z różnymi algorytmami sortującymi, takimi jak Collections.sort().

Metoda compare w interfejsie Comparator:
Główną metodą interfejsu Comparator jest compare, która jest odpowiedzialna za porównywanie dwóch obiektów.

java
Copy code
int compare(T obj1, T obj2);
obj1, obj2: Elementy do porównania.
Metoda compare zwraca wartość całkowitą:

Zwraca wartość ujemną, jeśli obj1 powinien być przed obj2 w porządku.
Zwraca wartość dodatnią, jeśli obj1 powinien być po obj2 w porządku.
Zwraca 0, jeśli obj1 i obj2 są równe.
Przykład użycia Comparator:
java
Copy code
import java.util.Comparator;
import java.util.Arrays;

class Student {
String name;
int age;

Student(String name, int age) {
    this.name = name;
    this.age = age;
} }

public class Main {
public static void main(String[] args) {
Student[] students = {
new Student(“Alice”, 22),
new Student(“Bob”, 20),
new Student(“Charlie”, 25)
};

    // Sortowanie za pomocą Comparator
    Arrays.sort(students, new Comparator<Student>() {
        @Override
        public int compare(Student s1, Student s2) {
            return Integer.compare(s1.age, s2.age);
        }
    });

    // Wyświetlanie posortowanych studentów
    for (Student student : students) {
        System.out.println("Name: " + student.name + ", Age: " + student.age);
    }
} } W tym przykładzie używamy Comparator do zdefiniowania niestandardowego porządku dla obiektów klasy Student. Sortujemy tablicę studentów według ich wieku. W przypadku bardziej nowoczesnych wersji Javy, można używać wyrażeń lambda dla bardziej zwięzłego kodu.

java
Copy code
Arrays.sort(students, (s1, s2) -> Integer.compare(s1.age, s2.age));
Comparator jest bardzo przydatny w przypadku niestandardowych porządków, sortowań malejących, sortowań po wielu kryteriach itp.

78
Q

Sety zasady działania, rodzaje

A

Set w języku Java to interfejs, który reprezentuje kolekcję unikalnych elementów, czyli takich, które nie zawierają duplikatów. Set nie gwarantuje żadnej konkretnej kolejności elementów i nie posiada indeksów. W Java istnieje kilka implementacji interfejsu Set, z których najczęściej używane to HashSet, LinkedHashSet i TreeSet.

HashSet:
Implementacja oparta na haszowaniu.
Oferuje szybkie dodawanie, usuwanie i sprawdzanie obecności elementów.
Nie zachowuje kolejności dodawania elementów.
Nie dopuszcza duplikatów.
java
Copy code
Set<String> hashSet = new HashSet<>();
hashSet.add("Element1");
hashSet.add("Element2");
hashSet.add("Element3");
LinkedHashSet:
Implementacja oparta na haszowaniu, ale zachowująca kolejność dodawania elementów.
Szybkie dodawanie, usuwanie i sprawdzanie obecności elementów.
Działa nieco wolniej niż HashSet, ale zachowuje kolejność.
java
Copy code
Set<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("Element1");
linkedHashSet.add("Element2");
linkedHashSet.add("Element3");
TreeSet:
Implementacja oparta na drzewie (czerwono-czarnym drzewie).
Zapewnia naturalne sortowanie elementów albo z użyciem dostarczonego Comparatora.
Wydajne przy operacjach związanych z porządkowaniem (np. first(), last(), higher(), lower()).
Wolniejsze dodawanie i usuwanie w porównaniu do HashSet i LinkedHashSet.
java
Copy code
Set<String> treeSet = new TreeSet<>();
treeSet.add("Element1");
treeSet.add("Element2");
treeSet.add("Element3");
Zasada Działania Set:
Set nie pozwala na przechowywanie duplikatów. Jeśli próbujesz dodać już istniejący element, operacja ta zostanie zignorowana.
W przypadku HashSet i LinkedHashSet, porównywanie elementów opiera się na wartościach haszy, co pozwala na efektywne sprawdzanie obecności.
TreeSet używa porównywania naturalnego (lub dostarczonego Comparatora) do zachowania porządku elementów.
Uwaga:
Implementacje Set są często używane, gdy zależy nam na zbiorze unikalnych elementów i nie zależy nam na zachowaniu kolejności.
Wybór konkretnej implementacji zależy od specyfiki problemu, wymagań dotyczących wydajności i zachowania elementów w kolekcji.</String></String></String>

79
Q

Listy zasada działania rodzaje

A

Listy w języku Java to dynamiczne struktury danych, które przechowują sekwencje elementów. W Javie interfejs List jest implementowany przez kilka klas, z których najczęściej używane to ArrayList, LinkedList i Vector. Poniżej omówię zasadę działania oraz krótko przedstawię rodzaje list.

Zasada Działania List:
Indeksowanie:

Elementy w liście są indeksowane, a indeksy zaczynają się od zera. Dostęp do elementów odbywa się za pomocą indeksów.
Dynamiczny Rozmiar:

Listy w Javie automatycznie dostosowują swój rozmiar w miarę dodawania lub usuwania elementów.
Duplikaty:

Listy pozwalają przechowywać duplikaty, czyli te same elementy mogą wystąpić wielokrotnie.
Iteracja:

Możliwość iteracji po elementach listy za pomocą pętli.
Rodzaje List:
1. ArrayList:
Implementacja oparta na tablicy dynamicznej.
Szybkie dostępy do elementów poprzez indeksy.
Efektywne w przypadku częstego odczytu, ale mniej wydajne przy częstych operacjach dodawania i usuwania elementów z początku lub środka listy.
java
Copy code
List<String> arrayList = new ArrayList<>();
arrayList.add("Element1");
arrayList.add("Element2");
arrayList.add("Element3");
2. LinkedList:
Implementacja oparta na liście dwukierunkowej.
Szybkie operacje dodawania i usuwania elementów na początku i w środku listy.
Dłuższy czas dostępu do elementów za pomocą indeksów w porównaniu do ArrayList.
java
Copy code
List<String> linkedList = new LinkedList<>();
linkedList.add("Element1");
linkedList.add("Element2");
linkedList.add("Element3");
3. Vector:
Starsza implementacja oparta na tablicy dynamicznej.
Operacje są synchronizowane, co oznacza, że jest bezpieczna w kontekście wielowątkowym.
Mniej efektywna niż ArrayList, ponieważ jest synchronizowana.
java
Copy code
List<String> vector = new Vector<>();
vector.add("Element1");
vector.add("Element2");
vector.add("Element3");
Uwagi:
Wybór między ArrayList a LinkedList zależy od specyfiki operacji, które będą wykonywane na liście. ArrayList lepiej sprawdza się w przypadku częstego odczytu, natomiast LinkedList w przypadku częstych operacji dodawania i usuwania elementów.
Vector jest rzadziej używany w nowym kodzie ze względu na mniej efektywną synchronizację w porównaniu do bardziej elastycznych mechanizmów dostępnych w Java Collections Framework.</String></String></String>

80
Q

SQL podstawowa składnia

A

SQL (Structured Query Language) to język służący do zarządzania relacyjnymi bazami danych. Oto kilka podstawowych instrukcji SQL:

SELECT:

Służy do pobierania danych z tabeli.
sql
Copy code
SELECT column1, column2, …
FROM table_name
WHERE condition;
INSERT:

Służy do dodawania nowych rekordów do tabeli.
sql
Copy code
INSERT INTO table_name (column1, column2, …)
VALUES (value1, value2, …);
UPDATE:

Służy do aktualizacji istniejących rekordów w tabeli.
sql
Copy code
UPDATE table_name
SET column1 = value1, column2 = value2, …
WHERE condition;
DELETE:

Służy do usuwania rekordów z tabeli.
sql
Copy code
DELETE FROM table_name
WHERE condition;
CREATE TABLE:

Służy do tworzenia nowej tabeli w bazie danych.
sql
Copy code
CREATE TABLE table_name (
column1 datatype,
column2 datatype,

);
ALTER TABLE:

Służy do modyfikacji struktury istniejącej tabeli.
sql
Copy code
ALTER TABLE table_name
ADD column_name datatype;
DROP TABLE:

Służy do usuwania istniejącej tabeli z bazy danych.
sql
Copy code
DROP TABLE table_name;
SELECT DISTINCT:

Służy do pobierania unikalnych wartości z kolumny.
sql
Copy code
SELECT DISTINCT column1, column2, …
FROM table_name;
WHERE:

Służy do filtrowania wyników zapytania.
sql
Copy code
SELECT column1, column2, …
FROM table_name
WHERE condition;
ORDER BY:

Służy do sortowania wyników zapytania.
sql
Copy code
SELECT column1, column2, …
FROM table_name
ORDER BY column1 ASC, column2 DESC;
GROUP BY:

Służy do grupowania wyników zapytania na podstawie jednej lub więcej kolumn.
sql
Copy code
SELECT column1, COUNT(*)
FROM table_name
GROUP BY column1;
HAVING:

Służy do filtrowania wyników grupowania.
sql
Copy code
SELECT column1, COUNT()
FROM table_name
GROUP BY column1
HAVING COUNT(
) > 1;
To tylko kilka podstawowych instrukcji SQL. SQL oferuje wiele bardziej zaawansowanych funkcji, takich jak JOIN, SUBQUERY, funkcje agregujące (SUM, AVG, MIN, MAX), indeksy, klucze obce, procedury przechowywane, itp.

81
Q

Union a union all

A

W języku SQL, UNION i UNION ALL to operatory używane do łączenia wyników zapytań. Oto główne różnice między nimi:

Duplikaty:

UNION: Usuwa duplikaty z wyników, co oznacza, że każdy wiersz zostanie zwrócony tylko raz, nawet jeśli istnieje wiele identycznych wierszy.
UNION ALL: Zachowuje duplikaty, wszystkie wiersze są zwracane, niezależnie od tego, czy występują powtórzenia.
Wydajność:

UNION: Może być bardziej kosztowne pod względem wydajności, ponieważ wymaga sortowania i usuwania duplikatów.
UNION ALL: Zazwyczaj jest bardziej wydajne, ponieważ nie ma potrzeby sortowania ani usuwania duplikatów.
Składnia:

UNION: Składa się z dwóch zapytań, a wyniki są łączone i sortowane przed zwróceniem.
UNION ALL: Po prostu łączy wyniki dwóch zapytań, bez usuwania duplikatów ani sortowania.
Przykład:

sql
Copy code
– UNION - usuwa duplikaty
SELECT column1 FROM table1
UNION
SELECT column1 FROM table2;

– UNION ALL - zachowuje duplikaty
SELECT column1 FROM table1
UNION ALL
SELECT column1 FROM table2;
Podsumowując, jeśli zależy ci na usunięciu duplikatów, użyj UNION. Jeśli chcesz zachować duplikaty i zależy ci na wydajności, UNION ALL może być lepszym wyborem.

82
Q

Rodzaje indexów

A

W bazach danych SQL istnieje kilka rodzajów indeksów, które pozwalają na optymalizację zapytań i przyspieszenie dostępu do danych. Oto kilka najczęściej stosowanych rodzajów indeksów:

Indeks klastrowany (Clustered Index):

Określa fizyczną organizację danych w tabeli.
Tabela może mieć tylko jeden indeks klastrowany, ponieważ definiuje ona kolejność sortowania rzeczywistych danych w tabeli.
Kiedy tabela ma indeks klastrowany, dane są przechowywane w kolejności tego indeksu.
Indeks niestrefowany (Non-Clustered Index):

Nie zmienia fizycznej organizacji danych w tabeli.
Tworzy oddzielne struktury indeksowe, które przechowują odnośniki do rzeczywistych danych.
Tabela może mieć wiele indeksów niestrefowanych.
Indeks unikalny (Unique Index):

Zapewnia, że wartości w kolumnie indeksowanej są unikalne, czyli nie mogą się powtarzać.
Może być zarówno klastrowany, jak i niestrefowany.
Indeks złożony (Composite Index):

Tworzony jest na więcej niż jednej kolumnie.
Ułatwia przyspieszenie zapytań, które obejmują warunki dla kilku kolumn jednocześnie.
Indeks pełnotekstowy (Full-Text Index):

Stosowany do obsługi zaawansowanych operacji wyszukiwania tekstowego.
Przydatny, gdy potrzebujesz efektywnie wyszukiwać słowa kluczowe i frazy w tekście.
Indeks przestrzenny (Spatial Index):

Przeznaczony do obsługi danych przestrzennych, takich jak punkty, linie i wielokąty.
Ułatwia wykonywanie zapytań opartych na relacjach przestrzennych.
Indeks filtrujący (Filtered Index):

Tworzony na podstawie warunku filtrującego, co oznacza, że indeks obejmuje tylko wiersze spełniające określony warunek.
Przydatny, gdy interesują cię tylko określone podzbiory danych w tabeli.
Indeks XML:

Specjalnie dostosowany do obsługi danych XML w kolumnach.
Rodzaj indeksu, który najlepiej się sprawdzi w danej sytuacji, zależy od struktury danych, typów zapytań wykonywanych na bazie danych oraz wymagań dotyczących wydajności.

83
Q

Przyklady indexy

A

Oto kilka przykładów tworzenia różnych rodzajów indeksów w języku SQL na przykładzie tabeli o nazwie PrzykladowaTabela:

Indeks klastrowany:

sql
Copy code
CREATE CLUSTERED INDEX IX_PrzykladowaTabela_Klucz ON PrzykladowaTabela(Klucz);
Indeks niestrefowany:

sql
Copy code
CREATE NONCLUSTERED INDEX IX_PrzykladowaTabela_Nazwisko ON PrzykladowaTabela(Nazwisko);
Indeks unikalny:

sql
Copy code
CREATE UNIQUE INDEX IX_PrzykladowaTabela_Email ON PrzykladowaTabela(Email);
Indeks złożony:

sql
Copy code
CREATE INDEX IX_PrzykladowaTabela_ImieNazwisko ON PrzykladowaTabela(Imie, Nazwisko);
Indeks pełnotekstowy:

sql
Copy code
CREATE FULLTEXT INDEX IX_PrzykladowaTabela_Tekst ON PrzykladowaTabela(Tekst);
Indeks przestrzenny:

sql
Copy code
CREATE SPATIAL INDEX IX_PrzykladowaTabela_Polozenie ON PrzykladowaTabela(Polozenie);
Indeks filtrujący:

sql
Copy code
CREATE INDEX IX_PrzykladowaTabela_Aktywni ON PrzykladowaTabela(ID) WHERE Aktywny = 1;
Indeks XML:

sql
Copy code
CREATE XML INDEX IX_PrzykladowaTabela_DaneXML ON PrzykladowaTabela(DaneXML);
Pamiętaj, że te przykłady są ogólnymi schematami, a rzeczywiste zapytania mogą się różnić w zależności od używanej bazy danych (np. SQL Server, MySQL, PostgreSQL) oraz struktury tabeli.

84
Q

Kontener springa

A

kontekście technologii Java i frameworka Spring, termin “kontener Springa” odnosi się do kontenera IoC (Inversion of Control), który jest kluczowym elementem architektury Spring. Kontener Springa zarządza komponentami aplikacji, zarówno ich cyklem życia, jak i zależnościami.

Oto kilka kluczowych aspektów kontenera Springa:

Inversion of Control (IoC): W tradycyjnym podejściu programistycznym, aplikacja kontroluje tworzenie i zarządzanie obiektami. W przypadku IoC, kontrola nad tym procesem zostaje odwrócona - framework (w tym przypadku Spring) przejmuje odpowiedzialność za zarządzanie obiektami i dostarcza im zależności.

Dependency Injection (DI): Jest to konkretny sposób implementacji IoC w Springu. Dependency Injection polega na dostarczaniu obiektowi jego zależności z zewnątrz, zamiast pozostawiania mu odpowiedzialności za ich tworzenie. W Springu, DI umożliwia wstrzykiwanie zależności poprzez konstruktor, metody ustawiające (setter) lub bezpośrednio do pól.

Zarządzanie cyklem życia komponentów: Kontener Springa monitoruje cykl życia komponentów, takich jak beany (obiekty zarządzane przez Spring), i zapewnia ich utworzenie, inicjalizację, a także, w odpowiednim przypadku, zniszczenie.

Deklaratywna konfiguracja: Spring umożliwia deklaratywną konfigurację za pomocą plików XML, adnotacji lub konfiguracji opartej na kodzie Java. To pozwala programistom skupić się na logice biznesowej, a nie na szczegółach technicznych konfiguracji.

Dzięki kontenerowi Springa, rozwijanie aplikacji staje się bardziej elastyczne, modularne i łatwiejsze w zarządzaniu. Dodatkowo, umożliwia on lepszą testowalność poprzez łatwiejsze wprowadzanie zastępczych implementacji (np. mocków) zależności w trakcie testowania jednostkowego.

85
Q

ACID

A

CID to akronim odnoszący się do zestawu właściwości transakcji w systemach zarządzania bazami danych (DBMS), które zapewniają niezawodność i spójność danych. ACID jest kluczowym pojęciem w dziedzinie baz danych, szczególnie w kontekście transakcji bazodanowych. Oto szczegółowe wyjaśnienie każdej z tych właściwości:

Atomicity (Atomowość)

Opis: Transakcja jest jednostką operacji, która jest niepodzielna. Atomowość zapewnia, że albo wszystkie operacje wchodzące w skład transakcji zostaną wykonane pomyślnie, albo żadna z nich nie zostanie wykonana. W razie jakiejkolwiek awarii, wszystkie zmiany wprowadzone przez transakcję są cofane.
Przykład w Javie: Użycie menadżera transakcji w JPA (Java Persistence API) do zarządzania transakcjami bazodanowymi.
java
Skopiuj kod
EntityTransaction transaction = entityManager.getTransaction();
try {
transaction.begin();
// Operacje bazodanowe
transaction.commit();
} catch (Exception e) {
transaction.rollback();
}
Consistency (Spójność)

Opis: Transakcja przenosi bazę danych z jednego spójnego stanu do innego spójnego stanu. Spójność zapewnia, że dane są zgodne z wszystkimi zasadami i ograniczeniami narzuconymi przez bazę danych (np. klucze obce, ograniczenia unikalności).
Przykład w Javie: Spójność jest zapewniana przez mechanizmy walidacji i ograniczeń w definicjach schematów baz danych.
java
Skopiuj kod
// Przykład z użyciem JPA
@Entity
public class Account {
@Id
private Long id;

@Column(nullable = false)
private BigDecimal balance;

// Konstruktorzy, gettery i settery } Isolation (Izolacja)

Opis: Izolacja zapewnia, że równocześnie wykonywane transakcje nie wpływają na siebie nawzajem. W praktyce oznacza to, że wyniki operacji jednej transakcji są niewidoczne dla innych transakcji, dopóki ta pierwsza transakcja nie zostanie zakończona.
Przykład w Javie: Poziomy izolacji mogą być ustawione w konfiguracji połączenia z bazą danych.
java
Skopiuj kod
Connection connection = dataSource.getConnection();
connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
Durability (Trwałość)

Opis: Trwałość gwarantuje, że raz zatwierdzone dane są trwale zapisywane w bazie danych, nawet w przypadku awarii systemu. Po zatwierdzeniu transakcji dane nie mogą zostać utracone.
Przykład w Javie: Zapewnienie trwałości odbywa się na poziomie bazy danych, ale można to zobaczyć przez zatwierdzanie transakcji.
java
Skopiuj kod
EntityTransaction transaction = entityManager.getTransaction();
try {
transaction.begin();
// Operacje bazodanowe
transaction.commit(); // Zatwierdzenie transakcji
} catch (Exception e) {
transaction.rollback();
}
ACID jest kluczową koncepcją zapewniającą niezawodność operacji na danych w systemach bazodanowych. W kontekście Javy i JPA, zarządzanie transakcjami jest zazwyczaj realizowane przez odpowiednie API i konfiguracje połączeń z bazą danych.

86
Q

@Transactional

A

Adnotacja @Transactional w języku Java jest używana w ramach Springa do zarządzania transakcjami w sposób deklaratywny. Transakcje są kluczowym elementem operacji na bazach danych, zapewniając integralność i spójność danych.

Oto szczegółowy przegląd działania @Transactional oraz jak można jej używać:

  1. Podstawowe użycie
    Adnotacja @Transactional może być stosowana do metody lub klasy. Gdy jest stosowana na poziomie klasy, dotyczy wszystkich publicznych metod w tej klasie.

Przykład:
java
Skopiuj kod
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class MyService {

@Transactional
public void performTransactionalOperation() {
    // Logika biznesowa obejmująca operacje na bazie danych
} } 2. Propagacja transakcji Propagacja określa, jak transakcje odnoszą się do siebie nawzajem. Oto niektóre z powszechnych zachowań propagacji:

REQUIRED (domyślna): Jeśli istnieje transakcja, dołącz do niej; jeśli nie, rozpocznij nową.
REQUIRES_NEW: Zawsze rozpoczynaj nową transakcję, zawieszając istniejącą.
MANDATORY: Musi działać w ramach istniejącej transakcji; jeśli nie ma żadnej, zgłasza wyjątek.
SUPPORTS: Jeśli istnieje transakcja, dołącz do niej; jeśli nie, działaj poza transakcją.
NOT_SUPPORTED: Zawsze działaj poza transakcją, zawieszając wszelkie istniejące.
NEVER: Działaj poza transakcją; jeśli istnieje jakakolwiek transakcja, zgłasza wyjątek.
NESTED: Jeśli istnieje transakcja, utwórz transakcję zagnieżdżoną; w przeciwnym razie, zachowuj się jak REQUIRED.
3. Izolacja transakcji
Poziom izolacji definiuje, jak transakcja jest izolowana od innych równocześnie działających transakcji. W Springu dostępne są następujące poziomy izolacji:

DEFAULT: Używa domyślnego poziomu izolacji bazy danych.
READ_UNCOMMITTED: Pozwala na odczyt niezapisywanych zmian (może prowadzić do brudnych odczytów).
READ_COMMITTED: Zapewnia, że można odczytywać tylko zapisane zmiany (eliminuje brudne odczyty).
REPEATABLE_READ: Gwarantuje, że jeśli dane zostaną odczytane, to nie mogą być zmieniane przez inne transakcje (eliminuje niepowtarzalne odczyty).
SERIALIZABLE: Zapewnia pełną izolację transakcji (eliminuje powtórne odczyty i fantomowe odczyty).
4. Zarządzanie wyjątkami
Spring automatycznie zarządza transakcjami w przypadku wyjątków. Domyślnie transakcja zostanie wycofana, jeśli metoda adnotowana @Transactional zgłosi wyjątek niekontrolowany (runtime exception). Można to zmienić za pomocą atrybutów rollbackFor i noRollbackFor.

Przykład:
java
Skopiuj kod
@Transactional(rollbackFor = Exception.class)
public void performTransactionalOperation() {
// Logika biznesowa obejmująca operacje na bazie danych
}
5. Dodatkowe atrybuty
timeout: Określa maksymalny czas (w sekundach), przez jaki transakcja może trwać przed wycofaniem.
readOnly: Jeśli ustawione na true, optymalizuje transakcję do odczytu, zapobiegając operacjom zapisu.
Przykład:
java
Skopiuj kod
@Transactional(readOnly = true, timeout = 30)
public void performReadOnlyOperation() {
// Logika tylko do odczytu
}
6. Przykładowa konfiguracja
Konfiguracja za pomocą JavaConfig:
java
Skopiuj kod
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.TransactionManagementConfigurer;

@Configuration
@EnableTransactionManagement
public class AppConfig implements TransactionManagementConfigurer {

@Bean
public PlatformTransactionManager txManager() {
    return new DataSourceTransactionManager(dataSource());
} } Adnotacja @Transactional jest potężnym narzędziem w Springu do zarządzania transakcjami, ułatwiając ich obsługę i zwiększając bezpieczeństwo danych w aplikacjach.
87
Q

proxy

A

Proxy w Javie jest techniką, która pozwala na stworzenie obiektów, które kontrolują dostęp do innego obiektu. Proxy może być wykorzystywane do różnych celów, takich jak kontrola dostępu, logowanie, lazy loading, czy cache’owanie.

Oto szczegółowy przegląd różnych aspektów używania proxy w Javie:

  1. Rodzaje proxy w Javie
    a. Dynamiczne Proxy (Java Reflection API)
    Dynamiczne proxy są częścią pakietu java.lang.reflect. Umożliwiają tworzenie obiektów proxy w trakcie działania programu, które implementują jedną lub więcej interfejsów.

Przykład:
java
Skopiuj kod
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class DynamicProxyExample {
interface MyInterface {
void doSomething();
}

static class MyInterfaceImpl implements MyInterface {
    public void doSomething() {
        System.out.println("Doing something");
    }
}

static class MyInvocationHandler implements InvocationHandler {
    private final Object target;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before method call");
        Object result = method.invoke(target, args);
        System.out.println("After method call");
        return result;
    }
}

public static void main(String[] args) {
    MyInterface target = new MyInterfaceImpl();
    MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
        MyInterface.class.getClassLoader(),
        new Class<?>[]{MyInterface.class},
        new MyInvocationHandler(target)
    );

    proxy.doSomething();
} } b. Proxy przy użyciu biblioteki CGLIB CGLIB (Code Generation Library) pozwala na tworzenie proxy dla klas, które nie implementują interfejsów. Spring wykorzystuje CGLIB do tworzenia proxy dla klas, jeśli klasy te nie implementują żadnych interfejsów.

Przykład:
java
Skopiuj kod
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class CglibProxyExample {
static class MyService {
public void doSomething() {
System.out.println(“Doing something”);
}
}

static class MyServiceInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before method call");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("After method call");
        return result;
    }
}

public static void main(String[] args) {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(MyService.class);
    enhancer.setCallback(new MyServiceInterceptor());

    MyService proxy = (MyService) enhancer.create();
    proxy.doSomething();
} } 2. Zastosowania proxy Kontrola dostępu: Proxy może decydować, czy pozwolić na wywołanie metody czy nie. Logowanie: Proxy może logować informacje przed i po wywołaniu metody. Lazy loading: Proxy może opóźniać ładowanie zasobów do momentu, gdy są one rzeczywiście potrzebne. Cache'owanie: Proxy może przechowywać wyniki wywołań metod, aby uniknąć ponownego ich wykonywania. 3. Proxy w Spring Spring intensywnie korzysta z proxy do różnych celów, takich jak zarządzanie transakcjami, aspektowość (AOP) i wstrzykiwanie zależności.

a. Zarządzanie transakcjami:
Spring używa proxy do zarządzania transakcjami poprzez adnotację @Transactional. Gdy metoda oznaczona @Transactional jest wywoływana, proxy otwiera nową transakcję, a po zakończeniu metody zatwierdza ją lub wycofuje w przypadku wyjątku.

b. Aspektowość (AOP):
Spring AOP używa proxy do wprowadzania dodatkowych zachowań (aspektów) do obiektów bez zmiany ich kodu źródłowego. Na przykład, można dodać logowanie, kontrolę bezpieczeństwa czy monitorowanie wydajności.

Przykład z Spring AOP:
java
Skopiuj kod
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {
@Before(“execution(* com.example.MyService.*(..))”)
public void logBefore() {
System.out.println(“Log before method execution”);
}
}
c. Wstrzykiwanie zależności:
Spring używa proxy, aby wstrzykiwać zależności, szczególnie przy użyciu adnotacji @Autowired w przypadku zależności typu singleton i scope proxy w przypadku innych scope’ów.

Proxy w Javie to potężne narzędzie umożliwiające realizację różnych zaawansowanych scenariuszy programistycznych, takich jak aspektowość, zarządzanie transakcjami czy wzorzec proxy.