Java – podstawy

Klasa

Podstawowy element programowania obiektowego, który stanowi szablon do tworzenia obiektów. Definiuje ona właściwości (pola) oraz zachowania (metody) obiektów, które na jej podstawie zostaną utworzone.

Składnikami klasy są:
Pola – zmienne, które przechowują dane (stany) obiektu.
Metody – funkcje, które definiują zachowanie obiektu, pozwalając na operacje na danych.
Konstruktory – specjalne metody służące do tworzenia i inicjalizacji obiektów danej klasy.

Specyficzną klasą wbudowaną w język Java jest klasa String, która służy do reprezentacji sekwencji znaków (czyli ciągu tekstowego). Jest to jedna z najczęściej używanych klas w języku Java.

Kluczowe cechy klasy String:
Niezmienność – obiekty klasy String są niemutowalne, co oznacza, że raz stworzony obiekt String nie może zostać zmieniony. Każda operacja modyfikująca ciąg znaków (np. konkatenacja) zwraca nowy obiekt String, a oryginalny obiekt pozostaje niezmieniony.
Wewnętrzne buforowanie – ze względu na niezmienność, Java korzysta z puli stringów (ang. string pool), co oznacza że wszystkie literały w String są współdzielone. Jeśli dwa literały mają tę samą wartość, to będą wskazywać na ten sam obiekt w pamięci.
String s1 = "Hello";
String s2 = "Hello";

W rzeczywistości to będzie ten sam obiekt w pamięci.

Metody klasy String:
length() – zwraca długość znaków
charAt (int index) – zwraca znak na danej pozycji
substring (int beginIndex) – zwraca podciąg do danego indeksu
concat(String str) – łączy dwa ciągi znaków
toUpperCase() / toLowerCase() – zmienia wielkość liter

Package

Mechanizm służący do katalogowania projektu. Tworzenie powiązanych ze sobą klas, interfejsów i innych elementów co pozwala na lepszą organizację kodu oraz zapobieganie konfliktom nazw.

Przykład:

com.nazwa_domeny.nazwa_aplikacji.przykład_pierwszy
com.nazwa_domeny.nazwa_aplikacji.przykład_drugi


Jeżeli piszemy klasy, które znajdują się w różnych package’ach musimy zaimportować daną klasę, którą chcemy zadeklarować w innej klasie. Jeżeli klasy znajdują się w tym samym package’u to nie ma konieczności importowania ich jeżeli chcemy utworzyć obiekt jednej klasy w drugim.

Modyfikatory dostępu

Słowa kluczowe, które przypisujemy polom, które określają dostępność do danego pola.

public int polePubliczne; //dostępny wszędzie
protected int poleDziedziczenie //dostępny w dziedziczeniu oraz w tym samy package'u
private int polePrywatne //dostępny w obrębie klasy, w której istnieje
int poleBezNiczego //package - dostępny w tym samym package'u

Static

Są to pola współdzielone przez wszystkie obiekty danej klasy. Oznacza to, ze istnieje tylko jedna kopia takiej zmiennej, niezależnie od tego ile obiektów takiej klasy zostanie utworzonych.

Do takiej zmiennej statycznej można uzyskać dostęp bez tworzenia obiektu klasy, po prostu odwołując się do niej przez nazwę klasy. Analogicznie działają metody statyczne.

NazwaKlasy.poleStatic = 15; 
obiekt.poleStatic = 123;

Final

Słowo, które służy do definiowania elementów, których wartość lub definicja nie może zostać zmieniona po ich nadaniu. Może być stosowana do zmiennych, metod oraz klas. W każdym z tych kontekstów ma nieco inne znaczenie.

  • final dla zmiennych – zmienna nie może zostać zmieniona po przypisaniu jej wartości.
  • final dla metod – metoda nie może być modyfikowana przez podklasy. Jest używanan w sytuacjach, gdzie chcesz uniemożliwić modyfikację zachowania metody w klasach dziedziczących.final dla klas – klasa nie może być dziedziczona przez inne klasy.

Dziedziczenie klasy

mechanizm programowania obiektowego, który umożliwia jednej klasie przejęcie właściwości i metod innej klasy. Klasa, która dziedziczy nazywana jest klasą pochodną lub podklasą. Klasa, której dziedziczy to klasa bazowa lub nadklasa.

Kluczowe cechy dziedziczenia
  • Podklasa dziedziczy właściwości i metody nadklasy. Wszystkie pola i metody klasy bazowej są dostępne w klasie pochodnej (chyba, że są oznaczone jako prywatne).
  • Podklasa może dodawać nowe metody i pola
  • Podklasa może nadpisywać metody klasy. Mechanizm ten nazywa się nadpisywaniem metod.
Zasady dziedziczenia
  • Prywatne elementy nie są dziedziczone
  • Konstruktory nie są dziedziczone.
  • Podklasa może nadpisywać metody, chyba, że jest ona oznaczona jako final.
  • Polimorfizm

Super

Słowo kluczowe, które pozwala na dostęp do elementów metod i pół nadklasy, które zostały nadpisane. Może być używane zarówno w konstruktorach, jak i metodach.

Rzutowanie obiektów

Zmiana typu referencji obiektu, który istnieje w pamięci na inny typ. Umożliwia to używanie obiektu w kontekście innego typu klasy, co jest często potrzebne, gdy korzystamy z mechanizmów dziedziczenia lub polimorfizmu.

Klasa wewnętrzna

Klasa zdefiniowana w obrębie innej klasy.

Klasa anonimowa

Specjalny rodzaj klasy wewnętrznej, która nie ma swojej nazwy i jest tworzona w locie. Jest używana głównie wtedy, gdy potrzebujemy jednorazowej implementacji interfejsu lub nadpisania metod klasy, bez konieczności tworzenia pełnej, nazwanej klasy. Klasy anonimowe wykorzystujemy kiedy wiemy, że w jakimś wyjątkowym przypadku będziemy chcieli nadpisać lub zaimplementować jakąś metodę.

Klasa abstrakcyjna

Klasa, której nie można bezpośrednio zainicjalizować, czyli nie można bezpośrednio utworzyć jej obiektu. Służy ona jako szablon dla innych klas i zazwyczaj zawiera metody abstrakcyjne, które muszą zostać zaimplementowane przez klasy dziedziczące. Może również zawierać normalne metody z pełnymi implementacjami.
Klasy abstrakcyjne są używane wtedy, gdy chcemy zdefiniować wspólne zachowania i właściwości dla grupy klas, ale nie chcemy, aby klasa była bezpośrednio instancjonowana.

// Definicja klasy abstrakcyjnej
abstract class Shape {
    // Abstrakcyjna metoda do obliczania pola
    abstract double area();

    // Metoda do wyświetlania typu kształtu
    void display() {
        System.out.println("This is a shape.");
    }
}

// Klasa Circle dziedzicząca po klasie Shape
class Circle extends Shape {
    private double radius;

    // Konstruktor klasy Circle
    public Circle(double radius) {
        this.radius = radius;
    }

    // Implementacja metody area() dla okręgu
    @Override
    double area() {
        return Math.PI * radius * radius; // Pole okręgu = πr²
    }
}

Cechy klasy abstrakcyjnej
  • Klasa abstrakcyjna może zawierać zarówno abstrakcyjne, jak i nieabstrakcyjne (zwykłe) metody.
  • Klasa abstrakcyjna nie może być instancjonowana bezpośrednio, czyli nie można utworzyć jej obiektu.
  • Aby używać klasy abstrakcyjnej, trzeba ją rozszerzyć (extends), a następnie zaimplementować wszystkie jej abstrakcyjne metody w klasie pochodnej.
  • Abstrakcyjna metoda to metoda zadeklarowana w klasie abstrakcyjnej bez jej implementacji. Klasy dziedziczące muszą nadpisać i zdefiniować metody.

Interfejs

Specjalny typ klasy, który definiuje zbiór metod, ale nie zawiera ich implementacji (przed Java 8). Jest to mechanizm, który pozwala na tworzenie kotraktów – klasa, która implementuje dany interfejs, musi dostarczyć iimplementacje wszystkich metod zdefiniowanych w tym interfejsie.
Interfejsy są głównie używane do definiowania zachowań, które mogą być współdzielone przez różne klasy, niezależnie od pochodzenia w hierarchii klas. Dzięki interfejsom Java wspiera wielodziedziczenie zachowań, co oznacza, że klasa moze implementować wiele interfejsów jednocześnie.

// Definicja interfejsu
interface Animal {
    // Metody interfejsu (domyślnie są one publiczne i abstrakcyjne)
    void makeSound(); // Metoda do wydawania dźwięku przez zwierzę
    void eat();      // Metoda do jedzenia przez zwierzę
}

// Klasa Dog implementująca interfejs Animal
class Dog implements Animal {
    // Implementacja metody makeSound
    public void makeSound() {
        System.out.println("Bark");
    }

    // Implementacja metody eat
    public void eat() {
        System.out.println("Dog is eating.");
    }
}

Klasa Thread

Jest to wbudowana klasa, która umożliwia tworzenie i zarządzanie wątkami w programie. Wątek to niezależna ścieżka wykonywania, co oznacza, że można wykonywać jednocześnie wiele zadań w ramach jednej aplikacji. Wątki są podstawą programowania wielowątkowego, które pozwala na wykonywanie wielu operacji jednocześnie.

// Dziedziczenie po klasie Thread
class MyThread extends Thread {
    // Metoda run() zawiera kod, który będzie wykonany przez wątek
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(Thread.currentThread().getName() + " - Licznik: " + i);
            try {
                Thread.sleep(1000); // Uśpienie wątku na 1000 ms (1 sekunda)
            } catch (InterruptedException e) {
                System.out.println(e);
            }
        }
    }
}

public class Main {
    public static void main(String[] args) {
        // Tworzenie instancji klasy MyThread
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();

        // Uruchamianie wątków
        thread1.start();  // Uruchamia nowy wątek
        thread2.start();  // Uruchamia nowy wątek
    }
}

join()

Metoda, która używana jest w kontekście programowania wielowątkowego i służy do synchronizacji wątków. Jej głównym celem jest umożlwienie jednemu wątkowi czekania na zakończenie innego wątku, zanim kontynuuje swoje działanie. Jest to przydatne w sytuacjach, gdy proces wykonywania programu zależy od wyniku działania innego wątku lub chcemy zapewnić, że pewne operacje będą wykonane w odpowiedniej kolejności.

isAlive()

Metoda używana do sprawdzania, czy dany wątek jest aktualnie aktywny. Metoda zwraca wartość typu boolean.

interrupt()

Jest używana do przerywania wątku. Gdy wątek wywołuje interrupt() na innym wątku, oznacza to, że chce sygnalizować temu wątkowi, aby zaprzestał atualnej pracy, jeśli to możliwe. Wątki mają wbudowany mechanizm obsługi przerwań, który umożliwia reagowanie na takie sygnały.

Klasa scanner

Jest to wbudowana klasa, która służy do odczytywania danych wejściowych z różnych źródeł, takich jak klawiatura, pliki, ciągi tekstowe itp. Umożliwia łatwe przetwarzanie przetwarzanie danych wejściowych, w tym analizowanie tekstu, rozdzielanie do na tokeny oraz konwertowanie na różne typy danych (np. int, double, String)

Metody klasy scanner
Metody do odczytywania różnych typów danych:
next() – odczytuje pojedyncze słowo (ciąg znaków aż do spacji lub innego separatora)
nextLine() – odczytuje całą linię tekstu, w tym białe znaki
nextInt() – odczytuje liczbę całkowitą (int)
nextDouble() – odczytuje liczbę zmiennoprzecinkową typu double
nextFloat() – odczytuje liczbę zmiennoprzecinkową typu float
nextLong() – odczytuje liczbę całkowitą typu long
nextShort() – odczytuje liczbę całkowitą typu short
nextByte() – odczytuje liczbę całkowitą typu byte
nextBoolean() – odczytuje wartość typu boolean
netBigInteger() – odczytuje liczbę całkowitą typu BigInteger
nextBigDecimal() – odczytuje liczbę zmiennoprzecinkową typu BigDecimal
Metody sprawdzające dostępność danych wejściowych:
hasNext() – sprawdza czy istnieje następny token w strumieniu danych
hasNextLine() – sprawdza czy istnieje kolejna linia danych
hasNextInt() – sprawdza czy następny token może być zinterpretowany jako int
hasNextDouble() – sprawdza czy następny token może być zinterpretowany jako double
hasNextFloat() – sprawdza czy następny token może być zinterpretowany jako float
itd.
Metody zarządzające strumieniami danych:
close() – zamyka skaner i zwalnia zasoby
useDelimiter(Pattern pattern) – ustawia niestandardowy delimiter (separator), np. znak przecinka lub tabulator
reset() – restartuje skaner do domyślnych ustawień
skip(Pattern pattern) – pomija dane wejściowe, dopasowując je do określonego wzorca
findInLine(Pattern pattern) – znajduje pierwszy występujący wzorzec w bieżącej linii

Paradygmaty programowania obiektowego

Abstrakcja – pojęcie to odnosi się do tworzenia klas i obiektów w taki sposób, że użytkownik klasy widzi tylko publiczne metody i właściwości(interfejs), a szczegóły implementacji są ukryte wewnątrz klasy. Programista definiuje ogólne funkcjonalności, które są kluczowe dla korzystania z obiektu, bez ujawniania, jak te funkcjonalności są realizowane.
Enkapsulacja – polega na ukrywaniu szczegółów implementacji klasy oraz kontrolowaniu dostępu do jej danych. Innymi słowy, enkapsulacja pozwala na zamknięcie danych pól oraz metod w obrębie klasy, aby chronić je przed nieautoryzowaną manipulacją z zewnątrz i zapewnić lepszą kontrolę nad tym, jak te dane są używane.
Polimorfizm – polega na zdolności obiektów różnych klas do reagowania na te same metody w różny sposób. Pozwala obiektom różnych typów mieć wspólne interfejsy, ale z unikalnymi zachowaniami. W praktyce używamy dwóch głównych technik do wykorzystania możliwości poliformizmu.
– Przeciążenia metod (Overloading), gdzie kompilator wybiera metodę na podstawie sygnatury metody, czyli liczby, typu i kolejności parametrów.
– Przesłaniania metod (Overriding), gdzie podczas działania programu, JVM decyduje, którą wersjęe metody wywołać, w zależności od typu obiektu, a nie zmiennej referencyjnej.
Umożliwia to pisanie bardziej elastycznego kodu wielokrotnego użytku, ułatwia obsługę różnorodnych obiektów w zunifikowany sposób
Dziedziczenie klasy

Enumy

To specjalny typ danych, który pozwala definiować zestaw stałych wartości. Enumy są szczególnie przydatne, gdy masz do czynienia z ograniczonym zbiorem możliwości, które są wzajemnie wykluczające się, np. dni tygodnia, pory roku, kierunki geograficzne itp.

Cechy enumów:
Enum to rodzaj klasy w Javie
Enumy są typami wyliczeniowymi, czyli definiują zbiór stałych wartości, które są jednocześnie obiektami tej klasy
Mogą zawierać metody, konstruktory, a nawet pola, co sprawia, że są bardziej zaawansowane niż w innych językach.
public enum Day {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

public class Main {
    public static void main(String[] args) {
        Day today = Day.MONDAY;
        
        if (today == Day.MONDAY) {
            System.out.println("It's Monday!");
        }
    }
}