Spis treści
- Rozdział 9 - Klasy
- Czym są klasy i do czego służą?
- Różnice między typami prymitywnymi i referencyjnymi
- Modyfikatory dostępu
- Podsumowanie i pytania – typy prymitywne i referencyjne, modyfikatory dostępu
- Pola klas
- Konstruktory
- Equals i porównywanie obiektów
- Referencje do obiektów
- Metody i pola statyczne
- Pakiety i importowanie klas
Od początku naszej przygody z językiem Java korzystaliśmy ze słowa kluczowego static – każda metoda main, którą do tej pory napisaliśmy, była statyczna, tzn. jej sygnatura zawierała słowo kluczowe static:
public class HelloWorld { public static void main(String[] args) { System.out.println("Witaj Swiecie!"); } }
W klasach możemy definiować dwa rodzaje metod i pól:
- statyczne (korzystając z modyfikatora static),
- niestatyczne, nazywane także polami i metodami instancji (z ang. non-static fields and methods, czy też, instance fields and methods) – definiujemy je pomijając modyfikator static.
Pola statyczne różnią się od pól instancji (niestatycznych) tym, że są one współdzielone przez wszystkie obiekty tej klasy, tzn. przynależą one do całej klasy, a nie konkretnie utworzonego obiektu. Metody statyczne, natomiast, różnią się od metod niestatycznych tym, że nie mogą korzystać z pól i metod niestatycznych.
Wszystkie obiekty klasy mają dostęp do pól i metody statycznych. Mało tego – do pól i metod statycznych mamy dostęp nawet wtedy, gdy nie utworzymy żadnego obiektu klasy. Wartości pól statycznych są współdzielone przez wszystkie obiekty klasy – w przeciwieństwie do pól niestatycznych (instancji), których własne egzemplarze ma każdy obiekt klasy, pola statyczne są tworzone jako pojedyncze wartości/obiekty.
Metody statyczne mają dostęp jedynie do metod i pól, które także są statyczne. Nie mogą one odnosić się do niestatycznych pól – nie operują one na konkretnych obiektach klasy, lecz w kontekście całej klasy – nie mają one dostępu do obiektu this, który wskazuje na obiekt, na rzecz którego metoda została wywołana.
Zobaczymy teraz przykłady każdej z powyżej opisanych cech pól i metod statycznych.
Pola statyczne¶
Poniższa klasa zawiera dwa pola – jedno statyczne, a drugie nie:
public class PrzykladStatic { private int poleInstancji; private static int poleStatyczne = 5; public PrzykladStatic(int poleInstancji) { this.poleInstancji = poleInstancji; } public static void main(String[] args) { System.out.println("Pole statyczne (przez klase): " + PrzykladStatic.poleStatyczne // 1 ); PrzykladStatic obiekt1 = new PrzykladStatic(10); System.out.println("Pole statyczne (przez obiekt1): " + obiekt1.poleStatyczne // 2 ); } }
Zauważmy, że w tym przykładzie odnosimy się do pola poleStatyczne (1), zanim w ogóle utworzymy obiekt klasy PrzykladStatic, Aby to osiągnąć, piszemy nazwę klasy, po której następuje kropka oraz nazwa pola statycznego:
PrzykladStatic.poleStatyczne // 1
Do pola statycznego możemy odnieść się także za pomocą obiektu klasy, w której to pole jest zdefiniowane. W powyższym programie wypisujemy drugi raz na ekran wartość pola poleStatyczne, ale tym razem odnosimy się do niego za pomocą zmiennej obiekt1 (2):
obiekt1.poleStatyczne // 2
W wyniku działania powyższego programu, na ekranie zobaczymy:
Do pól statycznych możemy odnosić się za pomocą zarówno nazwy klasy, jak i obiektów tej klasy, ale tego samego nie można powiedzieć o zmiennych niestatycznych – do takich zmiennych możemy odnosić się jedynie przez obiekty klasy – poniższa linijka kodu spowodowałaby błąd kompilacji, ponieważ pole poleInstancji nie jest statyczne:
System.out.println(PrzykladStatic.poleInstancji);
Na początku tego podrozdziału wspomnieliśmy, że pola statyczne są współdzielone przez wszystkie obiekty klasy – dodamy do metody main z powyższego przykładu jeszcze jeden obiekt klasy PrzykladStatic:
public static void main(String[] args) { System.out.println("Pole statyczne (przez klase): " + PrzykladStatic.poleStatyczne // 1 ); PrzykladStatic obiekt1 = new PrzykladStatic(10); System.out.println("Pole statyczne (przez obiekt1): " + obiekt1.poleStatyczne // 2 ); PrzykladStatic obiekt2 = new PrzykladStatic(15); obiekt2.poleStatyczne = -20; // 3 System.out.println("Pole statyczne po zmianie (przez klase): " + PrzykladStatic.poleStatyczne // 4 ); System.out.println("Pole statyczne po zmianie (przez obiekt1): " + obiekt1.poleStatyczne // 5 ); System.out.println("Pole statyczne po zmianie (przez obiekt2): " + obiekt2.poleStatyczne // 6 ); }
W tej wersji metody main, tworzymy drugi obiekt klasy PrzykladStatic o nazwie obiekt2, i za jego pomocą zmieniamy wartość pola statycznego poleStatyczne (3). Następnie, ponownie wypisujemy wartość tego pola za pomocą nazwy klasy (4), a także za pomocą obu obiektów (5) (6). Na ekranie zobaczymy:
Zmieniając wartość pola statycznego poleStatyczne w linii:
obiekt2.poleStatyczne = -20; // 3
zmieniamy pole, które jest wspólne dla wszystkich obiektów klasy PrzykladStatic – dlatego, gdy wypisujemy wartość tego pola na ekran za pomocą zarówno obiekt1 (5), jak i obiekt2 (6), widzimy na ekranie tą samą wartość.
Metody statyczne¶
Gdy wywołujemy metodę statyczną, nie mamy w niej dostępu do obiektu, na rzecz którego metoda jest wywołana – w końcu możemy wywołać taką metodę nie mając w ogóle jeszcze utworzonego żadnego obiektu tej klasy. Powoduje to, że metody statyczne:
- nie mogą odnosić się do pól niestatycznych,
- nie mogą wywoływać metod niestatycznych.
Spójrzmy na poniższy przykład:
public class PrzykladMetodyStatycznej { private String poleInstancji; private static int poleStatyczne = 20; public void metodaInstancji() { // ? } public static void metodaStatyczna() { // ? } }
Do jakich pól i metod mamy dostęp w metodach metodaInstancji i metodaStatyczna?
- Pierwsza metoda, metodaInstancji, to metoda niestatyczna. Możemy się w niej odnieść do, zarówno, pól i metod statycznych, jak i niestatycznych, więc mamy dostęp do:
- pola poleInstancji,
- pola poleStatyczne,
- metody metodaStatyczna.
- Metoda metodaStatyczna ma dostęp jedynie do innych metod statycznych i pól statycznych, więc w tej metodzie możemy się jedynie odnieść do pola statycznego poleStatyczne.
Spójrzmy na przykład wykorzystanie możliwych pól w metodzie metodaInstancji:
public void metodaInstancji() { System.out.println("Wywolales metode instancji."); // odwolanie do pola niestatycznego System.out.println( "poleInstancji (w metodzieInstancji): " + poleInstancji ); // odwolanie do pola statycznego System.out.println( "poleStatyczne (w metodzieInstancji): " + poleStatyczne ); System.out.println("Wywoluje metode statyczna z metody instancji:"); // wywolanie metody statycznej metodaStatyczna(); }
Wypisujemy na ekran wartości pola statycznego i niestatycznego, a także, na końcu, wywołujemy metodę statyczną.
Z kolei metoda metodaStatyczna mogłaby wyglądać następująco:
public static void metodaStatyczna() { System.out.println("Wywolales metode statyczna."); // blad! // kod zakomentowany - nie mozemy w metodzie statycznej // korzystac z pola niestatycznego //System.out.println( // "poleInstancji (w metodzieStatycznej):" + poleInstancji //); // ok - mozemy w metodzie statycznej // korzystac z pol statycznych System.out.println( "poleStatyczne (w metodzieStatycznej): " + poleStatyczne ); // blad! // nie mozemy tutaj wywolac metody niestatycznej // metodaInstancji(); }
Zauważmy, że w metodzie statycznej nie mamy dostępu ani do metody metodaInstancji, ani do pola poleInstancji. Spójrzmy na wywołanie obu powyższych metoda w metodzie main:
public static void main(String[] args) { System.out.println("Wywoluje metode statyczne za pomoca klasy"); PrzykladMetodyStatycznej.metodaStatyczna(); // 1 System.out.println("Tworze obiekt klasy PrzykladMetodyStatycznej"); PrzykladMetodyStatycznej obiekt = new PrzykladMetodyStatycznej(); obiekt.metodaInstancji(); // 2 obiekt.metodaStatyczna(); // 3 }
W metodzie main najpierw wywołujemy metodę statyczną (1), zanim w ogóle utworzymy obiekt klasy. Następnie, tworzymy obiekt klasy PrzykladMetodyStatycznej i wywołujemy zarówno metodę niestatyczną (2), jak i statyczną (3). Na ekranie zobaczymy:
Spójrzmy na jeszcze jeden przykład użycia metody i pola statycznego – w poniższej klasie Komunikat korzystamy z jednego pola statycznego i jednej metody statycznej w celu zliczania liczby obiektów tej klasy, które zostały utworzone:
public class Komunikat { private final String komunikat; private static int liczbaObiektowTejKlasy; public Komunikat(String komunikat) { this.komunikat = komunikat; liczbaObiektowTejKlasy++; // 1 } public static int ileObiektowUtworzono() { return liczbaObiektowTejKlasy; } public static void main(String[] args) { System.out.println("Liczba obiektow klasy Komunikat: " + Komunikat.ileObiektowUtworzono() ); // 2 Komunikat k1 = new Komunikat("Witaj"); Komunikat k2 = new Komunikat("Witam!"); Komunikat k3 = new Komunikat("Halo?"); System.out.println("Liczba obiektow klasy Komunikat: " + Komunikat.ileObiektowUtworzono() ); // 3 } }
W konstruktorze klasy Komunikat poza tym, że ustawiamy wartość pola instancji o nazwie komunikat, to zwiększamy także o 1 liczbę przechowywaną w statycznym polu liczbaObiektowTejKlasy (1).
W metodzie main najpierw wypisujemy liczbę przechowywaną w statycznym polu, która zwracana jest przez statyczną metodę ileObiektowUtworzono (2). Następnie tworzymy trzy obiekty klasy Komunikat i ponownie wypisujemy na ekran liczbę obiektów (3):
Dlaczego metoda main jest statyczna?¶
Wszystkie metody main, jakie do tej pory pisaliśmy, były statyczne – zawsze dodawaliśmy w ich sygnaturze słowo kluczowe static. Takie jest wymaganie odnośnie naszych programów, które mają być uruchamiane, ale z czego to wynika?
Twórcy języka Java postanowili, że podobnie, jak w programach pisanych w językach C i C++, na których język Java jest wzorowany, programy napisane w języku Java będą rozpoczynały swoje działanie od metody o nazwie main.
Dzięki temu, że main jest metodą statyczną, Maszyna Wirtualna Java, która uruchamia i wykonuje kod naszego programu, nie musi tworzyć obiektu klasy, w której metoda main jest zawarta. Klasa główna naszego programu (tzn. ta, która zawiera metodę main) mogłaby mieć wiele konstruktorów, bądź mieć konstruktor prywatny i nie pozwalać na tworzenie obiektów swojej klasy (w rozdziale o dziedziczeniu zobaczymy przykład prywatnych konstruktorów). Gdyby było wiele dostępnych konstruktorów, to który z nich powinien zostać użyty przez Maszynę Wirtualną Java do utworzenia obiektu uruchamianej klasy?
Statyczność metody main upraszcza ten proces – Maszyna Wirtualna Java uruchamiając nasz program szuka w klasie, którą uruchamiamy, statycznej metody main i ją wykonuje, rozpoczynając tym samym działanie naszego programu.
Kiedy stosować pola i metody statyczne?¶
Pola statyczne przydają się, gdy potrzebujemy przechować w klasie pewne dane, które nie powinny przynależeć do każdego obiektu w klasie osobno, lecz są wspólną cechą wszystkich obiektów tej klasy, lub dane te mogą być przydatne przez klasy, które będą z niej korzystały.
Metody statyczne są często wykorzystywane jako metody pomocnicze, które nie mają stanu i nie potrzebują zapisywać nic w polach klasy, w których zostały zdefiniowane.
Przykładem klasy pomocniczej z biblioteki standardowej Java jest klasa Math, które posiada jedynie pola i metody statyczne – jej pola to różne stałe matematyczne, jak liczba Pi, a zdefiniowane w niej metody służą do różnych obliczeń matematycznych, jak na przykład sin (funkcja sinus), round (zaokrąglanie liczby rzeczywistej), czy też sqrt (square root – pierwiastek).
Metody klasy Math działają na przesłanych do nich argumentach i nie potrzebują zapisywać nic w polach klasy Math, ani nic z nich później odczytywać – klasa Math nie ma w ogóle żadnych pól niestatycznych. Wszystkie jej metody są metodami pomocniczymi, które możemy wykorzystywać w naszych programach do różnych obliczeń matematycznych.
Dzięki temu, że wszystkie metody i pola tej klasy są statyczne, nie musimy za każdym razem, gdy chcemy z niej skorzystać, tworzyć nowego obiektu tej klasy – wystarczy po prostu wywołać daną metodę za pomocą nazwy klasy.
Spójrzmy na przykład użycia klasy Math:
public class KorzystanieZMath { public static void main(String[] args) { System.out.println("Liczba PI wynosi: " + Math.PI); System.out.println("Liczba E wynosi: " + Math.E); System.out.println( "Sinus 90 stopni wynosi: " + Math.sin(Math.toRadians(90)) ); System.out.println( "Zaokraglona liczba PI: " + Math.round(Math.PI) ); System.out.println( "Pierwiastek liczby 100 to " + Math.sqrt(100) ); } }
W powyższym programie korzystamy ze statycznych pól PI oraz E klasy Math, a także kilku metod statycznych: sin, toRadians, round, oraz sqrt. Zauważmy, jak wygodne jest używanie klasy Math – nie musieliśmy utworzyć obiekt tej klasy, aby z niej korzystać – wywołujemy po prostu metody, które są nam potrzebne i odnosimy się do pól klasy za pomocą nazwy tej klasy. Wygoda ta wnika z faktu, że te pola i metody są statyczne.
W wyniku działania powyższego programu, na ekranie zobaczymy:
Podsumowanie¶
- W klasach możemy definiować dwa rodzaje metod i pól:
- statyczne (korzystając z modyfikatora static),
- niestatyczne, nazywane także polami i metodami instancji (z ang. non-static fields and methods, czy też, instance fields and methods) – definiujemy je pomijając modyfikator static.
- Pola statyczne różnią się od pól instancji (niestatycznych) tym, że są ona współdzielone przez wszystkie obiekty tej klasy, tzn. przynależą one do całej klasy, a nie konkretnie utworzonego obiektu. W przeciwieństwie do pól niestatycznych (instancji), których własne egzemplarze ma każdy obiekt klasy, pola statyczne są tworzone jako pojedyncze wartości/obiekty.
- Metody statyczne różnią się od metod niestatycznych tym, że nie mogą korzystać z pól i metod niestatycznych.
- Do pól i metod statycznych mamy dostęp nawet wtedy, gdy nie utworzymy żadnego obiektu klasy – w poniższym przykładzie odwołujemy się pola poleStatyczne za pomocą nazwy klasy w pierwszej z zaznaczonych linii. Do pola statycznego możemy także odnieść się za pomocą obiektu klasy, do której pole (bądź metoda) przynależy (druga podświetlona linia):
public class PrzykladStatic { private int poleInstancji; private static int poleStatyczne = 5; public PrzykladStatic(int poleInstancji) { this.poleInstancji = poleInstancji; } public static void main(String[] args) { System.out.println("Pole statyczne (przez klase): " + PrzykladStatic.poleStatyczne ); PrzykladStatic obiekt1 = new PrzykladStatic(10); System.out.println("Pole statyczne (przez obiekt1): " + obiekt1.poleStatyczne ); } }
- Metody statyczne mają dostęp jedynie do metod i pól, które także są statyczne. Nie mogą one odnosić się do niestatycznych pól – nie operują one na konkretnych obiektach klasy, lecz w kontekście całej klasy – nie mają one dostępu do obiektu this, który wskazuje na obiekt, na rzecz którego metoda została wywołana.
- W poniższym przykładzie:
- metodaInstancji ma dostęp do, zarówno, pól i metod statycznych, jak i niestatycznych, więc mamy dostęp do pola poleInstancji, pola poleStatyczne, oraz metody metodaStatyczna,
- metodaStatyczna ma dostęp jedynie do innych metod statycznych i pól statycznych, więc w tej metodzie możemy się jedynie odnieść do pola statycznego poleStatyczne.
public class PrzykladMetodyStatycznej { private String poleInstancji; private static int poleStatyczne = 20; public void metodaInstancji() { // ? } public static void metodaStatyczna() { // ? } }
- Metoda main jest statyczna, ponieważ dzięki temu Maszyna Wirtualna Java nie musi tworzyć obiektu klasy, którą chcemy uruchomić – metodę statyczną można wywołać nie mając utworzonego żadnego obiektu klasy.
- Pola statyczne przydają się, gdy chcemy zawrzeć w klasie dane, które nie powinny przynależeć do każdego obiektu w klasie osobno, lecz są wspólną cechą wszystkich obiektów tej klasy.
- Metody statyczne są często wykorzystywane jako metody pomocnicze, które nie mają stanu i nie potrzebują zapisywać nic w polach klasy, w których zostały zdefiniowane.
- Przykładem klasy pomocniczej z biblioteki standardowej Java jest klasa Math, które posiada jedynie pola i metody statyczne – jej pola to różne stałe matematyczne, jak liczba Pi, a zdefiniowane w niej metody służą do różnych obliczeń matematycznych, jak na przykład sin (funkcja sinus), round (zaokrąglanie liczby rzeczywistej), czy też sqrt (square root – pierwiastek).
Zadania¶
Klasa użyteczna Obliczenia¶
Napisz klasę Obliczenia, która będzie zawierała dwie metody statyczne:
- silnia – metoda powinna zwracać silnię podanej jako argument liczby,
- sumaLiczb – metoda powinna przyjmować tablicę liczby typu int i zwracać ich sumę.
Podobne metody pisaliśmy już w zadaniach do poprzednich rozdziałów – możemy je skopiować z tamtych programów.
Napisz kolejną klasę, o nazwie WykonywanieObliczen, która użyje w metodzie main obie metody z klasy Obliczenia.
cześć, czy w przykładzie o polach statycznych nie chodziło o:
obiekt1.poleStatyczne // 5
obiekt2.poleStatyczne // 6
zamiast
PrzykladStatic.poleStatyczne // 5,6 ?
Dokładnie tak, dziękuję za komentarz, już poprawiłem 🙂