// Инициализация двухмерного массива class Matrix { public static void main(String a r g s []) { double m [][] = { { 0*0, 1*0, 2*0, 3*0 }, { 0*1, 1*1, 2*1, 3*1 }, { 0*2, 1*2, 2*2, 3*2 }, { 0*3, 1*3, 2*3, 3*3 } }; int i , j ; for(i=0; i<4; i + +) { f o r (j=0; j<4; j++) System.out.print(m[i ][j ] + " "При выполнении эта программа создает следующий вывод. Как видите, каждая строка массива инициализируется в соответствии со значениями, указанными в списках инициализации. Рассмотрим еще один пример использования многомерного массива. Следующая программа создает трехмерный массив размерностью 3x4x5. Затем она загружает каждый элемент произведением его индексов и, наконец, отображает эти произведения Демонстрация трехмерного массива class ThreeDMatrix { public static void main(String a r g s []) { int threeD[][][] = new i n t [3][4][5 ]; int i , j , k; for(i=0; i<3; i++) f o r (j=0; j <4 ; j++) f o r (k=0 ; k<5 ; k++) threeD[i][j ][k] = i * j * k; f o r (i=0; i<3 ; i++) { f o r (j = 0; j <4 ; j++) { f o r (k= 0 ; k<5 ; k++) System.out.print (threeD[i][j ][k] + " "Эта программа создает следующий вывод 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 0 2 4 6 8
Глава 3. Типы данных, переменные и массивы Об 12 О О О О О 0 2 4 6 О 4 8 12 О 6 12 18 Альтернативный синтаксис объявления массивов Для объявления массивов можно использовать и вторую форму. тип[] имя_переменной ; В этой форме квадратные скобки следуют за указателем типа, а не за именем переменной массива. Например, следующие два объявления эквивалентны a l [] = new int[3]; int[] a2 = new Приведенные ниже два объявления также эквивалентны twodl[][] = new char[3][4]; char[][] twod2 = new c h a r Вторая форма объявления удобна для одновременного объявления нескольких массивов. Например, объявление n t [] n u m s , nums2, nums3; // создание трех массивов создает три переменные массивов типа int. Оно эквивалентно следующему объявлению n u m s [], nums2[], nums3[]; // создание трех массивов Альтернативная форма объявления удобна также при указании массива в качестве возвращаемого типа метода. В этой книге используются обе формы объявлений. Несколько слово строках Как вы, вероятно, заметили, входе рассмотрения типов данных и массивов мы не упоминали строки или строковый тип данных. Это связано нес тем, что язык Java не поддерживает этот тип, — он его поддерживает. Просто строковый тип данных Java, имеющий имя String, не относится к элементарным типам. Он не является также и просто массивом символов. Строка, скорее, представляет собой объект, и для понимания его полного описания требуется понимание ряда характеристик объектов. Поэтому рассмотрим этот тип в последующих главах книги, после рассмотрения объектов. Однако чтобы читатели могли использовать простые строки в примерах программ, мы сейчас кратко опишем этот тип. Тип String используют для объявления строковых переменных. Можно также объявлять массивы строк. Переменной типа String можно присваивать заключенную в кавычки строковую константу. Переменная типа String может быть присвоена другой переменной типа String. Объект класса String можно применять в качестве аргумента метода println () . Например, рассмотрим следующий фрагмент кода str = "тестовая строка) ;
9 6 Часть I. Язык В этом примере г — объект класса String. Ему присвоена строка " тестовая строка, которая отображается методом println (Как будет показано в дальнейшем, объекты класса String обладают многими характерными особенностями и атрибутами, которые делают их достаточно мощными и простыми в использовании. Однако в нескольких последующих главах мы будем применять их только в простейшей форме. Замечание по поводу указателей для программистов на Опытные программисты на C/C++ знают, что эти языки поддерживают указатели. Однако в настоящей главе мы о них не упоминали. Причина этого проста Java не поддерживает и не разрешает использование указателей. (Точнее говоря, Java не поддерживает указатели, которые доступны и или могут быть изменены программистом) Язык Java не разрешает использование указателей, поскольку это позволило бы программам Java преодолевать защитный барьер между средой исполнения Java и содержащим ее компьютером. (Вспомните, что указателю может быть присвоен любой адрес в памяти — даже те адреса, которые могут находиться вне системы времени выполнения Java.) Поскольку в программах C/C++ указатели используются достаточно интенсивно, их утрата может казаться существенным недостатком Java. В действительности это не так. Среда Java спроектирована так, чтобы до тех пор, пока все действия выполняются в пределах среды исполнения, применение указателей не требовалось, и их использование не дает никаких преимуществ Язык Java предоставляет множество операторов. Большинство из них может быть отнесено к одной из следующих четырех групп арифметические операторы, побитовые операторы, операторы сравнения и логические операторы. В Java также определен ряд дополнительных операторов, применяемых в особых ситуациях. В этой главе описаны все операторы Java, за исключением оператора сравнения типов i n s t a n c e o f , который рассматривается в главе Арифметические операторыАрифметические операторы используются в математических выражениях, так как они применяются в алгебре. Арифметические операторы перечислены в табл. Таблица 4.1. Арифметические операторы Java Оператор Описание + Сложение (также унарный плюс) - Вычитание (также унарный минус) * Умножение / Деление % Деление по модулю+ +Инкремент+ Сложение с присваиванием- Вычитание с присваиванием Умножение с присваиванием Деление с присваиванием Деление по модулю с присваиванием- - Декремент Операнды арифметических операторов должны иметь числовой тип. Арифметические операторы нельзя применять к логическим типам, но можно применять к типам c h a r , поскольку в Java этот тип, по сути, является разновидностью типа i n t . 4 Основные арифметические операторы 8 Часть I. Язык Все основные арифметические операторы — сложение, вычитание, умножение и деление — воздействуют на числовые типы так, как этого можно было бы ожидать. Оператор унарного вычитания изменяет знак своего единственного операнда. Оператор унарной суммы просто возвращает значение своего операнда. Следует помнить, что в случае применения оператора деления к целочисленному типу результат не будет содержать дробного компонента. Следующий пример простой программы демонстрирует применение арифметических операторов. Он иллюстрирует также различие между делением сплава ющей точкой и целочисленным делением Демонстрация основных арифметических операторов class B asicMath {public static voi d ma i n ( S t r i n g a r g s []) {// арифметические операции с целочисленными значениями y s t e m . o u t . p r i n t l n (Целочисленная арифметика асс s t e m .ou t.p r i n t l n ("a = " + a)S y s t e m .out.p r i n t l n ("b = " + b)S y s t e m .out.p r i n t l n ("c = " + c)S y s t e m .out.p r i n t l n ("d = " + d)S y s t e m .out.p r i n t l n ("e = " + e)// арифметические операции со значениями типа doubleS y s t e m . o u t . p r i n t l n ("ХпАрифметика с плавающей точкой da = 1 + 1;double db = da * 3;double dc = db / 4;double dd = dc — a;double de = -dd;S y s t e m . o u t . p r i n t l n ("da = " + da)S y s t e m . o u t . p r i n t l n ("db = " + db)S y s t e m . o u t . p r i n t l n ("dc = " + dc)S y s t e m . o u t . p r i n t l n ("dd = " + dd)S y s t e m . o u t . p r i n t l n ("de = " + При выполнении этой программы на экране отобразится следующий вывод. Ц ело численная арифметика абс е = Арифметика da = 2.0 db = б . 0 dc = 1.5 dd = -0.5 de = с плавающей точкой Глава А Операторы 9 Оператор деления по модулюО ператор деления по модулю, %, возвращает остаток деления. Этот оператор можно применять как к типам с плавающей точкой, таки к целочисленным типам. Следующий пример программы демонстрирует применение оператора %. // Демонстрация использования оператора %. class Modulus {pu blic static v oi d ma i n( S tr i ng a r g s []) {int x = 42; double у = 42.25;S y s t e m . o u t . p r i n t l n ("x mod 10 = " + x % 10);S y s t e m . o u t .p r i n t l n ("y m o d 10 = " + у % При выполнении эта программа создает следующий вывод. х m o d 10 = 2 у mo d 10 = Составные арифметические операторы с присваиваниемВ языке Java имеются специальные операторы, объединяющие арифметические операторы с операцией присваивания. Как вы, вероятно, знаете, операторы вроде показанного ниже в программах встречаются достаточно часто. а = а + В Java этот оператор можно записать следующим образом. а += В этой версии использован составной оператор присваивания +=. Оба оператора выполняют тоже действие они увеличивают значение переменной а на 4. А вот еще один примера = а % Этот пример можно записать следующим образом. а %= В этом случае оператор % = вычисляет остаток отделения аи помещает результат обратно в переменную а. Составные операторы с присваиванием существуют для всех арифметических операторов с двумя операндами. Таким образом, любой оператор, имеющий форму переменная = переменная оператор выражение можно записать в следующем виде. переменная оператор выражение; Составные операторы с присваиванием предоставляют два преимущества. Во- первых, они позволяют уменьшить объем вводимого кода, поскольку являются сокращенным вариантом соответствующих длинных форм. Во-вторых, их реализация в системе времени выполнения Java эффективнее реализации эквивалентных длинных форм. Поэтому в профессионально написанных программах Java составные операторы с присваиванием будут встречаться очень часто 1 0 0 Часть I. Язык Ниже приведен пример программы, демонстрирующий практическое применение нескольких составных операторов с присваиванием Демонстрация применения нескольких операторов с присваиванием class OpEquals { public static void main(String a r g s []) { int a = 1; int b = 2; int с = 3 ; a += 5; b * = 4 ; c + — a * b с %= 6; System.out.p r intln("a = " + a); System.out.println("b = " + b ) ; System.out.println("c = " + c) Эта программа создает следующий вывода = 6 b = 8 с = Инкремент и декремент Операторы + + и - - являются операторами инкремента и декремента. Они были представлены в главе 2. А в этой главе мы рассмотрим их подробно. Как вы вскоре убедитесь, эти операторы имеют ряд особых свойств, которые делают их достаточно интересными. Рассмотрим, что именно делают операторы инкремента и декремента. Оператор инкремента увеличивает значение операнда на единицу. Оператор декремента уменьшает значение операнда на единицу. Например, выражение х = х + с применением оператора инкремента можно записать в таком виде. х++; Аналогично выражение х — х — эквивалентно следующему выражению. х -- Эти операторы отличаются тем, что могут быть записаны как в постфиксной форме, когда оператор следует за операндом, как в приведенных примерах, таки в префиксной, когда он предшествует операнду. В приведенных примерах применение любой из этих форм не имеет никакого значения. Однако, когда операторы инкремента и декремента являются частью более сложного выражения, проявляется внешне незначительное, но важное различие между этими двумя формами. В префиксной форме значение операнда увеличивается или уменьшается до извлечения значения для использования в выражении. В постфиксной форме предыдущее значение извлекается для использования в выражении, и лишь после этого значение операнда изменяется. х = Ух Глава 4. Операторы 1 0 В этом случае значение у устанавливается равным 43, как и можно было ожидать, поскольку увеличение значения выполняется перед присваиванием значения переменной х переменной у. Таким образом, строка ух эквивалентна следующим двум операторам. х = х + Ух Однако если операторы записать как х — 42; у = х++; значение переменной х извлекается до выполнения оператора инкремента, и поэтому значение переменной у равно 42. Конечно, в обоих случаях значение переменной х установлено равным 43. Следовательно, строка ух эквивалентна следующим двум операторам. У = х х = х + Следующая программа демонстрирует применение оператора инкремента Демонстрация применения оператора + +. class IncDec { public static void main(String a r g s []) { int a = 1; int b = 2; int с ; int d; с = ++b; d = a++; C + +; System.out.println("a = " + a ) ; System.out.println("b = " + b ) ; System.out.println("c = " + c ) ; System.out.println("d = " + d ) Вывод этой программы выглядит следующим образом. а = 2 Ь = 3 с = 4 d = Побитовые операторы Язык Java определяет несколько побитовых операторов, которые могут применяться к целочисленным типами . Эти операторы воздействуют на отдельные биты операндов. Они перечислены в табл. Поскольку побитовые операторы манипулируют битами в целочисленном значении, важно понимать, какое влияние подобные манипуляции могут оказать назначение. В частности, важно знать, как среда Java хранит целочисленные значения и как она представляет отрицательные числа. Поэтому, прежде чем продолжить рассмотрение операторов, кратко рассмотрим эти два вопроса Таблица 4.2. Побитовые операторы в Java 1 0 2 Часть I. Язык Оператор Описание
Побитовый унарный оператор NOT (НЕ) & Побитовое AND (И) 1 Побитовое OR (ИЛИ) Побитовое исключающее Сдвиг вправо >>> Сдвиг вправо с заполнением нулями << Сдвиг влево &= Побитовое AND с присваиванием Побитовое OR с присваиванием Побитовое исключающее OR с присваиванием Сдвиг вправо с присваиванием >>>= Сдвиг вправо с заполнением нулями с присваиванием Сдвиг влево с присваиванием Все целочисленные типы представляются двоичными числами различной длины. Например, значение типа b y t e , равное 42, в двоичном представлении имеет вид 00101010, в котором каждая позиция представляет степень числа два, начиная св крайнем справа бите. Битв следующей позиции будет представлять 21, или 2, следующий — 22, или 4, затем 8, 16, 32 и т.д. Таким образом, двоичное представление числа 42 содержит единичные биты в позициях 1, 3 и 5 (начиная с 0, крайней справа позиции. Следовательно, Все целочисленные типы (за исключением c h a r) — целочисленные типы со знаком. Это означает, что они могут представлять как положительные, таки отрицательные значения. В Java применяется кодирование, называемое двоичным дополнением, при котором отрицательные числа представляются в результате инвертирования всех битов значения (изменения 1 на 0 и наоборот) и последующего добавления 1 к результату. Например, -42 представляется в результате инвертирования всех битов в двоичном представлении числа 42, что дает значение 11010101, и добавления 1, что приводит к значению 11010110, или -42. Чтобы декодировать отрицательное число, необходимо вначале инвертировать все биты, а затем добавить 1 к результату. Например, инвертирование значения -42, или 11010110, приводит к значению 00101001, или 41, после добавления 1 к которому мы получаем Причина, по которой в языке Java (и большинстве других компьютерных языков) применяют двоичное дополнение, становится понятной при рассмотрении перехода через нуль. Если речь идет о значении типа b y te , нуль представляется значением 00000000. В случае применения единичного дополнения простое инвертирование всех битов создает значение, равное 11111111, которое представляет отрицательный нуль. Проблема в том, что отрицательный нуль — недопустимое значение в целочисленной математике. Применение двоичного дополнения для представления отрицательных значений позволяет решить эту проблему. При этом к дополнению добавляется 1, что приводит к числу 100000000. Единичный бит оказывается сдвинутым влево слишком далеко, чтобы умещаться в значении типа b y te . Тем самым достигается требуемое поведение, когда -0 эквивалентен 0, a l l l l l l l l — код значения, равного -1. Хотя в приведенном примере мы использовали значение типа b y te , тот же базовый принцип применяется и ко всем целочисленным типам Java.
Глава 4. Операторы 1 0 Поскольку в Java для хранения отрицательных значений используется двоичное дополнение — и поскольку в Java все целочисленные значения являются значениями со знаком — применение побитовых операторов может легко привести к неожиданным результатам. Например, установка самого старшего бита равным 1 может привести к тому, что результирующее значение будет интерпретироваться как отрицательное число, независимо оттого, к этому результату вы стремились или нет. Во избежание неприятных сюрпризов следует помнить, что независимо оттого, как он был установлен, старший бит определяет знак целого числа. Побитовые логические операторы Побитовые логические операторы — это &, I, Ли Результаты выполнения каждого из этих операторов приведены в табл. 4.3. Входе ознакомления с последующим материалом помните, что побитовые операторы применяются к каждому отдельному биту каждого операнда. Таблица 4.3. Результаты выполнения побитовых логических операторов А в А |1 в А & В А А В А 0 0 0 0 0 1 1 0 1 0 1 0 0 1 1 0 1 1 1 1 1 1 0 0 Побитовое Унарный оператор NOT (НЕ, , называемый также побитовым дополнением, инвертирует все биты операнда. Например, число 42, которое имеет следующую последовательность битов: 00101010 в результате применения оператора NOT преобразуется следующим образом. 11010101 Побитовое Значение бита, полученное в результате выполнения побитового оператора AND, &, равно 1, если соответствующие биты в операндах также равны 1. Во всех остальных случаях значение результирующего бита равно 0. 00101010 42 & 00001111 15 00001010 10 Побитовое Результирующий бит, полученный в результате выполнения оператора OR, I, равен 1, если соответствующий битв любом из операндов равен 1, как показано в следующем примере 42 I 00001111 15
1 0 Часть I. Язык Java 00101111 Побитовое Результирующий бит, полученный в результате выполнения оператора XOR, равен 1, если соответствующий бит только водном из операндов равен 1. Во всех других случаях результирующий бит равен 0. В следующем примере показано применение оператора Он демонстрирует также полезную особенность оператора XOR. Обратите внимание на инвертирование последовательности битов числа 42 во всех случаях, когда второй операнд содержит бит, равный 1. Во всех случаях, когда второй операнд содержит бит, равный 0, значение первого операнда остается неизменным. Это свойство пригодится при выполнении некоторых операций с битами ЛИ спользование побитовых логических операторов В следующей программе демонстрируется применение побитовых логических операторов Демонстрация побитовых логических операторов class BitLogic { public static void main(String a r g s []) { String b i n a r y [] = { " 0000 ", " 0001 ", " 0010 ", " 0011 ", " 0100 ", " 0101 ", " 0110 ", " 0111 ", " 1 0 0 0 ", " 1 0 0 1 ", " 1 0 1 0 ", " 1 0 1 1 ", " 1 1 0 0 ", " 1 1 0 1 ", " 1 1 1 0 ", " 1 1 1 1 " }; int a = 3; / / 0 + 2 + 1 или ООН в двоичном представлении int b = 6; / / 4 + 2 + 0 или ОНО в двоичном представлении int с = а Ь d = а & b; int е = а ЛЬ (а & b) | (а & b); int g = -a & 0x0 f ; System.out.println(" a = " + binary[a]); Sysrem.out.println(" b = " + binary[b]); System.out.println(" alb = " + binary[c]); System.out.println(" a&b = " + binary[d]); System.out.println(" a^b = " + binary[e]); System.out.println("a&bIa&b = " + binary[f]); System.out.println(" a = " + В этом примере последовательности битов переменных аи представляют все четыре возможные комбинации двух двоичных цифр 0-0, 0-1, 1-0 и 1-1. О действии операторов I и & на каждый бит можно судить по результирующим значениям переменных си. Значения, присвоенные переменным е и f , иллюстрируют действие оператора Массив строк binary содержит читабельные двоичные представления чисел от 0 до 15. В этом примере массив индексирован, что позволяет увидеть двоичное представление каждого результирующего значения. Массив построен так, чтобы соответствующее строковое представление двоичного значения п хранилось в элементе массива binary п. Чтобы его можно было вывести при помощи массива binary, значение а уменьшается до значения, меньшего 16,
Глава 4. Операторы 1 0 в результате его объединения со значением OxOf (0000 1111 в двоичном представлении) оператором AND. Вывод этой программы имеет следующий вида = ООН а АЬ = 0101 а & Ы а & Ь = 0101 а = Сдвиг влево Оператор сдвига влево, « , смещает все биты значения влево на указанное количество позиций. Он имеет следующую общую форму. значение « количество Здесь количество количество позиций, на которое нужно сдвинуть влево биты в значении значение То есть оператор << смещает влево биты указанного значения на количество позиций, указанных операндом количество При каждом сдвиге влево самый старший бит смещается за пределы допустимого диапазона (и теряется, а справа дописывается нуль. Это означает, что при применении оператора сдвига влево к операнду типа int биты теряются, как только они сдвигаются за пределы 31 позиции. Если операнд имеет тип long, биты теряются после сдвига за пределы 63 позиции. Автоматическое повышение типа, выполняемое в среде Java, приводит к непредвиденным результатам при выполнении сдвига в значениях типа byte и short. Как вы уже знаете, тип значений byte и short повышается до типа int при вычислении выражений. Более того, результат вычисления такого выражения также имеет тип int. Это означает, что результатом выполнения сдвига влево значения типа byte или short будет значение типа int, и сдвинутые влево биты не будут отброшены до тех пор, пока они не будут сдвинуты за пределы 31 позиции. Более того, при повышении до типа int отрицательное значение типа byte или short получит дополнительный знаковый разряд. Следовательно, старшие биты будут заполнены единицами. Поэтому выполнение оператора сдвига влево применительно к значению типа byte или short предполагает необходимость отбрасывания старших байтов результата типа int. Например, при выполнении сдвига влево в значении типа b y t e вначале будет осуществляться повышение типа значения до типа int и лишь затем сдвиг. Это означает, что для получения требуемого сдвинутого значения типа byte необходимо отбросить три старших байта результата. Простейший способ достижения этого — обратное приведение результата к типу byte. Следующая программа демонстрирует эту концепцию Сдвиг влево значения типа byte, class ByteShift { public static void main(String a r g s []) { byte a = 64, b; int i ; i = a << 2 ; b = (byte) (a << Первоначальное значение a: " + a); System.out.println("i and b: " + i + " " + b ) ; } }
1 0 Часть I. Язык Эта программа создает следующий вывод. Первоначальное значение а 64 i and b: 2 56 Поскольку для выполнения вычислений тип переменной а повышается до int, сдвиг влево на две позиции значения 64 (0100 0000) приводит к значению i , равному 256 (1 0000 0000). Однако переменная b содержит значение, равное 0, поскольку после сдвига младший байт равен 0. Единственный единичный бит оказывается сдвинутым за пределы допустимого диапазона. Поскольку каждый сдвиг влево на одну позицию, по сути, удваивает исходное значение, программисты часто используют это в качестве эффективной замены умножения на 2. Однако при этом следует соблюдать осторожность. При сдвиге единичного бита в старшую позицию (бит 31 или 63) значение становится отрицательным. Следующая программа демонстрирует это применение оператора сдвига влево Применение сдвига влево в качестве быстрого метода умножения на 2. class MultByTwo { public static void main(String a r g s []) { int i; int num = OxFFFFFFE; for(i=0; i<4; i++) { num = num < < Программа создает следующий вывод. 536870908 1073741816 2147483632 -32 Начальное значение было специально выбрано таким, чтобы после сдвига влево на 4 позиции оно стало равным -32. Как видите, после сдвига единичного бита в позицию 31 число интерпретируется как отрицательное. Сдвиг вправо О ператор сдвига вправо, >>, смещает все биты значения вправо на указанное количество позиций. В общем виде его можно записать следующим образом. значение » количество Здесь количество указывает количество позиций, на которое нужно сдвинуть вправо биты в значении значение То есть оператор >> перемещает все биты в указанном значении вправо на количество позиций, указанное операндом к о ли чест во Следующий фрагмент кода выполняет сдвиг вправо на две позиции в значении 32, в результате чего значение переменной а становится равным 8. int а = а = а >> 2; // теперь а содержит Когда какие-либо биты в значении сдвигаются прочь, они теряются. Например, следующий фрагмент кода выполняет сдвиг вправо на две позиции в значении 35, что приводит к потере двух младших битов и повторной установке значения переменной а равным 8.
Глава 4. Операторы 1 0 7 int а = а = а >> 2; / / а содержит Чтобы лучше понять, как выполняется этот оператор, рассмотрим его применение к двоичным представлениям 35 >> 2 00001000 При каждом сдвиге вправо выполняется деление значения на два с отбрасыванием любого остатка. Это свойство можно использовать для высокопроизводительного целочисленного деления на 2. Конечно, при этом нужно быть уверенным, что никакие биты не будут сдвинуты за пределы правой границы. При выполнении сдвига вправо старшие (расположенные в крайних левых позициях) биты, освобожденные в результате сдвига, заполняются предыдущим содержимым старшего бита. Этот эффект называется дополнительным знаковым разрядом и служит для сохранения знака отрицательных чисел при их сдвиге вправо. Например, результат выполнения оператора - 8 » 1 равен -4, что в двоичном представлении выглядит следующим образом -8 » 1 11111100 Интересно отметить, что результат сдвига вправо значения -1 всегда равен -1, поскольку дополнительные знаковые разряды добавляют новые единицы к старшим битам. Иногда при выполнении сдвига вправо появление дополнительных знаковых разрядов нежелательно. Например, следующая программа преобразует значение b y t e в соответствующее шестнадцатеричное строковое представление. Обратите внимание на то, что для обеспечения возможности использования значения в качестве индекса массива шестнадцатеричных символов сдвинутое значение маскируется за счет его объединения со значением 0x0 f оператором AND, что приводит к отбрасыванию любых битов дополнительных знаковых разрядов Маскирование дополнительных знаковых разрядов HexByte { static public void main(String a r g s []) { char h e x [] = { ■O', ' 1' , ' 2 \ ' 3 ' , '4' , ' 5 \ 1 6 ' , '7' , '8', '9', 'a', ' b 1 , ' с ' , 'd 1, 'e'( 1f ' }; byte b = (byte) Oxfl; System.out.println("b = Ox" + hex[(b >> 4) & OxOf] + hex[b Вывод этой программы выглядит следующим образом = Oxfl }
1 0 Часть I. Язык Сдвиг вправо без учета знака Как было показано, при каждом выполнении оператор >> автоматически заполняет старший бит его предыдущим содержимым. В результате знак значения сохраняется. Однако иногда это нежелательно. Например, при выполнении сдвига вправо в каком-либо значении, которое не является числовым, использование дополнительных знаковых разрядов может быть нежелательным. Эта ситуация часто встречается при работе со значениями пикселей и графическими изображениями. Как правило, в этих случаях требуется сдвиг нуля в позицию старшего бита независимо от его первоначального значения. Такое действие называют сдвигом вправо без учета знака Для его выполнения используют оператор сдвига вправо без учета знака Java, >>>, который всегда вставляет нуль в позицию старшего бита. Следующий фрагмент кода демонстрирует применение оператора >>>. В этом примере значение переменной а установлено равным -1, все 32 бит двоичного представления которого равны 1. Затем в этом значении выполняется сдвиг вправо набит с заполнением старших 24 бит нулями и игнорированием обычно используемых дополнительных знаковых разрядов. В результате значение а становится равным 255. int а = -а = а >>> Чтобы происходящее было понятнее, запишем эту же операцию в двоичной форме 11111111 11111111 11111111 -1 в двоичном виде типа int » > 24 00000000 00000000 00000000 11111111 255 в двоичном виде типа Часто оператор >>> не столь полезен, как хотелось бы, поскольку он имеет смысл только для 32- и разрядных значений. Помните, что в выражениях тип меньших значений автоматически повышается до int. Это означает применение дополнительных знаковых разрядов и выполнение сдвига по отношению к разрядным, а не 8- или разрядным значениям. То есть программист может подразумевать выполнение сдвига вправо без учета знака применительно к значению типа byte и заполнение нулями начиная сбита Однако в действительности это не так, поскольку фактически сдвиг будет выполняться в разрядном значении. Этот эффект демонстрирует следующая программа Сдвиг без учета знака значения типа byte, class ByteUShift { static public void main(String a r g s []) { char h e x [] = { ■O', '1' , '2' , *3' , '4' , •5' , '6' , '7' , ' 8 1 , '9', 'a', 'b1, ' с ' , 'd', ' e ' , 'f' }; byte b = (byte) Oxfl; byte с = (byte) (b >> 4); byte d = (byte) (b >>> 4); byte e = (byte) ((b & Oxff) >> 4); System.out.println(" b = Ox" + hex[(b >> 4) & OxOf] + hex[b & OxOf]); System.out.println(" b >> 4 = Ox" + hex[(c > > 4 ) & OxOf] + hex[c & OxOf]); System.o u t .p r i ntIn(" b >>> 4 = Ox"
Глава 4. Операторы 1 0 9 + hex[(d > > 4 ) & OxOf] + hex[d & OxOf]); System.out.p r intln("(b & Oxff) >> 4 = Ox" + hex[(e > > 4 ) & OxOf] + hex[e & Из следующего вывода этой программы видно, что оператор >>> не выполняет никаких действий по отношению к значениям типа b y t e . Для этого примера в качестве значения переменной b было выбрано произвольное отрицательное значение типа b y te . Затем переменной с присваивается значение переменной b типа b y t e , смещенное вправо на четыре позиции, которое в связи с применением дополнительных знаковых разрядов равно Oxff. Затем переменной d присваивается значение переменной Ь типа b y t e , сдвинутое вправо на четыре позиции без учета знака, которым должно было бы быть значение OxOf, нов действительности, из-за применения дополнительных знаковых разрядов вовремя повышения типа переменной b до i n t перед выполнением сдвига, значением оказывается Oxff. Последнее выражение устанавливает значение переменной е равным значению типа b y t e переменной Ь, замаскированному добит с помощью оператора AND, а затем сдвинутому вправо на четыре позиции, что дает ожидаемое значение, равное OxOf. Обратите внимание на то, что оператор сдвига вправо без учета знака не применялся к переменной d, поскольку состояние знакового бита после выполнения оператора AND было известно = Oxfl b » 4 = Oxff b >>> 4 = Oxff (b & Oxff) » 4 = Побитовые составные операторы с присваиванием Подобно алгебраическим операторам, все двоичные побитовые операторы имеют составную форму, которая объединяет побитовый оператор с оператором присваивания. Например, следующие два оператора, выполняющие сдвиг вправо на четыре позиции в значении переменной а, эквивалентны. а = а >> 4; а >>= Аналогично эквивалентны и следующие два оператора, которые присваивают переменной а результат выполнения побитовой операции a OR а = а I Ь а I = b Следующая программа создает несколько целочисленных переменных, а затем использует составные побитовые операторы с присваиванием для манипулирования этими переменными OpBitEquals { public static void main(String a r g s []) { int a = 1; int b = 2; int с = 3; a 1= 4; b » = 1; с <<= 1; а Л с ; System.out.println("a = " + a); System.out.println("b = " + b ) ;
1 1 Часть I. Язык с = " + с); } } Эта программа создает следующий вывода = 3 b = 1 с = Операторы сравнения Операторы сравнения определяют отношение одного операнда с другим. В частности, они определяют равенство и порядок следования. Операторы сравнения перечислены в табл. Таблица АЛ. Операторы сравнения в Описание Равно Неравно или равно Меньше или равно Результат выполнения этих операторов — логическое значение. Наиболее часто операции сравнения используют в выражениях, которые управляют оператором i f и различными операторами цикла. В Java можно сравнивать значения любых типов, в том числе целые значения, значения с плавающей точкой, символы и булевы значения, используя проверку равенства == и неравенства '= . Обратите внимание на то, что в Java равенство обозначают двумя знаками равно, а не одним. (Одиночный знак равно — оператор присваивания) Сравнение с помощью операторов упорядочения применимо только к числовым типам. То есть сравнение для определения того, какой из операндов больше или меньше другого, можно выполнять только для целочисленных операндов, операндов с плавающей точкой или символьных операндов. Как уже отмечалось, результат оператора сравнения представляет собой логическое значение. Например, следующий фрагмент кода вполне допустима с = а < В данном случае результат выполнения операции а < b (который равен f a l s e ) сохраняется в переменной с. Те читатели, которые знакомы с языками C/C++, должны обратить внимание наследующее. В программах на языке C /C ++ следующие типы операторов встречаются очень часто done; I I . . . if(Idone) ... // Допустимо в C/C++ if(done) ... // ноне в Java.
Глава U. Операторы 1 1 В программе Java эти операторы должны быть записаны следующим образом ==0) . . . // Это стиль Java,if(done != 0 ) Это обусловлено тем, что в языке Java определение значений истинно и ложно отличается от их определений в языках C/C++. В языке C/C++ истинным считается любое ненулевое значение, а ложным — нуль. В Java значения t r u e (истинно) и f a l s e (ложно) — нечисловые значения, которые никак не сопоставимы с нулевым или ненулевым значением. Поэтому, чтобы сравнить значение с нулевым или ненулевым значением, необходимо явно использовать один или несколько операторов сравнения. Логические операторы Описанные в этом разделе логические операторы работают только с операндами типа b o o le a n . Все логические операторы с двумя операндами объединяют два логических значения, образуя результирующее логическое значение. Логические операторы перечислены в табл. Таблица 4.5. Логические операторы в JavaОператорОписание&Логическое AND (И) I Логическое OR (ИЛИ) Логическое XOR (исключающее OR (ИЛИ Сокращенное Сокращенное Логическое унарное NOT (НЕ с присваиванием = OR с присваиванием л =XOR с присваиванием ==Равно I Неравно Троичный условный оператор Логические операторы &, I и А действуют применительно к значениям типа b o o le a n точно также, как и по отношению к битам целочисленных значений. Логический оператор ! инвертирует булево состояние I t r u e == f a l s e и ! f a l s e == t r u e . Результат выполнения каждого из логических операторов приведен в табл. Таблица 4.6. Результаты выполнения логических операторовАA I В & В * В 1 1 2 Часть I. Язык Ниже приведена программа, которая выполняет практически те же действия, что и пример программы B i t L o g ic , представленный ранее, но она работает с логическими значениями типа b o o le a n , а нес двоичными разрядами Демонстрация применения булевых логических операторов BoolLogic { public static void main(String a r g s []) { boolean a = true; boolean b = false; boolean с = a 1 b; boolean d = a & b; boolean e = a A b; boolean f = (!a & b) 1 (a & !b) ; boolean g = !a; System.out.println(" a = " + a) System.out.println(" b = " + b) System.out.println(" alb = " + c) System.out.println(" a&b = ” + d) System.out.p r intln(" aAb = 1 1 + e) System.out.println(" !a&b|a&ib = " + f) System.out.p r intln(" ! a = " +Выполняя эту программу, легко убедиться, что к значениям типа b o o l e a n применяются те же логические правила, что и к битам. Как видно из следующего вывода, в Java строковое представление значения типа b o o l e a n — значение одного из литералов t r u e или f a l s e а = true b = false alb = true a&b = false aAb = true !a&b|a&lb = true ia = Сокращенные логические операторы Язык Java предоставляет два интересных логических оператора, которые не встречаются во многих других языках программирования. Это вторые версии булевых операторов AND и OR, обычно называемые сокращенные логическими операторами. Как видно из ранее приведенной таблицы, результат выполнения оператора OR равен t r u e , когда значение операнда А равно t r u e , независимо от значения операнда В. Аналогично результат выполнения оператора AND равен f a l s e , когда значение операнда А равно f a l s e , независимо от значения операнда В. При использовании форм I I и && этих операторов вместо I и & программа Java не будет вычислять значение правого операнда, если результат выражения можно определить по значению одного левого операнда. Это свойство очень удобно в тех случаях, когда значение правого операнда зависит от значения левого. Например, следующий фрагмент кода демонстрирует преимущество применения сокращенных логических операторов для выяснения допустимости операции деления перед вычислением ее результата (denom ! = 0 && num / denom > 10)
Глава Д. Операторы 1 Благодаря применению сокращенной формы оператора AND (&&) исключается риск возникновения исключения времени выполнения в случае равенства знаменателя (denom) нулю. Если бы эта строка кода была записана с применением одинарного символа & оператора AND, программа вычисляла бы обе части выражения, что приводило бык исключению времени выполнения при равенстве значения переменной denom нулю. Сокращенные формы операторов AND и OR принято применять в тех случаях, когда требуются операторы булевой логики, а их односимвольные версии используются исключительно для побитовых операций. Однако существуют исключения из этого правила. Например, рассмотрим следующий оператор се В данном случае одиночный символ & гарантирует применение оператора инкремента к значению е независимо от равенства 1 значения с. На заметку Формальная спецификация языка Java называет сокращенные операторы условным И (conditional-end) и условным ИЛИ (Оператор присваивания Мы использовали оператор присваивания начиная с главы 2. Теперь пора рассмотреть этот оператор формально. Оператором присваивания служит одиночный знак равенства, =. В Java оператор присваивания работает аналогично тому, как и во многих компьютерных языках. Он имеет следующую общую форму. переменная = выражение ; В этом операторе тип переменная должен соответствовать типу выражение. Оператор присваивания имеет одну интересную особенность, с которой вы, возможно, еще незнакомы он позволяет создавать цепочки присваиваний. Например, рассмотрим следующий фрагмент кодах у, z ; x = y = z = 1 0 0 ; // устанавливает значения переменных х, у и z равными В этом фрагменте кода единственный оператор устанавливает значения трех переменных х, у и z равными 100. Это обусловлено тем, что оператор = использует значение правого выражения. Таким образом, значением выражения z = 100 будет 100, оно и присваивается переменной у, а затем — переменной х. Использование цепочки присваивания — удобный способ установки общего значения группы переменных. Оператор Синтаксис Java содержит специальный троичный условный оператор, которым можно заменять определенные типы операторов i f - t h e n - e l s e . Это — оператор ?. Вначале он может казаться несколько непонятным, но со временем вы убедитесь в его исключительной эффективности. Этот оператор имеет следующую общую форму. выражение! ? выражение : выражениеЗ
Часть I. Язык Здесь выражение 1 — любое выражение, приводящее к значению типа b o o le a n . Если значение выражение 1 — t r u e , программа вычисляет значение выражение В противном случае программа вычисляет значение выражение Результат выполнения оператора ? равен значению вычисленного выражения. И выражение, и выражениеЗ должны возвращать значение одинакового (или совместимого) типа, которым не может быть тип v o id Ниже приведен пример применения этого оператора = denom == 0 ? О : num / Когда программа Java вычисляет это выражение присваивания, вначале она проверяет выражение слева от знака вопроса. Если значение переменной d e nom равно 0, программа вычисляет выражение, указанное между знаками вопроса и двоеточия, и использует вычисленное значение в качестве значения всего выражения ?. Если значение переменной denom неравно, программа вычисляет выражение, указанное после двоеточия, и использует его в качестве значения всего выражения ?. Затем значение, полученное в результате выполнения оператора ?, присваивается переменной r a t i o Следующий пример программы демонстрирует применение оператора ?. Эта программа служит для получения абсолютного значения переменной Демонстрация использования оператора ?. class Ternary {public static void main(String a r g s []) {int i, k;i = 10;k = i < 0 ? - i : i; // получение абсолютного значения i Абсолютное значение "); System.out.p r i ntln(i + " равно " + k ) ;i = -10;k = i < 0 ? - i : i; // получение абсолютного значения i Абсолютное значение "); System.out.p r intIn(i + " равно " + k ) Эта программа создает следующий вывод. Абсолютное значение 10 равно 10 Абсолютное значение -10 равно Приоритет операторовПриоритеты операторов Java, от высшего к низшему, описаны в табл. 4.7. Операторы, расположенные в том же ряду таблицы, имеют равный приоритет. Бинарные операторы имеют порядок вычисления слева направо (за исключением присваивания, которое обрабатывается справа налево. Хотя технически скобки [ ] и () являются разделителями, они способны действовать как операторы. В этом качестве они имеют самый высокий приоритет Глава А Операторы 1 1 Таблица 4.7. Приоритеты операторов Java + + (постфиксный) ++ (префиксный) ★ + » > (постфиксный) (префиксный) / % > » << >= < <= 1 унарный s ta n c e o приведение унарный) типа) & /ч 1 && 11 7 Низший приоритет ор— Использование круглых скобок Круглые скобки повышают приоритет заключенных в них операторов. Часто это необходимо для получения требуемого результата. Например, рассмотрим следующее выражение. а >> b + Вначале это выражение добавляет 3 к значению переменной Ь, а затем сдвигает значение переменной а вправо на полученное количество позиций. Используя избыточные круглые скобки, это выражение можно было бы записать следующим образом. а >> (Ь + Но если вначале нужно выполнить сдвиг значения переменной а вправо на b позиций, а затем добавить 3 к полученному результату, необходимо использовать круглые скобки так. (а >> Ь) + Кроме изменения обычного приоритета операторов, иногда круглые скобки можно использовать для облегчения понимания смысла выражения. Сложные выражения могут оказаться трудными для понимания. Добавление избыточных, но облегчающих понимание смысла выражения круглых скобок может способство
1 1 Часть I. Язык Javaвать исключению недоразумений в будущем. Например, какое из следующих выражений легче прочесть? а I 4 + с >> b & ас Ь) & 7) И еще один немаловажный момент использование круглых скобок (избыточных или не избыточных) не ведет к снижению производительности программы. Поэтому добавление круглых скобок для повышения читабельности программы не оказывает на нее отрицательного влияния
5>4>3>5>4> |