Java излиза от пътя си за да гарантира че променливите са инициализирани преди да бъдат използвани. В случая на променливи които са определени локално в метод тази гаранция идва във формата на грешка при компилация. Така ако напишем:
void f() {
int i;
i++;
}
Ще получим съобщение за грешка в което се казва че i може да не е било инициализирано. Разбира се, компилаторът би могъл да даде на i стойност по подразбиране, но е по-вероятно това да е програмистка грешка, която ще се открие по тази начин. Заставянето на програмиста да даде стойността най-вероятно ще хване бъг.
Ако примитивът е член-данни на клас, обаче, нещата са по-различни. Тъй като всеки метод би могъл да инициализира данните, не е практично да се кара програмистът да го прави. Не е безопасно и да се оставя каквато се случи стойност, така че всеки член се гарантира да има определена начална стойност. Тези стойности могат да се видят тук:
//: c04:InitialValues.java
// Shows default initial values
class Measurement {
boolean t;
char c;
byte b;
short s;
int i;
long l;
float f;
double d;
void print() {
System.out.println(
"Data type Inital value\n" +
"boolean " + t + "\n" +
"char " + c + "\n" +
"byte " + b + "\n" +
"short " + s + "\n" +
"int " + i + "\n" +
"long " + l + "\n" +
"float " + f + "\n" +
"double " + d);
}
}
public class InitialValues {
public static void main(String[] args) {
Measurement d = new Measurement();
d.print();
/* In this case you could also say:
new Measurement().print();
*/
}
} ///:~
Програмата извежда следното:
Data type Inital value
boolean false
char
byte 0
short 0
int 0
long 0
float 0.0
double 0.0
char стойността е нула (нулева стойност, не цифрата нула - б.пр.) и затова не се вижда.
Ще видите по-нататък че при декларация на манипулатор в метод той се инициализира с нула по начало.
Може да се види, че макар и да не се дават стойности, променливите се инициализират. Така че най-малкото го няма наказанието работа с неинициализирани променливи.
Задаване на инициализация
Какво става ако искаме да зададем начална стойност? Един пряк начин е просто да присвоим стойността в мястото, където се обявява променливата в класа. (Забележете че това не може да се направи в C++, макар че C++ новаците винаги се опитват.) Ето дефинициите на полета за класа Measurement променени за да получават начални стойности:
class Measurement {
boolean b = true;
char c = 'x';
byte B = 47;
short s = 0xff;
int i = 999;
long l = 1;
float f = 3.14f;
double d = 3.14159;
//. . .
Също може да се инициализират и не-примитиви по този начин. Ако Depth е клас може да вмъкнете променлива и да я инициализирате така:
class Measurement {
Depth o = new Depth();
boolean b = true;
// . . .
Ако не сте дали на o начална стойност и продължите напред и се опитате така да го използвате, ще получите грешка по време на изпълнение от тип изключение (разгледан в глава 9).
Може даже да извикате метод за даване на начална стойност:
class CInit {
int i = f();
//...
}
Този метод може да има аргументи, разбира се, но те не могат да бъдат неинициализирани още променливи. Това е допустимо:
class CInit {
int i = f();
int j = g(i);
//...
}
Но това не е:
class CInit {
int j = g(i);
int i = f();
//...
}
Тук компилаторът, съвсем на място, се оплаква от позоваване напред, понеже то се отнася за реда на инициализация, а не за компилацията.
Този подход към инициализацията е прост и праволинеен. Той има ограничението че всеки обект от тип Measurement ще получи същите начални стойности. Понякога това е точно което искаме, но в други случаи трябва повече гъвкавост.
Инициализация в конструкторите
Конструкторът може да се използва за задаване на начални стойности и това е по-гъвкав начин, понеже по време на изпълнение може да се викат методи и да се определят стойностите. Трябва да се помни едно нещо, обаче: не се предотвратява автоматичната инициализация, която става преди викането на метода. Например ако напишем:
class Counter {
int i;
Counter() { i = 7; }
// . . .
i отначало ще стане нула, а после 7. Това се отнася за всички примитивни типове и манипулаторите на обекти, включително онези, които получават явно стойности при декларацията им. По тази причина компилаторът не се опитва да ви заставя да инициализирате елементи никъде в конструктора или преди да се използват – те са вече инициализирани.5
Ред на инициализация
Вътре в клас редът се определя от реда на деклариране на променливите. Ако и променливите да са размесени с дефиниции на методи, всички променливи се инициализират преди да се извика кой да е метод – даже конструктора. Например:
//: c04:OrderOfInitialization.java
// Demonstrates initialization order.
// When the constructor is called, to create a
// Tag object, you'll see a message:
class Tag {
Tag(int marker) {
System.out.println("Tag(" + marker + ")");
}
}
class Card {
Tag t1 = new Tag(1); // Before constructor
Card() {
// Indicate we're in the constructor:
System.out.println("Card()");
t3 = new Tag(33); // Re-initialize t3
}
Tag t2 = new Tag(2); // After constructor
void f() {
System.out.println("f()");
}
Tag t3 = new Tag(3); // At end
}
public class OrderOfInitialization {
public static void main(String[] args) {
Card t = new Card();
t.f(); // Shows that construction is done
}
} ///:~
В Card декларациите на Tag нарочно са разхвърляни за да се види, че инициализацията протича най-напред. В добавка t3 се преинициализира в конструктора. Изходът е:
Tag(1)
Tag(2)
Tag(3)
Card()
Tag(33)
f()
Така t3 манипулаторът бива инициализиран два пъти, един преди и един по време на извикването на конструктора. (Първият обект се бракува, така че може да бъде почистен по-късно.) На пръв поглед това може да изглежда неефективно, но то гарантира правилна инициализация – какво би станало ако би бил определен претоварен конструктор който не инициализира t3 и нямаше “дефолтна”инициализация за t3 в декларацията му?
Инициализация на статични данни
Когато данните са static е същото; ако е примитив и не го инициализирате получава стойност по подразбиране. Ако е манипулатор остава инициализирано с нула докато не създадете обект и свържете манипулатора с него.
Ако искате да сложите инициализацията в точката на декларацията, прави се също като за не-статични. Тъй като обаче има само едно място в паметта за static независимо от броя на създадените обекти възниква въпросът кога става инициализацията. Примерът прави въпроса по-ясен:
//: c04:StaticInitialization.java
// Specifying initial values in a
// class definition.
class Bowl {
Bowl(int marker) {
System.out.println("Bowl(" + marker + ")");
}
void f(int marker) {
System.out.println("f(" + marker + ")");
}
}
class Table {
static Bowl b1 = new Bowl(1);
Table() {
System.out.println("Table()");
b2.f(1);
}
void f2(int marker) {
System.out.println("f2(" + marker + ")");
}
static Bowl b2 = new Bowl(2);
}
class Cupboard {
Bowl b3 = new Bowl(3);
static Bowl b4 = new Bowl(4);
Cupboard() {
System.out.println("Cupboard()");
b4.f(2);
}
void f3(int marker) {
System.out.println("f3(" + marker + ")");
}
static Bowl b5 = new Bowl(5);
}
public class StaticInitialization {
public static void main(String[] args) {
System.out.println(
"Creating new Cupboard() in main");
new Cupboard();
System.out.println(
"Creating new Cupboard() in main");
new Cupboard();
t2.f2(1);
t3.f3(1);
}
static Table t2 = new Table();
static Cupboard t3 = new Cupboard();
} ///:~
Bowl подволява да се види създаването на клас и Table и Cupboard създават static членове на Bowl разпръснати в техните декларации на класовете. Забележете че Cupboard създава не-static Bowl b3 преди static декларациите. Изходът показва какво се е случило:
Bowl(1)
Bowl(2)
Table()
f(1)
Bowl(4)
Bowl(5)
Bowl(3)
Cupboard()
f(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f(2)
f2(1)
f3(1)
Инициализацията на static се прави само ако е необходимо. Ако не създадетеTable и никога не се отнесете към Table.b1 или Table.b2, static Bowl b1 и b2 никога няма да се създадат. Обаче те се създават само когато първия Table обект се създава (или първия достъп до static се върши). След това static обект не се реинициализира.
Редът на инициализацията е вички static първо, ако не са били инициализирани по време на предишното създаване на обект и тогава не-static обектите. Може да видите доказателство за това в изхода.
От помощ ще е да резюмираме процеса на създаване на обект. Нека класът се казва Dog:
-
Първият път когато се създава обект от тип Dog или когато за пръв път до static метод или static поле от клас Dog се извършва достъп Java интерпретаторът трябва да намери Dog.class, което той прави чрез търсене по "пътя за класовете".
-
Като е натоварен Dog.class (което създава Class обект, както ще научите по-нататък), всички негови staticинициализатори се пускат да работят. Така static инициализацията става само веднъж, когато Class обектът се натоварва за пръв път.
-
Когато се прави new Dog( ), процесът на конструиране на Dog обекта първо алокира достатъчно памет от хийпа за Dog обект.
-
Тази памет се нулира, автоматично задавайки стойностите по подразбиране на всички примитиви в Dog (нула за числата и еквивалент за boolean и char).
-
Всички инициализации които са при декларациите се изпълняват.
-
Конструкторите се изпълняват. Както ще видите в глава 6, това би могло да предизвика значителна активност, особено когато има наследяване.
Явна инициализация на static
Java позволява да се групират друг вид инициализации на static в специална “static конструкционна клауза” (понякога наричана static block) в класа. Това изглежда така:
class Spoon {
static int i;
static {
i = 47;
}
// . . .
Изглежда като метод, но е точно ключовата дума static следвана от тялото на метода. Този код, както другата static инициализация, се изпълнява само веднъж, първия път, когато се създава обект от този клас или се прави достъп до static член от него клас (даже и да не е създаден обект от този клас). Например:
//: c04:ExplicitStatic.java
// Explicit static initialization
// with the "static" clause.
class Cup {
Cup(int marker) {
System.out.println("Cup(" + marker + ")");
}
void f(int marker) {
System.out.println("f(" + marker + ")");
}
}
class Cups {
static Cup c1;
static Cup c2;
static {
c1 = new Cup(1);
c2 = new Cup(2);
}
Cups() {
System.out.println("Cups()");
}
}
public class ExplicitStatic {
public static void main(String[] args) {
System.out.println("Inside main()");
Cups.c1.f(99); // (1)
}
static Cups x = new Cups(); // (2)
static Cups y = new Cups(); // (2)
} ///:~
static инициализаторите за Cups ще се пуснат или като се прави достъп до static обекта c1 на реда отбелязан с (1), или редът (1) се постави на коментар а коментарът на (2) се махне. Ако и (1) и (2) се поставят на коментар, static инициализация за Cups не се прави въобще.
Инициализация на нестатични екземпляри
Java 1.1 има подобен синтаксис за инициализация на нестатични променливи за всеки обект. Ето един пример:
//: c04:Mugs.java
// Java 1.1 "Instance Initialization"
class Mug {
Mug(int marker) {
System.out.println("Mug(" + marker + ")");
}
void f(int marker) {
System.out.println("f(" + marker + ")");
}
}
public class Mugs {
Mug c1;
Mug c2;
{
c1 = new Mug(1);
c2 = new Mug(2);
System.out.println("c1 & c2 initialized");
}
Mugs() {
System.out.println("Mugs()");
}
public static void main(String[] args) {
System.out.println("Inside main()");
Mugs x = new Mugs();
}
} ///:~
Може да видите че клаузата за инициализация на екземпляра:
{
c1 = new Mug(1);
c2 = new Mug(2);
System.out.println("c1 & c2 initialized");
}
изглежда точно като клаузата за статичните, освен че я няма ключовата дума stati. Този синтаксис е необходим за поддържане на инициализацията на анонимни вътрешни класове (виж глава 7).
Сподели с приятели: |