1. Инициализиране на променливи

Както вече знаете, можете да декларирате няколко променливи във вашия клас и не само да ги декларирате, но и незабавно да ги инициализирате с първоначалните им стойности.

И същите тези променливи могат да бъдат инициализирани и в конструктор. Това означава, че на теория тези променливи могат да бъдат присвоени стойности два пъти. Пример

Код Забележка
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";
   }
}



На ageпроменливата е присвоена начална стойност.




Първоначалната стойност се презаписва.


Променливата за възрастта съхранява първоначалната си стойност.
Cat cat = new Cat("Whiskers", 2);
Това е позволено: първият конструктор ще бъде извикан
Cat cat = new Cat();
Това е позволено: ще бъде извикан вторият конструктор

Ето Howво се случва, когато Cat cat = new Cat("Whiskers", 2);се изпълни:

  • CatСъздава се обект
  • Всички променливи на екземпляра се инициализират с първоначалните си стойности
  • Конструкторът се извиква и codeът му се изпълнява.

С други думи, променливите първо получават първоначалните си стойности и едва след това се изпълнява codeът на конструктора.


2. Ред на инициализация на променливите в клас

Променливите не просто се инициализират преди изпълнението на конструктора - те се инициализират в добре дефиниран ред: реда, в който са декларирани в класа.

Нека да разгледаме един интересен code:

Код Забележка
public class Solution
{
   public int a = b + c + 1;
   public int b = a + c + 2;
   public int c = a + b + 3;
}

Този code няма да се компorра, тъй като по време на aсъздаването на променливата все още няма  променливи b и . cНо можете да напишете своя code по следния начин — този code ще се компorра и ще работи добре.

Код Забележка
public class Solution
{
   public int a;
   public int b = a + 2;
   public int c = a + b + 3;
}


0
0+2
0+2+3

Но не забравяйте, че вашият code трябва да е прозрачен за другите разработчици. По-добре е да не използвате техники като тази, тъй като това влошава четливостта на codeа.

Тук трябва да помним, че преди на променливите да бъде присвоена стойност, те имат стойност по подразбиране . За intтипа това е нула.

Когато JVM инициализира променливата a, тя просто ще присвои стойността по подразбиране за типа int: 0.

Когато достигне b, променливата a вече ще бъде известна и ще има стойност, така че JVM ще й присвои стойност 2.

И когато достигне cпроменливата, променливите aи bвече ще бъдат инициализирани, така че JVM лесно ще изчисли първоначалната стойност за c: 0+2+3.

Ако създадете променлива вътре в метод, не можете да я използвате, освен ако преди това не сте й присвоor стойност. Но това не е вярно за променливите на клас! Ако първоначална стойност не е присвоена на променлива от клас, тогава тя се присвоява стойност по подразбиране.


3. Константи

Докато анализираме How се създават обекти, струва си да се докоснем до инициализацията на константи, т.е. променливи с модификатора final.

Ако дадена променлива има finalмодификатор, тогава трябва да й бъде присвоена начална стойност. Вече знаете това и в това няма нищо изненадващо.

Но това, което не знаете е, че не е нужно да присвоите първоначалната стойност веднага, ако я присвоите в конструктора. Това ще работи добре за крайна променлива. Единственото изискване е, че ако имате няколко конструктора, тогава на крайна променлива трябва да бъде присвоена стойност във всеки конструктор.

Пример:

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. Код в конструктор

И още няколко важни бележки относно конструкторите. По-късно, докато продължавате да изучавате Java, ще се натъкнете на неща като наследяване, сериализация, изключения и т.н. Всички те влияят на работата на конструкторите в различна степен. Няма смисъл да се гмуркаме дълбоко в тези теми сега, но сме длъжни поне да ги засегнем.

Например, ето една важна забележка относно конструкторите. На теория можете да напишете code с всяHowва сложност в конструктор. Но не правете това. Пример:

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






Отворете поток за четене на файл
Прочетете file в byteов масив
Запазете byteовия масив като низ




Показване на съдържанието на file на екрана

В конструктора на клас FilePrinter незабавно отворихме поток от byteове на файл и прочетохме съдържанието му. Това е сложно поведение и може да доведе до грешки.

Ами ако няма такъв файл? Ами ако има проблеми с четенето на file? Ами ако беше твърде голям?

Сложната логика предполага голяма вероятност от грешки и това означава, че codeът трябва да обработва правилно изключенията.

Пример 1 — Сериализация

В стандартна програма на Java има много ситуации, в които не сте този, който създава обекти от вашия клас. Да предположим например, че решите да изпратите обект по мрежата: в този случай самата Java машина ще преобразува вашия обект в набор от byteове, ще го изпрати и ще създаде отново обекта от набора от byteове.

Но тогава да предположим, че вашият файл не съществува на другия компютър. Ще има грешка в конструктора и никой няма да се справи с нея. И това е напълно способно да доведе до прекратяване на програмата.

Пример 2 — Инициализиране на полета на клас

Ако вашият конструктор на клас може да хвърля проверени изключения, т.е. е маркиран с ключовата дума throws, тогава трябва да хванете посочените изключения в метода, който създава вашия обект.

Но Howво ще стане, ако няма такъв метод? Пример:

Код  Забележка
class Solution
{
   public FilePrinter reader = new FilePrinter("c:\\readme.txt");
}
Този code няма да се компorра.

Конструкторът FilePrinterна клас може да хвърли проверено изключение , което означава, че не можете да създадете FilePrinterобект, без да го обвиете в блок try-catch. А блокът try-catch може да бъде написан само в метод



5. Конструктор на базов клас

В предишните уроци обсъдихме малко наследяването. За съжаление, пълното ни обсъждане на наследяването и ООП е запазено за нивото, посветено на ООП, а наследяването на конструктори вече е от meaning за нас.

Ако вашият клас наследи друг клас, обект от родителския клас ще бъде вграден в обект от вашия клас. Нещо повече, родителският клас има свои собствени променливи и свои собствени конструктори.

Това означава, че е много важно за вас да знаете и разбирате How променливите се инициализират и конструкторите се извикват, когато вашият клас има родителски клас и вие наследявате неговите променливи и методи.

Класове

Как да разберем реда, в който се инициализират променливите и се извикват конструкторите? Нека започнем, като напишем codeа за два класа. Единият ще наследи другия:

Код Забележка
class ParentClass
{
   public String a;
   public String b;

   public ParentClass()
   {
   }
}

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

   public ChildClass()
   {
   }
}










Класът ChildClass наследява ParentClassкласа.

Трябва да определим реда, в който се инициализират променливите и се извикват конструкторите. Регистрирането ще ни помогне да направим това.

Сеч

Регистрирането е процес на записване на действия, извършвани от програма, докато се изпълнява, чрез записването им в конзолата or във файл.

Много е лесно да се определи, че конструкторът е извикан: в тялото на конструктора напишете съобщение до конзолата. Но How можете да разберете дали дадена променлива е инициализирана?

Всъщност това също не е много трудно: напишете специален метод, който ще върне стойността, използвана за инициализиране на променливата, и ще регистрирате инициализацията. Ето How може да изглежда codeът:

Краен code

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




Създаване на ChildClassобект


Този метод записва предадения текст в конзолата и го връща.





Декларирайте ParentClassкласа

Display text и инициализирайте променливите с него.




Напишете съобщение, че конструкторът е извикан. Игнорирайте върнатата стойност.


Декларирайте ChildClassкласа

Display text и инициализирайте променливите с него.




Напишете съобщение, че конструкторът е извикан. Игнорирайте върнатата стойност.

Ако изпълните този code, текстът ще се покаже на екрана, Howто следва:

Конзолен изход на методаMain.print()
ParentClass.a
ParentClass.b
ParentClass.constructor
ChildClass.c
ChildClass.d
ChildClass.constructor

Така че винаги можете лично да се уверите, че променливите на даден клас са инициализирани преди извикването на конструктора. Базовият клас се инициализира напълно преди инициализирането на наследения клас.