Programowanie średniozaawansowane #7: statyczne bloki i metoda toString

Dziś dokończenie lekcji odnośnie słowa kluczowego static. Poza polami i metodami Java umożliwia też tworzenie bloków statycznych*. Ogólnie, jeśli zobaczysz gdzieś słowo static to powinno dla Ciebie być znakiem, że nie mamy tu do czynienia z programowanie obiektowym. Często o Javie słyszysz, że jest to język w pełni obiektowy (w przeciwieństwo do np. C++). Osoby, używające tej terminologii, tłumaczą, że jest to spowodowane pisaniem w całości kodu w klasie (oczywiście poza importami). To tłumaczenie jest o tyle mylne, że poza klasą startową zawierającą main, w zasadzie nie musisz w ogóle korzystać z obiektów, żeby pisać kod w Javie. Możesz swobodnie wykorzystywać do tego słowo static, co oznacza, że nie będziesz ‚aktywował’ dany kod za pomocą instancji klasy (bez niej, nie użyjesz żadnego innego kodu, który nie jest statyczny), ale który będzie wczytywany od razu po wystartowaniu programu. Co więcej, nie ważne w której klasie go umieścisz. Można powiedzieć, że bloki statyczne to globalne instrukcje Javy, które zostaną wykonane w momencie wczytywania klasy przez wirtualną maszynę Javy.

public class InitTournament {
  private final String [] championsLeagueClubs= {"Manchester United", "Juventus FC", "Real Madrid", "Liverpool FC"};
  private static String [] topGoalScorers = new String [3];
  
  static {
    topGoalScorers[0] = "Lewandowski";
    topGoalScorers[1] = "Messi";
    topGoalScorers[2] = "Ronaldo";
  }
  
  public void populate(Tournament tournament) {
    tournament.setClubs(championsLeagueClubs);
    tournament.setTopGoalScorers(topGoalScorers);
  }
}

Ta prosta klasa posiada dwa pola: jedną tablicę finalną i drugą pustą tablicę statyczną. Dalej masz blok statyczny, w którym dokonuję inicjalizacji wartości w tablicy. W każdym takim bloku instrukcje są odczytywane od góry w dół. Na końcu napisałem prostą metodę, która wykorzysta zapisane wcześniej dane do wypełnienia obiektu Tournament.

public class Tournament {
  private String [] clubs;
  private String [] topGoalScorers;
  
  public Tournament() {}
  
  public Tournament(String[] clubs, String[] topGoalScorers) {
    this.clubs = clubs;
    this.topGoalScorers = topGoalScorers;
  }
  
  public String[] getClubs() {
    return clubs;
  }
  public void setClubs(String[] clubs) {
    this.clubs = clubs;
  }
  public String[] getTopGoalScorers() {
    return topGoalScorers;
  }
  public void setTopGoalScorers(String[] topGoalScorers) {
    this.topGoalScorers = topGoalScorers;
  }

  @Override
  public String toString() {
    return "Tournament [clubs=" + Arrays.toString(clubs) + ", topGoalScorers=" + Arrays.toString(topGoalScorers)
        + "]";
  }
}

Tournament to zwykłada klasa POJO. Zawiera dwa pola, gettery settery, dwa konstruktory i metodę toString. Jeden konstruktor to tzw. konstruktor parametrowy, który inicjalizuje pola (nic nowego). Mogę oczywiście napisać więcej konstruktorów, jeśli ich potrzebuje. Drugi z nich został napisany dlatego, że nie będę już wstanie stworzyć pustego’ obiektu (Tournament tournament = new Tournament()). Dlaczego, gdy nie napiszę, żadnego konstruktora w klasie mogę wciąż wywołać powyższą linię kodu, a gdy stworzę jakikolwiek konstruktor parametrowy to już nie? Odpowiedź jest bardzo prosta. Autorzy Javy dla uproszczenia nie wymagają od Ciebie, pisania za każdym razem pustego konstruktora. Jeśli go nie napiszesz, to kompilator zrobi to za Ciebie. W ten sposób kod jest krótszy. Pamiętaj jednak, że taki pusty konstruktor stworzony zostanie dopiero, gdy nie napiszesz żadnego swojego.

Na końcu klasy jest jeszcze tajemnicza metoda toString. Nie robi ona nic innego, tylko wyświetla zawartość klasy. Tu pewnie zaskoczyło Cię, że nad jej sygnaturą, używam adnotacji @OverridePomimo, że klasa nic nie dziedziczy. Otóż jest to kolejne uproszczenie w Javie, bo tak naprawdę każda klasa niejawnie dziedziczy po klasie Object. To taka klasa, która jest rodzicem dla każdej innej. Jeśli wszystkie klasy w jakie zobrazujesz sobie jako drzewo, to Object jest jego korzeniem. Celem stworzenia takiej klasy pomocniczej, było lenistwo programistów, którzy chcieli mieć kilka najczęściej używanych metod dostępnych w każdej klasie.**

public class StaticBlockMain {
  public static void main(String[] args) {
    Tournament tournament = new Tournament();
    InitTournament init = new InitTournament();
    init.populate(tournament);
    System.out.println(tournament.toString());
  }
}

W metodzie main tworzę obiekty tournament init, a następnie korzystam z metody populate do wypełnienia obiektu tournament danymi. Brawo, udało Cie się stworzyć blok statyczny, który prawidłowo inicjalizuje dane. W blokach statycznych możesz nie tylko inicjalizować dane, ale pisać każdą inną logikę. Poniżej przedstawiam wynik w konsoli:

Tournament [clubs=[Manchester United, Juventus FC, Real Madrid, Liverpool FC], topGoalScorers=[Lewandowski, Messi, Ronaldo]]

Ważne jest, żeby nie mieszać za dużo w takich blokach. Spójrz co się stanie, gdy z linii: private static String [] topGoalScorers = new String [3]; usunę fragment: = new String [3]. 

Exception in thread "main" java.lang.ExceptionInInitializerError
  at advanced.statics.StaticBlockMain.main(StaticBlockMain.java:6)
Caused by: java.lang.NullPointerException
  at advanced.statics.InitTournament.<clinit>(InitTournament.java:8)
  ... 1 more

Także staraj się używać tej konstrukcji z rozwagą.

 

* Język Java umożliwia też tworzenie klas statycznych, ale na razie nie chcę Ci wprowadzać tego terminu.

** Szczegółowe informacje o klasie Object udzielę Ci w innej lekcji.

Dodaj komentarz