KlasaCzas na deklarowanie klas
Pisząc kod w Javie nie da się uniknąć napisania klas czy interfejsów. Wewnątrz klas jak zapewne wiesz znajdują się zmienne i metody. Od tego, jak zadeklarujesz swoją klasę zależy jak twój kod będzie działał. Przykładowo metoda publiczna (public) jest dostępna w każdym punkcie kodu.
Metoda oznaczona jako “prywatna” (private) jest wymazana z dostępu wszędzie poza swoim miejscem deklaracji.
Develop code that declares classes (including abstract and all forms of nested classes), interfaces, and enums, and includes the appropriate use of package and import statements (including static imports).
Opracuj kod, który deklaruje klasy (włączając to klasy abstrakcyjne, jak i wszystkie formy zagnieżdżonych klas), interfejsy i typy wyliczeniowe, które się zawierają w odpowiednich paczkach. Użyj wyrażeń import w tym też statycznych. (tym razem tłumaczenie mogło mi nie wyjść)
W tym wpisie skoncentruję się na tym, jak zmodyfikować i zadeklarować klasę. Większość programistów Java myśli ,że wie jak większość modyfikatorów działa. Java nie jest moją główną specjalizacją, ale podobnie myślałem o C# i byłem w błędzie.
Dlatego przyjrzyjmy się dokładnie tworzeniu klas.
Zasady deklaracji w pliku źródłowym
Zanim zobaczymy jak poprawnie zadeklarować klasę zobaczymy zbiór zasad, które się pokrywają deklarowaniem klas, jak i wyrażeniami import i package.Oto zasady plików źródłowych.
Pisząc pierwszy raz aplikacje w Ecplisie zdziwiłem się ,że są takie zasady. Mają one jednak sens. Programując w C# byłem nieświadomy tylu rzeczy teraz wiem, że nauka Javy ma sens.
- W pliku źródłowym może być tylko jedna klasa publiczna.
- W pliku może być wiele klas niepublicznych.
- Komentarze mogą pojawić się na początku i na końcu linii w pliku źródłowym.
- Jeśli w pliku jest klasa publiczna to nazwa pliku źródłowego powinna być taka sama jak nazwa klasy. Przykładowo publiczna klasa Kot musiałaby znajdować się w pliku Kot.java.
- W plikach, w których nie ma klasy publicznej ich nazwa pliku źródłowego może być inna niż klas wewnątrz pliku.
- Jeśli klasa jest częścią paczki to wyrażenie package musi być w pierwszej linijce pliku źródłowego przed jakimkolwiek wyrażeniem import.
- Jeśli w pliku są wyrażenia importto muszą one znajdować się pomiędzy wyrażeniem package (jeśli takie jest) a deklaracją klasy. Jeśli nie ma wyrażenia package ,to wyrażenie import są w pierwszych linijkach pliku źródłowego. Jeśli nie ma wyrażenia package i import to deklaracja klasy powinna być na początku.
- Wyrażenia import i package dotyczą wszystkich klas wewnątrz pliku źródłowego. Czyli nie da się zadeklarować kilku klas, które posiadałyby różne paczki, jak i importy w tym samym pliku źródłowym.
Deklaracja klasy i modyfikatorów
Deklaracja klasy wygląda tak:
class Test {}
Ten kod jest poprawny, ale trzeba jeszcze dodać modyfikator przed klasą. Modyfikatory mogą być podzielone na dwie kategorie.
- Modyfikatory dostępu: public, protected, private
- Modyfikatory niedostępowe: final, abstract
Kontrola dostępu w Javie ma 4 poziomy, ale jak widzisz są trzy modyfikatory. Czwarty poziom dostępu, który nazywa się domyślnym dostępem do paczki jest uzyskany poprzez nie użycie żadnego z tych trzech modyfikatorów. W każdej klasie, metodzie bądź instancji zmiennej deklarujesz poziom dostępu – jawnie, czy nie - robisz to. W klasie dwa modyfikatory są najczęściej używane. Jest to modyfikator public i brak modyfikatora.
Dostępność klasy
Co oznacza dostępność dla klasy. Kiedy widzimy kod, w którym jedna klasa X ma dostęp do innej klasy Y, to wtedy klasa X może zrobić trzy rzeczy:
- Stworzyć instancje klasy Y
- Dziedziczyć (extend) po klasie Y.
- Uzyskać dostęp do pewnych zmiennych i metod wewnątrz klasy B w zależności od poziomu dostępu do tych metod i zmiennych.
W rezultacie dostępność oznacza widzialność. Jeśli klasa X nie widzi klasy Y to poziom dostępu metod i zmiennych wewnątrz klasy Y nie ma znaczenia.
Teraz zobaczymy jakie poziomy dostępu ma klasa.
Dostępność domyślna:Klasa bez żadnego modyfikatora dostępu określa dostępność domyślną. Domyślny dostęp jest na poziomie paczki, ponieważ klasa ta będzie widoczna tylko przez inne klasy w tej samej paczce.Czyli klasa znajdująca się w innej paczce nie uzyska dostępu do tej klasy. Czas na mały przykład.
Mam dwie klasy w różnych paczkach. Deklaracja klasy X w “innaPaczka” jest następująca:
package innaPaczka;
class KlasaX
{
}
Teraz gdy spróbuję utworzyć instancje tej klasy w klasie Y, która znajduje się w zielonej paczce to kompilator zasygnalizuje mi błąd ponieważ piszę program w Eclipse, to jestem powiadomiony o tym dość szybko.
Jak widzisz pomimo wyrażenia (import innaPaczka.*), które pozwala mi na użycie zasobów “innaPaczka” wciąż nie mam dostępu klasy X. Eclipse sugeruje zmianę dostępu KlasyX na protected ale zakładając ,że chcielibyśmy użyć tej klasy nie tylko w dziedziczeniu lepiej byłoby ustawić ją na public.
Możesz też utworzyć inną KlasęX w paczce “zielonaPaczka” to też rozwiąże ten problem.
Kiedy na egzaminie będzie pytanie związane z logiką klas warto na początku spojrzeć na modyfikator dostępu klas. Jeśli zobaczysz taki błąd będziesz wiedział ,że taki kod nie ma prawa się skompilować i nie będziesz musiał się martwić resztą pytania, ponieważ to już jest odpowiedź.
Dostępność publiczna:Klasa zadeklarowana modyfikatorem public jest dostępna dla wszystkich klas we wszystkich paczkach. Większość wbudowanych klas Java ma właśnie ten poziom dostępu, bo w końcu jakbyś tych klas używał.
Oczywiście, pomimo iż klasa jest publiczna aby jej użyć wciąż trzeba zaimportować jej paczkę. Chyba że obie przez ciebie używane klasy są w tej samej paczce.
Wracając do poprzedniego przykładu, jeśli klasaX jest publiczna to już żaden błąd nie będzie występował.
package innaPaczka;
public class KlasaX
{
}
Teraz klasaX jest widoczna przez wszystkie paczki w też“zielonaPaczka”. KlasaY może teraz też dziedziczyć po klasieX – no, chyba że posiada ona modyfikator “final”.
Inne modyfikator klas
Możesz też zmodyfikować zachowania klas używając słów kluczowych jak: final, abstract oraz strictfp. Tego ostatniego nie musisz znać jak chcesz poczytać o nim o to szybki wpis z Wikipedii* ,który znalazłem.
Za nimi omówię słowa kluczowe final i abstract powiem ,że nie wolno ich mieszać o czym zaraz się sam przekonasz.
Klasa “Final”: To słowo kluczowe symbolizuje ,że dana klasa nie może się stać klasą bazową. Czyli żadna inna klasa nie może dziedziczyć po niej ,a próba skończy się błędem kompilacji.
Dlaczego chciałbyś oznaczyć klasę jako “final”? To słowo kluczowe powinno pojawiać się w klasach, w których chcemy mieć absolutną pewność, że żadne zachowania tej klasy nie zostaną nadpisane przez inną klasę.
Słowo “final” daje ci gwarancje ,że nikt inny nie wykorzysta dalej twojej implementacji. Wiele klas w Javie jest oznaczona jako “final”. Przykładowo klasa “String” nie może być klasą bazową. Gdyby tak było nikt nie mógłbym zagwarantować ,że obiekt “String” zawsze i wszędzie działałby tak samo.
Słowo te jednak też nie jest wykorzystywane często, w końcu mówimy tutaj o języku obiektowym. Mając klasy niefinalne możesz naprawiać błędy w klasach bazowych, do których nie masz dostępu. Możesz napisać klasę dziedziczącą po tej klasie, która nadpisuję tę metodę. Później użyć tej klasy zamiast klasy bazowej i już problem X rozwiązany. Jeśli klasa byłaby oznaczona jako “final” powiedzmy, że szukałbyś innego rozwiązania .
Tak jak w poprzednim przykładzie i w tym również wykażę działanie słów kluczowych.
Teraz klasaX jest oznaczona jako finalna.
package innaPaczka;
public final class KlasaX
{
}
Oczywiście w czasie próby dziedziczenia otrzymałem błąd.
W praktyce słowo final jest używane tylko wypadkach, gdy nie chcemy aby inny programista dziedziczył po naszej klasie z różnych powodów jak np. bezpieczeństwo.
Klasa “abstract” :Abstrakcyjna klasa nigdy nie ma swojej instancji. Istnieją tylko do jednego celu do dziedziczenia. Chociaż oczywiście możesz używać abstrakcyjnej klasy, ale wciąż nie możesz stworzyć jej instancji.
Po co klasa, która nie tworzy instancji. Powiedzmy ,że w schemacie dziedziczenia masz klasę, która spełnia swój cel tylko jako magazyn implementacji dla różnych klas. Jest to np. klasa Ssak zawiera wszystkie ogólne cechy wszystkich ssaków. Teraz tworząc instancje klasy Ssak zastanów się jak to zwierzę wygląda ,czy chodzi na dwóch nogach, czy na czterech ,czy ma futro, czy nie.
Klasa Ssak nie reprezentuje konkretnego zwierzęcia, czy nawet człowieka tylko określa grupę wspólnych cech. Ciekawe jak instancja Ssak się zachowuje skoro nawet nie wiem, co to jest z punktu widzenia encji.
Oto przykład:
package innaPaczka;
abstract class Mammal
{
private String name;
private int numberOfLegs;
private boolean hasFur;
public abstract void walking();
public abstract void eating();
public abstract void sleeping();
//serious code goes here xD
}
Ten kod skompiluje się poprawnie. Oczywiście próba stworzenia instancji tej klasy skończy się niepowodzeniem.
Zapewne zwróciłeś uwagę na to, że metody w klasie abstrakcyjnej też zostały oznaczone jako “abstract” oraz nie mają nawiasów klamrowych. W klasie abstrakcyjnej mogą występować metody abstrakcyjne ,ale jeśli sama klasa nie jest oznaczona jako abstrakcyjna, to metoda tak nie może występować. Kto wie może otrzymasz pytanie z metodą abstrakcyjną i odpowiedź będzie polegała na dodaniu słowa abstract do klasy.
Pamiętać trzeba jednak ,że metody abstrakcyjne nie mają ciała i kończą się średnikiem. Na razie nie będę omawiał szczegółowo metod abstrakcyjnych.W dużym skrócie działają jako sygnatury metody w Interfejsach. Klasa dziedzicząca po klasie abstrakcyjnej musi stworzyć dowolną metodę zgodną z sygnaturą metody abstrakcyjnej.
package innaPaczka;
public final class KlasaX extends Mammal
{
@Override public void walking() {
// TODO Auto-generated method stub }
@Override public void eating() {
// TODO Auto-generated method stub }
@Override public void sleeping() {
// TODO Auto-generated method stub }
}
Oczywiście w klasie abstrakcyjnej możesz mieć też zwykłe metody, których zachowanie będzie dziedziczone. W sumie nawet po to są klasy w dziedziczeniu ,aby nie pisać tych samych implementacji dla następnych 120 klas.
Po przeczytaniu tych informacji teraz zapewne się domyślasz, dlaczego klasa nie może być równocześnie final i abstract. Klasa abstrakcyjna żyje po to, aby zostać dziedziczona ,a słowo final blokuje dziedziczenie.
Próba utworzenia klasy z tymi dwoma słowami kluczowymi skończy się błędem.
Deklarowanie Interfejsów
Czas na drugą część tego wpisu.
Develop code that declares an interface. Develop code that implements or extends one or more interfaces. Develop code that declares an abstract class. Develop code that extends an abstract class.
Opracuj kod, który deklaruje interfejs. Utwórz kod, który implementuje i dziedziczy po jednym lub więcej interfejsach. Opracuj kod, który deklaruje klasę abstrakcyjną. Utwórz kod, który dziedziczy po klasie abstrakcyjnej.
Tworząc interfejs definiujesz kontrakt określający co klasa potrafi bez określenie jak klasa to zrobi.
Powiedzmy, że mam interfejs “Shineable”.Każda klasa implementująca ten interfejs musi napisać kod do metody“shine()” i “setShineFactor()”.
Definiując interfejs chcesz aby każda klasa, która go implementuje określała obiekt, który może lśnić. Wiedząc, że klasa dziedziczy po tym interfejsie wiesz ,że obiekt będzie lśnić, oraz że implementuje te dwie metody.
Interfejs może być implementowany przez każdą klasę w jakimkolwiek momencie drzewa dziedziczenia. W końcu klasa może dziedziczyć po wielu interfejsach. Interfejs pozwala na nadanie szczególnych cech obiektom, które nie dziedziczą po sobie. Przykładko, klasa But i klasa Diament nie dziedziczą po sobie, ale obie mogą implementować ten lśniący interfejs. Używając interfejsu dla tych dwóch klas mówisz ,że te“obiekty będą lśnić”. Dla Javy będzie to znaczyło ,że obie te klasy mają metodę shine() i setShineFactor().
Pomyśl o interfejsie jako o całkowitej klasie abstrakcyjnej, w końcu dla kompilatora interfejs wygląda tak.
Jak zauważyłeś w pierwszej ilustracji metody nie mają ciał oraz kończą się średnikiem .W interfejsie nie trzeba deklarować ,że jest to metoda abstrakcyjna, ponieważ wszystkie one automatycznie już nimi są.
Trochę się zdziwiłem, ale można legalnie zadeklarować abstrakcyjny interfejs. Nie ma to sensu, ponieważ interfejs już jest abstrakcyjny. Potraktuj to jednak jako ciekawostkę, chociaż kto wie, jakie dziwne pytania mogą być na egzaminie.
Jakie są więc różnice pomiędzy interfejsem a klasą abstrakcyjną.
Klasa abstrakcyjna może deklarować metody abstrakcyjne i nieabstrakcyjne ,a interfejsy mają tylko metody abstrakcyjne. Inną różnicą jest fakt, że interfejsy mają ograniczenia:
- Wszystkie metody w interfejsie są publiczne i abstrakcyjne. Ponieważ tak jest nie musisz pisać tych słów kluczowych w czasie deklaracji metody ,ale wciąż metoda jest publiczna i abstrakcyjna, co widać na ilustracji powyżej.
- Interfejs jest deklarowany za pomocą słowa “interfejs”.
- Wszystkie zmienne w interfejsie muszą być publiczne (public) statyczne (static) i final co oznacza, że w interfejsie możesz deklarować tylko stałe ,a nie zmienne.
- W interfejsie metody nie mogą być statyczne.
- Interfejs może dziedziczyć po innych interfejsach.
- Interfejs może dziedziczyć tylko po interfejsie.
- Wewnątrz interfejsu nie możesz zadeklarować klasy czy interfejsu.
- Nie można utworzyć instancji interfejsu, ale można go użyć w sposób polimorficzny.
Interfejsy mają też modyfikator dostępu. Podobnie jak z klasami najważniejszymi modyfikatorami dla interfejsu są: public i domyślny (bez). Warto jednak o tym pamiętać, ponieważ nie pisząc modyfikatora właśnie go też ustalasz.
public interface Shineable
{
void shine();
void setBoundeFactor(int g,int l);
}
Wracając do wbudowanych właściwości interfejsów co się stanie jeśli zadeklaruję metody w interfejsie jako publiczne i abstrakcyjne.
Odpowiedź brzmi nic i ten obrazek z edytora Eclipse, na to wykazuje. Wciąż jest to marnotrawstwo kodu. Można wykonać różne kombinacje tych słów kluczowych i kompilator nie zwróci błędu. Musisz więc zapamiętać, że wszystkie metody w interfejsie są zawsze publiczne i abstrakcyjne.
Czy można źle zadeklarować metody w interfejsie ,ależ oczywiście? Używając innych niepotrzebnych słów kluczowych jak: private, protected,static i final kompilator zgłosi błąd. Ponieważ te słowa kluczowe próbują nadpisać znane nam zachowania.
Deklarowanie stałych w Interfejsie
Możesz umieścić stałe w interfejsie, robiąc to masz gwarancje ,że wszystkie klasy implementujące ten interfejs będą miały dostęp do tej samej stałej.
Umieszczając stałą w interfejsie sprawiasz ,że każda klasa, która będzie dziedziczyć po interfejsie będzie miała dostęp do tej stałej. Zupełnie jakby było to dziedziczenie pomiędzy klasami.
Składnia dla stałych w interfejsie jest następująca.
public static final int LIGHTCONSTANT = 1;
Warto jednak zaznaczyć, że w interfejsie nie tylko w taki sposób można zadeklarować stałą. Możesz nie pisać tych wszystkich słów kluczowych, ponieważ kompilator zrobi to za ciebie.
int LIGHTCONSTANT = 1;
Wygląda to, jak perfekcyjna pułapka na egzamin.
W końcu wszystkie kombinacje tych słów kluczowych bądź jej brak poprawnie deklarują stałą w interfejsie. Jak widać poniżej.
public int STALA = 1;
//stała która wygląda jak niestała
int STALA2 = 2;
//wygląda normalnie ,ale tak nie jest
static int STALA3 = 3;
public static int STALA4 = 4;
public static final int STALA5 = 5;
public final int STALA6 = 6;
static final int STALA7 = 7;
Na co jeszcze warto zwrócić uwagę. Nie można zmienić wartości stałej w klasie dziedziczącej, bo posiada ona modyfikator final.
Kod taki się nie skompiluje.
Co dalej:
Następnym razem przyjrzę się zasadom deklaracji elementów składowych klasy.
Spis treści: