Elementy statyczne (static)

Java jest językiem obiektowym i kojarzy się głównie z tworzeniem klas, na podstawie których powstają następnie obiekty. Obiekty mogą reprezentować pewien stan, wyrażony przy pomocy pól, ale dzięki definiowaniu metod, możemy na obiektach wykonywać operacje, które będą ten stan modyfikowały.

W Javie istnieje jednak też możliwość definiowania różnych elementów statycznych, czyli takich, do których możemy odwoływać się bez tworzenia obiektów. Elementy takie oznaczamy słowem kluczowym static.

Pola statyczne

W klasach możemy definiować pola statyczne. Pole takie powiązane będzie z klasą, a nie konkretną jej instancją. Do pola statycznego powinniśmy odwoływać się poprzez klasę, ale co ciekawe w Javie istnieje możliwość odwoływania się do takich pól także przez referencje wskazujące na obiekty. Pól statycznych możemy użyć do tego, aby współdzielić jakieś informacje między wszystkimi instancjami danego typu. Przykładowo jeżeli chcemy automatycznie nadawać identyfikatory zgodnie z naturalnym porządkiem, czyli 0, 1, 2, itd. to możemy to zrobić tak:

class Employee {
    static int count = 0;
    int id;

    Employee() {
        id = count++; //zwiększamy pole count, przy tworzeniu każdego obiektu Employee
    }
}
class Company {
    public static void main(String[] args) {
        Employee emp1 = new Employee();
        Employee emp2 = new Employee();
        //odwołanie przez klasę
        System.out.println("Employee count: " + Employee.count); //2
        //odwołanie przez referencję
        System.out.println("Employee count: " + emp1.count); //2
        System.out.println("Employee count: " + emp2.count); //2
    }
}

W przypadku odwoływania się do pola statycznego przez referencję, środowisko programistyczne, takie jak np. IntelliJ IDEA zasugeruje nam zmianę takiego zapisu, aby w wyraźny sposób wskazać to, że count jest polem statycznym, a nie polem instancji.

Z odwoływaniem się do pól statycznych poprzez referencje wiążą się różne nietypowe sytuacje, np. poniższy zapis nie wygeneruje wyjątku NullPointerException:

Employee emp1 = null;
System.out.println(emp1.count);

Pola statyczne najczęściej wykorzystywane są w połączeniu ze słowem final , co pozwala tworzyć stałe . Warto tutaj zwrócić uwagę na to, że zmienne finalne, czyli takie, które są opatrzone jako final , to nie to samo co stałe, czyli pola oznaczone jako static final.

Choć kolejność słów final i static nie ma znaczenia, czyli poprawne są oba poniższe zapisy, to preferowany jest drugi z nich:

class Calc {
    final static double PI = 3.14;
}
class Calc {
    static final double PI = 3.14; //zalecany zapis
}

Takie stałe można znaleźć w klasach w ramach samego JDK, np. w klasie Math. Jeżeli potrzebna jest nam wartość popularnych stałych matematycznych jak PI, czy E, to możemy się do nich odwołać w taki sposób:

System.out.println(Math.PI);
System.out.println(Math.E);

Kurs Java

Metody statyczne

Jeżeli definiujesz metodę, która jest bezstanowa, czyli nie musisz się w niej odwoływać do pól obiektu, to możesz ją zdefiniować jako metodę statyczną. Przykładowo jeżeli tworzymy metodę, która ma sumować dwie liczby, to najlepiej będzie ją zdefiniować właśnie jako statyczną. Metoda taka ma jasne wejście, np. dwie liczby i wyjście, czyli wynik sumowania.

class Calc {
    static int sum(int a, int b) {
        return a + b;
    }
}

Dzięki temu, że metoda jest statyczna, to w celu jej wywołania, nie musimy tworzyć obiektu:

class CalcTest {
    public static void main(String[] args) {
        int sum = Calc.sum(5, 10);
        System.out.println("Suma: " + sum); //15
    }
}

Gdyby metoda nie była statyczna, to musielibyśmy to zapisać tak:

class CalcTest {
    public static void main(String[] args) {
        Calc calc = new Calc();
        int sum = calc.sum(5, 10);
        System.out.println("Suma: " + sum); //15
    }
}

Metody statyczne posiadają pewne ograniczenia. Jeżeli chcemy się z nich odwoływać do innych składowych statycznych w danej klasie, to składowe te też muszą być statyczne. Najlepszym przykładem jest próba odwołania się do pola klasy z metody main:

class Foo {
    int value = 10;
    
    public static void main(String[] args) {
        System.out.println(value); //błąd, metoda main jest statyczna, a pole value nie
    }
}

Rozwiązaniem jest oznaczenie pola jako static:

class Foo {
    static int value = 10;

    public static void main(String[] args) {
        //ok, z metody statycznej możemy odwoływać się do pól i metod statycznych w tej samej klasie
        System.out.println(value);
    }
}

Ewentualnie musielibyśmy utworzyć obiekt klasy w samej sobie:

class Foo {
    int value = 10;

    public static void main(String[] args) {
        Foo foo = new Foo();
        System.out.println(foo.value);
    }
}

W JDK też znajdziemy przykład wielu metod statycznych. Najprostszym przykładem są metody służące do wykonywania obliczeń z klasy Math, np.:

class Foo {
    public static void main(String[] args) {
        long round = Math.round(5.12345);
        System.out.println("Zaokrąglona liczba " + round); //5
    }
}

Metody statyczne można przeciążać, tak jak zwykłe metody, ale nie można ich nadpisywać w klasach pochodnych. Dla metod statycznych nie działają więc wirtualne wywołania metod, tak jak dla zwykłych metod, gdy używamy polimorfizmu.

class Foo {
    static void myMethod() {
        System.out.println("Foo");
    }
}
class Bar extends Foo {
    // @Override metod statycznych nie można nadpisywać, nie można dodać adnotacji @Override
    static void myMethod() {
        System.out.println("Bar");
    }
}
class Test {
    public static void main(String[] args) {
        Foo.myMethod(); //Foo
        Bar.myMethod(); //Bar
    }
}

Import statyczny

Jeżeli korzystasz z pól, lub metod statycznych, które znajdują się w zewnętrznych pakietach i wymagają importu, to przydatny jest import statyczny. Dzięki niemu możesz pominąć nazwę klasy podczas odwoływania się do składowej statycznej:

W celu posortowania listy możemy wykorzystać metodę Collections.sort(), a do znalezienia największego elementu służy metoda Collections.sort(). Bez importu statycznego ich użycie wygląda tak:

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

class SortExample {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(10, 5, 3, 9, 6, 1);
        Collections.sort(list);
        System.out.println(list); //[1, 3, 5, 6, 9, 10]
        System.out.println(Collections.max(list)); //10
    }
}

Dzięki importowi statycznemu nie musimy za każdym razem jawnie odwoływać się do klasy Collections:

import java.util.Arrays;
import java.util.List;

import static java.util.Collections.max;
import static java.util.Collections.sort;

class SortExample {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(10, 5, 3, 9, 6, 1);
        sort(list);
        System.out.println(list); //[1, 3, 5, 6, 9, 10]
        System.out.println(max(list)); //10
    }
}

Statyczne bloki inicjujące

Mniej popularną konstrukcją, do której możemy wykorzystać słowo static jest statyczny blok inicjujący. Jest to fragment kodu w ramach klasy, który wykonywany jest tylko raz, podczas załadowania klasy przez wirtualną maszynę Javy.

class Foo {
    static int value = 0;

    static {
        value = 10;
    }
}
class Bar {
    public static void main(String[] args) {
        System.out.println(Foo.value); //10
    }
}

Statyczne bloki inicjujące były kiedyś wykorzystywane np. w sterownikach baz danych do zarejestrowania tego sterownika przez DriverManagera. Przykład klasy sterownika dla MySQL:

package com.mysql.jdbc;

import java.sql.DriverManager;
import java.sql.SQLException;

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

To właśnie w celu wykonania statycznego bloku inicjującego używa się metody Class.forName:

Class.forName("com.mysql.jdbc.Driver"); //załadowanie klasy do JVM i wymuszenie wykonania kodu ze statycznego bloku inicjującego

Kurs Java

Klasy zagnieżdżone (statyczne klasy wewnętrzne)

Słowo static pozwala także definiować klasy zagnieżdżone, czyli inaczej statyczne klasy wewnętrzne. W odróżnieniu od klasycznych klas wewnętrznych, do utworzenia obiektu statycznej klasy wewnętrznej nie jest wymagane tworzenie obiektu klasy opakowującej:

class Outer {
    
    static class Inner {
        //...
    }
}
class Test {
    public static void main(String[] args) {
        Outer.Inner inner = new Outer.Inner();
        // Outer.Inner inner = new Outer().new Inner(); //dla zwykłych klas wewnętrznych
        //...
    }
}

Przed Javą 16, jeżeli w klasie wewnętrznej znajdowało się co najmniej jedno pole statyczne, to sama klasa musiała być oznaczona jako static. Od Javy 16 wymóg ten został zniesniony.

class Outer {
    class Inner {
        //przed Javą 16 błąd kompilacji, od Javy 16 ok
        static int value = 10;
    }
}

Dyskusja i komentarze

Masz pytania do tego wpisu? Może chcesz się podzielić spostrzeżeniami? Zapraszamy dyskusji na naszej grupie na Facebooku.