JavaRush /Java блог /Random UA /Конструктори класів. Java JDK 1.5
articles
15 рівень

Конструктори класів. Java JDK 1.5

Стаття з групи Random UA
Конструктори класів.  Java JDK 1.5 - 1

Загальні відомості про конструкторів

Конструктор- це схожа з способом структура, призначення якої полягає у створенні екземпляра класу. Характеристики конструктора:
  • Ім'я конструктора має збігатися з ім'ям класу (за домовленістю, перша літера - заголовна, зазвичай іменник);
  • Конструктор є у будь-якому класі. Навіть якщо ви його не написали, компілятор Java сам створить стандартний конструктор (default constructor), який буде порожнім і не робить нічого, крім виклику конструктора суперкласу.
  • Конструктор схожий на метод, але не метод, він навіть не вважається членом класу. Тому його не можна успадковувати чи перевизначити у підкласі;
  • Конструктори не успадковуються;
  • Конструкторів може бути дещо у класі. І тут конструктори називають перевантаженими;
  • Якщо в класі не описаний конструктор, компілятор автоматично додає код конструктор без параметрів;
  • Конструктор не має типу, що повертається, ним не може бути навіть тип void, якщо повертається тип void, то це вже не конструктор а метод, незважаючи на збіг з ім'ям класу.
  • У конструкторі допускається оператор return, але тільки порожній, без будь-якого значення, що повертається;
  • У конструкторі допускається застосування модифікаторів доступу, можна задати один з модифікаторів: public, protected, privateабо без модифікатора.
  • Конструктор не може мати модифікаторів abstract, final, або ;nativestaticsynchronized
  • Ключове слово thisпосилається на інший конструктор у цьому класі. Якщо використовується, то звернення має бути першим рядком конструктора;
  • Ключове слово superвикликає конструктор батьківського класу. Якщо використовується, має звернення до нього бути першим рядком конструктора;
  • Якщо конструктор не здійснює виклик конструктора superкласу-предка (з аргументами або без аргументів), компілятор автоматично додає код виклику конструктора класу-предка без аргументів;

Конструктор за замовчуванням

Конструктор є у будь-якому класі. Навіть якщо його не написали, компілятор Java сам створить конструктор за замовчуванням (default constructor). Цей конструктор порожній і робить нічого, крім виклику конструктора суперкласса. Тобто. якщо написати:
public class Example {}
то це еквівалентно написанню:
public class Example
{
     Example()
     {
          super;
     }
}
В даному випадку явно класу предка не вказано, а за замовчуванням всі класи Java успадковують клас, Objectтому викликається конструктор класу Object. Якщо класі визначено конструктор з параметрами, а перевантаженого конструктора без параметрів немає, то виклик конструктора без параметрів є помилкою. Тим не менш, Java, починаючи з версії 1.5, можна використовувати конструктори з аргументами змінної довжини. І якщо є конструктор, який має аргумент змінної довжини, то виклик конструктора за промовчанням помилкою не буде. Не буде тому, що аргумент змінної довжини може бути пустим. Наприклад, наступний приклад не компілюватиметься, проте якщо розкоментувати конструктор з аргументом змінної довжини, то компіляція і запуск пройдуть успішно і в результаті роботи рядка коду DefaultDemo dd = new DefaultDemo(); викличеться конструкторDefaultDemo(int ... v). Природно, що в даному випадку потрібно скористатися JSDK 1.5. ФайлDefaultDemo.java
class DefaultDemo
{
 DefaultDemo(String s)
 {
  System.out.print("DefaultDemo(String)");
 }
 /*
 DefaultDemo(int ... v)
 {
  System.out.println("DefaultDemo(int ...)");
 }
 */

 public static void main(String args[])
 {
  DefaultDemo dd = new DefaultDemo();
 }
}
Результат виведення програми при конструкторі:
DefaultDemo(int ...)
Однак, у поширеному випадку, коли в класі взагалі не визначено жодного конструктора, виклик конструктора за замовчуванням (без параметрів) буде обов'язковим явищем, оскільки підстановка за замовчуванням конструктора відбувається автоматично.

Створення об'єкта та конструктори

Під час створення об'єкта послідовно виконуються такі действия:
  • Шукається клас об'єкта серед класів, що вже використовуються в програмі. Якщо його немає, він шукається у всіх доступних програмі каталогах і бібліотеках. Після виявлення класу в каталозі чи бібліотеці виконується створення та ініціалізація статичних полів класу. Тобто. для кожного класу статичні поля ініціалізуються лише один раз.
  • Виділяється пам'ять під об'єкт.
  • Виконується ініціалізація полів класу.
  • Відпрацьовує конструктор класу.
  • Формується посилання на створений та ініціалізований об'єкт. Це і є значенням висловлювання, що створює об'єкт. Об'єкт може бути створений за допомогою виклику методу newInstance()класу java.lang.Class. І тут використовується конструктор без списку параметрів.

Перевантаження конструкторів

Конструктори одного класу можуть мати однакове ім'я та різну сигнатуру. Така властивість називається поєднанням чи перевантаженням (overloading). Якщо клас має кілька конструкторів, то є перевантаження конструкторів.

Параметризовані конструктори

Сигнатура конструктора – це кількість та типи параметрів, а також послідовність їх типів у списку параметрів конструктора. Тип результату, що повертається, не враховується. Конструктор не повертає жодних параметрів. Це становище пояснює у сенсі, як Java розрізняє перевантажені конструктори чи методи. Java розрізняє перевантажені методи не за типом, що повертається, а за кількістю, типами і послідовністю типів вхідних параметрів. Конструктор не може повертати навіть тип void, інакше він перетвориться на звичайний метод, навіть не дивлячись на схожість з ім'ям класу. Наступний приклад демонструє це. ФайлVoidDemo.java
class VoidDemo
{
 /**
  * Это конструктор
  */
 VoidDemo()
 {
  System.out.println("Constructor");
 }

 /**
  * А это уже обычный метод, даже не смотря на сходство с
  * именем класса, поскольку имеется возвращаемый тип void
  */
 void VoidDemo()
 {
  System.out.println("Method");
 }

 public static void main(String s[])
 {
  VoidDemo m = new VoidDemo();
 }
}
В результаті програма виведе:
Constructor
Це вкотре доводить, що конструктором є метод без параметрів, що повертаються. Тим не менш, для конструктора можна задати один з трьох модифікаторів publicабо private. protectedІ приклад тепер виглядатиме так: ФайлVoidDemo2.java
class VoidDemo2
{
 /**
  * Это конструктор
  */
 public VoidDemo2()
 {
  System.out.println("Constructor");
 }

 /**
  * А это уже обычный метод, даже не смотря на сходство с
  * именем класса, поскольку имеется возвращаемый тип void
  */
 private void VoidDemo2()
 {
  System.out.println("Method");
 }

 public static void main(String s[])
 {
  VoidDemo2 m = new VoidDemo2();
 }
}
У конструкторі дозволяється записувати оператор return, але тільки порожній, без будь-якого значення, що повертається. ФайлReturnDemo.java
class ReturnDemo
{
 /**
  * В конструкторе допускается использование оператора
  * return без параметров.
  */
 public ReturnDemo()
 {
  System.out.println("Constructor");
  return;
 }

 public static void main(String s[])
 {
  ReturnDemo r = new ReturnDemo();
 }
}

Конструктори, які параметризовані аргументами змінної довжини

У Java SDK 1.5 з'явився довгоочікуваний інструмент - аргументи змінної довжини для конструкторів та методів (variable-length arguments). До цього змінну кількість документів обробляли двома незручними способами. Перший був розрахований на те, що максимальна кількість аргументів обмежена невеликою кількістю і заздалегідь відома. У такому випадку можна було створювати версії методу, що перевантажуються, по одній на кожен варіант списку передаються в метод аргументів. Другий спосіб розрахований на заздалегідь невідоме і велику кількість аргументів. І тут аргументи поміщалися в масив, і це масив передавався методу. Аргументи змінної довжини найчастіше задіяні у наступних маніпуляціях із ініціалізаціями змінних. Відсутність деяких із очікуваних аргументів конструктора чи методу зручно замінювати значеннями за умовчанням. Аргумент змінної довжини є масивом, і обробляється як масив. Наприклад, конструктор для класуCheckingзі змінним числом аргументів виглядатиме так:
class Checking
{
 public Checking(int ... n)
 {
 }
}
Символьна комбінація ... повідомляє компілятор про те, що буде використовуватися змінна кількість аргументів, і що ці аргументи будуть зберігатися в масиві, значення посилання на який міститься в змінній n. Конструктор може викликатися з різним числом аргументів, включаючи їхню повну відсутність. Аргументи автоматично поміщаються масив і передаються через n. У разі відсутності аргументів довжина масиву дорівнює 0. До списку параметрів поряд з аргументами змінної довжини можуть бути включені обов'язкові параметри. У цьому випадку параметр, що містить змінну кількість аргументів, повинен обов'язково бути останнім у списку параметрів. Наприклад:
class Checking
{
 public Checking(String s, int ... n)
 {
 }
}
Цілком очевидне обмеження стосується кількості параметрів зі змінною довжиною. У списку параметрів має бути лише один параметр змінної довжини. За наявності двох параметрів змінної довжини компілятор неможливо визначити, де закінчується один параметр і починається інший. Наприклад:
class Checking
{
 public Checking(String s, int ... n, double ... d) //ОШИБКА!
 {
 }
}
ФайлChecking.java Наприклад, є апаратура, здатна розпізнавати номери автомобілів та запам'ятовувати номери квадратів місцевості, де побував кожен із автомобілів за день. Необхідно із загальної маси зафіксованих автомобілів відібрати ті, які протягом дня побували у двох заданих квадратах, скажімо 22 та 15, згідно з картою місцевості. Цілком природно, що автомобіль може протягом дня побувати у багатьох квадратах, а може лише в одному. Очевидно, що кількість відвідуваних квадратів обмежена фізичною швидкістю автомобіля. Складемо невелику програму, де конструктор класу прийматиме як аргументи номер автомобіля як обов'язковий параметр і номери відвіданих квадратів місцевості, кількість яких може бути змінною. Конструктор перевірятиме, чи не з'явився автомобіль у двох квадратах, якщо з'явився, вивести його номер на екран.

Передача параметрів у конструктор

У мовах програмування існує переважно два види параметрів:
  • основні типи (примітиви);
  • посилання на об'єкти.
Термін виклик за значенням (call by value) означає, що конструктор отримує значення, передане йому модулем, що викликає. На противагу цьому, виклик за посиланням (call by reference) означає, що конструктор отримує від модуля, що викликає, адресау змінної. У мові Java використовується лише дзвінок за значенням. За значенням параметра та значенням посилання параметра. Java не використовує виклик за посиланням для об'єктів (хоча багато програмістів та авторів деяких книг це стверджують). Параметри під час передачі об'єктів Java здійснюються не за посиланням , а за значенням посилання на об'єкти ! У будь-якому випадку конструктор отримує копії значень усіх параметрів. Конструктор не може робити зі своїми вхідними параметрами:
  • конструктор неспроможна змінювати значення вхідних параметрів основних (примітивних) типів;
  • конструктор не може змінювати посилання вхідних параметрів;
  • конструктор неспроможна перепризначати посилання вхідних параметрів нові об'єкти.
Конструктор може робити зі своїми вхідними параметрами:
  • змінювати стан об'єкта, що передається як вхідний параметр.
Наступний приклад доводить, що Java вхідні параметри для конструктора передаються за значенням посилання на об'єкт. Також у цьому прикладі відбито те, що конструктор неспроможна змінювати посилання вхідних параметрів, а фактично змінює посилання копій вхідних параметрів. ФайлEmpoyee.java
class Employee
{
 Employee(String x, String y)
 {
  String temp = x;
  x = y;
  y = temp;
 }
 public static void main(String args[])
 {
  String name1 = new String("Alice");
  String name2 = new String("Mary");
  Employee a = new Employee(name1, name2);
  System.out.println("name1="+name1);
  System.out.println("name2="+name2);
 }
}
Результат виведення програми:
name1=Alice
name2=Mary
Якби в мові Java для передачі об'єктів як параметри використовувався виклик за посиланням, то конструктор поміняв би в цьому прикладі місцями name1і name2. Насправді конструктор не поміняє місцями об'єктні посилання, що зберігаються в змінних name1і name2. Це свідчить, що параметри конструктора ініціалізуються копіями цих посилань. Потім конструктор міняє місцями копії. Після завершення роботи конструктора змінні x та y знищуються, а вихідні змінні name1і name2продовжують посилатися на колишні об'єкти.

Зміна параметрів, що передаються конструктору.

Конструктор не може модифікувати передані параметри основних типів. Однак конструктор може модифікувати стан об'єкта, що передається як параметр. Наприклад, розглянемо таку програму: ФайлSalary1.java
class Salary1
{
 Salary1(int x)
 {
  x = x * 3;
  System.out.println("x="+x);
 }
 public static void main(String args[])
 {
  int value = 1000;
  Salary1 s1 = new Salary1(value);
  System.out.println("value="+value);
 }
}
Результат виведення програми:
x=3000
value=1000
Вочевидь, що такий спосіб змінить параметр основного типу. Тому після виклику конструктора значення змінної valueзалишається рівним 1000. По суті відбувається три дії:
  1. Змінна xініціалізується копією значення параметра value(тобто числом 1000).
  2. Значення змінної xпотроюється - тепер воно рівне 3000. Однак значення змінної valueзалишається рівним 1000.
  3. Конструктор завершує свою роботу і змінна xбільше не використовується.
У наступному прикладі зарплата співробітника успішно потроюється, тому що як параметр методу передається значення посилання об'єкта. ФайлSalary2.java
class Salary2
{
 int value = 1000;
 Salary2()
 {
 }
 Salary2(Salary2 x)
 {
  x.value = x.value * 3;
 }
 public static void main(String args[])
 {
  Salary2 s1 = new Salary2();
  Salary2 s2 = new Salary2(s1);
  System.out.println("s1.value=" +s1.value);
  System.out.println("s2.value="+s2.value);
 }
}
Результат виведення програми:
s1.value=3000
s2.value=1000
Як параметр використовується значення посилання на об'єкт. При виконанні рядка Salary2 s2 = new Salary2(s1); конструктору Salary2(Salary x)передасться значення посилання на об'єкт змінної s1, і конструктор фактично потроїть зарплату s1.value, оскільки навіть копія (Salary x), створювана всередині конструктора вказує на об'єкт змінної s1.

Конструктори параметризовані примітивами.

Якщо у параметрах перевантаженого конструктора використовується примітив, який може бути звужений (наприклад int <- double), то виклик методу зі звуженим значенням можливий, незважаючи на те, що методу перевантаженого з таким параметром немає. Наприклад: ФайлPrimitive.java
class Primitive
{
 Primitive(double d)
 {
  d = d + 10;
  System.out.println("d="+d);
 }
 public static void main(String args[])
 {
  int i = 20;
  Primitive s1 = new Primitive(i);
 }
}
Результат виведення програми:
d=30.0
Незважаючи на те, що в класі Primitiveвідсутній конструктор, який має параметр типу int, відпрацює конструктор з вхідним параметром double. Перед викликом конструктора змінна iбуде розширена від типу intдо типу double. Зворотний варіант, коли змінна iбула б типу double, а конструктор був би лише з параметром int, у цій ситуації призвів би до помилки компіляції.

Виклик конструктора та операторnew

Конструктор завжди викликається оператором new. При виклику конструктора оператором newконструктор завжди формує посилання на новий об'єкт. Змусити конструктор сформувати замість посилання на новий об'єкт посилання на вже існуючий об'єкт не можна, крім підстановки об'єкта, що десеріалізується. А з оператором new сформувати замість посилання на новий об'єкт посилання на існуючий об'єкт не можна. Наприклад: ФайлSalary3.java
class Salary3
{
 int value = 1000;
 Salary3()
 {
 }
 Salary3(Salary3 x)
 {
  x.value = x.value * 3;
 }
 public static void main(String args[])
 {
  Salary3 s1 = new Salary3();
  System.out.println("First object creation: "+s1.value);

  Salary3 s2 = new Salary3(s1);
  System.out.println("Second object creation: "+s2.value);
  System.out.println("What's happend with first object?:"+s1.value);

  Salary3 s3 = new Salary3(s1);
  System.out.println("Third object creation: "+s3.value);
  System.out.println("What's happend with first object?:"+s1.value);
 }
}
Результат виведення програми:
First object creation: 1000
Second object creation: 1000
What's happend with first object?: 3000
Third object creation: 1000
What's happend with first object?: 9000
Спочатку за допомогою рядка Salary3 s1 = new Salary3(); створюється новий об'єкт. Далі, якби за допомогою рядка Salary3 s2 = new Salary3(s1); або рядки Salary3 s3 = new Salary3(s1); можна було б створити посилання на вже існуючий об'єкт, то s1.value s2.valueй s3.valueзберігали однакове значення 1000. Насправді у рядку Salary3 s2 = new Salary3(s1); створиться новий об'єкт для змінної s2і зміниться стан об'єкта для змінної s1через передачу свого значення посилання на об'єкт, в параметрі конструктора. У цьому вся можна переконатися за результатами висновку. А при виконанні рядка Salary3 s3 = new Salary3(s1); створиться НОВИЙ об'єкт для змінної s3і знову зміниться стан об'єкта для змінної s1.

Конструктори та блоки ініціалізації, послідовність дій під час виклику конструктора

У розділі Створення об'єкта та конструктори перераховані дії загального характеру, які виробляються під час створення об'єкта. Серед них сполучаються процеси ініціалізації полів класу та відпрацювання конструктора класу, які у свою чергу теж мають внутрішній порядок:
  1. Усі поля даних ініціалізуються своїми значеннями, передбаченими за умовчанням (0, false чи null).
  2. Ініціалізатори всіх полів та блоки ініціалізації виконуються в порядку їх перерахування в оголошенні класу.
  3. Якщо першому рядку конструктора викликається інший конструктор, то виконується викликаний конструктор.
  4. Виконується тіло конструктора.
Конструктор має відношення до ініціалізації, оскільки Java існує три способи ініціалізації поля в класі:
  • присвоїти значення в оголошенні;
  • присвоїти значення в блоці ініціалізації;
  • задати його значення у конструкторі.
Звичайно, потрібно організувати код ініціалізації так, щоб у ньому було легко розібратися. Як приклад наведено наступний клас:
class Initialization
{
 int i;
 short z = 10;
 static int x;
 static float y;
 static
 {
  x = 2000;
  y = 3.141;
 }
 Initialization()
 {
  System.out.println("i="+i);
  System.out.println("z="+z);
  z = 20;
  System.out.println("z="+z);
 }
}
У наведеному прикладі змінні ініціалізуються в наступному порядку: спочатку ініціалізуються статичні змінні xта yзначеннями за умовчанням. Далі виконується статичний блок ініціалізації. Потім проводиться ініціалізація змінної iзначенням за умовчанням та ініціалізується змінна z. Далі у роботу вступає конструктор. Виклик конструкторів класу повинен залежати від порядку оголошення полів. Це може призвести до помилок.

Конструктори та успадкування

Конструктори не успадковуються. Наприклад:
public class Example
{
 Example()
 {
 }
 public void sayHi()
 {
  system.out.println("Hi");
 }
}

public class SubClass extends Example
{
}
Клас SubClassавтоматично успадковує метод, sayHi()визначений у батьківському класі. У той же час, конструктор Example()батьківського класу не успадковується його нащадком SubClass.

Ключове слово thisу конструкторах

Конструктори використовують this, щоб послатися на інший конструктор у цьому ж класі, але з іншим списком параметрів. Якщо конструктор використовує ключове слово this, воно має бути в першому рядку, ігнорування цього правила призведе до помилки компілятора. Наприклад: ФайлThisDemo.java
public class ThisDemo
{
 String name;
 ThisDemo(String s)
 {
  name = s;
     System.out.println(name);
 }
 ThisDemo()
 {
  this("John");
 }
 public static void main(String args[])
 {
  ThisDemo td1 = new ThisDemo("Mary");
  ThisDemo td2 = new ThisDemo();
 }
}
Результат виведення програми:
Mary
John
У цьому прикладі є два конструктори. Перший отримує рядок-аргумент. Другий не отримує жодних аргументів, він просто викликає перший конструктор, використовуючи ім'я "John" за замовчуванням. Таким чином, можна за допомогою конструкторів ініціалізувати значення полів явно і за замовчуванням, що часто потрібне в програмах.

Ключове слово superу конструкторах

Конструктори використовують super, щоб викликати конструктор суперкласу. Якщо конструктор використовує super, цей виклик повинен бути у першому рядку, інакше компілятор видасть помилку. Нижче наведено приклад: ФайлSuperClassDemo.java
public class SuperClassDemo
{
 SuperClassDemo()
 {
 }
}

class Child extends SuperClassDemo
{
 Child()
 {
  super();
 }
}
У цьому простому прикладі конструктор Child()містить виклик super(), який створює екземпляр класу SuperClassDemo, на додаток до класу Child. Так як superповинен бути першим оператором, що виконується в конструкторі підкласу, цей порядок завжди однаковий і не залежить від того, чи використовується super(). Якщо він не використовується, то спочатку буде виконано конструктор за умовчанням (без параметрів) кожного суперкласу, починаючи з базового класу. Наступна програма демонструє коли виконуються конструктори. ФайлCall.java
//Создать суперкласс A
class A
{
 A()
 {
  System.out.println("Inside A constructor.");
 }
}

//Создать подкласс B, расширяющий класс A
class B extends A
{
 B()
 {
  System.out.println("Inside B constructor.");
 }
}

//Создать класс (C), расширяющий класс В
class C extends B
{
 C()
 {
  System.out.println("Inside C constructor.");
 }
}

class Call
{
 public static void main(String args[])
 {
  C c = new C();
 }
}
Висновок цієї програми:
Inside A constructor.
Inside B constructor.
Inside C constructor.
Конструктори викликаються у порядку підпорядкованості класів. У цьому є певний зміст. Оскільки суперклас не має жодного знання про якийсь підклас, то будь-яка ініціалізація, яку йому потрібно виконати, є окремою. По можливості вона повинна передувати будь-якій ініціалізації, яка виконується підкласом. Тому вона і повинна виконуватися першою.

Конструктори, що настроюються

Механізм ідентифікації типу під час виконання є одним із потужних базових принципів мови Java, що реалізує поліморфізм. Однак такий механізм не страхує розробника від несумісного приведення типів у ряді випадків. Найчастіший випадок – маніпулювання групою об'єктів, різні типи яких заздалегідь невідомі та визначаються під час виконання. Оскільки помилки, пов'язані з несумісністю типів можуть виявитися лише з етапі виконання, це ускладнює їх пошук і ліквідацію. Введення типів, що настроюються в Java 2 5.0 частково відсуває виникнення подібних помилок з етапу виконання на етап компіляції і забезпечує недостатню типову безпеку. Відпадає потреба у явному наведенні типів під час переходу від типуObjectдо конкретного типу. Слід мати на увазі, що засоби налаштування типів працюють тільки з об'єктами і не поширюються на примітивні типи даних, що лежать поза деревом спадкування класів. Завдяки типам, що настроюються, всі приведення виконуються автоматично і приховано. Це дозволяє запобігти невідповідності типів і набагато частіше повторно використовувати код. Типи, що настроюються, можна використовувати в конструкторах. Конструктори можуть бути настроюваними, навіть якщо їх клас не є типом, що настроюється. Наприклад:
class GenConstructor
{
 private double val;
 <T extends Number> GenConstructor(T arg)
 {
   val = arg.doubleValue();
 }

 void printValue()
 {
  System.out.println("val: "+val);
 }
}

class GenConstructorDemo
{
 public static void main(String args[])
 {
  GenConstructor gc1 = new GenConstructor(100);
  GenConstructor gc2 = new GenConstructor(123.5F);

  gc1.printValue();
  gc2.printValue();
 }
}
Оскільки конструктор GenConstructorзадає параметр типу, що налаштовується, який повинен бути похідним класом від класу Number, його можна викликати з будь-якої
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ