Podstawy klas

AUTOR: MAREK SOWA

Klasy są nieodłączną częścią Javy. Każdy, nawet najprostszy program napisany w tym języku posiada przynajmniej jedną, najprostszą klasę. Co to jest?  To tak jakby kontener, w którym trzymamy pola i metody. Naturalną reprezentacją klasy jest obiekt. Tu warto zapamiętać, że zgodnie z zasadą Single responsibility principle (Zasada jednej odpowiedzialności) - Klasa powinna mieć tylko jedną odpowiedzialność (jak sama nazwa wskazuje). A to oznacza, że nigdy nie powinien istnieć więcej niż jeden powód do modyfikacji klasy. 

Aby to zobrazować, wyobraźcie sobie, że szkoła to program komputerowy. Program ten składa się z klas takich jak: wychowania fizycznego, matematyczna czy szatnia:

klasySzkola

Jak widzimy na obrazku klasy zaznaczone są na fioletowo. Mogą one składać się z pól i metod.
Zielone to pola, czyli właściwości klasy.
Szarym kolorem oznaczono metody, czyli czynności, które można wykonać dzięki tej klasie.
Po wstępnym zapoznaniu się z naszym programem "szkoła" możemy wywnioskować, że w tej "szkole" można, na przykład, dokonywać obliczeń metodą oblicz() po wywołaniu jej z klasy: matematyka.

class Matematyka{
    
    //pola
    Tablica tablica; 
    Kreda krreda;
    Biurko biurko;
    Kalkulator kalkulator;
    Rzutnik rzutnik;
    
    void wejdz(){
        // cialo metody wejdz
    }

    void przeczytaj(){
        // cialo metody przeczytaj
    }

    void oblicz(){
        // cialo metody oblicz
    }

    void napisz(){
        // cialo metody napisz
    }
    
}

//kod
Matematyka matematyka = new Matematyka();
matematyka.oblicz();

 * nie możemy stworzyć dwóch klas o tej samej nazwie w tym samym pakiecie

Pola (cechy, zmienne, właściwości, dane, elementy)

Wszystkie informacje o klasie przechowujemy w polach dzięki zmiennych. Oczywiście pola są opcjonalne, istnieją klasy, które przechowują w sobie tylko metody, czyli czynności. Są też i takie, które przechowują tylko pola. Inaczej mówiąc, są to swego rodzaju kontenery na dane. Takie magazyny informacyjne.

class Human{
    String name;
    int age;
    int pesel;
}

 

 

Obiekt

Jak już zostało wspomniane, obiekty reprezentują klasy. Aby dostać się do obiektu, musimy go najpierw stworzyć, używając słówka kluczowego new:

Human human = new Human();

Wyżej stworzyliśmy obiekt human, który reprezentuje swoją klasę. Aby dostać się do metod, które posiada klasa Human, trzeba odwołać się do nich po kropce:

    Human human = new Human();
    human.think(); // Human thinking
    }
}

class Human{
    String name;
    int age;
    int pesel;
    
    void think(){
        System.out.println("Human thinking");
    }
}

 

 

Konstruktor

Czasami bywa tak, że tworząc obiekt, chcielibyśmy od razu deklarować jego wstępne dane. Takie deklaracje pozwalają tworzyć wyróżniające się obiekty. Z pomocą przychodzi nam konstruktor klasy. Bardzo łatwo go stworzyć i rozpoznać, wygląda podobnie do zwykłej metody, ale o takiej samej nazwie, jak nazwa klasy. Spójrzcie poniżej:

class Human{
    String name;
    int age;
    int pesel;
    
    public Human() { 
    }
    
    void think(){
        System.out.println("Human thinking");
    }
}

Słówko kluczowe public to modyfikator dostępu - teraz go zignorujcie, będzie opisane w innym wpisie. Konstrukcja Human(){} oznacza konstruktor bezargumentowy. Działa dokładnie tak, jak metoda bezargumentowa. Ten konstruktor tworzy obiekt tak, jak to widzieliśmy do tej pory, bez przypisania polom konkretnych wartości. Aby zadeklarować nowy obiekt z konkretnymi wartościami, trzeba stworzyć odpowiedni konstruktor argumentowy:

public Human(String name, int age, int pesel) {
    this.name = name;
    this.age = age;
    this.pesel = pesel;
}

W przykładzie powyżej widzimy, że konstruktor przyjmuje wartości w nawiasie kolejno swoich pól. Zobaczmy to na przykładzie. Nasze pole w klasie Human{ String  name;} posiada taką samą nazwę jak argument w konstruktorze =] Dzięki takiemu konstruktorowi możemy tworzyć teraz obiekty i dynamicznie przypisywać im właściwości:

Human monia = new Human("Monika", 20, 123456789);
Human gucio = new Human("Gustaw", 30, 456789123);
Human frania = new Human("Franciszka", 40, 789456123);

Pierwszy przyjęty argument tego konstruktora to nasze pole 'name'. Analogicznie drugi to 'age', a trzeci to 'pesel'. Stworzone obiekty będą miały już zapisane swoje właściwości. Żeby to udowodnić, wystarczy się do nich odwołać:

System.out.println(monia.age); // 20
System.out.println(gucio.age); // 30
System.out.println(frania.age); // 40

Konstruktory można przeciążać tak, jak metody. Do tej pory już przeciążyliśmy konstruktor klasy Human, ponieważ posiadamy już konstruktor bezargumentowy: Human() oraz jeden konstruktor argumentowy: Human(String name, int age, int pesel).

Warto pamiętać, że konstruktorem domyślnym jest ten bezargumentowy: Human(), pomimo tego, że nie został utworzony w klasie. Będzie on dostępny zaraz po utworzeniu klasy oraz w przypadku, gdy klasa nie posiada żadnego innego konstruktora. Natomiast po stworzeniu przez nas jakiegokolwiek konstruktora argumentowego: Human(String name, int age, int pesel), to właśnie on będzie tym domyślnym. Aby znowu uzyskać dostęp do konstruktora bezargumentowego: Human(), trzeba go po prostu stworzyć. Przy okazji przeciążając konstruktor. Na szczęście nie musicie się tym zbytnio martwić. Kompilator poinformuje was o tym przy próbie tworzenia obiektu. Dodatkowo większość IDE posiada funkcje tworzenia konstruktorów za pośrednictwem odpowiedniego skrótu klawiszowego =] 

Tworząc konstruktor możemy zadeklarować dowolną liczbę przyjmowanych pól, ale tworząc obiekt z tego konstruktora, musimy już wszystkie pola podać w nawiasach. Niektórych pól nie musimy deklarować, a można je sobie wstępnie generować:

public Human(String name, int age) {
    this.name = name;
    this.age = age;
    this.pesel = age*10;
}

Human bolek = new Human("Bolesław", 25);
System.out.println(bolek.pesel); // 250

 

 

This

Aby kompilator połapał się, o którą dokładnie nazwę nam chodzi, używamy słowa kluczowego this. Oznacza ono, że mamy na myśli nazwę pola w danej klasie, a nie nazwę argumentu w konstruktorze. Zapis this.name odwołuje się do zmiennej name z pola klasy Human.

 

 

Abstrakt

W tym wstępie do klas w Javie warto wspomnieć na szybko o abstrakcyjności klas. Słówko abstract przed klasą oznacza twór abstrakcyjny. Klasa abstrakcyjna działa prawie tak samo, jak zwykła klasa. Może posiadać pola, konstruktory i metody, ale nie może zostać stworzony obiekt tej klasy. Zapytacie, jaki w takim razie to ma sens? Odpowiedź jest prosta. Ponieważ czasami musimy użyć klasy w naszym drzewku hierarchii. Stworzyć przodka oraz kilka klas potomnych zamiast wielkiej ilości pojedynczych klas. Takie rozwiązania są elastyczne i bezpieczne, tylko spójrzcie na przykład poniżej:

abstract class Animal{
    String name;
    int legs;
}

class Mammal extends Animal {
    
}

class Bird extends Animal {

}

Bird jaskółka = new Bird();
jaskółka.legs = 2;

Dzięki użyciu klasy abstrakcyjnej nie musimy deklarować w każdej klasie zwierzęcej pola leg; czy name;. Zadeklarowaliśmy to tylko w jednej klasie abstrakcyjnej Animal(). Wiemy, że każdy Animal() posiada nogi.

Co znaczy słówko extends oraz jak to działa, że pomimo braku pola leg; w klasie Bird() mogliśmy się do tego pola odwołać? To już pozostawimy sobie na inny wpis o dziedziczeniu.