Глава 3. Оператори и изрази

Автор

Лъчезар Божков

В тази тема...

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

Съдържание


Оператори

Във всички езици за програмиране се използват оператори, чрез които се изразяват някакви действия върху данни. Нека разгледаме операторите в Java и ви покажем за какво служат и как се използват.

Какво е оператор?

След като научихте как да декларирате и назначавате стойности на про­менливи, вероятно искате да извършите операции с тях. За целта ще се запознаем с операторите. Операторите позволят манипулиране на прими­тивни типове данни. Те са символи, които извършат специфични операции над един, два или три операнда и връщат резултат от извършените операции. Пример за операторите са символите за събиране, изваждане, делене и умножение в математиката (+, - , /, *) и операциите, които те извършват върху операндите, над които са приложени.

Операторите в Java

Операторите в Java могат да бъдат разделени в няколко различни категории:

-        Аритметични – също както в математиката, служат за извършване на прости математически операции.

-        Оператори за присвояване – позволяват присвояването на стойност на променливите.

-        Оператори за сравнение – дават възможност за сравнение на два литерала и/или променливи.

-        Логически оператори – оператори за работа с логически типове данни.

-        Побитови оператори – използват се за извършване на операции върху двоичното представяне на числови данни.

-        Оператори за преобразуване на типовете – позволяват преобразу­ването на данни от един тип в друг.

Категории оператори

Следва списък с операторите, разделени по категории:

Категория

Оператори

аритметични

-, +, *, /, %, ++, --

логически

&&, ||, !, ^

побитови

&, |, ^, ~, <<, >>, >>>

за сравнение

==, !=, >, <, >=, <=

за присвояване

=, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=, >>>=

съединяване на символни низове

+

за работа с типове

(type), instanceof

други

., new, (), [], ?:

Има три основни групи оператори разделени според, това колко аргумента приемат.

Оператори според броя аргументи

Следва списък на групите оператори, според броя аргументите, които приемат:

Тип оператор

Брой на аргументите (операндите)

едноаргументни (unary)

приема един аргумент

двуаргументни (binary)

приема два аргумента

триаргументни (ternary)

приема три аргумента

Всички двуаргументни оператори са ляво-асоциативни, означава, че изра­зите, в които участват се изчисляват от ляво на дясно, освен операторите за назначаване на стойности. Всички оператори за присвояване на стойности и условният оператор (:?) са дясно-асоциативни (изчисляват се от дясно на ляво).

Някой оператори в Java извършват различни операции, когато се приложат с различен тип данни. Пример за това е операторът +. Когато се използва с числени типове данни (int, long, float и др.), операторът извършва операцията математическо събиране. Когато обаче използваме оператора със символни низове, той слепва съдържанието на двете про­менливи / литерали и връща новополучения низ.

Оператори – пример

Ето един пример за използване на оператори:

int z = 4 + 8;

System.out.println(z); // 12

 

String firstName = "Lachezar";

String lastName = "Bozhkov";

 

// Do not forget the interval between them

String fullName = firstName + " " + lastName;

System.out.println(fullName); // Lachezar Bozhkov

Примерът показва как при използването на + с числа операторът връща числова стойност, а при използването му с низове връща низ.

Приоритет на операторите в Java

Някои оператори имат приоритет над други. Операторите с по-висок приоритет се изчисляват преди тези с по-нисък. Операторът () служи за промяна на приоритета на операторите и се изчислява пръв, също както в математиката.

В таблицата са показани приоритетите на операторите в Java:

Приоритет

Оператори

най-висок

++, -- (като суфикс), new, (type)

 

++, -- (като префикс), +, - (едноаргументни), !, ~

 

*, /, %

 

+ (свързване на низове)

 

+, -

 

<<, >>, >>>

 

<, >, <=, >=, instanceof

 

==, !=

 

&, ^, |

 

&&

 

||

 

?:

най-нисък

=, *=, /=, %=, +=, -=, <<=, >>=, >>>= &=, ^=, |=

Операторите, намиращи се по-нагоре в таблицата имат по-висок прио­ритет от тези, намиращи се след тях, съответно имат предимство при изчисляването на съответния израз. За да се предефинира приоритета може да се използват скоби.

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

// Ambiguous
x + y / 100 
 

// Unambiguous, recommended

x + (y / 100)

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

Аритметични оператори

Аритметичните оператори +, -, * са същите като в математика. Те извършват събиране, изваждане и умножение върху числови стойности. Когато се използва операторът за деление / с целочислен тип (integer), върнатият резултат е отново целочислен (без закръгляне). За да се вземе остатъкът от делене на цели числа се използва оператора %. Операторът за увеличаване с единица (increment) ++ добавя единица към стойността на променливата, съответно операторът -- (decrement) изважда единица от стойността.

Когато използваме операторите ++ и -- като суфикс (поставяме опера­торът непосредствено пред променливата) първо се пресмята новата стой­ност, а после се връща резултата и програмата продължава с решението на израза, докато при използването на операторите като постфикс (поста­вяме оператора непосредствено след променливата) първо се връща ори­гиналната стойност на операнда, а после се добавя или изважда единица към нея.

Аритметични оператори – примери

Ето няколко примера за аритметични оператори:

int squarePerimeter = 17;

double squareSide = squarePerimeter / 4.0;

double squareArea = squareSide * squareSide;

System.out.println(squareSide); // 4.25

System.out.println(squareArea); // 18.0625

 

int a = 5;

int b = 4;

System.out.println(a + b);      // 9

System.out.println(a + b++);    // 9

System.out.println(a + b);      // 10

System.out.println(a + (++b));  // 11

System.out.println(a + b);      // 11

System.out.println(14 / a);     // 2

System.out.println(14 % a);     // 4

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

Логическите оператори приемат булеви стойности и връщат булев резултат (true или false). Основните булеви оператори са И (&&), ИЛИ (||), изключващо ИЛИ (^) и логическо отрицание (!).

Следва таблица с логическите оператори в Java и операциите, които те извършват:

x

y

!x

x && y

x || y

x ^ y

true

true

false

true

true

false

true

false

false

false

true

true

false

true

true

false

true

true

false

false

true

false

false

false

От таблицата, както и от следващия пример става ясно, че логическото "И" връща истина, само тогава, когато и двете променливи съдържат истина. Логическото "ИЛИ" връща истина, когато поне един от операндите е истина. Операторът за логическо отрицание сменя стойността на аргумента. Например, ако операндът е имала стойност true и приложим оператор за отрицание, новата стойност ще бъде false. Операторът за отрицание се слага пред аргумента. Изключващото ИЛИ връща резултат true, когато само един от двата операнда има стойност true. Ако двата операнда имат различни стойности изключващото ИЛИ ще върне резултат true, ако имат еднакви стойности ще върне false.

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

Ето един пример за използване на логически оператори. Резултатът от действието на отделните логически оператори е даден като коментари:

boolean a = true;

boolean b = false;

System.out.println(a && b);         // false

System.out.println(a || b);         // true

System.out.println(!b);             // true

System.out.println(b || true);      // true

System.out.println((5>7) ^ (a==b)); // false

Закони на Де Морган

Логическите операции се подчиняват на законите на Де Морган от математическата логика:

!(a && b) == (!a || !b)

!(a || b) == (!a && !b)

Първият закон твърди, че отрицанието на конюнкцията (логическо и) на две съждения е равна на дизюнкцията (логическо или) на техните отрицания.

Вторият закон твърди, че отрицанието на дизюнкцията на две съждения е равна на конюнкцията на техните отрицания.

Оператор за съединяване на низове

Оператора + се използва за съединяване на символни низове (String). Това, което прави операторът е просто слепя два или повече низа и връща резултата като един нов низ. Ако поне един от аргументите в израза е от тип String, и има други операнди, които не са от тип String, то те автоматично ще бъдат преобразувана към тип String.

Оператор за съединяване на низове – пример

Ето един пример, в който съединяваме няколко символни низа:

String first = "Star";

String second = "Craft";

System.out.println(first + second); // StarCraft

String output = first + second + " ";

int number = 2;

System.out.println(output + number);

// StarCraft 2

В примера инициализираме две променливи от тип String и им задаваме стойности. На третия ред съединяваме двата стринга и подаваме резул­тата на метода println(), за да го отпечата на конзолата. На следващия ред съединяваме двата низа и добавяме интервал накрая. Върнатия резултат записваме в променлива наречена output. На последния ред съединяваме съдържанието на низа output с числото 2 (съдържанието на променливата number) и подаваме резултата отново за отпечатване. Върнатият резултат ще бъде автоматично преобразуван към тип String, защото двете променливи са от различен тип.

clip_image001

Конкатенацията (слепването на два низа) на стрингове е бавна операция и трябва да се използва внимателно. Препоръчва се използването на класовете StringBuilder или StringBuffer при нужда от итеративни (повтарящи се) операции върху символни низове.

В главата "Символни низове" ще обясним в детайли защо при операции над символни низове, изпълнени в цикъл, задължително се използват гореспоменатите класове.

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

Побитов оператор означава оператор, който действа над двоичното представяне на числовите типове. В компютрите всички данни и в част­ност числовите данни се представят като поредица от нули и единици. За целта се използва двоичната бройна система. Например числото 55 в двоична бройна система се представя като 00110111.

Двоичното представяне на данните е удобно, тъй като нулата и единицата в електро­никата могат да се реализират чрез логически схеми, в които нулата се представя като "няма ток" или примерно с напрежение -5V, а единицата се представя като "има ток" или  примерно с напрежение +5V.

Ще разгледаме в дълбочина двоичната бройна система в главата "Бройни системи", а за момента можем да си представяме, че числата в компют­рите се представят като нули и единици и че побитовите опера­тори служат за анализиране и промяна на точно тези нули и единици.

Побитовите оператори много приличат на логическите. Всъщност можем да си представим, че логическите и побитовите оператори извършат едно и също нещо, но върху различни типове променливи. Логическите опера­тори работят над стойностите true и false (булеви стойности), докато побитовите работят над числови стойности и се прилагат побитово, има се предвид 0 и 1 (битове). Също както при логическите оператори, тук има оператор за побитово "И" (&), побитово "ИЛИ" (|), побитово отрицание (~) и изключващо "ИЛИ" (^).

Побитови оператори и тяхното действие

Можем да видим символите на операторите и резултата от тяхната употреба в следната таблица:

x

y

~x

x & y

x | y

x ^ y

1

1

0

1

1

0

1

0

0

0

1

1

0

1

1

0

1

1

0

0

1

0

0

0

Както виждаме побитовите и логическите оператори си приличат много. Разликата в изписването на “И" и “ИЛИ" е че при логическите оператори се пише двойни амперсанд или права черта, а при битовите единични. Побитовият и логическият оператор за изключващо или е един и същ “^". За логическо отрицание се използва “!", докато за побитово отрицание се използва "~".

Има още два побитови оператора, които ги няма при логическите. Това са побитовото преместване в ляво (<<) и побитовото преместване в дясно (>>). Използвани над числови стойности те преместват всички битове на стойността, съответно на ляво или надясно. Операторите за преместване се използват по следния начин: от ляво на оператора слагаме промен­ливата (операндът), над която ще извършим операцията, вдясно на оператора поставяме число, символизиращо, с колко знака искаме да отместим битовете. 3 << 2 означава, че искаме да преместим два пъти наляво битовете на числото 3. Числото 3 представено в битове изглежда така: “0000 0011", когато го преместим два пъти в ляво неговата битова стойност ще изглежда така: “0000 1100", а на тези битове отговаря числото 12. Ако се вгледаме в примера можем да забележим, че реално сме умножили числото по 4. Самото побитово преместване може да се представи като умножение (побитово преместване вляво) или делене (преместване в дясно) на променливата на числото 2. Това явление е следствие от природата на двоичната бройна система.

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

Ето един пример за работа с побитови оператори. Двоичното представяне на числата и резултатите от различните оператори е дадено в коментари:

short a = 3;                // 0000 0011 = 3

short b = 5;                // 0000 0101 = 5

 

System.out.println( a | b); // 0000 0111 = 7

System.out.println( a & b); // 0000 0001 = 1

System.out.println( a ^ b); // 0000 0110 = 6

System.out.println(~a & b); // 0000 0100 = 4

System.out.println(a << 1); // 0000 0110 = 6

System.out.println(a << 2); // 0000 1100 = 12

System.out.println(a >> 1); // 0000 0001 = 1

В примера първо създаваме и инициализираме стойностите на две променливи a и b. По нататък в примера изкарваме на конзолата, резултатите от побитовите операции над двете променливи. Първата операция, която прилагаме е или. От примера се вижда, че за всички позиции, на които е имало 1 в променливите a и b, има 1 и в резултата. Втората операция е “И". Резултата от операцията съдържа 1 само в най-десния бит, защото и двете променливи имат 1 само в най-десния си бит. Изключващо “ИЛИ" връща единици само там, където a и b имат различни стойности на битовете. По-надолу можем да видим и резултатите от логическо отрицание и побитово преместване.

Оператори за сравнение

Операторите за сравнение в Java се използват за сравняване на две или повече операнди. Java поддържа шест оператора за сравнение:

-     по-голямо (>)

-     по-малко (<)

-     по-голямо или равно (>=)

-     по-малко или равно (<=)

-     оператора за равенство (==)

-     различие (!=)

Всички оператори за сравнение са двуаргументни (приемат два операнда), а върнатият от тях резултат е булев (true или false). Операторите за сравнение имат по-малък приоритет от аритметичните, но са с по-голям от операторите за присвояване на стойност.

Оператори за сравнение – пример

Следва примерна програма, която демонстрира употребата на операторите за сравнение в Java:

public class RelationalOperatorsDemo {

      public static void main(String args[]) {

            int x = 10, y = 5;

            System.out.println("x > y : " + (x > y)); // true

            System.out.println("x < y : " + (x < y)); // false

            System.out.println("x >= y : " + (x >= y)); // true

            System.out.println("x <= y : " + (x <= y)); // false

            System.out.println("x == y : " + (x == y)); // false

            System.out.println("x != y : " + (x != y)); // true

      }

}

В примерната програма, първо създадохме двете променливи x и y и им присвоихме стойностите 10 и 5. На следващия ред отпечатваме на конзо­лата, посредством метода println() на System.out, резултата от сравня­ването на двете променливи x и y посредством оператора >. Върнатият резултат е true, защото x има по-голяма стойност от y. На следващите 5 реда се отпечатва върнатият резултат от използването на останалите 5 оператора за сравнение с променливите x и y.

Оператори за присвояване

Операторът за присвояване на стойност на променливите е "=" (символът равно). Синтаксисът, който се използва за присвояване на стойности е следният:

операнд1 = литерал или операнд2;

Оператори за присвояване – пример

Ето един пример, в който използваме операторът за присвояване на стойност:

int x = 6;

String helloString = "Здравей стринг.";

int y = x;

В горния пример присвояваме стойност 6 на променливата x. На втория ред присвояваме текстов литерал на променливата helloString, а на третия ред копираме стойността от променливата x в променливата y.

Каскадно присвояване

Операторът за присвояване може да се използва и каскадно (да се използва повече от веднъж в един и същ израз). В този случай присвоя­ванията се извършват последователно отдясно наляво. Ето един пример:

int x, y, z;

x = y = z = 25;

На първия ред от примера създаваме три променливи, а на втория ред ги инициализираме със стойност 25.

clip_image001[1]

Операторът за присвояване в Java е "=", докато операто­рът за сравнение е "==". Размяната на двата оператора е честа причина за грешки при писането на код. Внимавайте да не объркате оператора за сравнение с оператора за присвояване.

Комбинирани оператори за присвояване

Освен оператора за присвояване в Java има и комбинирани оператори за присвояване. Те спомагат за съкращаването на обема на кода, като позволяват изписването на две операции чрез един оператор. Комбинира­ните оператори имат следния синтаксис:

операнд1 оператор = операнд2;

Горният израз е идентичен със следния:

операнда1 = операнда1 оператор операнда2;

Ето един пример за комбиниран оператор за присвояване:

int x = 2;

int y = 4;

 

x *= y; // Same as x = x * y;

System.out.println(x); // 8

Най-често използваните комбинирани оператори за присвояване са += (добавя стойността на операнд2 към операнд1), -= (изважда стойността на операнда в дясно от стойността на тази в ляво). Други съставни оператори за присвояване са *=, /= и %=.

Следващият пример дава добра представа за комбинираните оператори за присвояване и тяхната употреба:

int x = 6;

int y = 4;

 

System.out.println(y *= 2); // 8

int z = y = 3;              // y=3 and z=3 

 

System.out.println(z);      // 3

System.out.println(x |= 1); // 7

System.out.println(x += 3); // 10

System.out.println(x /= 2); // 5

В примера първо създаваме променливите x и y и им присвояваме стойностите 6 и 4. На следващият ред принтираме на конзолата y, след като сме присвоили нова стойност с оператора *= и литерала 2. Резултатът от операцията е 8.  По нататък в примера прилагаме други съставни оператори за присвояване и изкарваме получения резултат на конзолата.

Условен оператор ?:

Условния оператор ?: използва булевата стойност от един израз за да определи кой от други два израза да бъде пресметнат и върнат като резултат. Операторът работи над 3 операнда. Символът "?" се поставя между първия и втория операнд, а ":" се поставя между втория и третия операнд. Първият операнд (или израз) трябва да е от булев тип.

Синтаксисът на оператора е следният:

операнд1 ? операнд2 : операнд3

Ако операнд1 има стойност true, операторът връща резултат операнд2. Ако операнд1 има стойност false, операторът връща резултат операнд3.

По време на изпълнение се пресмята стойността на първия аргумент. Ако той има стойност true, тогава се пресмята втория (среден) аргумент и той се връща като резултат. Обаче, ако пресметнатият резултат от първия аргумент е false, то тогава се пресмята третия (последния) аргумент и той се връща като резултат.

Условен оператор ?: – пример

Ето един пример за употребата на оператора "?:":

int a = 6;

int b = 4;

System.out.println(a > b ? "a>b" : "b<=a"); // a>b

Други оператори

Досега разгледахме аритметичните оператори, логическите и побитовите оператори, оператора за конкатенация на символни низове, също и условният оператор ?:. Освен тях в Java има още няколко оператора:

-       Операторът за достъп "." се използва за достъп до член променли­вите на даден обект.

-       Квадратни скоби [] се използват за достъп до елементите на масив.

-       Скоби () се използват за предефиниране приоритета на изпълнение на изразите и операторите.

-       Оператора за преобразуване на типове (type) се използва за преобразуване на променлива от един съвместим тип в друг.

-       Операторът new се използва за създаването и инициализирането на нови обекти.

-       Операторът instanceof се използва за проверка дали даден обект е съвместим с даден тип.

Други оператори – примери

Ето няколко примера за операторите, които разгледахме в тази секция:

int a = 6;

int b = 3;

int c = 3;

 

System.out.println(c);         // 3

System.out.println((a+b) / 2); // 4

 

String s = "Beer";

System.out.println(s instanceof String); // true

 

int d = 0;

System.out.println(d);         // 0

System.out.println((a+b) / d); // ArithmeticException

Преобразуване на типовете

Операторите работят върху еднакъв тип данни. Въпреки това в Java има голямо разнообразие от типове данни, от които можем да избираме най-подходящия за определената цел. За да извършим операция върху променливи от два различни типа данни ни се налага да преобразуваме двата типа към един и същ.

Всички изрази в езика Java имат тип. Този тип може да бъде изведен от структурата на израза и типовете, променливите и литералите използвани в израза. Възможно е да се напише израз, който е с неподходящ тип за конкретния контекст. В някой случаи това ще доведе до грешка в компилацията на програмата, но в други контекста може да приеме тип, който е сходен или свързан с типа на израза. В този случай програмата извършва скрито преобразуване на типове.

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

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

-        Преобразуване от тип String към Object не изисква проверка. String е наследник на Object и може да бъде преобразуван към базовия си клас без опасност от грешка или загуба на данни. На наследяването ще се спрем в детайли в главата "Принципи на обектно-ориентираното програмиране".

-        Преобразуване от тип int към long може да се извърши без проверка по време на изпълнението, защото няма опасност от загуба на данни.

-        Преобразуване от тип double към long изисква преобразуване от 64-битова плаваща стойност към 64-битова целочислена. В зависимост от стойността, може да се получи загуба на данни, заради това е необходимо изрично преобразуване на типа.

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

-        Скрито преобразуване;

-        Изрично преобразуване.

Неявно (implicit) преобразуване на типове

Неявното (скритото) преобразуване на типове е възможно единствено, когато няма възможност от загуба на данни при преобразуването, тоест когато конвертираме от тип с по-малък обхват към тип с по-голям (примерно от int към long). За да направим неявно преобразуване не е нужно да използваме какъвто и да е оператор, затова се нарича скрито. Преобразу­ването става автоматично от компилатора, когато присвояваме стойност от по-малък обхват в променлива с по-голям обхват или когато в израза има типове с различен обхват. Тогава преобразуването става към типа с по-голям обхват.

Неявно преобразуване на типове – пример

Ето един пример за неявно (implicit) преобразуване на типове:

int myInt = 5;

System.out.println(myInt); // 5

 

long myLong = myInt;

System.out.println(myLong); // 5

 

System.out.println(myLong + myInt); // 10

В примера създаваме променлива myInt от тип int и присвояваме стойност 5. По-надолу създаваме променлива myLong от тип long и зада­ваме стойността, съдържаща се в myInt. Стойността запазена в myLong, автоматично се конвертира от тип int към тип long. Накрая в примера изкарваме резултата от събирането на двете променливи. Понеже променливите са от различен тип, те автоматично се преобразуват към типа с по-голям обхват, тоест към long и върнатият резултат, който се отпечатва на конзолата, отново е long. Всъщност подадения параметър на метода println() e от тип long, но вътре в метода той отново ще бъде конвертиран, този път към тим  String, за да може да бъде отпечатан на конзолата.

Възможни неявни преобразования

Това са възможните неявни преобразувания на примитивни типове в Java:

-          byte към short, int, long, float, или double

-          short към int, long, float, или double

-          char към int, long, float, или double

-          int към long, float, или double

-          long към float или double

-          float към double

При преобразуването на типове от по-малък обхват към по-голям няма загуба на данни. Числовата стойност остава същата след преобразу­ването. Както във всяко правило и тук има малко изключение. Когато преобразуваме тип int към тип float (32-битови стойности), разликата е, че int използва всичките си битове за представяне на едно целочислено число, докато float използва част от битовете си за представянето на плаващата запетая. Оттук следва, че е възможно при преобразуване от int към float да има загуба на точност, поради закръгляне. Същото се отнася при преобразуването на 64-битовите long към double.

Изрично (explicit) преобразуване на типове

Изричното преобразуване на типове е нужно, когато има вероятност за загуба на данни. Когато конвертираме тип с плаваща запетая към цело­числен тип, винаги има загуба на данни, идваща от плаващата запетая и е задължително използването на изрично преобразуване (double към long). За да направим такова конвертиране е нужно изрично да използваме оператора за преобразуване на данни (cast оператора): (type). Възможно е да има загуба на данни също, когато конвертираме от тип с по-голям обхват към тип с по-малък (double към float или long към int).

Изрично преобразуване на типове – пример

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

double myDouble = 5.1d;

System.out.println(myDouble); // 5.1

 

long myLong = (long)myDouble;

System.out.println(myLong); // 5

     

myDouble = 5e9d; // 5 * 10^9

System.out.println(myDouble); // 5.0E9

           

int myInt = (int) myDouble;

System.out.println(myInt); // 2147483647

System.out.println(Integer.MAX_VALUE); // 2147483647

На първия ред от примера присвояваме стойността 5,1 на променливата myDouble. След като я преобразуваме (изрично), посредством оператора (long) към тип long и изкараме на конзолата променливата myLong, виждаме, че променливата е изгубила стойността след плаващата запетая (защото long e целочислен тип). След това на седмия ред присвояваме на променливата myDouble стойност 5 милиарда. Накрая конвертираме myDouble към int посредством оператора (int) и разпечатваме променли­вата myInt. Резултатът e същия, както и когато отпечатаме Integer. MAX_VALUE, това е така, защото myDouble съдържа в себе си по-голяма стойност от обхвата на int.

clip_image001[2]

Не винаги е възможно да се предвиди каква ще бъде стойността на дадена промен­лива след препълване на обхвата и! Затова използвайте достатъчно големи типове и внимавайте при преминаване към "по-малък" тип.

Загуба на данни при преобразуване на типовете

Пример за загуба на информация при преобразуване на типове:

long myLong = Long.MAX_VALUE;

int myInt = (int)myLong;

 

System.out.println(myLong); // 9223372036854775807

System.out.println(myInt); // -1

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

Ето още няколко примера за преобразуване на типове:

float heightInMeters = 1.74f;    // Explicit conversion

double maxHeight = heightInMeters;          // Implicit

double minHeight = (double) heightInMeters; // Explicit

float actualHeight = (float) maxHeight;     // Explicit

 

float maxHeightFloat = maxHeight; // Compilation error!

В примера на последния ред имаме израз, който ще генерира грешка при компилирането. Това е така, защото се опитваме да конвертираме неявно от тип double към тип float, от което може да има загуба на данни. Java е строго типизиран език за програмиране и не позволява такъв вид прис­вояване на стойности.

Възможни изрични преобразования

Това са възможните явни (изрични) преобразувания и при всички тях има възмож­ност за загуба на данни, така че внимавайте:

-       short към byte или char

-       char към byte или short

-       int към byte, short или char

-       long към byte, short, char или int

-       float към byte, short, char, int или long

-       double към byte, short, char, int, long или float

Тези преобразувания могат да изгубят, както информация за големината на числото, така и информация за неговата точност (precision).

Когато преобразуваме byte към char имаме първо скрито конвертиране от byte към int, а след него изрично преобразуване от int към char.

Преобразуване към символен низ

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

Друг начин да преобразуваме различни обекти към тип низ е като извикаме метода toString() на променливата.

Преобразуване към символен низ – пример

Нека разгледаме няколко примера за преобразуване на различни типове данни към символен низ:

int a = 5;

int b = 7;

String s = "Sum=" + (a + b);

System.out.println(s);

 

String incorrect = "Sum=" + a + b;

System.out.println(incorrect);

 

System.out.println("Perimeter = " + 2 * (a + b) +

      ". Area = " + (a * b) + ".");

Резултатът от изпълнението на примера е следният:

Sum=12

Sum=57

Perimeter = 24. Area = 35.

От резултата се вижда, че долепването на число към символен низ връща като резултата символния низ, следван от текстовото представяне на числото. Забележете, че операторът "+" за залепване на низове може да предизвика неприятен ефект при събиране на числа, защото има еднакъв приоритет с оператора "+" за събиране. Освен, ако изрично не променим приоритета на операциите чрез поставяне на скоби, те винаги се изпъл­няват отляво надясно.

Изрази

Голяма част от работата на една програма е пресмятане на изрази. Изразите представляват поредици от оператори, литерали и променливи, които се изчисляват до определена стойност от някакъв тип (число, стринг, обект или друг тип). Ето няколко примера за изрази:

int r = (150-20) / 2 + 5;

 

// Expression for calculation of the surface of the circle

double surface = Math.PI * r * r;

 

// Expression for calculation of the perimeter of the circle

double perimeter = 2 * Math.PI * r;

 

System.out.println(r);

System.out.println(surface);

System.out.println(perimeter);

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

70

15393.804002589986

439.822971502571

Изчисляването на израз може да има и странични действия, защото изразът може да съдържа вградени оператори за присвояване, увелича­ване или намаляване на стойност (increment, decrement) и извикване на методи. Ето пример за такъв страничен ефект:

int a = 5;

int b = ++a;

 

System.out.println(a); // 6

System.out.println(b); // 6

Упражнения

1.    Напишете израз, който да проверява дали дадено цяло число е четно или нечетно.

2.    Напишете булев израз, който да проверява дали дадено цяло число се дели на 5 и на 7 без остатък.

3.    Напишете израз, който да проверява дали дадено цяло число съдържа 7 за трета цифра (отдясно на ляво).

4.    Напишете израз, който да проверява дали третия бит на дадено число е 1 или 0.

5.    Напишете програма, която за подадени дължина и височина на правоъгълник, изкарват на конзолата неговият периметър и лице.

6.    Напишете израз, който изчислява площта на трапец по дадени a, b и h.

7.    Силата на гравитационното поле на луната е приблизително 17% от това на земята. Напишете програма, която да изчислява тежестта на човек на луната, по дадената тежест на земята.

8.    Напишете програма, която проверява дали дадена точка О (x, y) е вътре в окръжността К ((0,0), 5).

9.    Напишете програма, която проверява дали дадена точка О (x, y) е вътре в окръжността К ((0,0), 5) и е извън правоъгълника ((-1, 1), (5, 5).

10. Напишете програма, която приема за вход четирицифрено число във формат abcd и след това извършва следните действия върху него:

-     Пресмята сбора от цифрите на числото.

-     Разпечатва на конзолата цифрите в обратен ред: dcba.

-     Поставя последната цифра, на първо място: dabc.

-     Разменя мястото на втората и третата цифра: acbd.

11. Дадено е число n и позиция p. Напишете поредица от операции, които да отпечатат стойността на бита на позиция p от числото n (0 или 1). Пример: n=35, p=5 -> 1. Още един пример: n=35, p=6 -> 0.

12. Дадено е число n, стойност v (v = 0 или 1) и позиция p. Напишете поредица от операции, които да променят стойността на n, така че битът на позиция p да има стойност v. Пример n=35, p=5, v=0 -> n=3. Още един пример: n=35, p=2, v=1 -> n=39.

13. Напишете програма, която проверява дали дадено число n (n < 100) е просто.

Решения и упътвания

1.    Вземете остатъкът от деленето на числото на 2 и проверете дали е 0 или 1 (четно, нечетно).

2.    Ползвайте логическо "И".

3.    Разделете числото на 100 и го запишете в нова променлива. Нея разделете на 10 и вземете остатъкът. Остатъкът от делението на 10 е третата цифра от първоначалното число. Проверете равна ли е на 7.

4.    Използвайте побитово "И" върху числото и число, което има 1 само на третия бит. Ако върнатият резултат е различен от 0, то третия бит е 1. 

5.    Използвайте класа за четене от конзолата.

6.    Формула за лице на трапец: S = (a + b) / 2 * h.

7.    Използвайте следния код, за да прочетете число от конзолата:

Scanner input = new Scanner(System.in);  

System.out.print("Enter number:");

int number = input.nextInt();

8.    Използвайте питагоровата теорема c2 = a2 + b2. За да е вътре в кръга, то c следва да е по-малко от 5.

9.    Използвайте кода от задача 8 и добавете проверка за правоъгълника.

10. За да вземете отделните цифри на числото, можете да го делите на 10 и да взимате остатъка 4 последователни пъти.

11. Ползвайте побитови операции:

int n = 35; // 00100011

int p = 6;

int i = 1; // 00000001

int mask = i << p; // Move the 1st bit with p positions

 

// If i & mask are positive then the p-th bit of n is 1

System.out.println((n & mask) != 0 ? 1 : 0);

12. Ползвайте побитови операции, по аналогия с предната задача.

13. Прочетете за цикли в Интернет. Използвайте цикъл и проверете чис­лото за делимост на всички числа от 1 до корен квадратен от числото.

Дискусионен форум

Коментирайте книгата и задачите в нея във: форума на софтуерната академия.


Share

21 отговора до “Глава 3. Оператори и изрази”

  1. Милен says:

    Здравейте,
    Благодаря за това, че сте създали тази безплатна онлайн книга за въвеждане в програмирането с Java.Чета я с голям интерес и стриктно спазвам указанията от предговора. Има нещо в упражнение три, което не мога да си обясня, ето кода ми за решаване на задачата:

    public class HeadTreeExTree {
    public static void main(String args[]){
    int number = 74742;
    double numberOne = (number / 100);
    System.out.println(numberOne);
    double rest = numberOne % 10;
    System.out.println(rest);
    boolean checking = (rest == 7);
    System.out.println(checking);
    }
    }

    като резултат се появява следното:
    747.0
    7.0
    true

    Въпросът ме е защо като разделя 74742 на 100 се получава 747.0, а не 747.42. Нарочно повиших точността като зададох тип double вместо float, но резултата е същият.Ако получа обяснение от Вас ще бъда благодарен.

    • Милен says:

      Отговорих си на въпроса, защото number е от тип int.
      Въпреки че numberOne е от тип double, неговата стойност се образува, като тип int се дели на тип int (100), от което се получава тип int със загуба на данни от последните два знака, в случая 747 и след това по подразбиране 747 се преобразува в тип doulbe, поради което се отпечатва 747.0.

  2. nakov says:

    Задавайте въпроси за C# книгата във форума: https://softuni.bg/forum/

  3. Emil says:

    Wsichko e dadeno sbito i po ne otwlecheno

  4. Петър Бончев says:

    Може ли някой да реши 13-та задача и да я обясни?

    • stoyan radnev says:

      import java.util.Scanner;

      public class zad13 {

      public static void main(String[] args) {
      System.out.println(“Enter a number from 1 to 100:”);
      Scanner input = new Scanner(System.in);
      int n = input.nextInt();
      boolean isPrime = true;
      for (int i=2; i<= Math.sqrt(n); i++){
      System.out.println(i);
      if(n % i == 0) {
      isPrime = false;
      }
      }
      System.out.println(isPrime);
      }
      }

  5. Феня says:

    /*
    1. Напишете израз,
    който да проверява дали дадено цяло число е четно или нечетно.
    */

    import java.util.Scanner;

    public class Helloween
    {

    static Scanner userInput = new Scanner(System.in);

    public static void main(String[] args)
    {

    System.out.print(“Въведете цяло число: “);

    int theNumber = userInput.nextInt();

    if (theNumber % 2 == 0)
    {
    System.out.println(“\nЧислото ” + theNumber + ” е четно!”);
    }
    else
    {
    System.out.println(“\nЧислото ” + theNumber + ” е нечетно!”);
    }
    }
    }

    • Феня says:

      run:
      Въведете цяло число: 256

      Числото 256 е четно!
      BUILD SUCCESSFUL (total time: 3 seconds)
      ——————
      run:
      Въведете цяло число: 257

      Числото 257 е нечетно!
      BUILD SUCCESSFUL (total time: 2 seconds)

  6. Феня says:

    /*
    2. Напишете булев израз,
    който да проверява дали дадено цяло число се дели на 5 и на 7 без остатък.
    */

    import java.util.Scanner;

    public class Helloween
    {

    static Scanner userInput = new Scanner(System.in);

    public static void main(String[] args)
    {

    System.out.print(“Въведете цяло число: “);

    int theNumber = userInput.nextInt();
    boolean task;

    if (theNumber % 5 == 0 && theNumber % 7 == 0)
    {
    task = true;
    System.out.println(“\nЧислото се дели на 5 и 7 БЕЗ остатък: ” + task);
    }
    else
    {
    task = false;
    System.out.println(“\nЧислото се дели на 5 и 7 БЕЗ остатък: ” + task);
    }
    }
    }

    • Феня says:

      run:
      Въведете цяло число: 34

      Числото се дели на 5 и 7 БЕЗ остатък: false
      BUILD SUCCESSFUL (total time: 3 seconds)
      —————
      run:
      Въведете цяло число: 35

      Числото се дели на 5 и 7 БЕЗ остатък: true
      BUILD SUCCESSFUL (total time: 2 seconds)

  7. Ивета says:

    Благодаря за книгата и поздравления за положения труд – оценявам го!
    Но бих искала да обърна внимание малко и на правописа. Конкретно тази глава (все още не съм прочела останалите) е пълна с правописни и граматически грешки, което е много, много неприятно. Нека, освен на Java, се научим да пишем и на български.

    Не пиша този коментар с цел заяждане. Напротив – пиша го с най-добри чувства. Надявам се, че приемате градивна критика. 😉

    • negramoten says:

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

  8. Четящ says:

    Чудесно четиво, много полезно и лесно усвояемо. P.S: Думите “суфикс” и “постфикс” са синоними и означават “наставка”. Думата “префикс” е правилната за “представка”. Поздрави!

  9. hello says:

    за втора задачка има и по – кратък вариант :
    package introToJava;
    import java.util.Scanner;
    public class introToJava {
    static Scanner userInput = new Scanner (System.in);

    public static void main (String [] args)
    {
    System.out.print(“Въведете цяло число: “);
    int theNumber = userInput.nextInt();
    System.out.println((( “Числото се дели на 5 и 7 без остатък :” + ” ” + ( 0 == theNumber % 5 && (0 == theNumber % 7 )))));
    }
    }

  10. Иван says:

    Много хубав сайт сте направили момчета! Някой може ли да ми обясни 9 задача.

  11. erntTt says:

    Господин Наков, искам да попитам дали е лошо това , че поглеждаме в Решения и упътвания за да решим дадено упражнение ? Зашото сам не мога да измисля точно как ще стане, но с упътванията ги решавам… на 2 глава съм.

    • Препоръката ми е да пробвате сами и когато зациклите, да поглеждате упътванията или да питате във форума: https://softuni.bg/forum

      • erntTt says:

        Благодаря , аз се обърках имах впредвид глава 3 … на глава 2 задачите са лесни. Благодаря и за уникалната книга.

  12. Игнасио says:

    short a = 3; // 0000 0011 = 3

    short b = 5; // 0000 0101 = 5

    System.out.println( a | b); // 0000 0111 = 7

    System.out.println( a & b); // 0000 0001 = 1

    System.out.println( a ^ b); // 0000 0110 = 6

    System.out.println(~a & b); // 0000 0100 = 4

    System.out.println(a << 1); // 0000 0110 = 6

    System.out.println(a <> 1); // 0000 0001 = 1

    Може ли някой да ми обясни този код по-просто. Прочетох няколко пъти, но не мога да схвана как се получава тези числа.
    Благодаря предварително! 🙂

  13. Игнасио says:

    Нямам нужда вече, разбрах ги с малко помощ от интернет.

Коментирай