Неофициален стандарт в Java е да се пише с главна буква първото име на класа. Ако името се състои от няколко думи, те се долепят (не се използва подчертаващо тире за съединяване на думите) и всяка дума е с главна биква като в:
class AllTheColorsOfTheRainbow { // ...
За почти всичко друго: методи, полета (член-променливи) и имена на манипулатори приетият стил е точно както за класовете освен че първата буква на идентификатора е малка. Например:
class AllTheColorsOfTheRainbow {
int anIntegerRepresentingColors;
void changeTheHueOfTheColor(int newHue) {
// ...
}
// ...
}
Разбира се, ще помните, че потребителят ще трябва да пише всички тези дълги имена и да бъдете милостиви.
Резюме
В тази глава се запознахте достатъчно с Java програмирането за да може да напишете проста програма, а също получихте общ поглед върху езика и някои от неговите основни идеи. Примерите до тук обаче бяха от вида “направи това, после направи нещо друго.”Ако искате програмата да прави избор, както “ако резултатът е червено, прави това, ако не е, тогава прави нещо друго”? Поддръжката на Java за тези съществени неща ще видите в следващата глава.
Упражнения -
Следвайки първия пример в тази глава създайте “Hello, World” която просто извежда това на екрана. Нужен е само един метод във вашия клас ( “main” , който се изпълнява когато програмата стартира). Не забравяйте да го направите static и да вмъкнете списък аргументи, макар и да не го използвате по-нататък. Компилирайте програмата с javac aи я стартирайте с java.
-
Напишете програма която да извежда трите аргумента на командния ред.
-
Намерете кода за втора версия на Property.java, който е прост пример за документационен коментар. Изпълнете javadoc с файла и разгледайте резултатите с броузера си.
-
Вземете програмата от упражнение 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 всеки примитивен тип може да бъде превърнат във всеки друг примитивен тип. Напомням, че трябва да внимавате за стесняващи превръщания, при които може да се загуби информация.
Сподели с приятели: |