1. Inicjalizacja zmiennych

Jak już wiesz, możesz zadeklarować kilka zmiennych klasowych w swojej klasie i nie tylko zadeklarować, ale natychmiast zainicjować je wartościami początkowymi.

Jednak te same zmienne można również zainicjować w konstruktorze. Dlatego teoretycznie jest możliwe, że tym samym zmiennym klasowym zostaną przypisane wartości dwukrotnie. Przykład

Kod Notatka
class Cat
{
   public String name;
   public int age = -1;

   public Cat(String name, int age)
   {
     this.name = name;
     this.age = age;
   }

   public Cat()
   {
     this.name = "Безымянный";
   }
}



Zmiennej agejest przypisywana wartość początkowa




Wartość początkowa jest nadpisywana


W przypadku wieku używana jest wartość początkowa
Cat cat = new Cat("Васька", 2);
Więc możesz: zostanie wywołany pierwszy konstruktor
Cat cat = new Cat();
Jest więc możliwe: zostanie wywołany drugi konstruktor

Oto, co się stanie, gdy kod zostanie wykonany Cat cat = new Cat("Васька", 2);:

  • Tworzony jest obiekt typuCat
  • Wszystkie zmienne klasy są inicjowane ich wartościami początkowymi.
  • Konstruktor jest wywoływany i wykonywany jest jego kod.

Te. zmienne klasy są najpierw inicjowane ich wartościami, a dopiero potem wykonywany jest kod konstruktorów.


2. Kolejność inicjalizacji zmiennych klasowych

Zmienne nie są po prostu inicjowane przed wykonaniem konstruktora: są one również inicjowane w ściśle określonej kolejności — kolejności deklaracji w klasie.

Spójrzmy na ten interesujący kod:

Kod Notatka
public class Solution
{
   public int a = b + c + 1;
   public int b = a + c + 2;
   public int c = a + b + 3;
}

Taki kod nie skompiluje się, ponieważ w momencie tworzenia zmiennej а, zmienne b i c jeszcze nie. Ale możesz napisać to w ten sposób, a ten kod skompiluje się idealnie i będzie działał.

Kod Notatka
public class Solution
{
   public int a;
   public int b = a + 2;
   public int c = a + b + 3;
}


0
0+2
0+2+3

Uwaga: pamiętaj jednak, że Twój kod powinien być przejrzysty dla innych programistów, więc lepiej nie stosować takich sztuczek - pogarsza to czytelność kodu.

Tutaj trzeba pamiętać, że wszystkie zmienne klasowe, zanim zostały im nadane wartości, mają wartość domyślną . Dla typu intjest to zero.

Kiedy JVM inicjuje zmienną а, po prostu przypisze jej domyślną wartość dla typu int, 0.

Kiedy kolejka osiągnie b, zmienna a będzie już znana i będzie zawierała wartość, więc JVM przypisze jej wartość 2.

Cóż, jeśli chodzi o zmienną c, zmienne аi bzostaną już zainicjowane, a JVM bez problemu obliczy wartość początkową dla с: 0+2+3.

Jeśli utworzysz zmienną w ramach metody, nie możesz jej użyć, chyba że wcześniej przypisałeś jej wartość. Nie dotyczy to zmiennych klas. Jeśli zmiennej klasowej nie jest przypisana wartość początkowa, wówczas przypisywana jest jej wartość domyślna.


3. Stałe

Ponieważ kontynuujemy analizę procesu tworzenia obiektu, warto poruszyć kwestię stałych inicjalizacyjnych – zmiennych klasy, które posiadają modyfikator final.

Jeśli zmienna klasy ma modyfikator final, musi mieć przypisaną wartość początkową. To już wiesz i nie ma w tym nic dziwnego.

Ale nie wiesz, że nie musisz od razu przypisywać wartości początkowej, jeśli przypiszesz ją w konstruktorze. I to zadziała dobrze dla zmiennej końcowej. Jedynym wymaganiem jest to, że jeśli istnieje wiele konstruktorów, zmienna końcowa musi mieć przypisaną wartość we wszystkich konstruktorach.

Przykład:

public class Cat
{
   public final int maxAge = 25;
   public final int maxWeight;

   public Cat (int weight)
   {
      this.maxWeight = weight; // занесение стартового значения в константу
   }
}


4. Kod w konstruktorze

I jeszcze kilka ważnych uwag na temat konstruktorów. W przyszłości, gdy będziesz uczyć się Javy, zetkniesz się z takimi rzeczami, jak dziedziczenie, serializacja, wyjątki i tak dalej. Wszystkie w różnym stopniu wpływają na pracę projektantów. Teraz nie ma sensu zagłębiać się w te tematy, ale przynajmniej musimy ich dotknąć.

Na przykład jedna ważna uwaga dotycząca konstruktorów. Teoretycznie w konstruktorze można pisać kod o dowolnej złożoności. Ale nie musisz. Przykład:

class FilePrinter
{
   public String content;

   public FilePrinter(String filename) throws Exception
   {
      FileInputStream input = new FileInputStream(filename);
      byte[] buffer = input.readAllBytes();
      this.content = new String(buffer);
   }

   public void printFile()
   {
      System.out.println(content);
   }
}






Otwórz strumień odczytu pliku
Wczytaj plik do tablicy bajtów
Zapisz tablicę bajtów jako ciąg znaków




Wydrukuj zawartość pliku na ekranie

W konstruktorze klasy FilePrinter od razu otworzyliśmy strumień bajtów do pliku i odczytaliśmy jego zawartość. Jest to dość złożone zachowanie, które może potencjalnie prowadzić do błędów.

Co by było, gdyby takiego pliku nie było? A gdyby były problemy z odczytaniem? A gdyby był za duży?

Złożona logika implikuje wysokie prawdopodobieństwo błędów i kod, który musi poprawnie obsługiwać wyjątki.

Przykład 1 — Serializacja

Istnieje wiele sytuacji w standardowym programie Java, w których obiekty Twojej klasy nie są tworzone przez Ciebie. Na przykład zdecydujesz się przesłać obiekt przez sieć: w tym przypadku maszyna Java sama zamieni twój obiekt w zestaw bajtów, prześle go i ponownie utworzy obiekt na podstawie zestawu bajtów.

I tutaj okazuje się, że twojego pliku nie ma na innym komputerze, w konstruktorze wystąpi błąd i nikt go nie przetworzy - co może doprowadzić do zamknięcia programu.

Przykład 2 - Inicjalizacja pól klasy

Jeśli twój konstruktor klasy może rzucać sprawdzone wyjątki - zawiera słowo kluczowe throws - musisz przechwycić ten wyjątek w metodzie, która tworzy twój obiekt.

A co jeśli nie ma takiej metody? Przykład:

Kod  Notatka
class Solution
{
   public FilePrinter reader = new FilePrinter("c:\\readme.txt");
}
Taki kod się nie skompiluje.

Konstruktor klasy FilePrinterzawiera sprawdzone wyjątki : nie można utworzyć obiektu FilePrinterbez zawinięcia go w try-catch. A try-catch można zapisać tylko w metodzie



5. Konstruktor klasy bazowej

W poprzednich wykładach trochę omówiliśmy dziedziczenie. Niestety pełne dziedziczenie i OOP omówimy na poziomie poświęconym OOP, a to dotyczy konstruktorów już teraz.

Jeśli odziedziczysz swoją klasę z innej klasy, obiekt twojej klasy nadrzędnej zostanie faktycznie osadzony w obiekcie twojej klasy. Co więcej, ta klasa nadrzędna ma własne zmienne klasowe i własne konstruktory.

Dlatego bardzo ważne jest, abyś wiedział i rozumiał, w jaki sposób inicjowane są parametry i wywoływane są konstruktory, gdy twoja klasa ma klasę nadrzędną, której zmienne i metody dziedziczysz.

Klasy

Skąd wiemy, w jakiej kolejności inicjowane są zmienne i wywoływane są konstruktory? Najpierw napiszmy kod dla dwóch klas, z których jedna dziedziczy po drugiej:

Kod Notatka
class ParentClass
{
   public String a;
   public String b;

   public ParentClass()
   {
   }
}

class ChildClass extends ParentClass
{
   public String c;
   public String d;

   public ChildClass()
   {
   }
}










Klasa ChildClass dziedziczy po ParentClass.

Musimy określić, w jakiej kolejności inicjowane są zmienne i wywoływane są konstruktory. Logowanie nam w tym pomoże.

Logowanie

Rejestrowanie to zapis w konsoli lub pliku działań, które mają miejsce podczas działania programu.

Ustalenie, że konstruktor został wywołany, jest dość proste: należy napisać o tym komunikat do konsoli w treści konstruktora. Ale jak ustalić, że zmienna została zainicjowana?

W rzeczywistości nie jest to również bardzo trudne: musisz napisać specjalną metodę, która zwróci wartość, z jaką zmienna klasy jest inicjowana, i zarejestrować ten fakt. Oto jak może wyglądać ten kod:

Kod końcowy

public class Main
{
   public static void main(String[] args)
   {
      ChildClass obj = new ChildClass();
   }

   public static String print(String text)
   {
      System.out.println(text);
      return text;
   }
}

class ParentClass
{
   public String a = Main.print("ParentClass.a");
   public String b = Main.print("ParentClass.b");

   public ParentClass()
   {
      Main.print("ParentClass.constructor");
   }
}

class ChildClass extends ParentClass
{
   public String c = Main.print("ChildClass.c");
   public String d = Main.print("ChildClass.d");

   public ChildClass()
   {
      Main.print("ChildClass.constructor");
   }
}




Tworzymy obiekt typu Ta metoda zapisuje przekazany tekst ChildClass


do konsoli i zwraca go . Zwracana wartość jest ignorowana. Deklarujemy Piszemy tekst i inicjujemy nim zmienne Piszemy komunikat o wywołaniu konstruktora do konsoli. Zwracana wartość jest ignorowana.





ParentClass









ChildClass






Jeśli uruchomisz ten kod, tekst zostanie wyświetlony na ekranie:

Zrzut ekranu metodyMain.print()
ParentClass.a
ParentClass.b
ParentClass.constructor
ChildClass.c
ChildClass.d
ChildClass.constructor

Dlatego zawsze możesz osobiście upewnić się, że zmienne klasy są inicjowane przed wywołaniem jej konstruktora. Cała inicjalizacja klasy bazowej poprzedza inicjalizację klasy pochodnej.