Книга е още в много ранна фаза на написване



страница35/73
Дата25.07.2016
Размер13.53 Mb.
#6732
1   ...   31   32   33   34   35   36   37   38   ...   73

Ключовата дума final


Ключовата дума final има малко различно значение в зависимост от контекста, но изобщо тя значи “Това не може да се променя.” Може да искате да пред­от­вра­тите промените по две причини: дизайн и ефективност. Понеже тези две не­ща са доста различни, възможно е да се използва думата final неправилно.

Следващите секции разглеждат трите места където final може да се използва: за дан­ни, методи и за клас.


Final данни


Много програмни езици имат начин да се заяви, че определени данни са “кон­стан­ти.” Константата е полезна по две причини:

  1. Може да бъде константа по време на компилация която никога няма да се промени.

  2. Може да бъде инициализирана по време на изпълнение и да не искате да се променя.

В случая на константа по време на компилация компилаторът може да използва кон­стантата навсякъде където трябва; тоест изчисленията се правят (веднъж -б.пр.) по време на компилация и не се правят допълнителни разходи по време на изпълнение. В Java този вид константи трябва да са примитиви и се отбе­ляз­ват с ключовата дума final. Стойност трябва да се даде в момента на де­фи­ни­ра­не­то на такава константа.

Поле което е и static и final има единствена частица от паметта, която не може да се променя.

Когато се използва final с обекти наместо с примитивни типове работата става мал­ко смущаваща. С примитив final прави стойността константа, но с обектов манипулатор, final прави манипулатора константа. Манипулаторът тряб­ва да бъде инициализиран с обект в момента на декларацията и мани­пу­ла­то­рът никога не може да се промени да сочи друг обект. Обаче обектът може да се променя; Java не дава начин да се направи някой произволен обект кон­стан­тен. (Може да напишете, обаче, клас, чиито обекти ефективно са кон­стант­ни.) Това ограничение включва масивите, които са също обекти.

Ето пример, който демонстрира полета final:

//: c06:FinalData.java

// The effect of final on fields


class Value {

int i = 1;

}
public class FinalData {

// Can be compile-time constants

final int i1 = 9;

static final int I2 = 99;

// Typical public constant:

public static final int I3 = 39;

// Cannot be compile-time constants:

final int i4 = (int)(Math.random()*20);

static final int i5 = (int)(Math.random()*20);

Value v1 = new Value();

final Value v2 = new Value();

static final Value v3 = new Value();

//! final Value v4; // Pre-Java 1.1 Error:

// no initializer

// Arrays:

final int[] a = { 1, 2, 3, 4, 5, 6 };


public void print(String id) {

System.out.println(

id + ": " + "i4 = " + i4 +

", i5 = " + i5);

}

public static void main(String[] args) {



FinalData fd1 = new FinalData();

//! fd1.i1++; // Error: can't change value

fd1.v2.i++; // Object isn't constant!

fd1.v1 = new Value(); // OK -- not final

for(int i = 0; i < fd1.a.length; i++)

fd1.a[i]++; // Object isn't constant!

//! fd1.v2 = new Value(); // Error: Can't

//! fd1.v3 = new Value(); // change handle

//! fd1.a = new int[3];
fd1.print("fd1");

System.out.println("Creating new FinalData");

FinalData fd2 = new FinalData();

fd1.print("fd1");

fd2.print("fd2");

}

} ///:~



Тъй като i1 и I2 са final примитиви със стойности известни по време на ком­пи­ла­цията, те могат да се третират като константи по време на компилацията и не са различни по никакъв съществен начин. I3 е по-типичен случай на дефиниране на такива констранти: public за да бъдат видими извън пакета, static за да се подчертае че е единствена и final за това че е константа. За­бе­ле­же­те че final static примитиви с константни начални стойности (т.е. константи по време на компилация) имат имена само от гоеми букви по конвенция. Също забе­лежете че i5 не може да бъде известно по време на компилация, така че име­то не е с големи букви.

Че нещо е final не значи непременно, че стойността му е известна по време на ком­пилация. Това е демонстрирано чрез инициализирането на i4 и i5 по време на изпълнение чрез използване на генератор на случайни числа. Тази част на при­мера също показва разликата между правенето final стойност static и не-static. Тази разлика се проявява само при инициализация по време на из­пъл­не­ние, понеже константите по време на компилация се третират еднакво от ком­пи­латора. (И предполагаемо се оптимизират предварително.) Разликата се виж­да от изхода на програмата:

fd1: i4 = 15, i5 = 9

Creating new FinalData

fd1: i4 = 15, i5 = 9

fd2: i4 = 10, i5 = 9

Забележете че стойностите на i4 за fd1 и fd2 са уникални, но стойността за i5 не е променена при създаването на втори FinalData обект. Това е защото е static и се инициализира веднъж при натоварването, а не всеки път при създаването на обект.

Променливите v1 до v4 демонстрират какво значи final манипулатор. Както мо­же да видите в main( ), само защото v2 е final не означава, че не можете да про­мените стойността му. Не може обаче да пресвържете v2 към нов обект точ­но защото е final. Това е, което final значи за манипулатор. Може да видите, че същото важи и за масив, който просто е друг тип манипулатор. (Не знам на­чин да направя самите манипулатори на масиви final.) Правенето на ма­ни­пу­ла­то­ри final изглежда по-малко полезно от равенето на примитиви final.


Празни final


Java 1.1 позволява създаването на blank finals, които са полета декларирани ка­то final но не им се дава инициализираща стойност. Във всички случаи те тряб­ва да бъдат инициализирани преди да бъдат използвани и компилаторът оси­гу­ря­ва това. Те дават много по-голяма гъвкавост в използването на клю­човата дума final понеже, например, final поле в клас може сега да бъди различно за все­ки обект и все пак да запази качеството си на "финален". Ето пример:

//: c06:BlankFinal.java

// "Blank" final data members
class Poppet { }
class BlankFinal {

final int i = 0; // Initialized final

final int j; // Blank final

final Poppet p; // Blank final handle

// Blank finals MUST be initialized

// in the constructor:

BlankFinal() {

j = 1; // Initialize blank final

p = new Poppet();

}

BlankFinal(int x) {



j = x; // Initialize blank final

p = new Poppet();

}

public static void main(String[] args) {



BlankFinal bf = new BlankFinal();

}

} ///:~



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

Final аргументи


Java 1.1 позволява да се правят аргументи final чрез декларирането им като та­ки­ва в аргументния списък. Това значи че вътре в метода не може да променяте то­ва, към което сочи манипулаторът:

//: c06:FinalArguments.java

// Using "final" with method arguments
class Gizmo {

public void spin() {}

}
public class FinalArguments {

void with(final Gizmo g) {

//! g = new Gizmo(); // Illegal -- g is final

g.spin();

}

void without(Gizmo g) {



g = new Gizmo(); // OK -- g not final

g.spin();

}

// void f(final int i) { i++; } // Can't change



// You can only read from a final primitive:

int g(final int i) { return i + 1; }

public static void main(String[] args) {

FinalArguments bf = new FinalArguments();

bf.without(null);

bf.with(null);

}

} ///:~


Забележете че може да се дава null манипулатор за аргумент който е final без ком­пилаторът да се вайка, точно както за нефинален аргумент.

Методите f( ) и g( ) показват какво става когато примитивни аргументи са final: мо­же да четете аргумента, но не може да го променяте.


Final методи


Има две причини за съществуването на final методи. Първата е да се “заключи” ме­тодът така че никой наследяващ клас да не може да му промени поведението. То­ва се прави по причини на дизайна когато искате да осигурите че ако класът се наследи методът няма да бъде подтиснат.

Втората причина е ефективността. Ако направите метод final, позволявате на ком­пилатора да превърне извикванията му в inline извиквания. Когато ком­пи­ла­торът види извикване на final той може (по своя преценка) да пропусне не­ща­та, които се правят при нормалния подход за извикване на методи (слагане на ар­гументите на стека, преход към кода на метода, преход обратно, почистване на стека и оправяне с върнатата стойност) и вместо тях може да сложи направо тя­лото на метода на мястото на извикването. Това елиминира допълнителните раз­ходи за викането на метода. Разбира се, ако методът е голям, вашият код ще на­бъбне и няма да усетите никакво подобрение на скоростта, понеже съ­кра­ща­ва­нето на работата е малко в сравнение с времето за изпълнение на самия ме­тод. Предвидено е Java компилаторът да е в състояние да познае такива въз­мож­ности и умно да прецени дали да направи даден final метод инлайн. Обаче е по-добре да не се надържаме на възможностите на компилатора и да правим ме­тоди final само ако са много малки или искаме явно да предотвратим въз­мож­ността за подтискането им.

Всеки private в клас е имплицитно final. Понеже нямате достъп до private ме­тод, не може да го подтиснете (ако и компилаторът не дава съобщение за греш­ка като се опитвате да го подтиснете, вие реално не го подтискате, а създавате нов метод). Може да добавите final спецификатора към private метод но това ни­що не му дава в повече.

Final класове


Като кажете че цял клас е final (чрез предхождане на дефиницията му с клю­чо­ва­та дума final), заявявате че не щете да наследявате от този клас и не щете ни­кой да го прави. С други думи, по някакви проектантски или от сигурността при­чини този клас никога не трябва да се променя или да се наследява от други кла­сове. Алтернативно, може да е причината ефективността и искате работата с то­зи клас да е толкова ефестивна, колкото е възможно.

//: c06:Jurassic.java

// Making an entire class final
class SmallBrain {}
final class Dinosaur {

int i = 7;

int j = 1;

SmallBrain x = new SmallBrain();

void f() {}

}
//! class Further extends Dinosaur {}

// error: Cannot extend final class 'Dinosaur'
public class Jurassic {

public static void main(String[] args) {

Dinosaur n = new Dinosaur();

n.f();


n.i = 40;

n.j++;


}

} ///:~


Забележете че данновите членове могат да бъдат final или не по ваш избор. Съ­щи­те правила важат за final за даннови членове без значение дали класът е final. Де­кларирането на клас като final просто предотвратява наследяването – нищо по­вече. Поради това предотвратяване обаче всички методи на final клас са им­пли­цитно final, понеже няма начин да се подтиснат. Така че компилаторът има съ­щата възможност за подобряване на ефективността, както ако ги бяхте обя­ви­ли final.

Може да добавите спецификатор final към метод на final клас, но нищо повече не се задава реално.


Final предпазливост


Може да изглежда добре да направите метод final докато проектирате клас. Мо­же да чувствате че ефективността е много важна или че никой не трябва да го променя. Понякога това е така.

Но бъдете внимателни с предположенията. Изобщо е трудно да се предвиди как да­ден клас ще се използва в бъдеще. Ако определите метод като final бихте пре­махнали възможността вашият метод да се подтисне от друг програмист са­мо защото не сте могли да си представите използването на вашия клас по не­го­вия начин.

Стандартната Java библиотека е добър пример за това. В частност Java 1.0/1.1 Vector класът бе много използван и щеше да бъде още по-полезен ако, в името на ефективността, всички методи не бяха направени final. Лесно е да се пред­ви­ди, че ще е полезно да може да се наследява такъв полезен и общ клас, но кой знае защо проектантите му решили да забранят това. Това е иронично по две при­чини. Първо, Stack е наследен от Vector, което значи че Stack е Vector, кое­то пък не е реално истина. Второ, много от най-важните методи на Vector, та­ки­ва като addElement( ) и elementAt( ) са synchronized, което както ще видите в гла­ва 14 вкарва значителни допълнителни разходи които по всяка вероятност ще унищожат подобренията вследствие на final. Това ни кара да даваме вяра на тео­рията, че програмистите са винаги лоши и се мъчат да познаят къде ще има опти­мизация. Особено лошо е че такова лошо проектиране е направено в стан­дарт­на библиотека и всички ние ще трябва да се оправяме с него. (За щастие би­блиотеката за колекциите на Java 2 заменя Vector с ArrayList, който има мно­го по-цивилизовано поведение.)

Ин­тересно е също да се отбележи че Hashtable, друг важен клас на стандартна би­блиотека, няма final методи. Както се спомена на друго място в тази книга, оче­видно е че различните класове са проектирани от съвършено различни хора. (За­бележете краткостта на имената в Hashtable сравнени с онези във Vector.) То­ва е нещо от вид, който не трябва да е очевиден за потребителите на би­блио­те­ката. Когато нещата са недомислени това прави повече работа за по­тре­би­те­ля. (Забележете че Java 2 библиотеката за колекциите заменя Hashtable със Hashmap.)





Сподели с приятели:
1   ...   31   32   33   34   35   36   37   38   ...   73




©obuch.info 2024
отнасят до администрацията

    Начална страница