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



страница22/73
Дата25.07.2016
Размер13.53 Mb.
#6732
1   ...   18   19   20   21   22   23   24   25   ...   73

Стил на кодиране


Неофициален стандарт в Java е да се пише с главна буква първото име на класа. Ако името се състои от няколко думи, те се долепят (не се използва подчер­та­ва­що тире за съединяване на думите) и всяка дума е с главна биква като в:

class AllTheColorsOfTheRainbow { // ...

За почти всичко друго: методи, полета (член-променливи) и имена на ма­ни­пу­ла­тори приетият стил е точно както за класовете освен че първата буква на идентификатора е малка. Например:

class AllTheColorsOfTheRainbow {

int anIntegerRepresentingColors;

void changeTheHueOfTheColor(int newHue) {

// ...

}

// ...



}

Разбира се, ще помните, че потребителят ще трябва да пише всички тези дълги име­на и да бъдете милостиви.


Резюме


В тази глава се запознахте достатъчно с Java програмирането за да може да на­пи­шете проста програма, а също получихте общ поглед върху езика и някои от не­говите основни идеи. Примерите до тук обаче бяха от вида “направи това, по­сле направи нещо друго.”Ако искате програмата да прави избор, както “ако ре­зултатът е червено, прави това, ако не е, тогава прави нещо друго”? Под­дръж­ката на Java за тези съществени неща ще видите в следващата глава.

Упражнения


  1. Следвайки първия пример в тази глава създайте “Hello, World” която просто из­вежда това на екрана. Нужен е само един метод във вашия клас ( “main” , кой­то се изпълнява когато програмата стартира). Не забравяйте да го на­пра­вите static и да вмъкнете списък аргументи, макар и да не го използвате по-на­татък. Компилирайте програмата с javac aи я стартирайте с java.

  2. Напишете програма която да извежда трите аргумента на командния ред.

  3. Намерете кода за втора версия на Property.java, който е прост пример за до­ку­ментационен коментар. Изпълнете javadoc с файла и разгледайте резул­та­ти­те с броузера си.

  4. Вземете програмата от упражнение 1 и добавете документация. Извлечете то­зи коментар в HTML файл с javadoc и я разгледайте в броузера.

3: Управление хо­да на програ­ма­та


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

В Java обектите и данните се манипулират чрез оператори, а решения се вземат чрез оператори за управление на изпълнението. Java е наследен от C++, така че по­вечето от тези оператори ще са познати на C и C++ програмистите. Java е до­ба­вил също някои подобрения и опростявания.


Използваме Java оператори


Операторът взима един или повече аргументи и произвежда нова стойност. Аргументите са с друга форма от тази на единичното извикване на метод, но ефек­тът е същият. Ще се чувствате доста комфортно с операторите благо­да­ре­ние на вашия предишен опит с операторите. Съъбиране (+), идваждане и унарен ми­нус (-), умножение (*), деление (/) и присвояване (=) — всичко работи както във всеки друг език.

Всички оператори произвеждат стойност от своите операнди. В добавка опе­ра­то­рът може да промени стойността на операнд. Това се нарича страничен ефект. Най-широко се употребяват оператори за получаване на страничен ефект, но ще помните че има и стойност достъпна за употреба точно както при опе­ра­торите без страничен ефект.

Почти всички оператори работят с примитиви. Изключенията са ‘=’, ‘==’ и ‘!=’, които работят с всички обекти (и са при`ина за обърквания с обектите). В до­бавка, класа String поддържа ‘+’ и ‘+=’.

Приоритет


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

A = X + Y - 2/2 + Z;

има друго значение от:

A = X + (Y - 2)/(2 + Z);


Присвояване


Присвояването се изпълнява с оператора =. Той значи “вземи стойността от дяс­ната страна (често наричано rvalue) и я копирай в лявата страна (често на­ри­ча­но lvalue). rvalue е някоя константа, променлива или израз която може да даде стой­ност, но lvalue трябва да бъде различна, именувана променлива. (Тоест, тряб­ва да има физическо място за запомняне на стойността.) Може да присвоявате константа на променлива (A = 4;); но не може да присвоявате ни­що на константа – тя не може да бъде lvalue. (Не може 4 = A;.)

Присвояването на примитивите е твърде праволинейно. Тъй като примитивът съ­държа стойност, а не манипулатор на обект, когато присвоявате примитив ко­пирате от едно място на друго. Например A = B за примитиви копира B в A. Ако продължите с модификация на A, B естествено не е засегната от това. Това е, което очаквате като програмист в повечето ситуации.

Обаче когато присвоявате обект нещата се променят. Щом манипулирате обект, манипулира се манипулаторът му, така чи когато присвоявате “от един обект на друг” фактически копирате манипулатора от едно място на друго. То­ва значи че ако се напише C = D свършвате с това, че C и D сочат към един и същ обект, този към който D сочеше оригинално. Следващия пример ще де­мон­стри­ра това.

Като странична реплика, първото нещо което виждате е package оператор за package c03, индициращ глава 3 на тази книга. Първата програма от всяка глава ще съдържа такъв оператор за да зададе номера на главата в останалите про­гра­ми в нея. В глава 17 ще видите като резултат всички листинги от глава 3 (освен оне­зи които имат различни имена на пакетите) ще се сложат автоматично в ди­рек­тория наречена c03, на глава 4 листингите ще бъдат в c04 и така нататък. Всич­ко това се случва чрез CodePackager.java програмата показана в глава 17, а в глава 5 концепцията на пакетите ще бъде напълно обяснена. Това което тряб­ва да знаете засега е, че за тази книга редовете с форма като на package c03 се използват точно за посочване на директория за програмите към главата.

За да се пусне програмата, трябва да се осигури classpath да съдържа кореновата директория където сте разположили книгата. (От тази директорияще виждате поддиректории c02, c03, c04, и т.н..)

За по-късните версии на Java (1.1.4 and on) когато main( ) е във файл с package оператор ще трябва да дадете пълното име на пакета преди името на про­гра­ма­та за да я стартирате. В този случай командният ред е:

java c03.Assignment

Помнете това винаги, когато пускате програма която е в package.

Ето го и примера:

//: c03:Assignment.java

// Присвояването на обекти е малко сложно

package c03;


class Number {

int i;


}
public class Assignment {

public static void main(String[] args) {

Number n1 = new Number();

Number n2 = new Number();

n1.i = 9;

n2.i = 47;

System.out.println("1: n1.i: " + n1.i +

", n2.i: " + n2.i);

n1 = n2;

System.out.println("2: n1.i: " + n1.i +

", n2.i: " + n2.i);

n1.i = 27;

System.out.println("3: n1.i: " + n1.i +

", n2.i: " + n2.i);

}

} ///:~


Класът Number е простичък и двата му екземпляра (n1 и n2) се създават в main( ). На i във всеки Number се дава различна стойност и после n2 се при­своя­­ва на n1, а n1 е променено. В много програмни езици бихте очаквали n1 и n2 да бъдат независими през цялото време, но понеже сте присвоили ма­ни­пу­ла­тор, ще видите следния шзход:

1: n1.i: 9, n2.i: 47

2: n1.i: 47, n2.i: 47

3: n1.i: 27, n2.i: 27

Промяната на n1 обекта се оказва че променя също и n2 обекта! Това е защото и n1 и n2 съдържат един и същ манипулатор, който сочи към един обект. (Оригиналния манипулатор в n1 който сочеше към обекта съдържащ 9 беше пре­записан при присвояването и фактически загубен; неговият обект ще бъде из­чи­стен от боклучаря.)

Този феномен е често наричан aliasing и е основен начин, по който Java работи с обекти. Но какво ще правим ако не искате да има псевдоними в този случай? Бих­те могли преди присвояването да напишете:

n1.i = n2.i;

Така остават два отделни обекта наместо подхвърляне на един и насочване на n1 и n2 към същия обект, но скоро ще разберете, че манипулирането на по­ле­та­та в един обект е объркана работа и в противоречие с принципите на ООП. Това не е тривиален въпрос и е оставен за глава 12, която е посветена на псев­до­ни­ми­те. Междувременно ще помните, че присвояването на обекти може да донесе из­ненади.


Aliasing при извикване на методи


Псевдоними ще се получат също и ако предавате обект като параметър на ме­тод:

//: c03:PassObject.java

// Даването на обекти на метод може да не е това,

// с което сте свикнали.


class Letter {

char c;


}
public class PassObject {

static void f(Letter y) {

y.c = 'z';

}

public static void main(String[] args) {



Letter x = new Letter();

x.c = 'a';

System.out.println("1: x.c: " + x.c);

f(x);


System.out.println("2: x.c: " + x.c);

}

} ///:~



В много езици методът f( ) щеше да направи копие на аргумента си Letter y въ­тре в обхвата на метода. Но пак манипулатор се изпраща, така че линията

y.c = 'z';

фактически променя обекта извън f( ). Изходът показва това:

1: x.c: a

2: x.c: z

Aliasing-ът и решението му са сложни въпроси; въпреки че трябва да чакате до гла­ва 12 за всички отговори, трябва да сте предупредени от сега, за да не по­па­да­те във вълчи ями.


Математически оператори


Основните математически оператори са същите като тези в повечето езици за програмиране: събиране (+), изваждане (-), деление (/), умножение (*) и модуло (%, дава остатъка при целочислено делене). Целочисленото делене отрязва, а не закръгля резултата.

Java използва също и съкратена нотация за получаване на присвояване и обра­бот­ка на един път. Тя е оператор, следван от знак за равенство и работи за всич­ки оператори на езика (навсякъде където има смисъл). Например за да добавим 4 към променливата x и присвоим резултата на x използваме: x += 4;.

Този пример показва приложение на математическите оператори:

//: c03:MathOps.java

// Демонстрира математическите оператори

import java.util.*;


public class MathOps {

// Create a shorthand to save typing:

static void prt(String s) {

System.out.println(s);

}

// shorthand to print a string and an int:



static void pInt(String s, int i) {

prt(s + " = " + i);

}

// shorthand to print a string and a float:



static void pFlt(String s, float f) {

prt(s + " = " + f);

}

public static void main(String[] args) {



// Create a random number generator,

// seeds with current time by default:

Random rand = new Random();

int i, j, k;

// '%' limits maximum value to 99:

j = rand.nextInt() % 100;

k = rand.nextInt() % 100;

pInt("j",j); pInt("k",k);

i = j + k; pInt("j + k", i);

i = j - k; pInt("j - k", i);

i = k / j; pInt("k / j", i);

i = k * j; pInt("k * j", i);

i = k % j; pInt("k % j", i);

j %= k; pInt("j %= k", j);

// Тестове за числа с плаваща запетая:

float u,v,w; // applies to doubles, too

v = rand.nextFloat();

w = rand.nextFloat();

pFlt("v", v); pFlt("w", w);

u = v + w; pFlt("v + w", u);

u = v - w; pFlt("v - w", u);

u = v * w; pFlt("v * w", u);

u = v / w; pFlt("v / w", u);

// the following also works for

// char, byte, short, int, long,

// and double:

u += v; pFlt("u += v", u);

u -= v; pFlt("u -= v", u);

u *= v; pFlt("u *= v", u);

u /= v; pFlt("u /= v", u);

}

} ///:~


Първото нещо, което ще видите са някои бързи начини за писане: prt( ) методът принт­ва String, pInt( ) печата String следван от int и pFlt( ) печата String след­ван от float. Разбира се, те всички използват System.out.println( ).

За да генерира числа, програмата първо създава Random обект. Понеже не се да­ват аргументи, Java използва текущото време като начало за генератора на слу­чайни числа. Програмата генерира множество типове случайни числа чрез Random обекта просто извиквайки различни методи: nextInt( ), nextLong( ), nextFloat( ) or nextDouble( ).

Операторът модуло, използван със случайни числа, ограничава стойността до гор­ната и граница минус едно (99 в този случай) (и я прави цяла - б.пр.).

Унарни минус и плюс оператори


Унарният минус (-) и унарният плюс (+) са същите като бинарните минус и плюс. Компилаторът разбира кой вид се иска от начина на записване. Например опе­раторът

x = -a;


има очевидно значение. Компилаторът е в състояние да разбере:

x = a * -b;

но читателят може да бъде смутен, та е по-добре да се напише:

x = a * (-b);

Унарният минус дава обратното на дадено число. Унарният плюс е за симетрия с минуса, макар и да не прави много.

Авто инкремент и декремент


Java, подобно на C, е пълен с кратки начини. Те могат да направят кодирането мно­го по-лесно, а също да направят четенето по-трудно, а понякога и по-лесно.

Два от най-тънките кратки начина са инкремент и декремент (често спо­ме­на­ва­ни като авто-инкремент и авто-декремент оператори). Декремент операторът е -- и значи “намали с единица.” Инкрементния оператор е ++ и значи “увеличи с еди­ница.” Ако A е int, например, изразът ++A е еквивалентен на (A = A + 1). Два­та оператора дават като резултат стойност на променлива.

Всеки от двата има две версии, често наричани префиксна и постфиксна версия. Пре-инкремент значи че ++ се появява преди променлива или израз, а пост-инкре­мент значи че ++ се появява след променлива или израз. Подобно, пре­де­кре­ментът значи че    се появява преди променлива или израз и постдекремент — че    оператор има след променлива или израз. За преинкремент и пре­де­кре­мент, (т.е. ++A и   A)операцията се изпълнява и се произвежда стойност. За пост- динкремента и декремента (т.е. A++ и A  ) първо се произвежда стойност и после се изпълнява операцията. Като пример:

//: c03:AutoInc.java

// Демонстрира ++ и -- оператори
public class AutoInc {

public static void main(String[] args) {

int i = 1;

prt("i : " + i);

prt("++i : " + ++i); // Pre-increment

prt("i++ : " + i++); // Post-increment

prt("i : " + i);

prt("--i : " + --i); // Pre-decrement

prt("i-- : " + i--); // Post-decrement

prt("i : " + i);

}

static void prt(String s) {



System.out.println(s);

}

} ///:~



Тази програма извежда следното:

i : 1


++i : 2

i++ : 2


i : 3

--i : 2


i-- : 2

i : 1


Може да се види, че при префиксната форма се изпълнява операцията преди да се получи стойност и обратно за постфиксната форма. Тези са единствените опе­ратори (освен онези които включват присвояване) които имат странични ефек­ти. (Променят операнда, а не да му използват стойността.)

Инкрементният оператор е едно обяснение на името C++, а именно “една стъп­ка оттатък C.” В отдавнашен Java разговор Bill Joy (един от създателите), каза че “Java=C++--“ (C плюс плюс минус минус), имайки пред вид че Java е C++ с мах­нати ненужно трудни части и по този начин пмого по-прост език. Като напредвате в тази книга ще виждате че много неща са по-прости, но все пак Java не е толкова по-лесен от C++.


Оператори за отношение


Операторите за отношение произвеждат boolean резултат. Те изчисляват отно­ше­нието между стойностите на операндите. Израз за отношение произ­вежда true ако отношението го има и false ако го няма. Релационните опе­ра­тори са по-малко (<), по-голямо (>), по-малко или равно (<=), по-голямо или равно (>=), еквивалентност (==) и нееквивалентни (!=). Еквивалентността и неекви­ва­лент­ност­та работят с всички вградени типове, но други сравнения нама да работят с типа boolean.

Проверка на еквивалентност на обекти


Операторите за сравнение == и != също работят с всички обекти, но работата им често смущава начинаещия Java програмист. Ето пример:

//: c03:Equivalence.java


public class Equivalence {

public static void main(String[] args) {

Integer n1 = new Integer(47);

Integer n2 = new Integer(47);

System.out.println(n1 == n2);

System.out.println(n1 != n2);

}

} ///:~


Изразът System.out.println(n1 == n2) ще изведе резултата от boolean срав­не­ние­то в себе си. Сигурно изходът ще бъде true и после false, понеже Integer обек­тите са един и същ обект. Но докато съдържанието на обектите е едно и също, манипулаторите са различни и == и != сравняват манипулатори. Така че ре­зултатът е false и после true. Естествено, това изненадва хората отначало.

Ами ако искате да сравните съдържанието на обектите? Трябва да използвате спе­циален метод equals( ) който съществува за всички обекти (непримитивни, те работят чудесно с == и !=). Ето как се използва:

//: c03:EqualsMethod.java
public class EqualsMethod {

public static void main(String[] args) {

Integer n1 = new Integer(47);

Integer n2 = new Integer(47);

System.out.println(n1.equals(n2));

}

} ///:~



Резултатът ще бъде true, както може да се очаква. Е, това не е толкова просто както другото. Ако създадете собствен клас, както тук:

//: c03:EqualsMethod2.java


class Value {

int i;


}
public class EqualsMethod2 {

public static void main(String[] args) {

Value v1 = new Value();

Value v2 = new Value();

v1.i = v2.i = 100;

System.out.println(v1.equals(v2));

}

} ///:~


отново сте на изходно положение: резултатът е false. Това е защото дефолтвото поведение на equals( ) е да сравнява манипулатори. Така че докато не подтиснете equals( ) във вашия нов клас няма да получите исканото поведение. За нещастие няма да учите за подтискането до глава 7, но да сте пре­ду­предени за поведението на equals( ) може да ви спести малко скръб меж­ду­вре­менно.

Повечето от библиотечните класове на Java прилагат такъв equals( ) че се срав­ня­ва съдържанието на обектите, а не манипулаторите им.


Логически оператори


Логическите оператори AND (&&), OR (||) и NOT (!) произвеждат boolean стой­ност true или false на основата на логическото отношение на аргументите. Този при­мер използва оператори за отношение и логически оператори:

//: c03:Bool.java

// Relational and logical operators

import java.util.*;


public class Bool {

public static void main(String[] args) {

Random rand = new Random();

int i = rand.nextInt() % 100;

int j = rand.nextInt() % 100;

prt("i = " + i);

prt("j = " + j);

prt("i > j is " + (i > j));

prt("i < j is " + (i < j));

prt("i >= j is " + (i >= j));

prt("i <= j is " + (i <= j));

prt("i == j is " + (i == j));

prt("i != j is " + (i != j));
// Treating an int as a boolean is

// not legal Java

//! prt("i && j is " + (i && j));

//! prt("i || j is " + (i || j));

//! prt("!i is " + !i);
prt("(i < 10) && (j < 10) is "

+ ((i < 10) && (j < 10)) );

prt("(i < 10) || (j < 10) is "

+ ((i < 10) || (j < 10)) );

}

static void prt(String s) {



System.out.println(s);

}

} ///:~



Може да прилагате AND, OR и NOT за boolean стойности само. Не може да из­пол­звате не-boolean сякаш са boolean в логически израз както в C и C++. Може да се убедите сами като махнете коментарите започващи с //!. След­ва­щи­те от­но­­шения, обаче, произвеждат boolean стойности чрез използване на срав­не­ния, а после логически оператори с резултатите.

Изходът изглежда като това:

i = 85

j = 4


i > j is true

i < j is false

i >= j is true

i <= j is false

i == j is false

i != j is true

(i < 10) && (j < 10) is false

(i < 10) || (j < 10) is true

Забележете че boolean автоматично се превръща в подходящ текст там, където тряб­ва да се очаква String.

Може да заместите определението за int в горната програма с който и да е при­ми­тивен тип освен boolean. Имайте предвид, обаче, че сравняването на числа с пла­ваща запетая е много точно. Число което и с най-малка част се отличава от дру­го е все още “неравно.” И най-мъничко да е числото над нула то все още е не­нулево.


Феноменът short-circuiting


Когато се работи с логически оператори се сблъскваме с феномен наречен “short circuiting.” То означава, че логическият израз се изчислява само докато мо­же еднозначно да се определи неговата истинност или неверност. Поради то­ва може да не се изчисляват всички части на израза. Следващия пример де­мон­стри­ра short-circuiting:

//: c03:ShortCircuit.java

// Demonstrates short-circuiting behavior

// with logical operators.


public class ShortCircuit {

static boolean test1(int val) {

System.out.println("test1(" + val + ")");

System.out.println("result: " + (val < 1));

return val < 1;

}

static boolean test2(int val) {



System.out.println("test2(" + val + ")");

System.out.println("result: " + (val < 2));

return val < 2;

}

static boolean test3(int val) {



System.out.println("test3(" + val + ")");

System.out.println("result: " + (val < 3));

return val < 3;

}

public static void main(String[] args) {



if(test1(0) && test2(2) && test3(2))

System.out.println("expression is true");

else

System.out.println("expression is false");



}

} ///:~


Всеки тест изпълнява логически операции над аргументите и връща лъжа или ис­тина. Също се извежда информация какво е било викано. Тестовете се из­полз­ват в израза:

if(test1(0) && test2(2) && test3(2))

Бихте могли естествено да очаквате, че и трите теста ще се изпълнят, но изхо­дът сочи друго:

test1(0)


result: true

test2(2)


result: false

expression is false

Първият тест дава true резултат, така че изчислението продължава. Вторият тест обаче дава false резултат. Тъй като това означава че целият израз ще има стой­ностe false, защо да се изпълнява останалата част? Това би могло да бъде скъ­по. Причината за short-circuiting-а, фактически, е точно това; Може да се по­лу­чи подобрение на бързодействието, ако не се изчисляват всичките изрази.

Побитови оператори


Побитовите опрератори дават възможност да се манипулират отделните битове в примитивен тип. Изпълнява се операция на булевата алгебра над съответните би­тове.

Побитовите оператори идват от ориентацията на C към работата на ниско ниво; често трябваше да се пипа директно по хардуера и да се работи с битове свър­за­ни с него. Java беше оригинално създадена за интеграция в TV устройства, та­ка че тази ориентация към ниското ниво още имаше смисъл. Едва ли ще из­полз­ва­те побитовите оператори много, обаче.

Побитовия AND оператор (&) дава единица като изходен бит ако и двата вход­ни са единица; иначе дава нула. Побитовия OR оператор (|) дава единица ако поне единият е единица и дава нула само ако и двата входни бита са нула. По­би­товото ИЗКЛЮЧВАЩО ИЛИ, или XOR (^) дава единица само ако някой от входните битове е единица, но не и двата. Побитовото NOT (~, също на­ри­ча­но ком­плементираш оператор) е унарен оператор; той приема само един аргумент. (Всичките други побитови оператори са двуместни.) Побитовото NOT дава обрат­ния на входния бит – ако входният бит е бил нула, изходният е еди­ница.

За побитовите оператори се използват същите знаци, както и за логическите, за­то­ва е полезно да имате евристика, която да напомня за значенията: тъй като би­товете са “малки,” само един знак има при побитовите оператори.

Побитовите оператори могат да се комбинират със знака = за обединяване на опе­рацията и присвояването: &=, |= and ^= всичките са законни. (Тъй като ~ е уна­рен оператор той не може да се комбинира със знака = .)

Типът boolean се третира като еднобитова стойност така че е малко различен. Мо­же да изпълните побитово AND, OR и XOR, но не и побитово NOT (ве­ро­ят­но за да се предотврати смесването с логическото NOT). За boolean-ите по­би­то­ви­те оператори имат същоя ефект както логическите, само дето не правят short circuit. Побитовите оператори за boolean дават също XOR логически оператор, кой­то не е включен в списъка на “логическите” оператори. Не се допуска из­полз­ването на booleans в shift изрази, които са описани по-долу.


Shift оператори


Те също манипулират битове. Може да се използват само върху примитивни, ця­лостни типове. Операторът за изместване на ляво (<<) дава от операнда вля­во на знака резултат, чийто битове са изместени толкова, колкото е числото вдяс­но от операнда (слагайки нули в най-малките значещи битове). Right-shift опе­раторът със знак (>>) дава операнда вляво от знака си изместени битове кол­кото е числото след знака. Той (>>) използва разширение на знака: ако стой­ност­та е положителна, нули се слагат на мястото на най-значещите битове; ако стой­ността е отрицателна — единици. В Java също е добавен беззнаков right shift >>>, който използва разширение с нула: без значение какъв е знака нули се сла­гат на мястото на най-значещите битове.Този оператор не съществува в C и C++. Ако побитово изместите char, byte, или short, то ще бъде произведено в int преди да се направи отместването и резултатът ще бъде int. Само петте най-мал­ко значещи бита на дясната страна ще се използват. Това предпазва от от­мест­вания по-големи от броя на битовете в int. Ако оперирате с long, long ще бъ­де резултатът. Само шестте най-малозначни бита на дясната страна ще се из­пол­зват за да се предотвратят отмествания по-големи от броя на битовете в long. Обаче има проблем с беззнаковия right shift. Ако го използвате с byte или short може да не получите коректни резултати. (Това е прекъснато в Java 1.0 и Java 1.1.) Тези се произвеждат в int и се прави побитово отместване надясно, но раз­ширение с нула не се прави, така че получавате -1 в тези случаи. Следващия при­мер може да се използва за проверка на вашето приложение:

//: c03:URShift.java

// Тест на беззнаковия right shift
public class URShift {

public static void main(String[] args) {

int i = -1;

i >>>= 10;

System.out.println(i);

long l = -1;

l >>>= 10;

System.out.println(l);

short s = -1;

s >>>= 10;

System.out.println(s);

byte b = -1;

b >>>= 10;

System.out.println(b);

}

} ///:~


Шифтовете могат да се комбинират със знака за равенство (<<= или >>= или >>>=). Lvalue-то се замества с lvalue-то отместено колкото казва rvalue-то.

Ето пример който показва всичките оператори за битове:

//: c03:BitManipulation.java

// Using the bitwise operators

import java.util.*;
public class BitManipulation {

public static void main(String[] args) {

Random rand = new Random();

int i = rand.nextInt();

int j = rand.nextInt();

pBinInt("-1", -1);

pBinInt("+1", +1);

int maxpos = 2147483647;

pBinInt("maxpos", maxpos);

int maxneg = -2147483648;

pBinInt("maxneg", maxneg);

pBinInt("i", i);

pBinInt("~i", ~i);

pBinInt("-i", -i);

pBinInt("j", j);

pBinInt("i & j", i & j);

pBinInt("i | j", i | j);

pBinInt("i ^ j", i ^ j);

pBinInt("i << 5", i << 5);

pBinInt("i >> 5", i >> 5);

pBinInt("(~i) >> 5", (~i) >> 5);

pBinInt("i >>> 5", i >>> 5);

pBinInt("(~i) >>> 5", (~i) >>> 5);
long l = rand.nextLong();

long m = rand.nextLong();

pBinLong("-1L", -1L);

pBinLong("+1L", +1L);

long ll = 9223372036854775807L;

pBinLong("maxpos", ll);

long lln = -9223372036854775808L;

pBinLong("maxneg", lln);

pBinLong("l", l);

pBinLong("~l", ~l);

pBinLong("-l", -l);

pBinLong("m", m);

pBinLong("l & m", l & m);

pBinLong("l | m", l | m);

pBinLong("l ^ m", l ^ m);

pBinLong("l << 5", l << 5);

pBinLong("l >> 5", l >> 5);

pBinLong("(~l) >> 5", (~l) >> 5);

pBinLong("l >>> 5", l >>> 5);

pBinLong("(~l) >>> 5", (~l) >>> 5);

}

static void pBinInt(String s, int i) {



System.out.println(

s + ", int: " + i + ", binary: ");

System.out.print(" ");

for(int j = 31; j >=0; j--)

if(((1 << j) & i) != 0)

System.out.print("1");

else

System.out.print("0");



System.out.println();

}

static void pBinLong(String s, long l) {



System.out.println(

s + ", long: " + l + ", binary: ");

System.out.print(" ");

for(int i = 63; i >=0; i--)

if(((1L << i) & l) != 0)

System.out.print("1");

else

System.out.print("0");



System.out.println();

}

} ///:~



Двата метода накрая, pBinInt( ) и pBinLong( ) вземат int или long, респективно, и го извеждат в двоичен формат заедно с описателен стринг. Може да игно­ри­ра­те имплементацията им засега.

Ще забележите използването на System.out.print( ) вместо System.out.println( ). print( ) методът не започва нов ред на края, така че поз­во­ля­ва да изведете реда на парчета.

Освен че демонстрира ефекта от всичките побитови оператори за int и long, то­зи пример също показва мимималната, максималната, +1 и -1 стойност за int и long така че може да видите как изглеждат. Забележете че най-десният бит пред­ставя знака: 0 значи положително и 1 значи отрицателно. Изходат за int част­та изглежда подобно на това:

-1, int: -1, binary:

11111111111111111111111111111111
+1, int: 1, binary:

00000000000000000000000000000001

maxpos, int: 2147483647, binary:

01111111111111111111111111111111

maxneg, int: -2147483648, binary:

10000000000000000000000000000000

i, int: 59081716, binary:

00000011100001011000001111110100

~i, int: -59081717, binary:

11111100011110100111110000001011

-i, int: -59081716, binary:

11111100011110100111110000001100

j, int: 198850956, binary:

00001011110110100011100110001100

i & j, int: 58720644, binary:

00000011100000000000000110000100

i | j, int: 199212028, binary:

00001011110111111011101111111100

i ^ j, int: 140491384, binary:

00001000010111111011101001111000

i << 5, int: 1890614912, binary:

01110000101100000111111010000000

i >> 5, int: 1846303, binary:

00000000000111000010110000011111

(~i) >> 5, int: -1846304, binary:

11111111111000111101001111100000

i >>> 5, int: 1846303, binary:

00000000000111000010110000011111

(~i) >>> 5, int: 132371424, binary:

00000111111000111101001111100000

Двоичното представяне на числата е двоично комплементарно със знак.

Триместен if-else оператор


Този оператор е необичаен понеже има три операнда. Наистина е оператор, защото произвежда стойност, за разлика от обикновения if-else който ще видите в края на главата. Изразът има формата
boolean-exp ? value0 : value1
Ако boolean-exp даде true, value0 се изчислява и получената стойност е тази, която се връща от оператора. Ако boolean-exp е false, value1 се изчислява и стойността се връща от оператора.

Разбира се, бихте могли да използвате обикновен if-else оператор (описан по-късно), но триместният оператор е много по-сбит. Въпреки че C се гордее със сби­тостта си и триместният оператор може би е въведен частично заради ефек­тив­ността, ще внимавате като го изпилзвате всекидневно – лесно е да се напише не­чи­таем код.

Операторът за условие може да бъде използван заради страничния му ефект или заради стойността, която произвежда, но обикновено искаме стойността му, понеже тя е това, което го прави различен от if-else-то. Ето пример:

static int ternary(int i) {

return i < 10 ? i * 100 : i * 10;

}

Може да видите че този код е по-компактен от това, което ще се напише без три­местния оператор:



static int alternative(int i) {

if (i < 10)

return i * 100;

return i * 10;

}

Втората форма е по-лесна за разбиране и не изисква много повече писане. Така че бъдете сигурни в причините когато избирате триместния оператор.


Операторът запетая


Запетаята се използва в C и C++ не само като разделител в списъците от ар­гу­мен­ти на функции, но също като оператор за последователно изчисляване. Един­ственото място където запетаята-оператор се използва в Java е във for цик­лите, които ще се опишат по-късно в тази глава.

String операторът +


Един е операторът със специално използване в Java: + операторът може да се из­ползва за конкатениране на стрингове, както вече знаете. Това изглежда есте­стве­но приложение на знака + въпреки че не се вмества в традиционния начин по който се използва + . Тази възможност изглеждаше добра идея в C++, така че натоварването на оператори с много възможности беше добавено в C++ за да позволи на C++ да добавя значения на почти всеки оператор. За нещастие, operator overloading-ът комбиниран с някои други ограничения в C++ стана твър­де сложен за програмистите да го вграждат в своите класове. Въпреки че operator overloading-ът би бил много по-лесен за прилагане в Java отколкото бе­ше в C++, тази черта беше счетена за твърде сложна, така че Java про­гра­ми­сти­те не могат да пренатоварват оператори както C++ програмистите могат.

Използването на String-овия + има нещо интересно в поведението си. Ако израз започва със String всички следващи оператори трябва да бъдат String-ове:

int x = 0, y = 1, z = 2;
String sString = "x, y, z ";
System.out.println(sString + x + y + z);

Тук Java компилаторът ще обърне x, y, и z в техните String представяния, на­мес­то да ги събере първо. Ако обаче напишете:

System.out.println(x + sString);

По-раншните версии на Java ще сигнализират за грешка. (Новите версии, обаче, ще обърнат x в String.) Така че ако слагате заедно String (използвайки по-ран­шна версия на Java) със събиране, осигурете първия елемент да е String (или на­низ от знаци, заграден в кавички, който компилаторът разпознава като String).


Обичайни капани при използването на операторите


Един от тях е опитът да се разминете без скоби когато има и най-малкото съм­не­ние относно реда за използване на операторите. Това е в сила и в Java.

Изключително широко допускана грешка в C и C++ изглежда като това:

while(x = y) {

// ....


}

Програмистът иска да провери за равенство (==) а не да прави присвояване. В C и C++ резултатът от присвояването винаги ще е true ако y не е нулаи най-ве­ро­ятно ще се получи безкраен цикъл. В Java резултатът оттози израз не е boolean, компилаторът очаква boolean и няма да превърне int, така че удобно ще даде грешка при компилирането, преди да се опитате да изпълнявате про­гра­мата. Така че капанът го няма в Java. (Единствения път когато няма да по­лу­чите грешка по време на компилация е когато x и y са boolean, в който случай x = y е правилен израз и в горния случай — вероятно грешка.)

Подобен проблем в C и C++ има при използването на побитови AND и OR вме­сто логически. Побитовите AND и OR използват един от знаците (& или |) до­ка­то логическите AND и OR използват два (&& и ||). Точно както с = и = =, лес­но е да се напише един знак вместо два. В Java компилаторът отново пре­дот­вра­тява това, понеже не позволява безцеремонно да използвате един знак къ­де­то са нужни два.

Casting оператори


Думата cast е използвана в смисъл на “слагане в калъп.” Java автоматично ще про­мени типа на една променлива в друг щом е необходимо. Например ако при­своявате цяла стойност на променлива с плаваща запетая компилаторът авто­матично ще превърне int във float. Casting-ът позволява подобни пре­връ­ща­ния да се направят явно, а също и да се застави компилаторът да ги направи в някои случаи.

За да направите каст, сложете жерания даннов тип (заедно с всички моди­фи­ка­то­ри) в скоби отляво на каквато и да е стойност. Ето пример:

void casts() {

int i = 200;

long l = (long)i;

long l2 = (long)200;

}

Както се вижда може да се направи кастинг както на числова стойност, така и на променлива. И в двата показани случая, обаче, кастингът е излишен, защото ком­пилаторът и автоматично би превърнал int стойността в long когато е необ­хо­димо. Все пак може да сложите каст за да подчертаете или пък за да стане ко­дът ви по-разбираем. В други случаи кастингът е нужен просто за да се ком­пи­ли­ра кода.



В C и C++ кастингът може да създаде главоболия. В Java кастингът е безопасен с изключение на случая когато се прави т. нар. стесняващо преобразуване (ко­га­то първоначалният тип може да съдържа повече информация от този, към кой­то се преобразува) когато може да се загуби информация. В този случай компи­латорът иска вие да направите кастинга, сякаш казвайки “това може да бъ­де опасно – ако искате да го направя, заявете го явно.” С разширяващото пре­връщане не е необходим явен каст, защото типът в който се превръща може да събере повече информация от първоначалния и информация не може да се за­губи.

Java позволява кастинг на всякакви примитивни типове към всякакви при­ми­тив­ни типове, освен boolean, с който не са позволени кастове въобще. Кла­со­ви­те типове не позволяват кастинг. За да се превърне един в друг трябват спе­циал­ни методи. (String е специален случай и ще видите по-късно в книгата, че мо­же да се прави кастинг на обекти в рамките на семейство типове; Oak може да се превърне в Tree и обратно, но не във външен клас като Rock.) (Дъб, Дърво и Скала са имената на класовете - б.пр.)


Литерали


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

//: c03:Literals.java


class Literals {

char c = 0xffff; // max char hex value

byte b = 0x7f; // max byte hex value

short s = 0x7fff; // max short hex value

int i1 = 0x2f; // Hexadecimal (lowercase)

int i2 = 0X2F; // Hexadecimal (uppercase)

int i3 = 0177; // Octal (leading zero)

// Hex and Oct also work with long.

long n1 = 200L; // long suffix

long n2 = 200l; // long suffix

long n3 = 200;

//! long l6(200); // not allowed

float f1 = 1;

float f2 = 1F; // float suffix

float f3 = 1f; // float suffix

float f4 = 1e-45f; // 10 to the power

float f5 = 1e+9f; // float suffix

double d1 = 1d; // double suffix

double d2 = 1D; // double suffix

double d3 = 47e47d; // 10 to the power

} ///:~

Шестнадесетичен (основа 16), който работи с всички цели типове, се означава с во­дещи 0x или 0X следвани от 0–9 и a–f големи или малки. Ако се опитате да инициа­лизирате променлива със стойност по-голяма отколкото тя може да съ­дър­жа (без значение в какво представяне е), компилаторът ще издаде съоб­ще­ние за грешка. Забележете в горния пример максималните шестнадесетично за­пи­сани стойности за char, byte, и short. Ако ги надминете, компилаторът авто­ма­тично ще направи int и ще ви каже, че трябва стесняващо превръщане за при­своя­ването. Ще научите, че сте престъпили линията.



Осмичен (основа 8) се означава с водеща нула и цифрите 0-7. Няма двоични литерали в C, C++ и Java.

Знак зад литерала показва типа. Голямо или малко L значи long, голямо или малко F значи float и малко или голямо D значи double.

Експонентите се означават по начин, който винаги съм намирал за малко слис­ващ: 1.39 e-47f. В науката и инженерството, ‘e’ означава основата на на­ту­рал­ни­те логаритми, приблизително 2.718. (По-точна double стойност е достъпна в Java като Math.E.) Използва се в експонентни изрази като 1.39 x e-47, което зна­чи 1.39 x 2.718-47. Когато FORTRAN обаче беше измислен реши се, че e есте­стве­но ще значи “десет на степен,” което е лош избор понеже FORTRAN беше проек­тиран за научни и инженерни цели и човек би трябвало да очаква, че про­ек­тантите ще са чувствителни към такъв род двусмислия.1 Както и да е, този оби­чай беше следван и в C, C++ и сега в Java. Така че ако сте свикнали да мис­ли­те в термините на e като основа на натуралните логаритми, ще трябва да на­пра­вите душевна транслация когато срещнете 1.39 e-47f в Java; то значи 1.39 x 10-47.

Забележете че знакът на края не е необходим, когато компилаторът може да разбере тчния тип. С

long n3 = 200;

няма двусмислие, така че L след 200 би било излишно. Обаче с

float f4 = 1e-47f; // 10 to the power

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


Разширяване


Ще откриете, че ако изпълнявате аритметични или побитови операции над ня­ка­кви типове по-малки от int (тоест: char, byte или short), стойностите ще бъ­дат разширени до int преди изпълнение на операциите и резултантната стой­ност ще бъде от тип int. Така че ако искате да присвоите обратно на изходния тип трябва да направите кастинг. (И, ако присвоявате на по-малък тип, би могла да се загуби информация.) Изобщо, най-големият тип който участва в израз опре­деля типа на резултата от този израз; ако умножите float и double, ре­зул­та­тът ще е double; ако съберете int с long резултатът ще бъде long.

Java няма “sizeof”


В C и C++ операторът sizeof( ) удовлетворява специфична нужда: дава колко байта се алокират за даден даннов тип. Най-непреодолимо sizeof( ) трява в C и C++ заради преносимостта. Различните типоме данни могат да бъдат с раз­лич­ни дължини на различните машини, така че програмистът трябва да узнае в ня­кои случаи колко са дълги. Например един компютър може да запомня целите в 32 бита, докато друг в 16. Програмите могат да запомнят по-големи стойности на цялото в първата машина. Както може да си представите, преносимостта е огром­но главоболие за C и C++ програмистите.

Java не се нуждае от sizeof( ) оператора за тази цел, понеже всичките типове данни са еднакви на всички машини. Не е необходимо да се мисли за прено­си­мост­та на това ниво — тя е впроектирана в езика.


Приоритетът по нов начин


Във връзка с оплакването ми от трудното запомняне на приоритета на един от моите семинари един студент предложи мнемоника, която същевременно е коментар: “Ulcer Addicts Really Like C A lot.”

Mnemonic

Operator type

Operators

Ulcer

Unary

+ - ++ – [[ rest…]]

Addicts

Arithmetic (and shift)

* / % + - << >>

Really

Relational

> < >= <= == !=

Like

Logical (and bitwise)

&& || & | ^

C

Conditional (ternary)

A > B ? X : Y

A Lot

Assignment

= (and compound assignment like *=)

Разбира се, с шифт и битовите оператори разхвърляни по таблицата тя не е пер­фек­т­на, но за не-битовите оператори работи.

Резюме на операторите


Следващият пример показва кои примитивни типове данни могат да се из­полз­ват с даден оператор. Основно това е един и същ пример повтарян пак и пак, но с различни типове данни. Файлът ще се компилира без грешка, понеже редовете кои­то биха предизвикали грешка са изкоментирани с //!.

//: c03:AllOps.java

// Tests all the operators on all the

// primitive data types to show which

// ones are accepted by the Java compiler.
class AllOps {

// To accept the results of a boolean test:

void f(boolean b) {}

void boolTest(boolean x, boolean y) {

// Arithmetic operators:

//! x = x * y;

//! x = x / y;

//! x = x % y;

//! x = x + y;

//! x = x - y;

//! x++;

//! x--;


//! x = +y;

//! x = -y;

// Relational and logical:

//! f(x > y);

//! f(x >= y);

//! f(x < y);

//! f(x <= y);

f(x == y);

f(x != y);

f(!y);


x = x && y;

x = x || y;

// Bitwise operators:

//! x = ~y;

x = x & y;

x = x | y;

x = x ^ y;

//! x = x << 1;

//! x = x >> 1;

//! x = x >>> 1;

// Compound assignment:

//! x += y;

//! x -= y;

//! x *= y;

//! x /= y;

//! x %= y;

//! x <<= 1;

//! x >>= 1;

//! x >>>= 1;

x &= y;


x ^= y;

x |= y;


// Casting:

//! char c = (char)x;

//! byte B = (byte)x;

//! short s = (short)x;

//! int i = (int)x;

//! long l = (long)x;

//! float f = (float)x;

//! double d = (double)x;

}

void charTest(char x, char y) {



// Arithmetic operators:

x = (char)(x * y);

x = (char)(x / y);

x = (char)(x % y);

x = (char)(x + y);

x = (char)(x - y);

x++;

x--;


x = (char)+y;

x = (char)-y;

// Relational and logical:

f(x > y);

f(x >= y);

f(x < y);

f(x <= y);

f(x == y);

f(x != y);

//! f(!x);

//! f(x && y);

//! f(x || y);

// Bitwise operators:

x= (char)~y;

x = (char)(x & y);

x = (char)(x | y);

x = (char)(x ^ y);

x = (char)(x << 1);

x = (char)(x >> 1);

x = (char)(x >>> 1);

// Compound assignment:

x += y;


x -= y;

x *= y;


x /= y;

x %= y;


x <<= 1;

x >>= 1;


x >>>= 1;

x &= y;


x ^= y;

x |= y;


// Casting:

//! boolean b = (boolean)x;

byte B = (byte)x;

short s = (short)x;

int i = (int)x;

long l = (long)x;

float f = (float)x;

double d = (double)x;

}

void byteTest(byte x, byte y) {



// Arithmetic operators:

x = (byte)(x* y);

x = (byte)(x / y);

x = (byte)(x % y);

x = (byte)(x + y);

x = (byte)(x - y);

x++;

x--;


x = (byte)+ y;

x = (byte)- y;

// Relational and logical:

f(x > y);

f(x >= y);

f(x < y);

f(x <= y);

f(x == y);

f(x != y);

//! f(!x);

//! f(x && y);

//! f(x || y);

// Bitwise operators:

x = (byte)~y;

x = (byte)(x & y);

x = (byte)(x | y);

x = (byte)(x ^ y);

x = (byte)(x << 1);

x = (byte)(x >> 1);

x = (byte)(x >>> 1);

// Compound assignment:

x += y;


x -= y;

x *= y;


x /= y;

x %= y;


x <<= 1;

x >>= 1;


x >>>= 1;

x &= y;


x ^= y;

x |= y;


// Casting:

//! boolean b = (boolean)x;

char c = (char)x;

short s = (short)x;

int i = (int)x;

long l = (long)x;

float f = (float)x;

double d = (double)x;

}

void shortTest(short x, short y) {



// Arithmetic operators:

x = (short)(x * y);

x = (short)(x / y);

x = (short)(x % y);

x = (short)(x + y);

x = (short)(x - y);

x++;

x--;


x = (short)+y;

x = (short)-y;

// Relational and logical:

f(x > y);

f(x >= y);

f(x < y);

f(x <= y);

f(x == y);

f(x != y);

//! f(!x);

//! f(x && y);

//! f(x || y);

// Bitwise operators:

x = (short)~y;

x = (short)(x & y);

x = (short)(x | y);

x = (short)(x ^ y);

x = (short)(x << 1);

x = (short)(x >> 1);

x = (short)(x >>> 1);

// Compound assignment:

x += y;


x -= y;

x *= y;


x /= y;

x %= y;


x <<= 1;

x >>= 1;


x >>>= 1;

x &= y;


x ^= y;

x |= y;


// Casting:

//! boolean b = (boolean)x;

char c = (char)x;

byte B = (byte)x;

int i = (int)x;

long l = (long)x;

float f = (float)x;

double d = (double)x;

}

void intTest(int x, int y) {



// Arithmetic operators:

x = x * y;

x = x / y;

x = x % y;

x = x + y;

x = x - y;

x++;

x--;


x = +y;

x = -y;


// Relational and logical:

f(x > y);

f(x >= y);

f(x < y);

f(x <= y);

f(x == y);

f(x != y);

//! f(!x);

//! f(x && y);

//! f(x || y);

// Bitwise operators:

x = ~y;


x = x & y;

x = x | y;

x = x ^ y;

x = x << 1;

x = x >> 1;

x = x >>> 1;

// Compound assignment:

x += y;


x -= y;

x *= y;


x /= y;

x %= y;


x <<= 1;

x >>= 1;


x >>>= 1;

x &= y;


x ^= y;

x |= y;


// Casting:

//! boolean b = (boolean)x;

char c = (char)x;

byte B = (byte)x;

short s = (short)x;

long l = (long)x;

float f = (float)x;

double d = (double)x;

}

void longTest(long x, long y) {



// Arithmetic operators:

x = x * y;

x = x / y;

x = x % y;

x = x + y;

x = x - y;

x++;

x--;


x = +y;

x = -y;


// Relational and logical:

f(x > y);

f(x >= y);

f(x < y);

f(x <= y);

f(x == y);

f(x != y);

//! f(!x);

//! f(x && y);

//! f(x || y);

// Bitwise operators:

x = ~y;


x = x & y;

x = x | y;

x = x ^ y;

x = x << 1;

x = x >> 1;

x = x >>> 1;

// Compound assignment:

x += y;


x -= y;

x *= y;


x /= y;

x %= y;


x <<= 1;

x >>= 1;


x >>>= 1;

x &= y;


x ^= y;

x |= y;


// Casting:

//! boolean b = (boolean)x;

char c = (char)x;

byte B = (byte)x;

short s = (short)x;

int i = (int)x;

float f = (float)x;

double d = (double)x;

}

void floatTest(float x, float y) {



// Arithmetic operators:

x = x * y;

x = x / y;

x = x % y;

x = x + y;

x = x - y;

x++;

x--;


x = +y;

x = -y;


// Relational and logical:

f(x > y);

f(x >= y);

f(x < y);

f(x <= y);

f(x == y);

f(x != y);

//! f(!x);

//! f(x && y);

//! f(x || y);

// Bitwise operators:

//! x = ~y;

//! x = x & y;

//! x = x | y;

//! x = x ^ y;

//! x = x << 1;

//! x = x >> 1;

//! x = x >>> 1;

// Compound assignment:

x += y;


x -= y;

x *= y;


x /= y;

x %= y;


//! x <<= 1;

//! x >>= 1;

//! x >>>= 1;

//! x &= y;

//! x ^= y;

//! x |= y;

// Casting:

//! boolean b = (boolean)x;

char c = (char)x;

byte B = (byte)x;

short s = (short)x;

int i = (int)x;

long l = (long)x;

double d = (double)x;

}

void doubleTest(double x, double y) {



// Arithmetic operators:

x = x * y;

x = x / y;

x = x % y;

x = x + y;

x = x - y;

x++;

x--;


x = +y;

x = -y;


// Relational and logical:

f(x > y);

f(x >= y);

f(x < y);

f(x <= y);

f(x == y);

f(x != y);

//! f(!x);

//! f(x && y);

//! f(x || y);

// Bitwise operators:

//! x = ~y;

//! x = x & y;

//! x = x | y;

//! x = x ^ y;

//! x = x << 1;

//! x = x >> 1;

//! x = x >>> 1;

// Compound assignment:

x += y;


x -= y;

x *= y;


x /= y;

x %= y;


//! x <<= 1;

//! x >>= 1;

//! x >>>= 1;

//! x &= y;

//! x ^= y;

//! x |= y;

// Casting:

//! boolean b = (boolean)x;

char c = (char)x;

byte B = (byte)x;

short s = (short)x;

int i = (int)x;

long l = (long)x;

float f = (float)x;

}

} ///:~


Забележете че boolean е твърде ограничен. Може да му присвоявате стой­но­сти­те true и false и може да проверявате за истинност, но не може да събирате и да пра­вите каквито и да е аритметични операции изобщо.

В char, byte и short може да видите разширяването при аритм. операции. Всяка арит­метична операция върху тях завършва с int резултат, който трябва явно да бъ­де превърнат в оригиналния тип (стесняваща конверсия която може да до­ве­де до загуба на информация) за да може да се присвои на същия тип. С int стой­но­стите, обаче, няма нужда от кастинг, понеже всичко вече е int. Не бъдете упоя­вани от мисълта че всичко е безопасно, все пак. Ако умножите две до­ста­тъч­но големи int ще препълните резултата. Следващия пример демонстрира то­ва:

//: c03:Overflow.java

// Surprise! Java lets you overflow.


public class Overflow {

public static void main(String[] args) {

int big = 0x7fffffff; // max int value

prt("big = " + big);

int bigger = big * 4;

prt("bigger = " + bigger);

}

static void prt(String s) {



System.out.println(s);

}

} ///:~



Извежда се това:

big = 2147483647

bigger = -4

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

Съставните присвоявания не изискват кастинг за char, byte и short, заже и да из­пълняват разширение както за аритметичните операции. От друга страна, лип­сата на каст операторът опростява кода.

Може да видите, че с изключение на boolean всеки примитивен тип може да бъ­де превърнат във всеки друг примитивен тип. Напомням, че трябва да вни­ма­ва­те за стесняващи превръщания, при които може да се загуби информация.





Сподели с приятели:
1   ...   18   19   20   21   22   23   24   25   ...   73




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

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