Rozdział 9 - Klasy - Metody i pola statyczne

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 egezmplarze 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:

Nazwa pliku: PrzykladStatic.java
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:

Pole statyczne (przez klase): 5 Pole statyczne (przez obiekt1): 5

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);
PrzykladStatic.java:14: error: non-static variable poleInstancji cannot be referenced from a static context System.out.println(PrzykladStatic.poleInstancji); ^ 1 error

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:

metoda main z pliku PrzykladStatic.java
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): " +
      PrzykladStatic.poleStatyczne // 5
  );

  System.out.println("Pole statyczne po zmianie (przez obiekt2): " +
      PrzykladStatic.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:

Pole statyczne (przez klase): 5 Pole statyczne (przez obiekt1): 5 Pole statyczne po zmianie (przez klase): -20 Pole statyczne po zmianie (przez obiekt1): -20 Pole statyczne po zmianie (przez obiekt2): -20

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:

Nazwa pliku: PrzykladMetodyStatycznej.java
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?

  1. 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:
    1. pola poleInstancji,
    2. pola poleStatyczne,
    3. metody metodaStatyczna.
  2. 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:

metoda z pliku PrzykladMetodyStatycznej.java
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:

fragment pliku PrzykladMetodyStatycznej.java
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:

Wywoluje metode statyczne za pomoca klasy Wywolales metode statyczna. poleStatyczne (w metodzieStatycznej): 20 Tworze obiekt klasy PrzykladMetodyStatycznej Wywolales metode instancji. poleInstancji (w metodzieInstancji): null poleStatyczne (w metodzieInstancji): 20 Wywoluje metode statyczna z metody instancji: Wywolales metode statyczna. poleStatyczne (w metodzieStatycznej): 20 Wywolales metode statyczna. poleStatyczne (w metodzieStatycznej): 20

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:

Nazwa pliku: Komunikat.java
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):

Liczba obiektow klasy Komunikat: 0 Liczba obiektow klasy Komunikat: 3
Konstruktory nie mogą być statyczne – nie miałoby to sensu – służą one do zainicjalizowania tworzonego obiektu, a metody statyczne działają poza konteksem wszelkich obiektów klas.

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 powinnien 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:

Nazwa pliku: KorzystanieZMath.java
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:

Liczba PI wynosi: 3.141592653589793 Liczba E wynosi: 2.718281828459045 Sinus 90 stopni wynosi: 1.0 Zaokraglona liczba PI: 3 Pierwiastek liczby 100 to 10.0
Więcej informacji o klasie Math można znaleźć w oficjalnej dokumentacji języka Java.

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 egezmplarze 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.

Rozwiązania do zadań

Dodaj komentarz

Twój adres email nie zostanie opublikowany.