1. Inițializarea variabilelor

După cum știți deja, puteți declara mai multe variabile în clasa dvs. și nu doar să le declarați, ci și să le inițializați imediat cu valorile lor inițiale.

Și aceleași variabile pot fi inițializate și într-un constructor. Aceasta înseamnă că, în teorie, acestor variabile li se pot atribui valori de două ori. Exemplu

Cod Notă
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 = "Nameless";
   }
}



Variabilei agei se atribuie o valoare inițială




Valoarea inițială este suprascrisă


Variabila vârstă își stochează valoarea inițială.
 Cat cat = new Cat("Whiskers", 2);
Acest lucru este permis: primul constructor va fi apelat
 Cat cat = new Cat();
Acest lucru este permis: al doilea constructor va fi apelat

Iată ce se întâmplă când Cat cat = new Cat("Whiskers", 2);este executat:

  • CatSe creează un obiect
  • Toate variabilele de instanță sunt inițializate cu valorile lor inițiale
  • Constructorul este apelat și codul său este executat.

Cu alte cuvinte, variabilele primesc mai întâi valorile inițiale și abia apoi este executat codul constructorului.


2. Ordinea inițializării variabilelor dintr-o clasă

Variabilele nu sunt doar inițializate înainte de rularea constructorului, ci sunt inițializate într-o ordine bine definită: ordinea în care sunt declarate în clasă.

Să ne uităm la un cod interesant:

Cod Notă
public class Solution
{
   public int a = b + c + 1;
   public int b = a + c + 2;
   public int c = a + b + 3;
}

Acest cod nu se va compila, deoarece la momentul acreării variabilei nu există încă b și c variabile. Dar vă puteți scrie codul după cum urmează - acest cod se va compila și va rula foarte bine.

Cod Notă
public class Solution
{
   public int a;
   public int b = a + 2;
   public int c = a + b + 3;
}


0
0+2
0+2+3

Dar rețineți că codul dvs. trebuie să fie transparent pentru alți dezvoltatori. Este mai bine să nu folosiți astfel de tehnici, deoarece afectează lizibilitatea codului.

Aici trebuie să ne amintim că înainte ca variabilelor să li se atribuie o valoare, acestea au o valoare implicită . Pentru inttip, acesta este zero.

Când JVM inițializează avariabila, va atribui pur și simplu valoarea implicită pentru tipul int: 0.

Când ajunge la b, variabila a va fi deja cunoscută și va avea o valoare, așa că JVM-ul îi va atribui valoarea 2.

Și când ajunge la cvariabilă, variabilele ași bvor fi deja inițializate, astfel încât JVM-ul va calcula cu ușurință valoarea inițială pentru c: 0+2+3.

Dacă creați o variabilă în interiorul unei metode, nu o puteți utiliza decât dacă i-ați atribuit anterior o valoare. Dar acest lucru nu este valabil pentru variabilele unei clase! Dacă o valoare inițială nu este atribuită unei variabile a unei clase, atunci i se atribuie o valoare implicită.


3. Constante

În timp ce analizăm modul în care sunt create obiectele, merită să atingem inițializarea constantelor, adică variabilele cu modificatorul final.

Dacă o variabilă are finalmodificatorul, atunci trebuie să i se atribuie o valoare inițială. Știți deja acest lucru și nu este nimic surprinzător.

Dar ceea ce nu știți este că nu trebuie să atribuiți valoarea inițială imediat dacă o atribuiți în constructor. Acest lucru va funcționa foarte bine pentru o variabilă finală. Singura cerință este ca, dacă aveți mai mulți constructori, atunci unei variabile finale trebuie să i se atribuie o valoare în fiecare constructor.

Exemplu:

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

   public Cat (int weight)
   {
     this.maxWeight = weight; // Assign an initial value to the constant
   }
}


4. Cod într-un constructor

Și mai multe note importante despre constructori. Mai târziu, pe măsură ce continuați să învățați Java, veți întâlni lucruri precum moștenirea, serializarea, excepții etc. Toate influențează munca constructorilor în grade diferite. Nu are sens să ne adâncim acum în aceste subiecte, dar suntem obligați să le atingem măcar.

De exemplu, iată o remarcă importantă despre constructori. În teorie, puteți scrie cod de orice complexitate într-un constructor. Dar nu face asta. Exemplu:

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);
   }
}






Deschideți un flux de citire a fișierului
Citiți fișierul într-o matrice de octeți
Salvați matricea de octeți ca șir




Afișați conținutul fișierului pe ecran

În constructorul clasei FilePrinter, am deschis imediat un flux de octeți pe un fișier și am citit conținutul acestuia. Acesta este un comportament complex și poate duce la erori.

Dacă nu ar exista un astfel de dosar? Ce se întâmplă dacă ar exista probleme cu citirea fișierului? Dacă ar fi prea mare?

Logica complexă implică o probabilitate mare de erori și asta înseamnă că codul trebuie să gestioneze corect excepțiile.

Exemplul 1 — Serializare

Într-un program standard Java, există o mulțime de situații în care nu ești cel care creează obiecte din clasa ta. De exemplu, să presupunem că decideți să trimiteți un obiect prin rețea: în acest caz, mașina Java însăși vă va converti obiectul într-un set de octeți, îl va trimite și va recrea obiectul din setul de octeți.

Dar apoi să presupunem că fișierul dvs. nu există pe celălalt computer. Va exista o eroare în constructor și nimeni nu o va gestiona. Și asta este destul de capabil să provoace terminarea programului.

Exemplul 2 — Inițializarea câmpurilor unei clase

Dacă constructorul clasei dumneavoastră poate arunca excepții verificate, adică este marcat cu cuvântul cheie throws, atunci trebuie să prindeți excepțiile indicate în metoda care creează obiectul dumneavoastră.

Dar dacă nu există o astfel de metodă? Exemplu:

Cod  Notă
class Solution
{
   public FilePrinter reader = new FilePrinter("c:\\readme.txt");
}
Acest cod nu se va compila.

Constructorul FilePrinterclasei poate arunca o excepție bifată , ceea ce înseamnă că nu puteți crea un FilePrinterobiect fără a-l încadra într-un bloc try-catch. Și un bloc try-catch poate fi scris doar într-o metodă



5. Constructorul clasei de bază

În lecțiile anterioare, am discutat puțin despre moștenire. Din păcate, discuția noastră completă despre moștenire și OOP este rezervată pentru nivelul dedicat OOP, iar moștenirea constructorilor este deja relevantă pentru noi.

Dacă clasa ta moștenește o altă clasă, un obiect al clasei părinte va fi încorporat într-un obiect al clasei tale. În plus, clasa părinte are propriile variabile și propriii ei constructori.

Asta înseamnă că este foarte important pentru tine să știi și să înțelegi cum sunt inițializate variabilele și cum sunt apelați constructorii atunci când clasa ta are o clasă părinte și moștenești variabilele și metodele acesteia.

Clase

Cum știm ordinea în care variabilele sunt inițializate și sunt chemați constructorii? Să începem prin a scrie codul pentru două clase. Unul îl va moșteni pe celălalt:

Cod Notă
class ParentClass
{
   public String a;
   public String b;

   public ParentClass()
   {
   }
}

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

   public ChildClass()
   {
   }
}










Clasa ChildClass moștenește ParentClassclasa.

Trebuie să determinăm ordinea în care variabilele sunt inițializate și sunt apelați constructorii. Logarea ne va ajuta să facem acest lucru.

Logare

Înregistrarea este procesul de înregistrare a acțiunilor efectuate de un program pe măsură ce rulează, prin scrierea lor în consolă sau într-un fișier.

Este destul de simplu să determinați că constructorul a fost apelat: în corpul constructorului, scrieți un mesaj pe consolă. Dar cum vă puteți da seama dacă o variabilă a fost inițializată?

De fapt, nici acest lucru nu este foarte dificil: scrieți o metodă specială care va returna valoarea folosită pentru a inițializa variabila și înregistrați inițializarea. Iată cum ar putea arăta codul:

Cod final

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");
   }
}




Creați un ChildClassobiect


Această metodă scrie textul transmis în consolă și, de asemenea, îl returnează.





Declarați ParentClassclasa

Display text și, de asemenea, inițializați variabilele cu ea.




Scrieți un mesaj că constructorul a fost apelat. Ignorați valoarea returnată.


Declarați ChildClassclasa

Display text și, de asemenea, inițializați variabilele cu ea.




Scrieți un mesaj că constructorul a fost apelat. Ignorați valoarea returnată.

Dacă executați acest cod, textul va fi afișat pe ecran după cum urmează:

Ieșirea în consolă a metodeiMain.print()
ParentClass.a
ParentClass.b
ParentClass.constructor
ChildClass.c
ChildClass.d
ChildClass.constructor

Deci vă puteți asigura personal întotdeauna că variabilele unei clase sunt inițializate înainte ca constructorul să fie apelat. O clasă de bază este inițializată complet înainte de inițializarea clasei moștenite.