Часть I. Введение в Java
Глава 1. Основы Java
Фандорин, у меня времени нет! Скажите по-человечески.
Я не понимаю этого языка.
Из к/ф «Статский советник»
Как отмечалось во вступлении, язык программирования Java является полно- стью объектно-ориентированным. Это означает, что для составления даже самой простой программы необходимо описать класс. Однако в языке программирова- ния Java, кроме классов и объектов, есть на что обратить внимание.
Рассмотрение методов программирования в Java начнем с наиболее простых случаев. При этом нам все же придется использовать классы. Чтобы не загро- мождать самое начало книги довольно отвлеченными и не всегда понятными для новичков в программировании вопросами по созданию классов и объектов, используем следующий прием. Постулируем некоторые базовые синтаксические конструкции как основу создания программы в Java, а затем, в главе 4, посвя- щенной классам и объектам, дадим более подробные объяснения по этому пово- ду, причем в контексте методов объектно-ориентированного программирования (сокращенно ООП ). Думается, такой подход, с одной стороны, позволит чита- телю, не знакомому с концепцией ООП, легче и быстрее усваивать новый мате- риал, а затем плавно перейти к созданию реальных объектно-ориентированных программ в Java. С другой стороны, практически не пострадают те, кто знаком с методами ООП (например, программирующие на C++), поскольку представ- ленный далее материал в любом случае важен для понимания принципов про- граммирования в Java.
Простые программы
Отлично, отлично! Простенько, и со вкусом!
Из к/ф «Бриллиантовая рука»
За исключением нескольких последних глав книги, в основном будут рассма- триваться консольные программы. Памятуя о том, что лучше один раз увидеть, чем сто раз услышать, рассмотрим достаточно простую программу, выводящую на экран сообщение.
Простые программы 17
Листинг 1.1. Простая программа
class Intro{
public static void main(String[] args){
System.out.println("Мы программируем на Java!");
}
}
После компиляции и запуска программы (например, в среде NetBeans) в окне вывода появляется сообщение Мы программируем на Java!. Рассмотрим программ- ный код подробнее. Приведенные далее комментарии о этому поводу предна- значены в основном для тех, кто никогда не имел дела с таким языком програм- мирования, как С++.
Во-первых, сразу отметим, что фигурными скобками в языке программирова- ния Java (как и C++ и C#) отмечаются блоки программного кода. Программный код размещается между открывающей (символ {) и закрывающей (символ }) фигурными скобками. В данном случае использовано две пары фигурных ско- бок. Первая, внешняя, пара использована для определения программного кода класса, вторая — для определения метода этого класса.
Как неоднократно отмечалось, для создания даже самой простой программы не- обходимо описать класс. Описание класса начинается с ключевого слова class. После этого следует уникальное имя класса. Непосредственно программный код класса заключается в фигурные скобки. Таким образом, синтаксической кон- струкцией class Intro{...} определяется класс с названием Intro.
Программный код класса Intro состоит всего из одного метода с названием main() (здесь и далее названия методов будут указываться с круглыми скобками после имени, чтобы отличать их от переменных). Название метода стандартное. Дело в том, что выполнение Java-программы начинается с вызова метода с именем main(). Другими словами, в методе main() представлен код, который выполня- ется в результате вызова программы. Программа содержит один и только один метод с именем main() (исключение составляют апплеты — у них метода main() нет). Метод main() иногда называют главным методом программы, поскольку во многом именно с этим методом отождествляется сама программа.
Ключевые слова public, static и void перед именем метода main() означают бук- вально следующее: public — метод доступен вне класса, static — метод статиче- ский и для его вызова нет необходимости создавать экземпляр класса (то есть объект), void — метод не возвращает результат. Уровни доступа членов класса, в том числе открытый (public) доступ, детально описываются в главе 6, посвя- щенной наследованию. Статические (static) члены класса и особенности рабо- ты с ними описываются в главе 4, посвященной созданию классов и объектов. Пояснения по поводу типа результата, возвращаемого методами (в том числе методом main()), даются в той же главе.
Инструкция String[] args в круглых скобках после имени метода main() озна- чает тип аргумента метода: формальное название аргумента args, и этот ар- гумент является текстовым массивом (тип String). Типу String посвящена
отдельная глава книги (см. главу 8). Массивы описываются в следующей главе. Желающие побольше узнать о способах передачи аргументов методам могут обратиться к главе 4, посвященной созданию классов и объектов. Квадратные скобки можно указывать после ключевого слова String или после имени ар- гумента args.
Тем, кто программирует в С++, многое из приведенного уже знакомо. Для тех, кто ничего знакомого во всем этом не увидел, резюмируем: на ближайшее время все наши программы будут иметь следующую структуру:
class имя_класса{
public static void main(String[] args){
программный_код
}
}
Название класса (параметр имя_класса) задается пользователем — это, фактиче- ски, название программы. В месте, где указан параметр программный_код, указыва- ется непосредственно выполняемый при вызове программы программный код.
В рассматриваемом примере программный код состоит всего из одной команды System.out.println("Мы программируем на Java!"). Команда заканчивается точкой с запятой — это стандарт для Java. Командой с помощью встроенного метода println() на консоль (по умолчанию консолью является экран) выводится со- общение "Мы программируем на Java!". Текст сообщения указан аргументом метода. Метод вызывается через поле-объект out объекта потока стандартного вывода System. Подробнее система ввода-вывода обсуждается во второй части книги, по- сле того как мы подробнее познакомимся с классами и объектами. Пока же сле- дует запомнить, что для вывода информации на экран в консольных приложе- ниях используется инструкция вида System.out.println(), где в круглых скобках указывается выводимый текст, числовые значения, имена переменных и т. д. — все то, что можно указывать аргументом метода println(), и каковы будут по- следствия, описано в главе 8, посвященной работе с объектами класса String и StringBuffer.
Если читатель испытывает трудности с компиляцией и запуском программы из листинга 1.1, рекомендуем ему обратиться к приложению в конце книги, посвя- щенному методам практического использования среды разработки NetBeans.
Комментарии
Это мелочи. Но нет ничего важнее мелочей!
Из к/ф «Приключения Шерлока Холмса
и доктора Ватсона»
Очень часто в программном коде необходимо оставить комментарий — не- которые пояснения, напоминания или просто текст, предназначенный для
пользователя. Важно отметить, что комментарий предназначен не для компи- лятора, поэтому компилятором он игнорируется. В Java существует три типа комментариев.
Однострочный комментарий. Такой комментарий начинается с двойной косой черты (символ //). Все, что находится в строке кода справа от двойной косой черты, компилятором игнорируется.
Многострочный комментарий. Такой комментарий начинается последователь- ностью символов /* и заканчивается последовательностью символов */. Все, что находится между символами /* и */, компилятором игнорируется.
Многострочный комментарий документационной информации. Начинается последовательностью символов /** и заканчивается последовательностью символов */. Обычно используется для выделения в качестве комментария данных справочного характера.
Не следует недооценивать значения комментариев. Хороший комментарий су- щественно улучшает читабельность программного кода и позволяет избежать многих неприятностей. К сожалению, большинство программистов учатся это- му правилу на своих горьких ошибках.
Простые типы данных и литералы
Все должно быть изложено так просто, как только возможно, но не проще.
А. Энштейн
Обычно программы пишут для того, чтобы обрабатывать данные. Методы и воз- можности по обработке данных в значительной степени зависят от типа данных. Язык Java относится к строго типизованным языкам. Это означает, что любая переменная в программе относится к определенному типу данных — одному и только одному. В Java все данные можно разделить на простые и ссылочные. Ссылочные данные реализуются через иерархию классов. Простые данные — это скорее дань традиции. Забегая наперед, отметим, что для простых типов данных существуют ссылочные аналоги.
Разница между простыми и ссылочными типами на практике проявляется при передаче аргументов методам. Простые типы данных передаются по значению, ссылочные — через ссылку. Читателям, знакомым хотя бы с одним из современ- ных языков программирования, эти термины должны быть знакомы. Способы передачи аргументов методам в языке Java подробно обсуждаются в главе 4, посвященной работе с классами и объектами. Пока же заметим, что простые типы данных являются, по сути, базовыми. Именно данные этих типов будут наиболее часто использоваться в первой части книги.
В Java существует четыре группы базовых типов: для работы с целыми числами, для работы с числами в формате с плавающей точкой (действительные числа),
символы и логический тип — таким образом, всего получается восемь базовых типов. Базовые типы Java перечислены в табл. 1.1.
Таблица 1.1. Базовые (простые) типы в Java
Тип данных (название) |
Количество битов |
Пояснение |
Класс- оболочка |
byte |
8 |
Целые числа в диапазоне от –128 до 127 |
Byte |
short |
16 |
Целые числа в диапазоне от –32768 до 32767 |
Short |
int |
32 |
Целые числа в диапазоне от –2147483648 до 2147483647 |
Integer |
long |
64 |
Целые числа в диапазоне от –9223372036854775808 до 9223372036854775807 |
Long |
float |
32 |
Действительные числа. По абсолютной величине изменяются в диапазоне от 3,4 10–38 до 3,4 1038 |
Float |
double |
64 |
Действительные числа двойной точности. По абсолютной величине изменяются в диапазоне от 1,7 10–308 до 1,7 10308 |
Double |
char |
16 |
Символьный тип для представления символьных значений (букв). Диапазон значений от 0 до 65536 (каждое значение соответствует определенному символу) |
Character |
boolean |
– |
Логический тип данных. Переменная этого типа может принимать два значения: true (истина) и false (ложь) |
Boolean |
В этой же таблице приведены названия классов-оболочек для базовых типов. Классы-оболочки используются в тех случаях, когда переменную соответствую- щего типа необходимо рассматривать как объект. Далее изучим каждую группу базовых типов отдельно. В первую очередь стоит обратить внимание на цело- численные типы данных.
В Java существует четыре типа целочисленных данных: byte, short, int и long. Отличаются типы количеством битов, выделяемых для записи значения соот- ветствующего типа. Размер в битах увеличивается от 8 для типа byte до 32 для типа long (с шагом дискретности 8 бит). На практике выбор подходящего типа осуществляется в соответствии с предполагаемым диапазоном изменения зна- чения переменных. Разумеется, для надежности разумно использовать наиболее
«широкий» тип данных, однако при этом не следует забывать и о том, что си- стемные ресурсы даже самого производительного компьютера не безграничны. Для работы с действительными числами используются типы float и double. С помощью этих типов реализуется формат числа с плавающей точкой. В этом формате действительное число задается посредством двух чисел: мантиссы
и показателя степени. Заданное таким образом число равно произведению ман- тиссы на десять в соответствующей второму числу степени. Поскольку размер в битах, выделяемый для типа double, в два раза больше размера для данных типа float, тип double называют типом действительных чисел двойной точности. На практике обычно используется тип double.
Поскольку в Java для символьных данных (тип char) выделяется 16 бит, такая широта размаха позволяет охватить практически все имеющиеся и использую- щиеся на сегодня символы, включая китайские иероглифы. Этот демократизм, свойственный далеко не каждому языку программирования, является следстви- ем курса разработчиков Java на создание универсального языка программи- рования, ориентированного на работу в Интернете. Символам расширенного 8-разрядного набора ISO-Latin-1 соответствует интервал значений от 0 до 255, а интервал значений от 0 до 127 определяет стандартные символы ASCII.
Что касается логического типа boolean, то переменные этого типа могут прини- мать всего два значения: true и false. В свете этого обстоятельства говорить о раз- мере (в битах) переменной типа boolean как-то не принято. В действительности ответ на этот вопрос зависит от типа используемой виртуальной Java-машины. Как правило, логические выражения применяются в условных инструкциях при создании точек ветвления программы.
Указать тип переменной недостаточно. Переменной рано или поздно придется присвоить значение. Делается это с помощью литералов. Литерал — это посто- янное значение, предназначенное для восприятия человеком, которое не может быть изменено в программе. В рассмотренном ранее примере уже использовался строчный литерал — фраза "Мы программируем на Java!". Читатель, вероятно, не удивится, узнав, что целочисленные литералы вводятся с помощью арабских цифр от 0 до 9. Также вводятся действительные числа. При этом в качестве десятичного разделителя используется точка. Символы вводятся в одинарных кавычках (не путать с текстом, который заключается в двойные кавычки!), а для ввода логических значений указывают ключевые слова true и false.
Что касается непосредственно объявления переменных в Java, то выполняется оно по следующим правилам. В первую очередь при объявлении переменной перед ее именем в обязательном порядке указывается идентификатор типа. На- пример, инструкцией int n объявляется переменная n целочисленного типа int. Впоследствии этой переменной может быть присвоено значение. В качестве оператора присваивания в Java используется оператор =. Следующими коман- дами объявляется целочисленная переменная, после чего ей присваивается зна- чение 12:
int n; n=12;
При этом всю означенную конструкцию из двух команд можно объединить в одну инструкцию вида int n=12. Более того, объявлять и инициализировать можно сразу несколько переменных, которые перечисляются через запятую
после идентификатора типа. Сразу при объявлении переменной допускается присваивать ей начальное значение, как показано далее:
long n, m;
int x, y=3, z=5; char sym='a';
В приведенном фрагменте первой инструкцией объявляются две целочислен- ные переменные типа long, после чего следующей командой объявляются три пере- менных типа int, причем для двух из них указано начальное значение. Третьей ко- мандой инициализируется символьная переменная sym со значением a (символы- значения заключаются в одинарные кавычки). Что касается доступности перемен- ных, то она определяется блоком, в котором эта переменная объявлена. Блок, в свою очередь, выделяется парой фигурных скобок (то есть { и }).
Инструкции объявления и инициализации переменных могут размещаться в лю- бом месте программы. Самое главное, чтобы переменная в программе исполь- зовалась (вызывалась) после того, как эта переменная инициализирована (ей присвоено значение). Пример программы, в которой применяются переменные разных типов и литералы, приведен в листинге 1.2.
Листинг 1.2. Переменные и литералы
class VarDemo{
public static void main(String[] args){
// Инициализация переменных: byte age=34;
char sex='м';
double weight=103.6; int height=182;
// Вывод данных:
System.out.println("Персональные данные пользователя:"); System.out.println("Возраст: "+age+" лет"); System.out.println("Пол (м/ж): "+sex+"."); System.out.println("Вес: "+weight+" кг"); System.out.println("Рост: "+height+" см");
}
}
Результат выполнения этой программы:
Персональные данные пользователя: Возраст: 34 лет
Пол (м/ж): м. Вес: 103.6 кг
Рост: 182 см
В программе объявлено с одновременной инициализацией несколько перемен- ных разных типов. Переменные предназначены для хранения персональных данных пользователя (таких как возраст, рост, вес и пол). Выражения в правой
Приведение типов 23
части от операторов присваивания (присваиваемые переменным значения) яв- ляются примерами литералов.
Числовые литералы, кроме обычного десятичного представления, могут быть записаны в восьмеричной и шестнадцатеричной системах счисления. Восьме- ричные литералы начинаются с нуля. Следующие цифры в позиционной записи восьмеричного литерала могут принимать значения в диапазоне от 0 до 7 вклю- чительно. Например, восьмеричный литерал 012 означает десятичное число 10. Шестнадцатеричные литералы начинаются с префикса 0x. Для позиционного представления шестнадцатеричного числа используются цифры от 0 до 9 и бук- вы от А до F. Например, шестнадцатеричный литерал 0x12 означает десятичное число 18.
Наконец, в формате '\xxx' задаются восьмеричные символы Unicode, а в форма- те '\uxxx' — шестнадцатеричные (символами x обозначены позиции кода).
Приведение типов
Я там столкнулся с одним очень нахальным типом.
Из к/ф «Приключения Шерлока Холмса
и доктора Ватсона»
Строгая типизация переменных вместе с очевидными преимуществами при- вносит и ряд не столь очевидных проблем. Поясним это на простом при- мере. Предположим, что в программе объявлены две числовые переменные: одна типа int и другая типа double. Переменным присвоены значения. Далее мы хотим к переменной типа double прибавить значение переменной типа int и результат записать в первую переменную. С формальной точки зрения здесь нет никакой проблемы, поскольку целые числа являются подмноже- ством множества действительных чисел. С точки зрения программной ло- гики ситуация не такая простая, ведь складываются переменные разных ти- пов. Понятно, что на самом деле здесь проблемы не возникает и описанную операцию можно выполнить (в том числе и в Java), причем возможность выполнения подобного рода операций достижима благодаря автоматическо- му приведению типов. Другими словами, если нужно вычислить выражение, в которое входят переменные разных типов, автоматически выполняется пре- образование входящих в выражение переменных к общему формату. Процесс автоматического преобразования типов подчиняется нескольким базовым пра- вилам. Вот они.
Типы переменных, входящих в выражение, должны быть совместимыми. Например, целое число можно преобразовать в формат действительного чис- ла, чего не скажешь о текстовой строке.
Целевой тип (тип, к которому выполняется приведение) должен быть «шире» исходного типа. Другими словами, преобразование должно выполняться без потери данных.
Перед выполнением арифметической операции типы byte, short и char рас- ширяются до типа int.
Если в выражении есть операнды типа long, то расширение осуществляется до типа long.
Если в выражении есть операнды типа float, то расширение осуществляется до типа float.
Если в выражении есть операнды типа double, то расширение осуществляется до типа double.
К этим правилам следует добавить не менее важные правила интерпретации ли- тералов. Действительно, как следует рассматривать, например, число (литерал) 2? Как значение типа int, типа long или, например, типа double? Следующие прави- ла дают ответы на подобные вопросы.
Литералы, обозначающие целые числа, интерпретируются как значения типа
int.
Литералы, обозначающие действительные числа, интерпретируются как зна- чения типа double.
Хотя эти правила представляются логичными и простыми, нередко автоматиче- ское приведение типов приводит к непредсказуемым, на первый взгляд, резуль- татам и ошибкам там, где их быть в принципе не должно. Например, следующая последовательность команд приводит к ошибке:
byte a=1,b=2,c;
// Ошибка: c=a+b;
Ошибку вызывает последняя команда. Хотя все три переменные относятся к типу byte, при вычислении выражения a+b выполняется автоматическое преобразова- ние к типу int. В результате имеет место попытка присвоить значение типа int переменной типа byte. Поскольку в Java преобразования с возможной потерей точности не допускаются, программа с таким кодом не скомпилируется.
Еще один пример ошибки, связанной с автоматическим преобразованием ти- пов:
float x=2.7;
В данном случае проблема связана с тем, что литерал 2.7, использованный для инициализации переменной x типа float, интерпретируется как значение типа double.
Для обработок ошибок подобного рода, а также для ряда других целей в Java предусмотрено явное приведение типов и явное определение типа литерала с помощью суффиксов типа.
Для приведения выражения к нужному типу перед этим выражением указы- вается имя типа, заключенное в круглые скобки. Например, следующий код является корректным:
byte a=1,b=2,c;
// Нет ошибки – явное приведение типа: c=(byte)(a+b);
Командой (byte)(a+b) вычисляется сумма значений переменных a и b, а резуль- тат преобразуется к типу byte. Поскольку в правой части от оператора при- сваивания стоит переменная того же типа, проблем не возникает. Тем не менее следует понимать, что явное приведение типа потенциально опасно, поскольку может приводить к потере значения. Такие ситуации должен отслеживать про- граммист — системой они не отслеживаются.
Аналогичную процедуру можно применять и к литералам. Кроме того, изменять тип литералов можно с помощью суффиксов. Так, суффикс L у целочисленного литерала (например, 123L) означает, что он принадлежит к типу long, а суф- фикс F у литерала, обозначающего действительное число (например, 12.5F), озна- чает, что этот литерал относится к типу float. В свете сказанного корректными являются такие команды:
float x=2.7F;
float x=(float)2.7;
Кроме прочего, явное приведение типов часто используется вместе с операто- ром деления.
В Java, как и в С++, допускается динамическая инициализация переменных. При динамической инициализации значение переменной присваивается при объявлении, причем значением является выражение, содержащее другие пере- менные. Пример динамической инициализации переменной:
int a=3,b=4; int c=a*a+b*b;
В данном случае переменная c инициализируется выражением a*a+b*b, то есть получает значение 25. Главное и единственное условие для динамической ини- циализации — все переменные, входящие в соответствующее выражение, долж- ны быть предварительно объявлены и им должны быть присвоены значения.
Основные операторы Java
Мне кажется, давно уже пора приступить к разработке документа, в котором будет четко оговорено, что граждане могут делать в свое свободное время, а чего они делать не должны.
Из к/ф «Забытая мелодия для флейты»
Все операторы Java можно разделить на четыре группы: арифметические, логиче- ские, побитовые и сравнения. Рассмотрим последовательно каждую группу опе- раторов. Начнем с арифметических. Эти операторы перечислены в табл. 1.2.
Таблица 1.2. Арифметические операторы Java
Оператор |
Название |
Пояснение |
+ |
Сложение |
Бинарный оператор. Результатом команды a+b являет- ся сумма значений переменных a и b |
- |
Вычитание |
Бинарный оператор. Результатом команды a-b являет- ся разность значений переменных a и b |
* |
Умножение |
Бинарный оператор. Результатом команды a*b являет- ся произведение значений переменных a и b |
/ |
Деление |
Бинарный оператор. Результатом команды a/b являет- ся частное от деления значений переменных a и b. Для целочисленных операндов по умолчанию выполняется деление нацело |
% |
Остаток |
Бинарный оператор. Результатом команды a%b яв- ляется остаток от целочисленного деления значений переменных a и b |
+= |
Сложение (упро- щенная форма с присваиванием) |
Упрощенная форма оператора сложения с присваива- нием. Команда a+=b является эквивалентом команды a=a+b |
-= |
Вычитание (упро- щенная форма с присваиванием) |
Упрощенная форма оператора вычитания с присваива- нием. Команда a-=b является эквивалентом команды a=a-b |
*= |
Умножение (упро- щенная форма с присваиванием) |
Упрощенная форма оператора умножения с присваи- ванием. Команда a*=b является эквивалентом команды a=a*b |
/= |
Деление (упро- щенная форма с присваиванием) |
Упрощенная форма оператора деления с присваива- нием. Команда a/=b является эквивалентом команды a=a/b |
%= |
Остаток (упро- щенная форма) |
Упрощенная форма оператора вычисления остатка с присваиванием. Команда a%=b является эквивален- том команды a=a%b |
++ |
Инкремент |
Унарный оператор. Команда a++ (или ++a) является эквивалентом команды a=a+1 |
-- |
Декремент |
Унарный оператор. Команда a-- (или --a) является эквивалентом команды a=a-1 |
Эти операторы имеют некоторые особенности. В первую очередь обращаем вни- мание на оператор деления /. Если операндами являются целые числа, в ка- честве значения возвращается результат целочисленного деления. Рассмотрим последовательность команд:
int a=5,b=2; double x=a/b;
В данном примере переменная x получает значение 2.0, а не 2.5, как можно было бы ожидать. Дело в том, что сначала вычисляется выражение a/b. Поскольку
операнды целочисленные, выполняется целочисленное деление. И только по- сле этого полученное значение преобразуется к формату double и присваивается переменной x.
Для того чтобы при целочисленных операндах выполнялось обычное деление, перед выражением с оператором деления указывается в круглых скобках иден- тификатор типа double (или float). Например, так:
int a=5,b=2;
double x=(double)a/b;
Теперь значение переменной x равно 2.5.
В Java, как и в С++, есть группа упрощенных арифметических операторов с присваиванием. Если op — один из операторов сложения, умножения, деления и вычисления остатка, то упрощенная форма этого оператора с присваиванием имеет вид op=. Это тоже бинарный оператор, как и оператор op, а команда вида x op=y является эквивалентом команды x=x op y.
Еще два исключительно полезных унарных оператора — операторы инкремента (++) и декремента (--). Действие оператора декремента сводится к увеличению на единицу значения операнда, а оператор декремента на единицу уменьшает операнд. Другими словами, команда x++ эквивалентна команде x=x+1, а команда x-- эквивалентна команде x=x-1. У операторов инкремента и декремента есть не только представленная здесь постфиксная форма (оператор следует после опе- ранда: x++ или x--), но и префиксная (оператор располагается перед операндом:
++x или --x). С точки зрения действия на операнд нет разницы в том, префикс- ная или постфиксная формы оператора использованы. Однако если выражение с оператором инкремента или декремента является частью более сложного вы- ражения, различие в префиксной и постфиксной формах операторов инкремента и декремента существует. Если использована префиксная форма оператора, сна- чала изменяется значение операнда, а уже после этого вычисляется выражение. Если использована постфиксная форма оператора, сначала вычисляется выраже- ние, а затем изменяется значение операнда. Рассмотрим небольшой пример:
int n,m; n=10;
m=n++;
В этом случае после выполнения команд переменная n будет иметь значение 11, а переменная m — значение 10. На момент выполнения команды m=n++ значение переменной n равно 10. Поскольку в команде m=n++ использована постфиксная форма оператора инкремента, то сначала выполняется присваивание значения переменной m, а после этого значение переменной n увеличивается на единицу.
Иной результат выполнения следующих команд:
int n,m; n=10;
m=++n;
Обе переменные (n и m) в этом случае имеют значение 11. Поскольку в команде m=++n использована префиксная форма инкремента, сначала на единицу увели- чивается значение переменной n, а после этого значение переменной n присваи- вается переменной m.
Следующую группу образуют логические операторы. Операндами логических операторов являются переменные и литералы типа boolean. Логические опера- торы Java перечислены в табл. 1.3.
Таблица 1.3. Логические операторы Java
Оператор |
Название |
Пояснение |
& |
Логическое И |
Бинарный оператор. Результатом операции A&B явля- ется true, если значения обоих операндов равны true. В противном случае возвращается значение false |
&& |
Сокращенное логическое И |
Бинарный оператор. Особенность оператора, по срав- нению с оператором &, состоит в том, что если значе- ние первого операнда равно false, то значение второго операнда не проверяется |
| |
Логическое ИЛИ |
Бинарный оператор. Результатом операции A|B являет- ся true, если значение хотя бы одного операнда равно true. В противном случае возвращается значение false |
|| |
Сокращенное логическое ИЛИ |
Бинарный оператор. Особенность оператора, по срав- нению с оператором |, состоит в том, что если значе- ние первого операнда равно true, то значение второго операнда не проверяется |
^ |
Исключающее ИЛИ |
Бинарный оператор. Результатом операции A^B являет- ся true, если значение одного и только одного опе- ранда равно true. В противном случае возвращается значение false |
! |
Логическое отрицание |
Унарный оператор. Результатом команды !A является true, если значение операнда A равно false. Если зна- чение операнда A равно true, результатом команды !A является значение false |
Логические операторы обычно используются в качестве условий в условных операторах и операторах цикла.
В табл. 1.4 перечислены операторы сравнения, используемые в Java.
Таблица 1.4. Операторы сравнения Java
Оператор |
Название |
Пояснение |
== |
Равно |
Результатом операции A==B является значения true, если операнды A и B имеют одинаковые значения. В противном случае значением является false |
Оператор |
Название |
Пояснение |
< |
Меньше |
Результатом операции A<B является значения true, если значение операнда A меньше значения операнда B. В противном случае значением является false |
<= |
Меньше или равно |
Результатом операции A<=B является значения true, если значение операнда A не больше значения операн- да B. В противном случае значением является false |
> |
Больше |
Результатом операции A>B является значения true, если значение операнда A больше значения операнда B. В противном случае значением является false |
>= |
Больше или равно |
Результатом операции A>=B является значения true, если значение операнда A не меньше значения опе- ранда B. В противном случае значением является false |
!= |
Не равно |
Результатом операции A!=B является значения true, если операнды A и B имеют разные значения. В про- тивном случае значением является false |
Операторы сравнения обычно используются совместно с логическими операто- рами.
Для понимания принципов работы поразрядных операторов необходимо иметь хотя бы элементарные познания о двоичном представлении чисел. Напомним читателю некоторые основные моменты.
В двоичном представлении позиционная запись числа содержит нули и еди- ницы.
Старший бит (самый первый слева) определяет знак числа. Для положитель- ных чисел старший бит равен нулю, для отрицательных — единице.
Перевод из двоичной системы счисления положительного числа с позицион-
ной записью
bnbn1...b2b1b0
(bi могут принимать значения 0 или 1, старший
бит для положительных чисел bn = 0) в десятичную выполняется так:
0 1 2
n1 n
b0 2
b12
b2 2
... bn12
bn 2 .
Для перевода отрицательного двоичного числа в десятичное представление производится побитовое инвертирование кода (об операции побитового ин- вертирования — см. далее), полученное двоичное число переводится в деся- тичную систему, к нему прибавляется единица (и добавляется знак минус).
Для перевода отрицательного числа из десятичной в двоичную систему от модуля числа отнимают единицу, результат переводят в бинарный код и затем этот код инвертируют.
Умножение числа на два эквивалентно сдвигу влево на один бит позиционной записи числа (с заполнением первого бита нулем).
Побитовые операторы Java описаны в табл. 1.5.
Таблица 1.5. Побитовые операторы Java
Оператор |
Название |
Пояснение |
& |
Побитовое И |
Бинарный оператор. Логическая операция И приме- няется к каждой паре битов операндов. Результатом является 1, если каждый из двух сравниваемых битов равен 1. В противном случае результат равен 0 |
| |
Побитовое ИЛИ |
Бинарный оператор. Логическая операция ИЛИ при- меняется к каждой паре битов операндов. Результатом является 1, если хотя бы один из двух сравниваемых битов равен 1. В противном случае результат равен 0 |
^ |
Побитовое ИСКЛЮЧА- ЮЩЕЕ ИЛИ |
Бинарный оператор. Логическая операция ИСКЛЮЧАЮЩЕЕ ИЛИ применяется к каждой паре битов операндов. Результатом является 1, если один и только один из двух сравниваемых битов равен 1. В противном случае результат равен 0 |
~ |
Побитовое отрицание |
Унарный оператор. Выполняется инверсия двоичного кода: 0 меняется на 1, а 1 меняется на 0 |
>> |
Сдвиг вправо |
Бинарный оператор. Результатом является число, получаемое сдвигом вправо в позиционном пред- ставлении первого операнда (слева от оператора) на количество битов, определяемых вторым операндом (справа от оператора). Исходное значение перво- го операнда при этом не меняется. Младшие биты теряются, а старшие заполняются дублированием знакового бита |
<< |
Сдвиг влево |
Бинарный оператор. Результатом является число, получаемое сдвигом влево в позиционном представле- нии первого операнда (слева от оператора) на количе- ство битов, определяемых вторым операндом (справа от оператора). Исходное значение первого операнда при этом не меняется. Младшие биты заполняются нулями, а старшие теряются |
>>> |
Беззнаковый сдвиг вправо |
Бинарный оператор. Результатом является число, по- лучаемое сдвигом вправо в позиционном представле- нии первого операнда (слева от оператора) на количе- ство битов, определяемых вторым операндом (справа от оператора). Исходное значение первого операнда при этом не меняется. Младшие биты теряются, а старшие заполняются нулями |
&= |
Упрощенная форма побито- вого оператора & с присваиванием |
Команда вида A&=B является эквивалентом команды A=A&B |
|= |
Упрощенная форма побито- вого оператора | с присваиванием |
Команда вида A|=B является эквивалентом команды A=A|B |
Оператор |
Название |
Пояснение |
^= |
Упрощенная |
Команда вида A^=B является эквивалентом команды |
форма побито- |
||
вого оператора ^ |
A=A^B |
|
с присваиванием |
||
>>= |
Упрощенная |
Команда вида A>>=B является эквивалентом команды |
форма побитово- |
||
го оператора >> |
A=A>>B |
|
с присваиванием |
||
<<= |
Упрощенная |
Команда вида A<<=B является эквивалентом команды |
форма побитово- |
||
го оператора << |
A=A<<B |
|
с присваиванием |
||
>>>= |
Упрощенная |
Команда вида A>>>=B является эквивалентом команды |
форма побитово- |
||
го оператора >>> |
A=A>>>B |
|
с присваиванием |
За редким исключением, побитовые операции используются в случаях, когда необходимо оптимизировать программу в отношении быстродействия.
Помимо перечисленных операторов, в Java есть единственный тернарный опе- ратор (у оператора три операнда). Формально оператор обозначается как ?:. Синтаксис вызова этого оператора следующий:
условие?значение_1:значение_2
Первым операндом указывается условие — выражение, возвращающее в качестве значения логическое значение. Если значение выражения-условия равно true, в качестве значения тернарным оператором возвращается значение_1. Если зна- чением выражения-условия является false, тернарным оператором в качестве значения возвращается значение_2.
Несколько замечаний по поводу оператора присваивания (оператор =). В Java оператор присваивания возвращает значение. Команда вида x=y выполняется следующим образом. Сначала вычисляется выражение y, после чего это выра- жение приводится к типу переменной x и затем записывается в эту переменную. Благодаря тому, что, в отличие от других операторов с равными приоритетами, присваивание выполняется справа налево, в Java допустимыми являются ко- манды вида x=y=z. В этом случае значение переменной z присваивается сначала переменной y, а затем значение переменной y присваивается переменной x.
Еще одно замечание касается упрощенных форм операторов с присваиванием, то есть операторов вида op=. Хотя утверждалось, что команда вида A op=B эк- вивалента команде A=A op B, это не совсем так. При выполнении команды вида A op=B сначала вычисляется выражение A op B, затем полученное значение при- водится к типу переменной A и только после этого присваивается переменной A. Поскольку приведение к типу переменной A выполняется, фактически, явно, а в команде A=A op B приведение типов неявное, может проявиться разница
в использовании полной и упрощенной форм команд присваивания. Рассмо- трим простой пример:
byte a=10,b=20;
// Правильно: a+=20;
// Неправильно: a=a+b;
В данном случае команда a+=20 является корректной, а команда a=a+b — нет. В первом случае литерал 20 типа int «насильственно» приводится к типу byte в силу особенностей оператора +=. Во втором случае результат вычисления вы- ражения a+b автоматически расширяется до типа int, а автоматическое приведе- ние типа int к типу byte запрещено.
Напоследок приведем в табл. 1.6 данные о приоритете различных операторов в Java.
Таблица 1.6. Приоритеты операторов в Java
Приоритет |
Операторы |
1 |
Круглые скобки ( ), квадратные скобки [ ] и оператор «точка» |
2 |
Инкремент ++, декремент --, отрицания ~ и ! |
3 |
Умножение *, деление / и вычисление остатка % |
4 |
Сложение + и вычитание - |
5 |
Побитовые сдвиги >>, << и >>> |
6 |
Больше >, больше или равно >=, меньше или равно <= и меньше < |
7 |
Равно == и неравно != |
8 |
Побитовое И & |
9 |
Побитовое исключающее ИЛИ ^ |
10 |
Побитовое ИЛИ | |
11 |
Логическое И && |
12 |
Логические ИЛИ || |
13 |
Тернарный оператор ?: |
14 |
Присваивание = и сокращенные формы операторов вида op= |
Операторы равных приоритетов (за исключением присваивания) выполняются слева направо. В случаях когда возникают сомнения в приоритете операторов и последовательности вычисления выражений, рекомендуется использовать круглые скобки.
