Встроенные типы данных, операции над ними


Приступая к изучению нового языка, полезно поинтересоваться, какие исходные данные могут обрабатываться средствами этого языка, в каком виде их можно задавать, и какие стандартные средства обработки этих данных заложены в язык. Это довольно скучное занятие, поскольку в каждом развитом языке программирования множество типов данных и еще больше правил их использования. Однако несоблюдение этих правил приводит к появлению скрытых ошибок, обнаружить которые иногда бывает очень трудно. Ну что же, в каждом ремесле приходится сначала "играть гаммы", и мы не можем от этого уйти.
Все правила языка Java исчерпывающе изложены в его спецификации, сокращенно называемой JLS. Иногда, чтобы понять, как выполняется та или иная конструкция языка Java, приходится обращаться к спецификации, но, к счастью, это бывает редко, правила языка Java достаточно просты и естественны.
В этой главе перечислены примитивные типы данных, операции над ними, операторы управления, и показаны "подводные камни", которых следует избегать при их использовании. Но начнем, по традиции, с простейшей программы.
  Первая программа на Java
По давней традиции, восходящей к языку С, учебники по языкам программирования начинаются с программы "Hello, World!". He будем нарушать эту традицию. В листинге 1.1 эта программа в самом простом виде, записанная на языке Java.
Листинг 1.1. Первая программа на языке Java;
class HelloWorld{
public static void main(String[] args){ 
System.out.println("Hello, XXI Century World!"); 

}
Вот и все, всего пять строчек! Но даже на этом простом примере можно заметить целый ряд существенных особенностей языка Java.

  • Всякая программа представляет собой один или несколько классов, в этом простейшем примере только один класс (class).
  • Начало класса отмечается служебным словом class , за которым следует имя класса, выбираемое произвольно, в данном случае Helloworld . Все, что содержится в классе, записывается в фигурных скобках и составляет тело класса (class body).
  • Все действия производятся с помощью методов обработки информации, коротко говорят просто метод (method). Это название употребляется в языке Java вместо названия "функция", применяемого в других языках.
  • Методы различаются по именам. Один из методов обязательно должен называться main , с него начинается выполнение программы. В нашей простейшей программе только один метод, а значит, имя ему main .
  • Как и положено функции, метод всегда выдает в результате (чаще говорят, возвращает (returns)) только одно значение, тип которого обязательно указывается перед именем метода. Метод может и не возвращать никакого значения, играя роль процедуры, как в нашем случае. Тогда вместо типа возвращаемого значения записывается слово void , как это и сделано в примере.
  • После имени метода в скобках, через запятую, перечисляются аргументы (arguments) -или параметры метода. Для каждого аргумента указывается его тип и, через пробел, имя. В примере только один аргумент, его тип — массив, состоящий из строк символов. Строка символов — это встроенный в Java API тип string , а квадратные скобки — признак массива. Имя массива может быть произвольным, в примере выбрано имя args .
  • Перед типом возвращаемого методом значения могут быть записаны модификаторы (modifiers). В примере их два: слово public означает, что этот метод доступен отовсюду; слово static обеспечивает возможность вызова метода main () в самом начале выполнения программы. Модификаторы вообще необязательны, но для метода main () они необходимы.

Замечание
В тексте этой книги после имени метода ставятся скобки, чтобы подчеркнуть, что это имя именно метода, а не простой переменной.

  • Все, что содержит метод, тело метода (method body), записывается в фигурных скобках.

Единственное действие, которое выполняет метод main () в примере, заключается в вызове другого метода со сложным именем System.out.println и передаче ему на обработку одного аргумента, текстовой константы "Hello, 2lth century world!" . Текстовые константы записываются в кавычках, которые являются только ограничителями и не входят в состав текста.
Составное имя System.out.println означает, что в классе System , входящем в Java API, определяется переменная с именем out , содержащая экземпляры одного из классов Java API, класса PrintStream , в котором есть метод println() . Все это станет ясно позднее, а пока просто будем писать это длинное имя.
Действие метода println () заключается в выводе своего аргумента в выходной поток, связанный обычно с выводом на экран текстового терминала, в окно MS-DOS Prompt или Command Prompt или Xterm, в зависимости от вашей системы. После вывода курсор переходит на начало следующей строки экрана, на что указывает окончание ln , слово println — сокращение слов print line. В составе Java API есть и метод print () , оставляющий курсор в конце выведенной строки. Разумеется, это прямое влияние языка Pascal.
Сделаем сразу важное замечание. Язык Java различает строчные и прописные буквы, имена main, Main, MAIN различны с "точки зрения" компилятора Java. В примере важно писать String, System с заглавной буквы, a main с маленькой. Но внутри текстовой константы неважно, писать Century или century , компилятор вообще не "смотрит" на нее, разница будет видна только на экране.
Замечание
Язык Java различает прописные и строчные буквы.
Свои имена можно записывать как угодно, можно было бы дать классу имя helloworid или helloworid , но между Java-программистами заключено соглашение, называемое "Code Conventions for the Java Programming Language", хранящееся по адресу http://java.sun.com/docs/codeconv/index.html . Вот несколько пунктов этого соглашения:

  • имена классов начинаются с прописной буквы; если имя содержит несколько слов, то каждое слово начинается с прописной буквы;
  • имена методов и переменных начинаются со строчной буквы; если имя содержит несколько слов, то каждое следующее слово начинается со строчной буквы;
  • имена констант записываются полностью прописными буквами; если имя состоит из нескольких слов, то между ними ставится знак подчеркивания.

Конечно, эти правила необязательны, хотя они и входят в JLS, п. 6.8, но сильно облегчают понимание кода и придают программе характерный для Java стиль.
Стиль определяют не только имена, но и размещение текста программы по строкам, например, расположение фигурных скобок: оставлять ли открывающую фигурную скобку в конце строки с заголовком класса или метода или переносить на следующую строку? Почему-то этот пустяшный вопрос вызывает ожесточенные споры, некоторые средства разработки, например JBuilder, даже предлагают выбрать определенный стиль расстановки фигурных скобок. Многие фирмы устанавливают свой, внутрифирменный стиль. В книге мы постараемся следовать стилю "Code Conventions" и в том, что касается разбиения текста программы на строки (компилятор же рассматривает всю программу как одну длинную строку, для него программа — это просто последовательность символов), и в том, что касается отступов (indent) в тексте.
Итак, программа написана в каком-либо текстовом редакторе, например, Notepad. Теперь ее надо сохранить в файле, имя которого совпадает с именем класса, содержащего метод main () , и дать имени файла расширение Java. Это правило очень желательно выполнять. При этом система исполнения Java будет быстро находить метод main() для начала работы, просто отыскивая класс, совпадающий с именем файла.
Совет
Называйте файл с программой именем класса, содержащего метод main () , соблюдая регистр букв.
В нашем примере, сохраним программу в файле с именем HelloWorld.java в текущем каталоге. Затем вызовем компилятор, передавая ему имя файла в качестве аргумента:
javac HelloWorld.java
Компилятор создаст файл с байт-кодами, даст ему имя Helloworid.class и запишет этот файл в текущий каталог.
Осталось вызвать интерпретатор, передав ему в качестве аргумента имя класса (а не файла):
Java HelloWorld
На экране появится:
Hello, 21st Century World!
Замечание
Не указывайте расширение class при вызове интерпретатора.
.
При работе в интегрированной среде все эти действия вызываются выбором соответствующих пунктов меню или "горячими" клавишами — единых правил здесь нет.
 
Комментарии
В текст программы можно вставить комментарии, которые компилятор не будет учитывать. Они очень полезны для пояснений по ходу программы. В период отладки можно выключать из действий один или несколько операторов, пометив их символами комментария, как говорят программисты, "закомментарив" их. Комментарии вводятся таким образом:

  • за двумя наклонными чертами подряд //, без пробела между ними, начинается комментарий, продолжающийся до конца строки;
  • за наклонной чертой и звездочкой /* начинается комментарий, который может занимать несколько строк, до звездочки и наклонной черты */ (без пробелов между этими знаками).

Комментарии очень удобны для чтения и понимания кода, они превращают программу в документ, описывающий ее действия. Программу с хорошими комментариями называют самодокументированной. Поэтому в Java введены комментарии третьего типа, а в состав JDK — программа javadoc , извлекающая эти комментарии в отдельные файлы формата HTML и создающая гиперссылки между ними: за наклонной чертой и двумя звездочками подряд, без пробелов, /** начинается комментарий, который может занимать несколько строк до звездочки (одной) и наклонной черты */ и обрабатываться программой javadoc . В такой комментарий можно вставить указания программе javadoc , которые начинаются с символа @.
Именно так создается документация к JDK.
Добавим комментарии к нашему примеру (листинг 1.2).
Листинг 1.2. Первая программа с комментариями
/**
* Разъяснение содержания и особенностей программы...
* @author Имя Фамилия (автора)
* @version 1.0 (это версия программы)
*/
class HelloWorld{         // HelloWorld — это только имя 
// Следующий метод начинает выполнение программы 
public static void main(String[] args){   // args не используются 
/* Следующий метод просто выводит свой аргумент
* на экран дисплея */
System.out.println("Hello, 21st Century World!"); 
// Следующий вызов закомментирован, 
// метод не будет выполняться
// System.out.println("Farewell, 20th Century!"); 

}
Звездочки в начале строк не имеют никакого значения, они написаны просто для выделения комментария. Пример, конечно, перегружен пояснениями (это плохой стиль), здесь просто показаны разные формы комментариев.
Константы
В языке Java можно записывать константы разных типов в разных видах. Перечислим их.
Целые
Целые константы можно записывать в трех системах счисления:

  • в десятичной форме: +5, -7, 12345678 ;
  • в восьмеричной форме, начиная с нуля: 027, -0326, 0777 ; в записи таких констант недопустимы цифры 8 и 9;

Замечание
Число, начинающееся с нуля, записано в восьмеричной форме, а не в десятичной.

  • в шестнадцатеричной форме, начиная с нуля и латинской буквы х или X: 0xff0a, 0xFC2D, 0x45a8, 0X77FF ; здесь строчные и прописные буквы не различаются.

Целые константы хранятся в формате типа int (см. ниже).
В конце целой константы можно записать букву прописную L или строчную l , тогда константа будет сохраняться в длинном формате типа long (см. ниже): +25L, -0371, OxffL, OXDFDF1 .
Совет
Не используйте при записи длинных целых констант строчную латинскую букву l , ее легко спутать с единицей.
Действительные
Действительные константы записываются только в десятичной системе счисления в двух формах:

  • c фиксированной точкой: 37.25, -128.678967, +27.035 ;
  • с плавающей точкой: 2.5е34, -0.345е-25, 37.2Е+4 ; можно писать строчную или прописную латинскую букву Е ; пробелы и скобки недопустимы.

В конце действительной константы можно поставить букву F или f , тогда константа будет сохраняться в формате типа float (см. ниже): 3.5f, -45.67F, 4.7e-5f . Можно приписать и букву D (или d ): 0.045D, -456.77889d , означающую тип double , но это излишне, поскольку действительные константы и так хранятся в формате типа double .
Символы
Для записи одиночных символов используются следующие формы.

  • Печатные символы можно записать в апострофах: ' а ', ' N ', ' ? '.
  • Управляющие символы записываются в апострофах с обратной наклонной чертой:
    • ' \n ' — символ перевода строки newline с кодом ASCII 10;
    • ' \r ' — символ возврата каретки CR с кодом 13;
    • ' \f ' — символ перевода страницы FF с кодом 12;
    • ' \b ' — символ возврата на шаг BS с кодом 8;
    • ' \t ' — символ горизонтальной табуляции НТ с кодом 9;
    • ' \\ ' — обратная наклонная черта;
    • ' \" ' — кавычка;
    • ' \' ' — апостроф.
  • Код любого символа с десятичной кодировкой от 0 до 255 можно задать, записав его не более чем тремя цифрами в восьмеричной системе счисления в апострофах после обратной наклонной черты: ' \123 ' — буква S , ' \346 ' — буква Ж в кодировке СР1251. Не рекомендуется использовать эту форму записи для печатных и управляющих символов, перечисленных в предыдущем пункте, поскольку компилятор сразу же переведет восьмеричную запись в указанную выше форму. Наибольший код ' \377 ' — десятичное число 255.
  • Код любого символа в кодировке Unicode набирается в апострофах после обратной наклонной черты и латинской буквы ц ровно четырьмя шестнад-цатеричными цифрами: ' \u0053 ' — буква S , ' \u0416 ' — буква Ж .

Символы хранятся в формате типа char (см. ниже).
Примечание
Прописные русские буквы в кодировке Unicode занимают диапазон от ' \u0410 ' — заглавная буква А , до ' \u042F ' — заглавная Я , строчные буквы от ' \u0430 ' — а , до ' \044F ' — я .
В какой бы форме ни записывались символы, компилятор переводит их в Unicode, включая и исходный текст программы.
Замечание
Компилятор и исполняющая система Java работают только с кодировкой Unicode.
Строки
Строки символов заключаются в кавычки. Управляющие символы и коды записываются в строках точно так же, с обратной наклонной чертой, но, разумеется, без апострофов, и оказывают то же действие. Строки могут располагаться только на одной строке исходного кода, нельзя открывающую кавычку поставить на одной строке, а закрывающую — на следующей.
Вот некоторые примеры:
"Это строка\nс переносом"
"\"Спартак\" — Чемпион!"
Замечание
Строки символов нельзя начинать на одной строке исходного кода, а заканчивать на другой.
Для строковых констант определена операция сцеплений, обозначаемая плюсом.
" Сцепление " + "строк" дает в результате строку "Сцепление строк" .
Чтобы записать длинную строку в виде одной строковой константы, надо после закрывающей кавычки на первой и следующих строках поставить плюс +; тогда компилятор соберет две (или более) строки в одну строковую константу, например:
"Одна строковая константа, записанная "+ 
"на двух строках исходного текста"
Тот, кто попытается выводить символы в кодировке Unicode, например, слово "Россия":
System.out.println("\u0429\u043e\u0441\u0441\u0438\u044f");
должен знать, что Windows 95/98/ME вообще не работает с Unicode, a Windows NT/2000 использует для вывода в окно Command Prompt шрифт Terminal, в котором русские буквы, расположены в начальных кодах Unicode, почему-то в кодировке СР866, и разбросаны по другим сегментам Unicode.
Не все шрифты Unicode содержат начертания (glyphs) всех символов, поэтому будьте осторожны при выводе строк в кодировке Unicode.
Совет
Используйте Unicode напрямую только в крайних случаях.
Имена
Имена (names) переменных, классов, методов и других объектов могут быть простыми (общее название — идентификаторы (idenifiers)) и составными (qualified names). Идентификаторы в Java составляются из так называемых букв Java (Java letters) и арабских цифр 0—9, причем первым символом идентификатора не может быть цифра. (Действительно, как понять запись 2е3 : как число 2000,0 или как имя переменной?) В число букв Java обязательно входят прописные и строчные латинские буквы, знак доллара $ и знак подчеркивания _ , а так же символы национальных алфавитов.
Замечание
Не указывайте в именах знак доллара. Компилятор Java использует его для записи имен вложенных классов.
Вот примеры правильных идентификаторов:
a1      my_var    var3_5   _var    veryLongVarName 
aName   theName   a2Vh36kBnMt456dX
В именах лучше не использовать строчную букву l , которую легко спутать с единицей, и букву о, которую легко принять за нуль.
Не забывайте о рекомендациях "Code Conventions".
В классе Character , входящем в состав Java API, есть два метода, проверяющие, пригоден ли данный символ для использования в идентификаторе: isJavaidentifierStarto , проверяющий, является ли символ буквой Java, и isJavaldentifierPart() ,  выясняющий, является ли символ - буквой или цифрой.
Служебные слова Java, такие как class , void , static , зарезервированы, их нельзя использовать в качестве идентификаторов своих объектов.
Составное имя (qualified name) — это несколько идентификаторов, разделенных точками, без пробелов, например, уже встречавшееся нам имя System.out.println.
 
Примитивные типы данных и операции
Все типы исходных данных, встроенные в язык Java, делятся на две группы: примитивные типы (primitive types) и ссылочные типы (reference types).
Ссылочные типы делятся на массивы (arrays), массы (classes) и интерфейсы (interfaces).
Примитивных типов всего восемь. Их можно разделить на логический (иногда говорят булев) тип boolean и числовые (numeric).
К числовым типам относятся целые (integral [Название "integral" не является устоявшимся термином. Так названа категория целых типов данных в книге The Java Language Specification, Second Edition. James Gosling, Bill Joy, Guy Steele, Gilad Bracha (см. введение). — Ред.]) и вещественные (floating-point) типы.
Целых типов пять: byte , short , int , long , char .
Символы можно использовать везде, где используется тип int , поэтому JLS причисляет их к целым типам. Например, их можно использовать в арифметических вычислениях, скажем, можно написать 2 + 'ж' , к двойке будет прибавляться кодировка Unicode '\u04i6' буквы 'ж' . В десятичной форме это число 1046 и в результате сложения получим 1048.
Напомним, что в записи 2 + "Ж" плюс понимается как сцепление строк, двойка будет преобразована в строку, в результате получится строка "2ж" .
Вещественных типов два: float и double .
Поскольку по имени переменной невозможно определить ее тип, все переменные обязательно должны быть описаны перед их использованием. Описание заключается в том, что записывается имя типа, затем, через пробел, список имен переменных, разделенных запятой. Для всех или некоторых переменных можно указать начальные значения после знака равенства, которыми могут служить любые константные выражения того же типа. Описание каждого типа завершается точкой с запятой. В программе может быть сколько угодно описаний каждого типа.
Замечание для специалистов
Java — язык со строгой типизацией (strongly typed language).
Разберем каждый тип подробнее.
Логический тип
Значения логического типа boolean возникают в результате различных сравнений, вроде 2 > з, и используются, главным образом, в условных операторах и операторах циклов. Логических значении всего два: true (истина) и false (ложь). Это служебные слова Java. Описание переменных этого типа выглядит так:
boolean b = true, bb = false, bool2;
Над логическими данными можно выполнять операции присваивания, например, bool2 = true , в том числе и составные с логическими операциями; сравнение на равенство b == bb и на неравенство b != bb , а также логические операции.
Логические операции
Логические операции:

  • отрицание (NOT) ! (обозначается восклицательным знаком); 
  • конъюнкция (AND) & (амперсанд);
  • дизъюнкция (OR) | (вертикальная черта); 
  • исключающее ИЛИ (XOR) ^ (каре).

Они выполняются над логическими данными, их результатом будет тоже логическое значение true или false . Про них можно ничего не знать, кроме того, что представлено в табл. 1.1.
Таблица 1.1. Логические операции

Словами эти правила можно выразить так: 

  • отрицание меняет значение истинности; 
  • конъюнкция истинна, только если оба операнда истинны; 
  • дизъюнкция ложна, только если оба операнда ложны; 
  • исключающее ИЛИ истинно, только если значения операндов различны.

Замечание
Если бы Шекспир был программистом, фразу "То be or not to be" он написал бы так: 2b | ! 2b.  
Кроме перечисленных четырех логических операций есть еще две логические операции сокращенного вычисления:

  • сокращенная конъюнкция (conditional-AND) && ; 
  • сокращенная дизъюнкция (conditional-OR) || .

Удвоенные знаки амперсанда и вертикальной черты следует записывать без пробелов.
Правый операнд сокращенных операций вычисляется только в том случае, если от него зависит результат операции, т. е. если левый операнд конъюнкции имеет значение true , или левый операнд дизъюнкции имеет значение false .
Это правило очень удобно и ловко используется, например, можно записывать выражения (n != 0) && (m/n > 0.001) или (n == 0) || (m/n > 0.001) не опасаясь деления на нуль.
Замечание

Практически всегда в Java используются именно сокращенные логические операции.

Целые типы
Спецификация языка Java, JLS, определяет разрядность (количество байтов, выделяемых для хранения значений типа в оперативной памяти) и диапазон значений каждого типа. Для целых типов они приведены в табл. 1.2.
Таблица 1.2. Целые типы

Впрочем, для Java разрядность не столь важна, на некоторых компьютерах она может отличаться от указанной в таблице, а вот диапазон значений должен выдерживаться неукоснительно.
Хотя тип char занимает два байта, в арифметических вычислениях он участвует как тип int , ему выделяется 4 байта, два старших байта заполняются нулями.
Примеры определения переменных целых типов:
byte b1 = 50, b2 = -99, bЗ;
short det = 0, ind = 1;
int i = -100, j = 100, k = 9999;
long big = 50, veryBig = 2147483648L;
char c1 = 'A', c2 = '?', newLine = '\n';
Целые типы хранятся в двоичном виде с дополнительным кодом. Последнее означает, что для отрицательных чисел хранится не их двоичное представление, а дополнительный код этого двоичного представления.
Дополнительный же код получается так: в двоичном предс?авлении все нули меняются на единицы, а единицы на нули, после чего к результату прибавляется единица, разумеется, в двоичной арифметике.
Например, значение 50 переменной b1 , определенной выше, будет храниться в одном байте с содержимым 00110010 , а значение -99 переменной b2 — в байте с содержимым, которое вычисляем так: число 99 переводим в двоичную форму, получая 01100011 , меняем единицы и нули, получая 10011100 , и прибавляем единицу, получив окончательно байт с содержимым 10011101 .
Смысл всех этих сложностей в том, что сложение числа с его дополнительным кодом в двоичной арифметике даст в результате нуль, старший бит просто теряется. Это означает, что в такой странной арифметике дополнительный код числа является противоположным к нему числом, числом с обратным знаком. А это, в свою очередь, означает, что вместо того, чтобы вычесть из числа А число В, можно к А прибавить дополнительный код числа В. Таким 'образом, операция вычитания исключается из набора машинных операций.
Над целыми типами можно производить массу операций. Их набор восходит к языку С, он оказался удобным и кочует из языка в язык почти без изменений. Особенности применения этих операций в языке Java показаны на примерах.
 
Операции над целыми типами
Все операции, которые производятся над целыми числами, можно разделить на следующие группы.
Арифметические операции
К арифметическим операциям относятся:

  • сложение + (плюс);
  • вычитание - (дефис);
  • умножение * (звездочка);
  • деление / (наклонная черта — слэш);
  • взятие остатка от деления (деление по модулю) % (процент);
  • инкремент (увеличение на единицу) ++ ;
  • декремент (уменьшение на единицу) --

Между сдвоенными плюсами и минусами нельзя оставлять пробелы. Сложение, вычитание и умножение целых значений выполняются как обычно, а вот деление целых значений в результате дает опять целое (так называемое "целое деление"), например, 5/2 даст в результате 2 , а не 2.5 , а 5/(-3) даст -1 . Дробная часть попросту отбрасывается, происходит усечение частного. Это поначалу обескураживает, но потом оказывается удобным для усечения чисел.
Замечание
В Java принято целочисленное деление.
Это странное для математики правило естественно для программирования: если оба операнда имеют один и тот же тип, то и результат имеет тот же тип. Достаточно написать 5/2.0 или 5.0/2 или 5.0/2.0 и получим 2.5 как результат деления вещественных чисел.
Операция деление по модулю определяется так: а % b = а - (а / b) * b ; например, 5%2 даст в результате 1 , а 5% (-3) даст, 2 , т.к. 5 = (-3) * (-1) + 2 , но (-5)%3 даст -2 , поскольку -5 = 3 * (-1) - 2 .
Операции инкремент и декремент означают увеличение или уменьшение значения переменной на единицу и применяются только к переменным, но не к константам или выражениям, нельзя написать 5++ или (а + b)++ .
Например, после приведенных выше описаний i++ даст -99 , a j—- даст 99 .
Интересно, что эти операции 'можно записать?и перед переменной: ++i , — j . Разница проявится только в выражениях: при первой формe записи (постфиксной) в выражении участвует старое значение переменной и только потом происходит увеличение или уменьшение ее значения. При второй форме записи (префиксной) сначала изменится переменная и ее новое значение будет участвовать в выражении.
Например, после приведенных выше описаний, (k++) + 5 даст в результате 10004 , а переменная k примет значение 10000 . Но в той же исходной ситуации (++k) + 5 даст 10005 , а переменная k станет равной 10000 .
 
Приведение типов
Результат арифметической операции имеет тип int, кроме того случая, когда один из операндов типа long . В этом случае результат будет типа long .
Перед выполнением арифметической операции всегда происходит повышение (promotion) типов byte , short , char . Они преобразуются в тип int , а может быть, и в тип long , если другой операнд типа long . Операнд типа int повышается до типа long , если другой операнд типа long . Конечно, числовое значение операнда при этом не меняется.
Это правило приводит иногда к неожиданным результатам. Попытка откомпилировать простую программу, представленную в листинге 1.3, приведет к сообщениям компилятора, показанным на  3.
Листинг 1.3. Неверное определение переменной
class InvalidDef{
public static void main (String [] args) { 
byte b1 = 50, b2 = -99;
short k = b1 + b2; // Неверно! '
System.out.println("k=" + k);
}
}
Эти сообщения означают, что в файле InvalidDef.java, в строке 4, обнаружена возможная потеря точности (possible loss of precision). Затем приводятся обнаруженный (found) и нужный (required) типы, выводится строка, в которой обнаружена (а не сделана) ошибка, и отмечается символ, при разборе которого найдена ошибка. Затем указано общее количество обнаруженных (а не сделанных) ошибок (1 error).
В таких случаях следует выполнить явное приведение типа. В данном случае это будет сужение (narrowing) типа int до типа short . Оно осуществляется операцией явного приведения, которая записывается перед приводимым значением в виде имени типа в скобках. Определение
short k = (short)(b1 + b2) ;
будет верным.
Сужение осуществляется просто отбрасыванием старших битов, что необходимо учитывать для больших значений. Например, определение
byte b = (byte) 300;
даст переменной b значение 44 . Действительно, в двоичном представлении числа 300 , равном 100101100 , отбрасывается старший бит и получается 00101100 .
Таким же образом можно произвести и явное расширение (widening) типа, если в этом есть необходимость. .
Если результат целой операции выходит за диапазон своего типа int или long , то автоматически происходит приведение по модулю, равному длине этого диапазона, и вычисления продолжаются, переполнение никак не отмечается.
Замечание
В языке Java нет целочисленного переполнения.
Операции сравнения
В языке Java шесть обычных операций сравнения целых чисел по величине: 

  • больше > ; 
  • меньше < ;
  • больше или равно >= ; 
  • меньше или равно <= ; 
  • равно == ; 
  • не равно != .

Сдвоенные символы записываются без пробелов, их нельзя переставлять местами, запись => будет неверной.
Результат сравнения — логическое значение: true , в результате, например, сравнения 3 != 5 ; или false , например, в результате сравнения 3 == 5 .
Для записи сложных сравнений следует привлекать логические.операции. Например, в вычислениях часто приходится делать проверки вида а < х < b . Подобная запись на языке Java приведет к сообщению об ошибке, поскольку первое сравнение, а < х , даст true или false , a Java не знает, больше это, чем b , или меньше. В данном случае следует написать выражение (а < х) && (х < b) , причем здесь скобки можно опустить, написать просто а < х && х < b , но об этом немного позднее.
Побитовые операции
Иногда приходится изменять значения отдельных битов в целых данных. Это выполняется с помощью побитовых (bitwise) операций путем наложения маски. В языке Java есть четыре побитовые операции:

  • дополнение (complement) ~ (тильда); 
  • побитовая конъюнкция (bitwise AND) & ; 
  • побитовая дизъюнкция (bitwise OR) | ; 
  • побитовое исключающее ИЛИ (bitwise XOR) ^ .

Они выполняются поразрядно, после того как оба операнда будут приведены к одному типу int или long , так же как и для арифметических операций, а значит, и к одной разрядности. Операции над каждой парой битов выполняются согласно табл. 1.3.
Таблица 1.3. Побитовые операции

В нашем примере b1 == 50 , двоичное представление 00110010, b2 == -99 , двоичное представление 10011101 . Перед операцией происходит повышение до типа int . Получаем представления из 32-х разрядов для b1 — 0...00110010 , для b2 — 1...l0011101 . В результате побитовых операций получаем:

  • ~b2 == 98 , двоичное представление 0...01100010 ;
  • b1 & b2 == 16 , двоичное представление 0...00010000 ;
  • b1 | b2 == -65 , двоичное представление 1...10111111 ;
  • b1 ^ b2 == -81 , двоичное представление 1...10101111 . 

Двоичное представление каждого результата занимает 32 бита. 
Заметьте, что дополнение ~х всегда эквивалентно (-x)-1 .
Сдвиги
В языке Java есть три операции сдвига двоичных разрядов: 

  • сдвиг влево <<; 
  • сдвиг вправо >>; 
  • беззнаковый сдвиг вправо >>>.

Эти операции своеобразны тем, что левый и правый операнды в них имеют разный смысл. Слева стоит значение целого типа, а правая часть показывает, на сколько двоичных разрядов сдвигается значение, стоящее в левой части.
Например, операция b1<< 2 сдвинет влево на 2 разряда предварительно повышенное значение 0...00110010 переменной b1, что даст в результате 0...011001000, десятичное 200. Освободившиеся справа разряды заполняются нулями, левые разряды, находящиеся за 32-м битом, теряются.
Операция b2 << 2 сдвинет повышенное значение 1...10011101 на два разряда влево. В результате получим 1...1001110100, десятичное значение —396.
Заметьте, что сдвиг влево на п разрядов эквивалентен умножению числа на 2 в степени n.
Операция b1 >> 2 даст в результате 0...00001100, десятичное 12, а b2 >> 2 — результат 1..11100111, десятичное -25, т. е. слева распространяется старший бит, правые биты теряются. Это так называемый арифметический сдвиг.
Операция беззнакового сдвига во всех случаях ставит слева на освободившиеся места нули, осуществляя логический сдвиг. Но вследствие предварительного повышения это имеет эффект только для нескольких старших разрядов отрицательных чисел. Так, b2 >>> 2 имеет результатом 001...100111, десятичное число 1 073 741 799.
Если же мы хотим получить логический сдвиг исходного значения loomoi переменной b2, т. е., 0...00100111, надо предварительно наложить на b2 маску, обнулив старшие биты: (b2 & 0XFF) >>> 2.
Замечание
Будьте осторожны при использовании сдвигов вправо.
Вещественные типы
Вещественных типов в Java два: float и double. Они характеризуются разрядностью, диапазоном значений и точностью представления, отвечающим стандарту IEEE 754-1985 с некоторыми изменениями. К обычным вещественным числам добавляются еще три значения»
1. Положительная бесконечность, выражаемая константой POSITIVE_INFINITY и возникающая при переполнении положительного значения, например, в результате операции умножения 3.0*6е307.
2. Отрицательная бесконечность NEGATIVE_INFINITY .
3. "Не число", записываемое константой NaN (Not a Number) и возникающее при делении вещественного числа на нуль или умножении нуля на бесконечность.
В главе 4 мы поговорим о нихподробнее.
Кроме того, стандарт различает положительный и отрицательный нуль, возникающий при делении на бесконечность соответствующего знака, хотя сравнение о.о == -о.о дает true.
Операции с бесконечностями выполняются по обычным математическим правилам.
Во всем остальном вещественные типы — это обычные, вещественные значения, к которым применимы все арифметические операции и сравнения, перечисленные для целых типов. Характеристики вещественных типов приведены в табл. 1.4.
Знатокам C/C++
В языке Java взятие остатка*от деления %, инкремент ++ и декремент — применяются и к вещественным типам.
Таблица 1.4. Вещественные типы

Примеры определения вещественных типов:
float х = 0.001, у = -34.789; 
double 21 = -16.2305, z2;
Поскольку к вещественным типам применимы все арифметические операции и сравнения, целые и вещественные значения можно смешивать в операциях. При этом правило приведения типов дополняется такими условиями:

  • если в операции один операнд имеет тип double, то и другой приводится к типу double;
  • если один операнд имеет тип float, то и другой приводится к типу float; 
  • в противном случае действует правило приведения целых значений.

 

Операции присваивания


Простоя операция присваивания (simple assignment operator) записывается знаком равенства =, слева от которого стоит переменная, а справа выражение, совместимое с типом переменной:
х = 3.5, у = 2 * (х - 0.567) / (х + 2), b = х < у, bb = х >= у && b.
Операция присваивания действует так: выражение, стоящее после знака равенства, вычисляется и приводится к типу переменной, стоящей слева от знака равенства. Результатом операции будет приведенное значение правой части.
Операция присваивания имеет еще одно, побочное, действие: переменная, стоящая слева, получает приведенное значение правой части, старое ее значение теряется.
В операции присваивания левая и правая части неравноправны, нельзя написать 3.5 = х. После операции х = у изменится переменная х, став равной у, а после у = х изменится у.
Кроме простой операции присваивания есть еще 11 составных операций присваивания (compound assignment operators):
+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>= ; >>>=.
Символы записываются без пробелов, нельзя переставлять их местами.
Все составные операции присваивания действуют по одной схеме:
х ор= а э квивалентно х = (тип х), т. е. (х ор а).
Напомним, что переменная ind типа short определена у нас со значением 1. Присваивание ind +=7.8 даст в результате число 8, то же значение получит и переменная ind. Эта операция эквивалентна простой операции присваивания ind = (short)(ind + 7.8).
Перед присваиванием, при необходимости, автоматически производится приведение типа. Поэтому:
byte b = 1;
b = b + 10; // Ошибка!
b += 10; // Правильно!
Перед сложением ь + 50 происходит повышение ь до типа int, результат сложения тоже будет типа int и, в первом случае, не может быть Присвоен переменной ь без явного приведения типа. Во втором случае перед присваиванием произойдет сужение результата сложения до типа byte.
 
Условная операция
Эта своеобразная операция имеет три операнда. Вначале записывается произвольное логическое выражение, т. е. имеющее в результате true или false, затем знак вопроса, потом два произвольных выражения, разделенных двоеточием, например,
х < 0 ? 0 : х 
х > у ? х  — у : х + у
Условная операция выполняется так. Сначала вычисляется логическое выражение. Если получилось значение true, то вычисляется первое выражение после вопросительного знака ? и его значение будет результатом всей операции. Последнее выражение при этом не вычисляется. Если же получилось значение false, то вычисляется только последнее выражение, его значение будет результатом операции.
Это позволяет написать n == о ? да : m / n не опасаясь деления на нуль. Условная операция поначалу кажется странной, но она очень удобна для записи небольших разветвлений.
Выражения
Из констант и переменных, операций над ними, вызовов методов и скобок составляются выражения (expressions). Разумеется, все элементы выражения должны быть совместимы, нельзя написать, например, 2 + true. При вычислении выражения выполняются четыре правила:
1. Операции одного приоритета вычисляются слева направо: х + у + z вычисляется как (х + у) + z. Исключение: операции присваивания вычисляются справа налево: х = у = z вычисляется как х = (у = z).
2. Левый операнд вычисляется раньше правого.
3. Операнды полностью вычисляются перед выполнением операции.
4. Перед выполнением составной операции присваивания значение левой части сохраняется для использования в правой части.
Следующие примеры показывает особенности применения первых трех правил. Пусть
int а = 3, b = 5;
Тогда результатом выражения ь + (Ь = 3) будет число 8; но результатом выражения (Ь = 3) + ь будет число 6. Выражение ь += (Ь = 3) даст в результате 8, потому что вычисляется как первое из приведенных выше выражений.
Знатокам C/C++
Большинство компиляторов языка C++ во всех этих случаях вычислят значение 8.
Четвертое правило можно продемонстрировать так. При тех же определениях а и ь в результате вычисления выражения ь += а += ь += 7 получим 20. Хотя операции присваивания выполняются справа налево и после первой, правой, операции значение ь становится равным 12, но в последнем, левом, присваивании участвует старое значение ь, равное 5. А в результате двух последовательных вычислений а += b += 7; b += а; получим 27, поскольку во втором выражении участвует уже новое значение переменной ь, равное 12.
Знатокам C/C++
Большинство компиляторов C++ в обоих случаях вычислят 27.
Выражения могут иметь сложный и запутанный вид. В таких случаях возникает вопрос о приоритете операций, о том, какие операции будут выполнены в первую очередь. Естественно, умножение и деление производится раньше сложения и вычитания. Остальные правила перечислены в следующем разделе.
Порядок вычисления выражения всегда можно отрегулировать скобками, их можно Ътавить сколько угодно. Но здесь важно соблюдать "золотую середину". При большом количестве скобок снижается наглядность выражения и легко ошибиться в расстановке скобок. Если выражение со скобками корректно, то компилятор может отследить только парность скобок, но не правильность их расстановки.
 
Приоритет операций
Операции перечислены в порядке убывания приоритета. Операции на одной строке имеют одинаковый приоритет.
1. Постфиксные операции ++ и —.
2. Префиксные операции ++ и —, дополнение ~ и отрицание !.
3. Приведение типа (тип).
4. Умножение *, деление / и взятие остатка %.
5. Сложение + и вычитание -.
6. Сдвиги <<, >>, >>>.
7. Сравнения >, <, >=, <=.
8. Сравнения ==, !=.
9. Побитовая конъюнкция &.
10. Побитовое исключающее ИЛИ ^.
11. Побитовая дизъюнкция | .
12. Конъюнкция &&.
13. Дизъюнкция | | .
14. Условная операция ?: .
15. Присваивания =, +=, -=, *=, /=, %=, &=, ^=, |=, <<, >>, >>>.
Здесь перечислены не все операции языка Java, список будет дополняться по мере изучения новых операций.
Знатокам C/C++
В Java нет операции "запятая", но список выражений используется в операторе цикла for.
 
Операторы
Как вы знаете, любой алгоритм, предназначенный для выполнения на компьютере, можно разработать, используя только линейные вычисления, разветвления и циклы.
Записать его можно в разных формах: в виде блок-схемы, на псевдокоде, на обычном языке, как мы записываем кулинарные рецепты, или как-нибудь еще "алгоритмы". ,-.
Всякий язык программирования должен иметь средства записи алгоритмов. Они называются операторами (statements) языка. Минимальный набор опе-
раторов должен содержать оператор для записи линейных вычислений, условный оператор для записи разветвлении и оператор цикла.
Обычно состав операторов языка программирования шире: для удобства записи алгоритмов в язык включаются несколько операторов цикла, оператор варианта, операторы перехода, операторы описания объектов.
Набор операторов языка Java включает:

  • операторы описания переменных и других объектов (они были рассмотрены выше);
  • операторы-выражения; 
  • операторы присваивания; 
  • условный оператор if;
  • три оператора цикла while, do-while, for;
  • оператор варианта switch;
  • Операторы перехода break, continue и return;
  • блок {};
  • пустой оператор — просто точка с запятой.

Здесь приведен не весь набор операторов Java, он будет дополняться по мере изучения языка.
Замечание
В языке Java нет оператора goto.
Всякий оператор завершается точкой с запятой.
Можно поставить точку с запятой в конце любого выражения, и оно станет оператором (expression statement). Но смысл это имеет только для операций присваивания, инкремента и декремента и вызовов методов. В остальных случаях это бесполезно, потому что вычисленное значение выражения потеряется.
Знатокам Pascal
Точка с запятой в Java не разделяет операторы, а является частью оператора.
Линейное выполнение алгоритма обеспечивается последовательной записью операторов. Переход со строки на строку в исходном тексте не имеет никакого значения для компилятора, он осуществляется только для наглядности и читаемости текста.
 
Блок
Блок заключает в себе нуль или несколько операторов с целью использовать их как один оператор в тех местах, где по правилам языка можно записать только один оператор. Например, {х = 5; у = ?;}. Можно записать и пустой блок, просто пару фигурных скобок {}.
Блоки операторов часто используются для ограничения области действия переменных и просто для улучшения читаемости текста программы.
Операторы присваивания
Точка с запятой в конце любой операции присваивания превращает ее в оператор присваивания. Побочное действие операции — присваивание — становится в операторе основным.
Разница между операцией и оператором присваивания носит лишь теоретический характер. Присваивание чаще используется как оператор, а не операция.
 
Условный оператор
Условный оператор (if-then-else statement) в языке Java записывается так:
if (логВыр) оператор1 else оператор2
и действует следующим образом. Сначала вычисляется логическое выражение логвыр. Если результат true, то действует оператор! и на этом действие условного оператора завершается, оператор2 не действует, далее будет выполняться следующий за if оператор. Если результат false, то действует оператор2, при этом оператор,! вообще не выполняется.
Условный оператор может быть сокращенным (if-then statement):
if (логВыр) оператор!
и в случае false не выполняется ничего.
Синтаксис языка не позволяет записывать несколько операторов ни в ветви then, ни в ветви else. При необходимости составляется блок операторов в фигурных скобках. Соглашения "Code Conventions" рекомендуют всегда использовать фигурные скобки и размещать оператор на нескольких строках с отступами, как в следующем примере:
if (а < х) {
х = а + b; } else {
х = а — b; 
}
Это облегчает добавление операторов в каждую ветвь при изменении алгоритма. Мы не будем строго следовать этому правилу, чтобы не увеличивать объем книги.
Очень часто одним из операторов является снова условный оператор, например:
if (п == 0}{ 
sign = 0;
} else if (n < 0){
sign = -1; 
} else { 
sign = 1;
}
При этом может возникнуть такая ситуация ("dangling else"):
int ind = 5, х = 100;
if (ind >= 10) if (ind <= 20) x = 0; else x = 1;
Сохранит переменная х значение юо или станет равной 1? Здесь необходимо волевое решение, и общее для большинства языков, в. том числе и Java,. правило таково: ветвь else относится к ближайшему слева услдвиюif, не имеющему своей ветви else. Поэтому в нашем примере переменная х останется равной юо.
Изменить этот порядок можно с помощью блока:
if (ind > 10) {if (ind < 20) x = 0; else x = 1;}
Вообще не стоит увлекаться сложными вложенными условными операторами. Проверки условий занимают много времени. По возможности лучше использовать логические операции, например, в нашем примере можно написать
if (ind >= 10 && ind <= 20) х = 0; else х = 1;
В листинге 1.4 вычисляются корни квадратного уравнения ах 2 + bх + с = 0 для любых коэффициентов, в том числе и нулевых.
Листинг 1.4. Вычисление корней квадратного уравнения
class QuadraticEquation{
public static void main(String[] args){
double a = 0.5, Ъ = -2.7, с = 3.5, d, eps=le-8; 
if (Math.abs(a) < eps) 
if (Math.abs(b) < eps)
if (Math.abs(c) < eps) // Все коэффициенты равны нулю
System.out.println("Решение —любое число"); 
else
System.out.println("Решений нет"); 
else
System.out.println("xl = x2 = " +(-c / b) ) ;
else { // Коэффициенты не равны нулю 
if((d = b**b — 4*a*c)< 0.0){ // Комплексные корни 
d = 0.5 * Math.sqrt(-d) / a; 
a = -0.5 * b/ a; 
System.out.println("xl = " +a+ " +i " +d+
",x2 = " +a+ " -i " +d); 
} else {
// Вещественные корни
d =0.5 * Math.sqrt(d) / a; 
a = -0.5 * b / a;
System.out.println("x1 = " + (a + d) + ", x2 = " +(a - d)); 

}
)
}
В этой программе использованы методы вычисления модуля absо и кш; ратного корня sqrt о вещественного числа из встроенного в Java API класса Math. Поскольку все вычисления-С вещественными числами производятся приближенно, мы считаем, что коэффициент уравнения равен нулю, если его модуль меньше 0,00000001. Обратите внимание на то, как в методе println о используется сцепление строк, и на то, как операция присваивания при вычислении дискриминанта вложена в логическое выражение.



Заключение
Уф-ф-ф!! Вот вы и одолели базовые конструкции языка. Раз вы добрались до этого места, значит, умеете уже очень много. Вы можете написать программу на Java, отладить ее, устранив ошибки, и выполнить. Вы способны запрограммировать любой не слишком сложный вычислительный алгоритм, обрабатывающий числовые данные.
Теперь можно перейти к вопросам создания сложных производственных программ. Такие программы требуют тщательного планирования. Сделать это помогает объектно-ориентированное программирование, к которому мы теперь переходим.

 

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г. Яндекс.Метрика