В много от по-традиционните програмни езици програмите се товарят изведнъж като част от процеса на стартирането им. Това се следва от инициализация, а после програмата започва. Процесът на инициализация в тези езици трябва да бъде грижливо управляван така че statics да не причинява неприятности. C++, например, има проблеми ако един static очаква друг static да бъде валиден преди втория да бъде инициализиран.
Java няма този проблем поради различния си подход към товаренето. Понеже всичко в Java е абект много активности се удават по-лесно и тази е една от тях. Както ще научите в следващата глава, кодът за всеки обек е разположен в отделен файл. Този файл не се товари докато кодът не стане непосредствено необходим. Изобщо може да се каже, че кодът няма да се качва, докато не стане нужда да се конструира обект. Понеже може да има някои тънкости във връзка със static методите може също да се каже “Кодът на класа се товари в точката на първото използване.”
Точката на първото използване е също където се инициализира static. Всичките static обекти и static кодови блокове ще се инициализират в текстова последователност (тоест в реда в който сте ги дефинирали в описанието на класа) в точката на товаренето. static, разбира се, се инициализират само веднъж.
Инициализация с наследяване
Полезно е да се погледне инициализационния процес в цялост, включително наследяването, за да се види цялата картина на това което става. Да вземем следващия код:
//: c06:Beetle.java
// The full process of initialization.
class Insect {
int i = 9;
int j;
Insect() {
prt("i = " + i + ", j = " + j);
j = 39;
}
static int x1 =
prt("static Insect.x1 initialized");
static int prt(String s) {
System.out.println(s);
return 47;
}
}
public class Beetle extends Insect {
int k = prt("Beetle.k initialized");
Beetle() {
prt("k = " + k);
prt("j = " + j);
}
static int x2 =
prt("static Beetle.x2 initialized");
static int prt(String s) {
System.out.println(s);
return 63;
}
public static void main(String[] args) {
prt("Beetle constructor");
Beetle b = new Beetle();
}
} ///:~
Изходът за тази програма е:
static Insect.x1 initialized
static Beetle.x2 initialized
Beetle constructor
i = 9, j = 0
Beetle.k initialized
k = 63
j = 39
Първото нещо което се случва като тръгне Java с Beetle е че лоудерът излиза и намира въпросния клас. В процеса на товаренето му лоудерът намира, че той има базов клас (това е, което ключовата дума extendsказва), който тогава той товари. Това ще стане независимо дали ще правите обект от този базов клас. (Опитайте да изкоментирате създаването на клас за да го докажете на себе си.)
Ако базовият клас има базов клас, този втори базов клас тогава ще се натовари и така нататък. После static инициализация в кореновия базов клас (в този случай Insect) се изпълнява, после в съседния извлечен клас и т.н. Това е важно, защото инициализацията на статичните членове на наследения клас може да зависи от това дали членовете на базовия клас са инициализирани правилно.
В тази точка всички необходими класове са натоварени така че може да се създаде обект. Първо всички примитиви в обекта се снабдяват със техните стойности по подразбиране и всички манипулатори получават стойност null. Тогава ще се извика конструкторът на базовия клас. В този случай извикването е автоматично, но също може да укажете извикване на конструктор (като първа операция в Beetle( ) конструктора) чрез super. Конструирането на базовия клас става в същия ред както на извлечения клас. След като констректорът на базовия клас завърши работата си, променливите на екземпляра се инициализират по текстовия ред. Накрая се изпълнява останалата част от тялото на конструктора.
Резюме
И композицията и наследяването позволяват да се получат нови типове от съществуващи типове. Типично се използва композицията за използване на съществуващата реализация на даден тип и наследяване, когато искате повторно да използвате интерфейса. Тъй като извлеченият клас има интерфейса на базовия клас,може да се направи upcast към базовия, което е критично важно за полиморфизма, както ще научите в следващата глава.
Напук на силното ударение върху наследяването в ООП, когато започнете проектиране най-вече ще предпочитате композицията като начин и ще използвате наследяване само когато това е явно необходимо. (Както ще видите в следващата глава.) Композицията има тенденция да бъде по-гъвкава. В добавка, използвайки донадената от наследяването хитрост, може да променяте точния тип, а с това и повезението, на членовете-обекти по време на изпълнение. Затова може да променяте поведението на композиран обект по време на изпълнение.
Въпреки че композицията и наследяването помагат за бързото разработване на проекти, изобщо ще искате да преразгледате йерархията си, преди да дадете на други програмисти да се занимават с нея. Вашата цел е всеки клас да има специфична употреба и да не бъде нито твърде голям (събирайки толкова функционалност, че да е съмнително дали ще се използва пак) нито дразнещо малък (та да не може да се използва без добавяне на функционалност). Като завършите класовете си те трябва да са лесни за многократно използване.
-
Създайте два класа, A и B, с конструктори по подразбиране (празни аргументни списъци) които съобщават за себеси. Наследете нов клас наречен C от A и създайте член B вътре в C. Не създавайте конструктор за C. Създайте обект от клас C и наблюдавайте резултатите.
-
Променете упражнение 1 така че A и B да имат конструктори с аргументи вместо конструктори по подразбиране. Напишете конструктор за C и направете всичката инициализация в конструктора на C.
-
Вземете файла Cartoon.java и изкоментирайте конструктора на класа Cartoon. Обяснете какво става.
-
Вземете файла Chess.java и изкоментирайте конструктора на класа Chess. Обяснете какво става.
Сподели с приятели: |