Core Java 2 Том I Глава 3 |
В этой главе...
- Простая программа на языке Java
- Комментарии
- Типы данных
- Переменные
- Операторы
- Строки
- Ввод и вывод
- Поток управления
- Работа с большими числами
- Массивы
Будем считать, что вы успешно инсталлировали JDK. и выполнили простые программы, приведенные в главе 2. Настало время приступать непосредственно к программированию. В этой главе вы узнаете, как в языке Java реализуются основные концепции программирования, например типы данных, ветви и циклы.
К сожалению, на языке Java нелегко написать программу с графическим интерфейсом, — Для этого нужно изучить множество вопросов, связанных с окнами, полями ввода, кнопками и т.п. Поскольку описание таких технических подробностей увело бы нас далеко в сторону от нашей основной цели — анализа основных языковых конструкций, в этой главе мы рассмотрим лишь простые программы, иллюстрирующие то или иное понятие. Все эти программы используют для ввода и вывода данных консоль.
В заключение заметим, что программисты, имеющие опыт работы на языке С/С++- могут просто бегло просмотреть эту главу. Разработчикам, использующим другие языки, например Visual Basic, многие понятия также окажутся знакомыми, хотя синтаксис выражений будет существенно отличаться. Таким читателям мы советуем тщательно изучить эту главу.
Простая программа на языке Java
Рассмотрим самую простую программу, какую только можно себе представить. В процессе работы она лишь выводит сообщение в консольное окно.
/**
@version 1.01 1997-03-22
@author Gary Cornell
*/
/*
This is the first sample program in Core Java Chapter 3
Copyright (C) 1997 Cay Horstmann and Gary Cornell
*/
public class FirstSample
{
public static void main(String[] args)
{
System.out.println("We will not use 'Hello, World!'");
}
}
Этому примеру стоит посвятить столько времени, сколько потребуется, чтобы привыкнуть к особенностям языка; и подробно рассмотреть характерные особенности Java-программ, которые будут встречаться во всех приложениях. Первое, на что надо обратить внимание, — в языке Java учитывается регистр символов. Если вы перепутаете их (например, введете Main вместо main), программа выполняться не будет.
Теперь просмотрим исходный код построчно. Ключевое слово public называется модификатором доступа (access modifier); такие модификаторы управляют обращением к коду из других частей программы. Более подробно мы рассмотрим этот вопрос в главе 5. Ключевое слово class напоминает нам, что все элементы Java-программ находятся в составе классов. Классы будут детально рассматриваться в следующей главе, а пока мы будем считать их некими "контейнерами", в которых' реализована логика программы, определяющая работу приложения. Как указывалось в главе 1, классы — это "строительные блоки", из которых состоят все приложения и аплеты, написанные на языке Java.
За ключевым словом class следует имя класса. Правила формирования имен классов не слишком строги. Имя должно начинаться с буквы, а остальная его часть может представлять собой произвольное сочетание букв и цифр. Длина имени не ограничена. В качестве имени класса нельзя использовать зарезервированные слова языка Java (например, public или class). {Список зарезервированных слов приведен в приложении А.)
Согласно соглашениям об именовании, имя класса должно начинаться с прописной буквы (именно так сформировано имя FirstSample). Если имя состоит из нескольких слов, каждое из них должно начинаться с прописной буквы. (Правила, по которым в середине слова может стоять символ верхнего регистра, иногда называют "camel case", или, в соответствии с этими же правилами, CamelCase.)
Файл, содержащий исходный текст, должен называться так же, как и общедоступный (public) класс, и иметь расширение .java. Таким образом, код рассматриваемого здесь класса мы должны поместить в файл FirstSample. java. (Как и следует ожидать, регистр символов учитывается, поэтому имя firstsample .java не подходит.)
Если вы правильно назвали файл и не допустили ошибок в исходном тексте программы, то в результате компиляции получите файл, содержащий байтовые коды данного класса. Компилятор языка Java автоматически назовет этот файл FirstSample. class и сохранит его в том же каталоге, в котором содержится исходный файл. Осталось выполнить байтовые коды с помощью интерпретатора языка Java, набрав команду "*~java FirstSample
(Расширение .class не указывается!) Выполняясь, программа выведет на экран строку "We will not use 'Hello, World'!".
Когда для запуска скомпилированной программы используется команда java имя_класса
интерпретатор языка Java всегда начинает свою работу с выполнения метода main ( ) указанного класса. Следовательно, чтобы программа могла выполняться, в классе должен присутствовать метод main ( ). Разумеется, в класс можно добавить и другие методы. (Мы покажем, как создавать такие методы, в следующей главе.)
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
В соответствии со спецификацией языка Java метод main ( ) должен быть объявлен как public. (Спецификация языка Java является официальным документом. Его можно скопировать, обратившись по адресу http : / / j ava. sun. com/docs /books/j Is.) Однако некоторые версии интерпретатора Java допускали выполнение программ, даже когда метод main () не имел модификатора public. Эта ошибка была помещена в список замеченных недостатков, представленный на сайте https://developer.java. sun.com/developer/bugParade, и получила номер 4252539. Однако она была помечена как "исправлению не подлежит". Разработчики компании Sun выступили с разъяснениями, что спецификация виртуальной машины языка Java не требует, чтобы метод main( ) был общедоступным (см. Web-страницу https: / /java.sun.com/docs/books/ vmspec), а попытка исправить эту ошибку "может вызвать проблемы". К счастью, здравый смысл в итоге восторжествовал. Интерпретатор языка Java в пакете JDK1.4 требует, чтобы метод main ( ) был общедоступным. Эта история не оставляет равнодушных разработчиков. С одной стороны, становится как-то неуютно от того, что инженеры, призванные гарантировать высокое качество программ, не всегда оказываются квалифицированными специалистами и позволяют себе "отмахиваться" от замеченных ошибок. С другой стороны, стоит отметить тот факт, что компания Sun разместила список ошибок и способы их исправления на Web-сайте, открыв его для всеобщего доступа. Эта информация весьма полезна для программистов. Вы даже можете проголосовать за вашу "любимую" ошибку. Ошибки, набравшие наибольшее число голосов, будут исправлены в следующих выпусках пакета JDK. |
Обратите внимание на фигурные скобки в исходном тексте программы. В языке Java, так же, как и в языке C/C++, фигурные скобки используются для выделения блоков программы, В языке java код любого метода должен начинаться с открывающей фигурной скобки ({) и завершаться закрывающей фигурной скобкой (} ).
Расстановка фигурных скобок всегда вызвала споры. Обычно мы стараемся располагать скобки одну под другой, выравнивая их с помощью пробелов. В то же время компилятор языка Java игнорирует пробелы, поэтому, фигурные скобки можно располагать где угодно. Изучая различные операторы цикла, мы поговорим о скобках более подробно.
Пока мы не будем обращать внимание на ключевые слова static void, считая их просто необходимой частью программы на языке Java. В конце главы 4 мы полностью раскроем смысл этих слов. Сейчас важно помнить, что каждое приложение на языке Java должно иметь метод main ( ), заголовок которого приведен ниже.
public class имя_класса {
public static void main(String[] args) { команды }
}
{{Note|text=
Если вы программируете на языке С++, то, конечно же, знаете, что такое класс. Классы в языке Java похожи на классы в языке C/C++, однако между ними есть существенные различия. Например* в языке Java все методы принадлежат тому или иному классу. (Термин метод в языке Java соответствует термину функция-член в С++.) Следовательно, в языке Java должен существовать класс, которому принадлежит метод main (). Вероятно, вы знакомы с понятием статических фучкций-членов в языке С++. Это функции-члены, определенные внутри класса и не принадлежащие ни одному объекту. Метод main () в языке Java всегда является статическим. В заключение, как и в языке C/C++, ключевое слово void означает, что метод не возвращает никакого значения. В отличие от языка C/C++, метод main {) не передает операционной системе код завершения. Если данный метод корректно завершает свою работу, код завершения равен 0. Чтобы изменить код завершения, надо использовать метод
System.exit ()
Теперь обратите внимание на следующий фрагмент кода:
{
System.out.println("We will not use 'Hello, World!");
}
Фигурные скобки отмечают начало и конец тела метода, содержащего лишь одну строку кода. Как и в большинстве языков программирования, операторы языка Java можно считать предложениями. В языке Java каждый оператор должен заканчиваться точкой с запятой. В частности, символ конца строки не означает конец оператора, поэтому оператор может занимать столько строк, сколько потребуется.
В нашем случае при выполнении метода main () на консоль выводится одна строка текста.
В данном примере мы используем объект System.out и вызываем его метод println (). Заметьте, что метод отделяется от объекта точкой. В общем случае вызов метода имеет следующий вид:
объект*. метод (параметры)
В нашем примере мы вызываем метод println (), передавая ему в.качестве параметра текстовую строку. Метод выводит строку текста на консоль, дополняя ее символом перевода строки. В языке Java, как и в языке С/С++, строковый литерал помещается в двойные кавычки. (Далее в этой главе мы рассмотрим работу со строками подробнее.)
Методам в языке Java, как и функциям в любом другом языке программирования, может передаваться один или несколько параметров; метод также может вызываться без параметров. (В некоторых языках параметры принято называть аргументами). Даже если метод не имеет параметров, после его имени надо ставить скобки. Например, при вызове метода println () без параметров на экран выводится пустая строка. Такой вызов выглядит следующим образом:
System.out.println();
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
В объекте System. out есть метод print (), который выводит строку текста, не добавляя к ней символ перехода на новую строку. Например, выражение System, out.print ("Hello") выводит текст "Hello" и оставляет курсор в конце строки. Следующие данные, выводимые на экран, появятся сразу за буквой "о". |
Комментарии
Комментарии в языке Java, как и в большинстве языков программирования, игнорируются при выполнении программы. Таким образом, в программу можно добавлять столько комментариев, сколько потребуется, не опасаясь увеличить ее объем. В языке Java есть три способа выделения комментариев в тексте. Чаще всего используются две косые черты (//), при этом комментарий начинается сразу за символами //и продолжается до конца строки.
System.out.println("We will not use 'Hello, World!'"); // Остроумно, не правда ли?
Если нужны комментарии, состоящие из нескольких строк, можно каждую строку начинать символами //. Кроме того, для создания больших блоков комментариев можно использовать разделители / * и * /, как показано в листинге 3.1.
/**
@version 1.01 1997-03-22
@author Gary Cornell
*/
/*
This is the first sample program in Core Java Chapter 3
Copyright (C) 1997 Cay Horstmann and Gary Cornell
*/
public class FirstSample
{
public static void main(String[] args)
{
System.out.println("We will not use 'Hello, World!'");
}
}
В заключение отметим, что в языке Java есть и третья разновидность комментариев, которую можно использовать для автоматической генерации документации. Эти комментарии начинаются символами / * * и заканчиваются символами * /. Более подробную информацию об этом виде комментариев и автоматической генерации документации можно найти в главе 4.
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
Комментарии, выделяемые символами /* и */. в языке Java не могут быть вложенными. Это значит, что фрагмент кода нельзя отключить, просто окружив его парами символов /* и */, поскольку в составе этого кода в свою очередь могут содержаться разделители /* и */. |
Типы данных
Язык Java является строю типизированным. Это значит, что тип каждой переменной должен быть объявлен. В языке Java есть восемь основных, или простых типов (primitive types) данных. Четыре из них представляют целые числа, два — действительные числа с плавающей точкой, один — символы в формате Unicode и последний — логические значения.
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
В языке Java предусмотрен пакет для выполнения арифметических действий с произвольной точностью. Однако так называемые "большие числа" в языке Java являются объектами. Позднее в этой главе мы покажем, как с ними работать. |
Целые числа
Целочисленные типы используются для представления как положительных, так и отрицательных чисел, не имеющих дробной части. В языке Java есть четыре целочисленных типа. Они представлены в табл. 3.1.
Таблица 3.1. Целочисленные типы в языке Java
Тип | Требуемый объем памяти (байты) | Диапазон (включительно) |
---|---|---|
int | 4 | от -2147483648 до 2147483647 (больше 2 миллиардов) |
short | 2 | от -32768 до 32767 |
long | 8 | от -9223372036854775808 до -9223372036854775807 |
byte | 1 | от-128 до 127 |
В большинстве случаев тип int наиболее удобен. Если нужно задать количество жителей в самой густонаселенной стране, нет никакой необходимости прибегать к типу long. Типы byte и short в основном используются в специальных приложениях, например, при низкоуровневой обработке файлов или с целью экономии памяти при формировании больших массивов.
В языке Java диапазоны целочисленных типов не зависят от машины, на которой выполняется программа. Это существенно упрощает перенос программного обеспечения с одной платформы на другую. Сравните данный подход с языками С и С++, где для каждого конкретного процессора используется тип, наиболее эффективный именно на нем. В результате программа на языке С, которая отлично работает на 32-разрядном процессоре, может привести к целочисленному переполнению в 16-разрядной системе.
Длинные целые числа имеют суффикс L (например, 4000000000L). Шестнадцате-ричные числа имеют префикс Ох (например, OxCAFE). Восьмеричные числа имеют префикс 0. Например, 010 — это число 8. Такая запись иногда приводит к недоразумениям, поэтому мы не рекомендуем применять восьмеричные числа.
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
В языках С и С++ int означает целочисленный тип, зависящий от машины, для которой предназначена программа. На 16-разрядном процессоре, например процессоре 8086, целые числа занимают 2 байта. На 32-разрядном процессоре, например процессоре Sun SPARK, они занимают 4 байта. На процессоре Intel Pentium размер целого типа в языках С и С++ зависит от операционной системы: в DOS и Windows 3.1 целые числа занимают 2 байта. При использовании 32-разрядного режима работы в системе Windows целые числа занимают 4 байта. В языке Java размеры всех числовых типов не зависят от платформы, на которой выполняется программа, Заметим, что в языке Java нет беззнаковых типов unsigned. |
Числа с плавающей точкой
Типы с плавающей точкой представляют значения, имеющие дробную часть. В языке Java есть два типа для чисел с плавающей точкой, они приведены в табл. 3.2.
Таблица 3.2. Числа с плавающей точкой в языке Java
Тип | Требуемый объем памяти (байты) Диапазон |
---|---|
float | Приблизительно ±3,40282347E+38F (6-7 значащих десятичных цифр) |
double | Приблизительно ±1,7976931348623157E+308F (15 значащих десятичных цифр) |
Имя double означает, что точность этих чисел вдвое превышает точность чисел типа float. (Некоторые называют их числами с двойной точностью.) В большинстве приложений тип double является наиболее удобным. Ограниченной точности чисел типа float во многих случаях попросту недостаточно. Семи значимых (десятичных) цифр, возможно, хватит для того, чтобы точно выразить вашу годовую зарплату в долларах и центах, но не зарплату президента вашей компании. Причины, по которой тип float все еще используется, — это скорость обработки чисел (для чисел типа float она выше) и экономия памяти при хранении (это важно для больших массивов действительных чисел).
Числа типа float имеют суффикс F, например 3 .402F. Числа с плавающей точкой, не имеющие суффикса F (например, 3 . 402), всегда рассматриваются как числа типа double. Для их представления можно (но не обязательно) использовать суффикс D, например 3 . 4 02D.
В JDK 5.0 допустимо задавать числа с плавающей точкой в шестнадцатеричном формате. Например, 0,125— то же, что Oxl.Op-З. В шестнадцатеричной записи для указания степени вместо е используется р.
Все вычисления, производящиеся над числами с плавающей точкой, следуют стандарту IEEE 754. В частности, в языке Java есть три специальных значения с плавающей точкой:
- положительная бесконечность;
- отрицательная бесконечность;
- NaN (не число).
Они используются для обозначения переполнения и ошибок. Например, результат деления положительного числа на 0 равен положительной бесконечности. Вычисление 0/0 или извлечение квадратного корня из отрицательного числа равно NaN.
В языке Java существуют константы Double. positive_infinity, Double. negative_ infinity и Double.NaN (а также соответствующие константы типа float). Однако на практике они редко используются. В частности, для того, чтобы убедиться, что некий результат равен константе Double. NaN, нельзя выполнить проверку
if (х == Double.NAN) // Так нельзя проверить,
// является ли результат числом.
Все величины, "не являющиеся числами", считаются разными. Однако можно вызывать метод Double. isNaN ():
if (Double.isNaN(x)) // Такая проверка допустима.
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
Числа с плавающей точкой нельзя использовать в финансовых вычислениях, где ошибки округления недопустимы. Например, в результате выполнения команды system.out.println(2.0 - 1.1) будет выведено не 0,9, как логично ожидать, а 0,8999999999999999. Подобные ошибки связаны с внутренним двоичным представлением чисел. Подобно тому, как в десятичной системе счисления нельзя точно представить результат деления 1/3, так и в двоичной системе невозможно точно представить 1/10. Если вам надо исключить ошибки округления, следует использовать класс BigDecimal, который будет рассмотрен далее в этой главе. |
Символы
Для того чтобы правильно использовать тип char, надо иметь представление о принципах кодировки Unicode*. До появления Unicode существовало несколько различных стандартов: ASCII, ISO 8859-1, KOI-8, GB18030, BIG-5 и т.д. При этом возникали две проблемы. Во-первых, один и тот же код в разных кодировках соответствовал различным символам. Во-вторых, в языках с большим набором символов использовался код различной длины: часто употребляющиеся символы представлялись одним байтом, другие знаки — двумя, тремя и большим количеством байтов.
Для решения этих проблем была разработана кодировка Unicode. В результате исследований, направленных на унификацию кодов символов, выяснилось, что двухбайтового кода более чем достаточно для представления всех символов, использующихся во всех языках; при этом оставался достаточный резерв для любых мыслимых расширений. В 1991 г. была выпущена спецификация Unicode 1.0, в которой было использовано меньше половины из возможных 65536 кодов. В Java изначально были приняты 16-битовые символы Unicode, что стало еще одним преимуществом перед другими языками.
Однако впоследствии случилось непредвиденное: количество символов превысило допустимые 65536. Причиной тому стали чрезвычайно большие наборы иероглифов китайского, японского и корейского языков. Поэтому в настоящее'время 16-битового типа char недостаточно для описания всех символов Unicode.
Чтобы понять, как эта проблема решается в Java, начиная с JDK 5.0, надо ввести несколько терминов. Назовем кодовой точкой (code point) значение, связанное с символом в схеме кодирования. Согласно стандарту Unicode, кодовые точки записываются в шест-наддатеричном формате и предваряются символами U+. Например, для буквы А кодовая точка равна U+0041. В Unicode кодовые точки объединяются в 17 кодовых плоскостей (code plane). Первая кодовая плоскость, называемая основной многоязыковой плоскостью (basic multilingual plane), состоит из "классических" символов Unicode с кодовыми точками от U+0000 до U+FFFF. Шестнадцать дополнительных плоскостей с кодовыми точками от U+10000 до U+10FFFF содержат дополнительные символы (supplementary character).
Кодировка UTF-16 — это способ представления всех кодов Unicode последовательностью переменной длины. Символы из основной многоязыковой плоскости представляются 1бйбитовыми значениями, называемыми кодовыми единицами (code unit). Дополнительные символы обозначаются последовательными парами кодовых единиц. Каждое из значений пары попадает на используемую 2048-байтовую область основной многоязыковой плоскости, называемой областью подстановки (surrogates area); от U+D800 до U+DBFF для первой кодовой единицы и от U+DC00 до U+DFFF для второй кодовой единицы. Такой подход позволяет сразу определить, соответствует ли значение коду конкретного символа или является ли частью кода дополнительного символа. Например, математическому коду символов, обозначающему множество целых чисел, соответствует кодовая точка U+1D56B и две кодовых единицы, U+D835 и U+DD6B (описание алгоритма кодирования можно найти по адресу https://en.wikipedia.org/wiki/UTF-16).
В Java тип char описывает кодовую единицу TJTF-16.
Начинающим программистам мы советуем использовать коды UTF-16 лишь в случае крайней необходимости. Если это возможно, используйте строки.
В некоторых случаях применение типа char вполне оправдано. Речь идет о работе с символьными константами. Например, символьной константой является 'А', которой соответствует значение 65. Не следует путать символ ' А' со строкой "А", состоящей из одного символа. Кодовые единицы Unicode можно выражать в виде шестнадцатерич-ных чисел в диапазоне от \u0000 до \uFFFF. Например, значение \u2122 соответствует символу торговой марки (™), а \u03C0 — греческой букве тс.
Кроме префикса \и, который предваряет кодовую единицу Unicode, существует также несколько специальных символьных последовательностей, показанных в табл. 3.3. Эти последовательности можно применять в составе символьных констант или строк, например ' \u2122 ' или "Неllо\n". Последовательности, начинающиеся с \u (и никакие другие), можно даже указывать за пределами символьных констант или строк. Приведенный ниже пример корректен, так как последовательности \u005В и \u005D соответствуют символам [ и ].
public static void main(String\u005B\u005D args)
Таблица 3.3. Специальные символы
Специальный символ | Описание | Значение Unicode |
---|---|---|
\b | Возврат на одну позицию | \u0008 |
\t | Табуляция | \u0009 |
\n | Переход на новую строку | \u000a |
\r | Возврат каретки | \u000d |
\ | Двойная кавычка | \u0022 |
\' | Одинарная кавычка | \u0027 |
\\ | Обратная косая черта | \u005c |
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
Теоретически в приложении или аплете на языке Java можно использовать любой символ в формате Unicode, однако будет ли он отображаться на экране дисплея, зависит от вашего браузера (для аплетов) и от операционной системы. |
Логические значения
Для типа boolean предусмотрены два значения: false и true. Они соответствуют результатам вычисления логических выражений. Преобразование логических переменных в целочисленные и наоборот невозможно.
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
В языке С++ вместо логических значений можно использовать числа и даже указатели. Значение 0 эквивалентно логическому значению false, а ненулевые величины — значению true. В языке Java представлять логические значения посредством других типов невозможно. Следовательно, программист на языке Java защищен от недоразумений, подобных следующему:if (х = 0) // Вместо проверки х==0 выполнили присваивание! В языке С++ эта строка компилируется и выполняется проверка, причем выражение всегда равно false. В языке Java наличие такой строки приведет к ошибке на этапе компиляции, поскольку целочисленное выражение х = о нельзя преобразовать а логическое значение. |
Переменные
В языке Java каждая переменная имеет тип. При объявлении переменной сначала указывается ее тип, а затем ее имя. Ниже приведено несколько примеров объявления переменных.
double salary;
int vacationDays;
long earthPopulation;
char yesChar;
boolean done;
Обратите внимание на точку с запятой в конце каждого выражения. Она необходима, поскольку объявление в языке Java считается оператором.
Имя переменной должно начинаться с буквы и представлять собой сочетание букв и цифр. Термины "буквы'* и "цифры" в Java имеют более широкое значение, чем в большинстве других языков программирования. Буквами считаются символы ' А' —' Z', 'a'-'z', '_' и любой другой символ в кодировке Unicode, соответствующий букве. Например, немецкие пользователи в именах переменных могут использовать символ 1 а1, а греческие пользователи могут воспользоваться буквой л. Аналогично цифрами считаются как обычные десятичные цифры, ' 0 ' - ' 9 ', так и любые символы в кодировке Unicode, использующиеся для обозначения цифры в каком-либо языке. Символы наподобие ' +' или ' ©', а также пробел нельзя использовать в именах переменных. Все символы в имени переменной важны, причем регистр также учитывается. Длина имени переменной не ограничена.
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
Если вы действительно хотите знать, какие символы в формате Unicode считаются "буквами" в языке Java, воспользуйтесь методами isJavaldentifierStart () и isJavaldentifierPart() класса Character. |
В качестве имен переменных нельзя использовать зарезервированные слова, (Список зарезервированных слов приведен в приложении А.)
В одной строке программы можно размещать несколько объявлений, например:
int i,j; // Обе переменные — целочисленные.
Однако мы не рекомендуем следовать такому стилю. Если объявить каждую переменную в отдельной строке, читать программу станет гораздо легче.
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
Как уже было сказано, в языке Java различаются прописные и строчные буквы, например имена hireday и hireDay считаются разными. Иногда для переменной трудно подобрать подходящее имя. Многие программисты в этих случаях дают переменной имя, совпадающее с именем типа, но отличающееся регистром символов. Например: Box box; |
Инициализация переменных
После объявления переменной ее нужно инициализировать с помощью оператора присваивания, поскольку использовать переменную, которой не присвоено никакого значения, невозможно. Например, приведенный ниже фрагмент кода будет признан ошибочным уже на этапе компиляции.
int vacationDays;
System.out.printin(vacationDays); // Ошибка!
// Переменная не инициализирована.
Для присвоения ранее объявленной переменной какого-либо значения нужно указать слева ее имя, поставить знак равенства (=), а справа записать некоторое выражение на языке Java, задающее требуемое значение.
int vacationDays; vacationDays = 12;
При желании вы можете одновременно объявить и инициализировать переменную. Например:
int vacationDays = 12;
В языке Java объявление переменной можно размещать в любом месте кода; например, приведенный ниже фрагмент вполне допустим.
double salary = 65000.0; System.out.println(salary);
int vacationDays =12; // Здесь можно объявить переменную.
При создании программ на Java рекомендуется объявлять переменную как можно ближе к той точке кода, где она будет использована.
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
В языках С и С++ различаются объявление и определение переменной. Ниже приведен пример определения переменной. int i = 10; Объявление переменной выглядит следующим образом: extern int i; В языке Java объявление и определение переменных не различаются. |
Константы
В языке Java для обозначения констант используется ключевое слово final, например:
public class Constants {
public static void main(String[] args) I
final double CM_PER_IHCH = 2.54; double paperWidth = 8.5; double PaperHeight = 11;
System.out.printIn("Размер страницы в сантиметрах: " + paperWidth * CM_PER_INCH + "на" + paperheight * CM_PER_INCH);
}
}
Ключевое слово final означает, что присвоить какое-либо значение данной переменной можно лишь один раз и изменять его нельзя. Использовать в именах констант только прописные буквы необязательно.
В языке Java часто возникает необходимость в константах, доступных нескольким методам внутри одного класса. Обычно они называются константами класса (class constant). Константы класса объявляются с помощью ключевых слов static final. Ниже приведен пример использования константы класса.
public class Constants2 {
public static void main(String[] args) {
double paperWidth = * 8.5; double PaperHeight = 11;
System.out.println{"Размер страницы в сантиметрах: " + paperWidth * CM_PER_INCH + "на" + paperHeight * CM_PER_INCH);
}
public static final double CM_PER_INCH =2.54;
}
Константа класса задается вне метода main (), поэтому ее можно использовать в других методах того же класса. Более того, если (как в данном примере) константа объявлена как public, методы из других классов также могут получить к ней доступ. В нашем примере это можно сделать с помощью выражения Constants2 . CM_PER_INCH.
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
В языке Java слово const является зарезервированным, однако сейчас оно уже не употребляется. Для объявления констант следует использовать ключевое слово final. |
Операторы
Для обозначения операций сложения, вычитания, умножения и деления в языке Java используются обычные арифметические операторы + - * /. Оператор / обозначает целочисленное деление, если оба его аргумента являются целыми числами. В противном случае этот оператор обозначает деление чисел с плавающей точкой. Остаток от деления целых чисел обозначается символом %. Например, 15/2 равно 7; 15%2 равно 1, а 15.0/2 = 7.5.
Заметим, что целочисленное деление на 0 генерирует исключение, в то время как результатом деления на 0 чисел с плавающей точкой является бесконечность или NaN.
В языке Java предусмотрена сокращенная запись бинарных арифметических операторов (т.е. операторов, предполагающих два операнда). Например, выражение
х += 4; эквивалентно х = х + 4,- (В сокращенной записи символ арифметической операции, например * или %, размещается перед знаком равенства, например *= или %=.)
Одной из заявленных целей языка Java является машинная независимость. Вычисления должны приводить к одинаковому результату, независимо от того, какая виртуальная машина их выполняет. Для арифметических вычислений над числами с плавающей точкой это неожиданно оказалось трудной задачей. Тип double использует для хранения числовых значений 64 бита, однако некоторые процессоры применяют 80-разрядные регистры с плавающей точкой. Эти регистры обеспечивают дополнительную точность на промежуточных этапах вычисления. Рассмотрим в качестве примера следующее выражение:
double w = х * у / z;
Многие процессоры компании Intel вычисляют выражение х * у и сохраняют этот промежуточный результат в 80-разрядном регистре, затем делят его на значение переменной z и в самом конце округляют ответ до 64 бит. Так можно повысить точность вычислений, избежав переполнения. Однако этот результат может оказаться иным, если в процессе всех вычислений используется 64-разрядный процессор. По этой причине в первоначальном описании виртуальной машины Java указывалось, что все промежуточные вычисления должны округляться. Это вызвало протест многих специалистов. Округление не только может привести к переполнению. Вычисления при этом происходят медленнее, поскольку операции округления занимают определенное время. В результате разработчики языка Java изменили свое мнение, стремясь разрешить конфликт между оптимальной производительностью и воспроизводимостью результатов. По умолчанию в виртуальной машине 8 промежуточных вычислениях может использоваться повышенная точность. Однако методы, помеченные ключевым словом strictfp, должны использовать операции над числами с плавающей точкой, гарантирующие воспроизводимость результатов. Например, метод main О можно записать так, как показано ниже.
public static strictfp void main(String[] args)
В этом случае все команды внутри метода main {) будут выполнять точные операции над числами с плавающей точкой.
Детали выполнения этих операций тесно связаны с особенностями работы процессоров Intel. По умолчанию промежуточные результаты могут использовать расширенный показатель степени, но не расширенную мантиссу. (Микросхемы компании Intel поддерживают округление мантиссы без потери производительности.) Следовательно, единственное различие между вычислениями по умолчанию и точными вычислениями состоит в возможности переполнения.
Если сказанное кажется вам слишком сложным, не волнуйтесь. Переполнение при вычислениях чисел с плавающей точкой в большинстве случаев не возникает. В примерах, рассматриваемых в этой книге, ключевое слово strictfp использоваться не будет.
Операторы инкрементирования и декрементирования
Программисты, конечно, знают, что одной из наиболее распространенных операций с числовыми переменными является добавление или вычитание единицы. В языке Java, как и в языках С и С++, есть операторы инкрементирования и декрементирования: в результате вычисления выражения х++ к текущему значению переменной х прибавляется единица, а х—уменьшает х на единицу. Например, в результате обработки следующего фрагмента значение переменной n становится равным 13.
int n = 12; n++;
Поскольку эти операторы ++ и -- изменяют значение переменной, их нельзя применять к самим числам. Например, выражение 4++ является недопустимым.
Существуют два вида операторов инкрементирования и декрементирования. Bbinie показана постфиксная форма, в которой символы операции размещаются после операнда. Есть и префиксная форма — ++п. Оба этих оператора изменяют значение переменной на единицу. Разница между ними проявляется, только когда эти операторы присутствуют в выражениях. Префиксная форма сначала изменяет значение переменной и использует новое значение для дальнейших вычислений, а постфиксная форма использует старое значение этой переменной и лишь после этого изменяет его.
int m = 7; int.n = 7;
int а = 2 * // Теперь значение а равно 16, am равно 8.
int b = 2 * n++; // Теперь значение b равно 14, an равно 8.
Мы не рекомендуем использовать операторы инкрементирования и декрементирования внутри выражений, поскольку это зачастую запутывает код и приводит к досадным ошибкам.
(Поскольку именно оператор ++ дал имя языку С++, это послужило поводом к первой шутке о нем. Недоброжелатели отмечают, что даже имя этого языка содержит в себе ошибку: "Язык следовало бы назвать ++С, потому что мы хотим использовать его только после улучшения".)
Операторы отношения и логические операторы
Язык Java содержит полный комплекг операторов отношения. Чтобы проверить равенство, следует использовать символы ==. Например, значение выражения 3 == 7 равно false.
Для проверки неравенства используются символы ! =. Так, значение выражения 3 ! = 7 равно true.
Кроме того, в языке Java есть обычные операторы < (меньше), > (больше), <= (меньше или равно) и => (больше или равно).
В языке Java, как и в С++, используются символы && для обозначения логического оператора "и", а также символы ] j для обозначения логического оператора "или". Как обычно, знак восклицания означает логический оператор отрицания. Операторы && и | | задают вычисление по сокращенной схеме, согласно которой, если первый элемент определяет значение всего выражения, то остальные элементы не вычисляются. Рассмотрим два выражения, объединенных оператором &&.
выражение_1 && выражение_2
Если первое выражение ложно, то вся конструкция не может быть истинной. Поэтому второе выражение вычислять нет смысла. Например, в приведенном ниже выражении вторая часть не вычисляется, если значение переменной х равно нулю.
х!=0 && 1/х > х+у // Не делить на 0.
Таким образом, деление на нуль не происходит.
Аналогично, значение выражение_1 || выражение_2 истинно, если истинным является значение первого выражения. В этом случае вычислять второе выражение нет необходимости.
В языке Java есть также тернарный оператор ? :, который иногда оказывается полезным.
условие ? выражение_1 : выражение_2
Если условие истинно, то вычисляется первое выражение, а если ложно — второе выражение. Например, х < у ? х : у возвращает меньшее из чисел х и у.
Побитовые операции
Работая с любыми целочисленными типами, можно применять операторы, непосредственно обрабатывающие биты, из которых состоят целые числа. Это значит, что для определения состояния отдельных битов числа можно использовать маски. В языке Java есть следующие .побитовые операторы: & ("и") , | ("или"), ^ ("исключающее или"), ~ ("не"). Например, если n — это целое число, то приведенное ниже выражение равно единице только в том случае, если четвертый бит в двоичном представлении числа равен единице.
int fourthBitFromRight = (n & 8) / 8;
Используя оператор & в сочетании с соответствующей степенью двойки, можно замаскировать все биты, кроме одного.
В применении к логическим переменным операторы & и | дают логические значения.
Эти операторы аналогичны операторам && и | |, за исключением того, что вычисление производится
по полной схеме, т.е. обрабатываются все элементы выражения.
В языке Java есть также операторы >> и <<, сдвигающие битовое представление числа вправо или влево. Эти операторы часто оказываются удобными, если нужно построить битовое представление на основе маски:
int fourthBitFromRight = (n & . {1 << 3)) >> 3;
В языке есть даже оператор >>>, заполняющий старшие разряды нулями, в то время как оператор >> восстанавливает в старших разрядах знаковый бит. Оператора <<< в языке Java нет.
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
Значение, присутствующее в правой части операторов побитового сдвига, сокращается по модулю 32 (если левая часть является числом типа long, правая часть сокращается по модулю 64). Например, значение выражения 1<<35 эквивалентно выражению 1<<3, или 8. |
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
В языках C/C++ не определено, какой сдвиг выполняет оператор >> : арифметический (при котором знаковый бит восстанавливается) или логический (при котором старшие разряды заполняются нулями). Разработчики средств реализации языка могут выбрать тот вариант, который покажется им более эффективным. Это значит, что результат выполнения операции сдвига вправо в языке C/C++ определен лишь для неотрицательных чисел. В языке Java данная проблема разрешена путем ввода оператора >>>. |
Математические функции и константы
Класс Math содержит набор математических функций, которые часто оказываются необходимыми при решении практических задач.
Чтобы извлечь квадратный корень из числа, применяют метод sqrt ().
double х = 4;
double у = Math.sqrt{x);;
System.out.println(y); // Выводит число 2.0.
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
Между методами println () и sqrt () есть небольшая разница. Метод println () принадлежит объекту system.out. (Напомним, что out — это объект, определенный в классе system и представляющий стандартное устройство вывода.) Метод sqrt () принадлежит классу Math, а не объекту. Такие методы называются статическими. Они будут рассматриваться в главе 4. |
В языке Java нет оператора возведения в степень: для этого нужно использовать метод pow () класса Math. В результате выполнения следующей строки кода переменной у присваивается значение переменной х, возведенное в степень а .
double у = Math.pow(x,а);
Оба параметра метода pow (), а также возвращаемое им значение имеют тип double.
Класс Math содержит методы для вычисления обычных тригонометрических функций:
Math.sin()
Math.cos()
Math.tan()
Math.atan()
Math.atan2()
Кроме того, в него включены экспоненциальная и обратная к ней логарифмическая функции (натуральный логарифм):
Math.ехр
Math.log
В данном классе также определены две константы — приближенное представление чисел п и e.
Math.PI
Math.E
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
Начиная с JDK 5.0, при вызове методов для математических вычислений класс Math можно не указывать, включив вместо этого в начало файла с исходным кодом следующее выражение: import static java.lang.Math.* ; Например, при компиляции приведенной ниже строки кода ошибка не возникает. System.out.printin("The square root of \u03C0 is " + sqrt(PI)); Подробно вопросы статического импортирования мы рассмотрим в главе 4. |
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
Для повышения своей производительности функции в классе Math используют программы из аппаратного модуля, предназначенного для вычислений с плавающей точкой. Если для вас не очень существенна скорость работы, а важнее получить предсказуемые результаты, используйте класс strictMath. Он реализует алгоритмы из библиотеки fdlibm, гарантирующей идентичность результатов на всех платформах. Исходные тексты программ, реализующих эти алгоритмы, можно найти на Web-странице https://www.netlib.org/fdlibm/index.html. (Поскольку в библиотеке fdlibm каждая функция определена неоднократно, в классе strictMath в соответствии с IEEE 754 имена функций начинаются с буквы "е".) |
Преобразование числовых типов
Часто возникает необходимость преобразовать один числовой тип в другой. На рис. 3.1 показаны допустимые преобразования.
Рис. 3.1. Допустимые преобразования числовых типов
Шесть сплошных линий со стрелками обозначают преобразования, которые выполняются без потери информации. Три штриховые линии, также со стрелками, означают преобразования, при которых может произойти потеря точности. Например, количество цифр в длинном целом числе 123456789 превышает количество цифр, которое может быть представлено типом float. Число, преобразованное в тип float, имеет тот же порядок, но несколько меньшую точность.
int n = 123456789;
float f = n; // Содержимое f равно 1.234567892Е8.
Если два значения объединяются бинарным оператором (например, n+f, где n — целое число, a f — число с плавающей точкой), то перед выполнением операции оба операнда преобразовываются в числа одинакового типа.
- Если хотя бы один из операндов имеет тип double, то второй тоже преобразовывается в число типа double.
- В противном случае, если хотя бы один из операндов имеет тип float, то второй тоже преобразовывается в тип float.
- В противном случае, если хотя бы один из операндов имеет тип long, то второй тоже преобразовывается в число типа long.
- В противном случае оба операнда преобразовываются в числа типа int.
Приведение числовых типов
Как уже было сказано, при необходимости значения типа int автоматически преобразовываются в значения типа double. С другой стороны, в ряде ситуаций число типа double должно рассматриваться как целое. Преобразования чисел в языке Java возможны, однако, разумеется, при этом может происходить потеря информации. Такие преобразования называются приведением типов (cast). Синтаксически приведение типа задается парой скобок, внутри которых указывается желательный тип, а затем имя переменной. Например:
double х = 9.997;
int nx = (int)x;
Теперь в результате приведения значения с плавающей точкой к целому типу переменная nх равна 9, поскольку при этом дробная часть числа отбрасывается.
Если нужно округлить число с плавающей точкой до ближайшего целого числа (что во многих случаях является намного более полезным), используется метод Math.round ().
double х = 9.997;
int nx = (int)Math.round(x);
Теперь переменная nx равна 10. При вызове метода round () по-прежнему нужно выполнять приведение (int), поскольку возвращаемое им значение имеет тип long.
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
При попытке приведений типов результат может выйти за пределы допустимого диапазона. В этом случае произойдет усечение. Например, при вычислении выражения (byte) 300 будет получено значение 44. |
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
Приведение логических значений к целым и наоборот невозможно. Это предотвращает появление ошибок. В редких случаях, когда действительно необходимо представить логическое значение в виде целого, можно использовать условное выражение b ? 1 : 0. |
Иерархия операторов
В табл. 3.4 приведена информация о приоритете операторов. Если скобки не используются, сначала выполняются более приоритетные операции. Операторы, находящиеся на одном уровне иерархии, выполняются слева направо, за исключением операторов, имеющих правую ассоциативность, как показано в таблице. Например, поскольку оператор && приоритетнее | | выражение а && b | | с эквивалентно (а && b) || с. Так как оператор+= ассоциируется справа налево, выражение а +-b += с означает а += (b += с). В данном случае значение b += с (значение b после прибавления к нему значения с) присваивается переменной а.
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
В отличие от языков С и С++, язык Java не имеет оператора "запятая". Однако в операторе for в качестве первого и третьего операторов можно использовать список выражений, разделенных запятыми. |
Нумерованные типы
В некоторых случаях переменной должны присваиваться лишь значения из ограниченного набора. Предположим, например, что вы продаете пиццу четырех размеров: малого, среднего, большого и очень большого. Конечно, вы можете представить размеры целыми числами, например 1, 2, 3 и 4, или буквами S, М, L и X. Однако такой подход чреват ошибками. В процессе составления программы можно присвоить переменой недопустимое значение, например 0 или m.
В JDK 5.0 появилась возможность использовать в подобных ситуациях нумерованный тип. Например, вы можете включить в программу приведенное ниже выражение.
enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE };
После этого можно определять переменные данного типа. Size S = Size.MEDIUM;
Переменная типа Size может содержать только предопределенные значения. Допустимо также значение null, указывающее на то, что данной переменной не присвоена никакая величина.
Подробно нумерованные типы будут рассмотрены в главе 5.
Строки
Строка Java— это последовательность символов Unicode. Например, строка "Java\u2122 " состоит из пяти символов: J, a, v, а и ™. В языке Java нет встроенного типа для строк. Вместо этого стандартная библиотека языка содержит класс String. Каждая строка, помещенная в кавычки, представляет собой экземпляр класса String.
String е = ""; // Пустая строка.
String greeting = "Hello";
Кодовые точки и кодовые единицы
В языке Java строки реализованы как последовательности значений типа char. Как было сказано ранее, тип char позволяет задавать кодовые единицы, представляющие кодовые точки Unicode в кодировке UTF-16. Наиболее часто используемые символы Unicode представляются одной кодовой единицей. Дополнительные символы задаются парами кодовых единиц.
Метод length () возвращает число кодовых единиц для данной строки в кодировке UTF-16. Ниже приведен пример использования данного метода.
String greeting = "Hello";
int n = greeting.length(); // Значение n равно 5.
Чтобы определить реальную длину, представляющую собой число кодовых точек, надо использовать следующий вызов:
int cpCount = greeting.codePointCount(0, greeting.length());
Метод s . char At (n) возвращает кодовую единицу в позиции п, где п находится в интервале от 0 до s. length () - 1. Ниже приведены примеры вызова данного метода.
char first = greeting.charAt(0); // Первый символ - 'Н'
char last = greeting.charAt(4); // Последний символ - 'о'
Для получения i-й кодовой точки надо использовать приведенные ниже выражения.
int index = greeting.offsetByCodePoints(0, i);
int cp = greeting.codePointAt(index);
Использование кодовых единиц может привести к недоразумениям. Рассмотрим приведенную ниже строку.
Z is the set of integers
Для представления символа Z используются две кодовые единицы UTF-16. Приведенный ниже вызов метода даст не код пробела, а второй код символа Z. Чтобы избежать возникновения данной проблемы, не следует использовать тип char, так как он представляет символы на слишком низком уровне,
char ch = sentence.charAt(i);
Если вы хотите просмотреть строку посимвольно, т.е. получить по очереди каждую кодовую точку, вам надо использовать фрагмент кода, подобный приведенному ниже,
int cp = sentence.codePointAt(i);
if (Character.isSupplementaryCodePoint(cp)) i += 2;
else i++;
Метод codePointAt () определяет, является ли кодовая единица первой или второй частью дополнительного символа, и всегда возвращает корректный результат. Поэтому вы можете организовать просмотр строки и в обратном направлении.
i --
int cp = sentence.codePointAt(i);
if (Character. is Supplement aryCodePoint (cp)) i --;
Подстроки
С помощью метода substring () класса String можно выделить подстроку данной строки. Например, в результате выполнения приведенного ниже кода формируется строка "Hel".
String greeting = "Hello";
String s = greeting.substring(0, 3);
Второй параметр метода substring ( ) — это первая кодовая единица, которую не следует включать в состав подстроки. В данном примере мы хотим скопировать символы из позиций 0,1 и 2, поэтому задаем при вызове метода substring ( ) значения 0 и 3.
Описанный способ вызова метода substring ( ) имеет положительную особенность: подсчет кодовых единиц в подстроке осуществляется исключительно просто. Строка s.substring {а, b) всегда содержит b - а кодовых единиц. Например, в сформированной выше строке "Hel" содержится 3 - 0 = 3 кодовых единиц.
Изменение строк
В классе String отсутствуют методы, которые позволяли бы изменять символы в существующих строках. Если, например, вы хотите изменить строку greeting с "Hello" на "Help ! ", то заменить требуемые два символа невозможно. Для разработчиков, использующих язык С, это звучит, по крайней мере, странно. "Как же модифицировать строку?" — спросят они. В Java внести необходимые изменения можно, выполнив конкатенацию подстроки greeting и символов "р! "
greeting = greeting.substring(0, 3) + "р!";
В результате текущим значением переменной greeting становится строка " Help! ".
Поскольку, программируя на языке Java, вы не можете изменять отдельные символы в строке, в документации для описания объектов String используется термин неизменяемые, или немодифицируемие (immutable). Как число 3 всегда равно 3, так и строка "Hello " всегда содержит символы ' H ', 'е \ '1', '1' и 'о'. Изменить эти значения невозможно. Однако, как мы только что убедились, можно изменить содержимое строковой переменной greeting и заставить ее ссылаться на другую строку так же, как числовой переменной, в которой хранится число 3, можно присвоить число 4.
Не снижается ли при этом эффективность? Кажется, было бы намного проще изменять символы, чем создавать новую строку заново. Возможно, это и так. Действительно, неэффективно создавать новую строку посредством конкатенации строк "Hel" и "р! ". Однако неизменяемые строки имеют одно большое преимущество: компилятор может делать строки совместно используемыми.
Чтобы понять этот принцип, представьте, что в совместно используемом пуле находятся разные строки. Строковые переменные указывают объекты в этом пуле. При копировании строки переменной и оригинал, и копия содержат одну и ту же последовательность символов. Логично не прибегать к дублированию строк, а поместить в переменные ссылки одну и ту же область памяти. Одним словом, разработчики языка Java решили, что эффективность совместного использования памяти перевешивает неэффективность редактирования строк путем выделения подстрок и конкатенации.
Посмотрите на свою программу; мы подозреваем, что большую часть времени вы проводите, не изменяя строки, а сравнивая их. Разумеется, бывают случаи, когда непосредственные манипуляции со строками более эффективны. (Одна из таких ситуаций возникает, когда нужно образовать строку из отдельных символов, поступающих из файла или клавиатуры.) Для этих ситуаций в языке Java предусмотрен отдельный класс StringBuffer, который будет описан в [Core Java 2 Том I Глава 12 | главе 12]. Если же эффективность обработки строк для вас не важна (как это часто случается во многих приложениях на языке Java), вы можете не применять класс StringBuffer, а использовать лишь класс String.
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
Когда программисты, работающие на языке С, видят строки в Java-программе, они обычно попадают в тупик, поскольку привыкли рассматривать строки как массивы символов: char greeting[] = "Hello"; Это не совсем подходящая аналогия: строка в языке Java больше напоминает указатель *сhаг: char* greeting = "Hello"; При замене содержимого greeting другой строкой Java-программа выполняет примерно следующее: char* temp = malloc(6); Разумеется, теперь переменная greeting указывает на строку "Help!". И даже самые убежденные поклонники языка С должны признать, что синтаксис Java более элегантен, чем последовательность вызовов функции strncpy (). А что будет, если мы присвоим строке greeting еще одно значение? greeting = "Howdy"; Не останется ли занятой ранее выделенная память? К счастью, в языке Java есть механизм автоматической "сборки мусора". Если память больше не нужна, она вскоре будет освобождена. Если вы программируете на языке С++ и применяете класс string, определенный в стандарте ANSI С++, вам будет намного легче работать с объектами string в языке Java. Объекты класса string в языке С++ также обеспечивают автоматическое выделение и освобождение памяти. Однако строки в языке С++ могут изменяться — отдельные символы в строке можно модифицировать. |
Конкатенация
Язык Java, как и большинство языков программирования, дает возможность использовать знак + для объединения (конкатенации) двух строк.
String expletive = "Вставка";
String PG13 = "удаленная";
String message = expletive + PG13;
Код, приведенный выше, присваивает переменной message строку "Вставкаудаленная". (Обратите внимание на отсутствие пробела между словами: знак + объединяет в точностите строки, которые были заданы.)
При конкатенации строки со значением, отличным от строкового, это значение преобразовывается в строку. Например, в результате выполнения приведенного ниже кода переменной rating присваивается строка " PG13 ".
int age = 13;
String rating = "PG" + age;
Это свойство широко используется в операторах вывода; например, приведенное ниже выражение выводит результат выполненных ранее вычислений (обратите внимание на пробел после слова Ответ).
System.out.println("Ответ " + answer);
Проверка совпадения строк
Чтобы проверить, совпадают ли две строки, следует использовать метод equals (). Приведенное ниже выражение возвращает значение true, если строки s и t равны между собой, в противном случае возвращается значение false.
s.equals(t)
Заметим, что в качестве s и t могут быть использованы как переменные, так и константы. Например, следующее выражение вполне допустимо:
"Hellol".ecruals(greeting);
Чтобы проверить идентичность строк, игнорируя различие между прописными и строчными буквами, следует использовать метод equalslgnoreCase().
"Hello".equalsIgnoreCase("hello");
Для проверки строк на равенство нельзя применять оператор ==! Он лишь определяет, хранятся ли обе строки в одной и той же области памяти.
Разумеется, если обе строки хранятся в одном и том же месте, они должны совпадать между собой. Однако вполне возможна ситуация, при которой идентичные строки хранятся в разных местах.
String greeting = "Hello"; // Инициализирует переменную greeting строкой.
if (greeting = "Hello") ...
// Возможно, это условие истинно,
if (greeting.substring(0, 3) == "Hel") ...
// Возможно, это условие ложно.
Если виртуальная машина всегда обеспечивает совместное использование одинаковых строк, то для проверки их равенства можно применять оператор ==. Однако совместно использовать можно лишь константы, а не строки, являющиеся результатами таких операций, как + или substring. Следовательно, либо нужно навсегда отказаться от проверок строк на равенство с помощью оператора ==, либо вы получите программу, содержащую наихудшую из возможных ошибок, проявляющуюся лишь время от времени, — ошибку, возникновение которой невозможно предсказать.
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
Если вы привыкли использовать класс string в языке С++, будьте особенно внимательны при проверке совпадения строк. В классе string оператор == перегружен и позволяет проверять идентичность содержимого строк. Возможно, в языке Java разработчики напрасно отказались от возможности работать со строками как с числовыми значениями, однако это позволило сделать строки похожими на указатели. Разработчики могли переопределить оператор == для строк так, как они это сделали с оператором +. Что ж, каждый язык имеет свои недостатки. Программисты, создающие продукты на языке С, никогда не используют для проверки строк на равенство оператор ==, вместо этого они вызывают функцию strcmp (). Метод compareTo() в языке Java представляет собой точный аналог функции strcmp (). Можно, конечно, использовать выражения вида if (greeting.compareTo("Help") ==0) ... Однако нам кажется, что применение метода equals () делает программу более удобочитаемой. |
Класс String в языке Java содержит более 50 методов. Многие из них оказались очень полезными и используются довольно часто. Приведенный ниже фрагмент описания API содержит наиболее полезные из них.
Время от времени мы будем включать в текст книги фрагменты описания прикладного программного интерфейса Java (Application Programming Interface — API). Каждый такой фрагмент начинается с имени класса, например java.lang.string. В данном случае java.lang — это пакет; механизм пакетов будет подробно рассмотрен в главе 4. После имени класса следуют имена конкретных методов и их описание.
Мы также приводим номер версии, в которой был реализован описываемый класс. Если тот или иной метод был добавлен позже, это указывается отдельно.
Обычно в тексте книги не перечисляются все методы отдельного класса; мы ограничиваемся лишь наиболее часто используемыми методами.
Полный список методов можно найти в справочной системе.
API java.lang.string 1.0
- char charAt(int index)
Возвращает символ, расположенный в указанной позиции. Вызывать этот метод следует только в том случае, если вас интересуют низкоуровневые кодовые единицы.
- int codePointAt(int index) 5.0
Возвращает кодовую точку, начало или конец которой находится в указанной позиции.
- int offsetByCodePoints(int startlndex, int cpCount) 5.0
Возвращает индекс кодовой точки, которая определяется cpCount относительно startlndex.
- int compareTo(String other)
Возвращает отрицательное значение, если данная строка лексикографически предшествует строке other, положительное значение — если строка other предшествует данной строке, и 0 — если строки идентичны.
- boolean endsWith(String suffix) Возвращает значение true, если строка заканчивается подстрокой suffix.
- boolean equals(Object other) Возвращает значение true, если данная строка совпадает со строкой other.
- boolean equalsIgnoreCase(String other)
Возвращает значение true, если данная строка совпадает со строкой other без учета регистра символов.
- int indexOf(String str)
- int indexOf(String str, int fromlndex)
- int indexOf(int cp)
- int indexOf{int cp, int fromlndex)
Возвращает индекс начала первой подстроки, совпадающей со строкой str, либо индекс указанной кодовой точки. Отсчет начинается с позиции 0 или formlndex.
Если указанная подстрока в составе строки отсутствует, возвращается значение, равное-1.
- int lastlndexOf(String str)
- int lastlndexOf(String str, int fromlndex)
- int lastlndexOf(int cp)
- int lastlndexOf(int cp, int fromlndex)
Возвращает начало последней подстроки, равной строке str, либо индекс указанной кодовой точки. Отсчет начинается с позиции 0 или formlndex. Если указанная подстрока в составе строки отсутствует, возвращается значение, равное-1.
- int length() Возвращает длину строки.
- int codePointCount(int startlndex, int endlndex) 5.0
Возвращает число кодовых точек между startlndex и endlndex -1. Половина пары, обозначающей дополнительный индекс, считается как полноправная кодовая точка.
- String replace(CharSequence oldString, CharSecruence newString)
Возвращает новую строку, которая получается путем замены всех подстрок, соответствующих oldString, на строку newString. В качестве параметров CharSequence могут выступать объекты String или StringBuilder.
- boolean startWith(String prefix)
Возвращает значение true, если строка начинается подстрокой prefix.
- String substring(int beginlndex)
- String substring(int beginlndex, int endlndex)
Возвращает новую строку, состоящую из всех кодовых единиц, начиная с позиции Beginlndex и заканчивая концом строки или позицией endlndex -1.
- String toLowerCase()
Возвращает новую строку, состоящую из всех символов исходной строки. Отличие между исходной и результирующей строкой состоит в том, что все буквы преобразуются в нижний регистр.
- String toupperCase()
Возвращает новую строку, состоящую из всех символов исходной строки. Отличие между исходной и результирующей строкой состоит в том, что все буквы преобразуются в верхний регистр.
- String trim ()
Возвращает новую строку, из которой исключены все предшествующие и завершающие пробелы.
Интерактивная документация по API
Как вы уже знаете, класс String имеет много методов. Более того, в стандартной библиотеке существует несколько сотен классов, содержащих огромное количество методов. Совершенно невозможно запомнить всю информацию, которая может понадобиться при написании программ. Следовательно, очень важно уметь пользоваться интерактивной справочной системой, содержащей документацию об API, и находить нужные классы и методы. Документация является составной частью набора инструментальных средств JDK. Она представлена в формате HTML. Обратитесь спомощью вашего браузера к документу docs/api/index.html в каталоге, в котором установлен JDK. Вы увидите информацию, показанную на рис. 3.2.
Окно браузера разделено на три фрейма. В небольшом фрейме в верхнем левом углу отображаются имена всех доступных пакетов. Под ним во фрейме побольше перечислены все классы. Щелкните мытью на любом из имен классов, и информация об этом классе будет показана в большом фрейме, расположенном справа (рис. 3.3). Например, чтобы получить дополнительные сведения о методах класса String, прокрутите содержимое левого нижнего фрейма, пока не увидите ссылку String, и щелкните на ней.
Затем прокрутите содержимое правого фрейма до тех пор, пока не увидите краткое описание всех методов. Методы расположены в алфавитном порядке (рис. 3.4). Щелкните на имени интересующего вас метода, чтобы получить его детальное описание (рис. 3.5). Например, если вы щелкнете на ссылке compareToIgnoreCase, то получите описание метода с этим именем.
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
Сразу сделайте закладку в браузере, указывающую на документ docs/api/index.html. |
Ввод и вывод
Для того чтобы немного "оживить" программы, рассматриваемые в качестве примеров, организуем ввод информации и форматирование выходных данных. В современных приложениях для ввода используются средства графического пользовательского интерфейса, однако в данный момент вы еще не обладаете знаниями, достаточными для формирования интерфейсных элементов. Поскольку наша текущая цель — лучше узнать языковые средства Java, мы ограничимся вводом и выводом посредством консоли. Вопросы программирования графического интерфейса будут подробно рассмотрены в главах 7 - 9.
Чтение входных данных
Вы уже знаете, что информацию можно вывести на стандартное устройство вывода (т.е. в консольное окно), вызвав метод System.out.println ().
Однако до появления JDK 5.0 отсутствовали удобные средства чтения информации со стандартного устройства ввода (т.е. клавиатуры). К счастью, в настоящее время проблемы, возникающие при этом, практически разрешены.
Для того чтобы организовать чтение информации с консоли, вам надо создать объект Scanner и связать его со стандартным входным потоком
System. in.
Scanner in = new Scanner(System.in);
Сделав это, вы получите в свое распоряжение многочисленные методы класса Scanner, предназначенные для чтения входных данных. Например, метод nextLine () обеспечивает прием строки текста.
System.out.print("Как вас зовут? ");
String name = in.nextLine();
В данном случае мы использовали метод nextLine ( ), потому что входная строка может содержать пробелы. Для того чтобы прочитать одно слово (разделителями между словами считаются пробелы), можно использовать следующий вызов:
String firstName = in.next();
Для чтения целочисленного значения предназначен метод nextlnt ( ).
System.out.print("Сколько вам лет? ");
int age = in.nextlnt();
Как нетрудно догадаться, метод nextDouble ( ) читает очередное число в формате с плавающей точкой.
Программа, код которой представлен в листинге 3.2, запрашивает имя пользователя и его возраст, а затем выводит сообщение типа
Сау, в следующем году вам будет 46
В первой строке содержится выражение
import java.util.*;
Класс Scanner принадлежит пакету java.util package. Если вы собираетесь использовать в программе класс, не содержащийся в базовом пакете java. lang, вам надо включить в состав кода директиву import. Подробно пакеты и директива import будут рассмотрены в главе 4.
Листинг 3.2. Содержимое файла InputTest.java
/**
@version 1.10 2004-02-10
@author Cay Horstmann
*/
import java.util.*;
public class InputTest
{
public static void main(String[] args)
{
Scanner in = new Scanner(System.in);
// get first input
System.out.print("What is your name? ");
String name = in.nextLine();
// get second input
System.out.print("How old are you? ");
int age = in.nextInt();
// display output on console
System.out.println("Hello, " + name + ". Next year, you'll be " + (age + 1));
}
}
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
Если вы не имеете возможности работать с JDK 5.0 или более поздними версиями, то организовать ввод данных будет несколько труднее. Вам придется использовать диалоговое окно, показанное на рис. 3.6.String inut = JOptionPane.showInputDialog(promptString); Возвращаемое значение представляет собой строку, введенную пользователем. Например, следующая строка кода позволяет запросить имя пользователя вашей программы: String name = JOptionPane.showInputDialog("Как вас зовут?"); Чтобы считать число, нужно выполнить более сложную работу. Метод JOptionPane. showInputDialog {) возвращает строку, а не число. Для преобразования этой строки в число нужно использовать метод integer .parseint () или Double. parseDouble (). Пример такого преобразования приведен ниже. String input = JOptionPane.showInputDialog("Сколько вам лет?"); int age = Integer.parseint(input); Если пользователь введет число 45, то строковой переменной input будет присвоена строка "45". Метод integer. parseint () преобразовывает строку в соответствующее число, т.е. 45. Класс JOptionPane принадлежит пакету javax. swing, поэтому вам надо включить в состав исходного файла следующее выражение: import j avax.swing.*; И наконец, если ваша программа вызывает метод JOptionPane. showInputDialog (), вам надо завершить ее вызовом system, exit (0). Дело в том, что при создании диалогового окна формируется новый управляющий поток. Когда выполнение метода main () оканчивается, автоматического завершения потока не происходит. Для того чтобы прекратить работу всех потоков, надо вызвать метод system.exit (). Дополнительную информацию о потоках можно найти в главе 1 второго тома. Следующая программа обеспечивает те же возможности, что и программа, представленная в листинге 3.2, но не использует средства JDK 5.0. import javax.swing.*; |
Форматирование выходных данных
Число х можно вывести на консоль с помощью выражения System, out .printin (х). В результате на экране отобразится число с максимальным количеством значащих цифр, допустимых для данного типа. Например, в результате выполнения приведенного ниже фрагмента кода на экран будет выведено число 3333.3333333333335.
double х = 10000.0 /3.0; System.out.print(x);
В ряде случаев это создает проблемы. Так, например, если вы хотите вывести на экран сумму в долларах и центах, большое количество цифр затруднит восприятие.
До появления JDK 5.0 процесс форматирования чисел был сопряжен с определенными трудностями. В JDK 5.0 был реализован метод printf (), привычный всем программистам, использующим язык С. Например, с помощью приведенного ниже выражения мы можем вывести значение х в виде числа, размер поля которого составляет 8 цифр, а дробная часть равна двум цифрам. (Число цифр дробной части называют также точностью.)
System.out.printf("%8.2f", x);
В результате на экран будет выведено, не считая ведущих пробелов, семь символов:
3333.33
Метод printf () позволяет задавать произвольное число параметров. Пример вызова с несколькими параметрами приведен ниже.
System.out.printf("%s, в следующем году вам будет %d", name, age);
Каждый спецификатор формата, начинающийся с символа %, заменяется соответствующим параметром. Символ преобразования, которым завершается спецификатор формата, задает тип форматируемого значения: f — число с плавающей точкой; s — строка; d — десятичное число.
Символы преобразования описаны в табл. 3.5.
В составе спецификатора формата могут присутствовать флаги, управляющие форматом выходных данных. Назначение всех флагов описано в табл. 3.6. Например, запятая, используемая в качестве флага, формирует разделитель групп. Так, в результате выполнения приведенного ниже выражения на экран будет выведена строка 3,333.33.
System.out.printf ("%, .2f", 10000.0 / 3.0);
В одном спецификаторе формата можно использовать несколько флагов, например последовательность символов "%, {.2Г указывает на то, что при выводе будут использованы разделители групп, а отрицательные числа будут помещены в скобки).
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
Преобразование s можно использовать для форматирования любого объекта. Если этот объект реализует интерфейс Formattable, вызывается метод formatTo(). В противном случае для преобразования объекта в строку применяется метод toString(). Метод toString() будет обсуждаться в главе 5, а интерфейсы — в главе 6. |
Для создания форматированной строки без вывода ее можно использовать статический метод String.format ().
String message = String.format("%s, в следующем году вам будет %d", name, age);
Несмотря на то, что тип Date будет, подробно рассмотрен лишь в главе 4, нам, чтобы закончить разговор о методе printf (), желательно подробно рассмотреть средства форматирования даты и времени. Для этой цели используется последовательность из двух символов, начинающаяся с буквы t, за которой следует одна из букв, приведенных в табл. 3.7. Пример такого форматирования приведен ниже.
System.out.printf("%tc", new Date());
В результате выполнения данного выражения выводится текущая дата и время.
Mon Feb 09 18:05:19 PST 2004
Как видно в табл. 3.7, некоторые форматы предполагают отображение лишь отдельных компонентов даты — дня или месяца. Было бы неразумно многократно задавать дату лишь для того, чтобы отформатировать различные ее элементы. По этой причине в строке, определяющей формат, может задаваться индекс форматируемого параметра. Индекс должен следовать непосредственно за символом % и завершаться знаком Пример использования индекса приведен ниже.
System.out.printf("%1$s %2$tB %2$te, %2$tY", "Дата:", newDate());
Файл:Cj2I.tab3.7.png
Файл:Cj2I.tab3.7 2.png
В результате обработки данного выражения будет выведена следующая строка:
Дата: February 9, 2004
Вы также можете использовать флаг <, который означает, что форматированию подлежит тот же параметр, который был сформатирован последним. Так, приведенное ниже выражение дает тот же результат, что и рассмотренное ранее.
System.out.printf("%s %tB %<te, %<tY", "Дата:", new Date());
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
Индексы начинаются с единицы. Так, выражение %1$ задает форматирование первого параметра. Это сделано для того, чтобы избежать конфликтов с флагом 0. |
В данном разделе были рассмотрены не все особенности метода printf (). На рис. 3.7 показана диаграмма, которая строго задает синтаксис спецификатора формата.
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
Ряд правил форматирования зависит от специфических условий конкретной страны или региона. Так, например, в Германии разделителем групп является не запятая, а точка, а вместо Monday выводится имя Montag. Вопросы интернационализации приложений будут рассмотрены во втором томе. |
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
Если вы работаете с одной из версий, предшествующих JDK 5.0, вместо метода printf () вам придется использовать классы NumberFormat и DateFormat. |
Поток управления
В языке Java, как и в любом другом языке программирования, есть условные операторы и циклы. Начнем с условных операторов, а затем перейдем к циклам. Рассмотрение потока управления мы завершим довольно неуклюжим оператором switch, который можно применять при проверке большого количества значений одного выражения.
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
Поток управления в языке Java построен точно так же, как и в языках С и С++, за исключением двух особенностей. В нем нет оператора goto, однако существует версия оператора break с метками, который можно использовать для выхода из вложенного цикла (в языке С для этого пришлось бы применять оператор goto). Кроме того, в JDK 5.0 реализован вариант оператора for, который не имеет аналогов в С или С++. Его можно сравнить с оператором foreach в С#. |
Блоки
Перед изучением управляющих структур нам необходимо рассмотреть блоки.
Блок, или составной оператор, — это произвольное количество простых операторов языка Java, заключенных в фигурные скобки. Блоки определяют область видимости переменных. Блоки могут быть вложенными один в другой. Ниже приведен пример блока, вложенного в другой блок в методе main ().
public static void main(String[] args) {
int n;
...
{
int k;
...
} // Переменная к определена только в этом блоке.
}
В языке Java невозможно объявить переменные с одним именем в двух вложенных блоках. Например, приведенный ниже фрагмент кода содержит ошибку и не будет скомпилирован.
public static void raain(String[] args) {
int n; {
int k;
int n; // Ошибка — невозможно переопределить переменную // n во внутреннем блоке.
}
}
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
В языке С++ переменные во вложенных блоках можно переопределять. Внутреннее определение маскирует внешнее. Это может привести к ошибкам, поэтому в языке Java подобный подход не реализован. |
Условные выражения
Условный оператор в языке Java имеет следующий вид: if (условие) оператор
Условие должно указываться в скобках.
В языке Java, как и в большинстве других языков программирования, часто необходимо выполнять много операторов в зависимости от выполнения одного условия. В этом случае используется блок, формируемый следующим образом:
{
Выражение_1; Выражение_2;
}
Например,
if (yourSales >= target) {
performance = "Удовлетворительно"; bonus = 100;
}
В этом фрагменте все операторы, помещенные в фигурные скобки, будут выполнены, если значение переменной yourSales больше значения переменной target или равно ему (рис. 3.8).
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
Блок (иногда называемый составным оператором) позволяет включать несколько (простых) операторов в любую структуру языка Java, которая в ином случае состояла бы лишь из одного (простого) оператора. |
Условное выражение в языке Java может также иметь приведенную ниже форму. Его действие показано на рис. 3.9.
if {условие) оператор_1 else оператор_2
Например:
if (yourSales >= target) {
performance = "Удовлетворительно");
bonus = 100 + 0.01 * (yourSales - target);
}
else
{
performance = "Неудовлетворительно"; bonus = 0;
}
Часть else не является обязательной. Она объединяется с ближайшим оператором if. Таким образом, в следующем фрагменте кода оператор else соответствует второму оператору if:
if (х <= 0) if (х == 0) sign = 0; else sign = -1;
В программах часто встречаются также операторы типа if ... else (рис. З.9). Пример такой языковой конструкции приведен ниже.
if {yourSales >= 2 * target)
performance = "Великолепно"; bonus = 1000;
else if (yourSales >= 1.5 * target)
performance = "Хорошо"; bonus = 500;
else if (yourSales >= target)
performance = "Удовлетворительно"; bonus = 100;
else
System.out.println("Bы уволены");
Файл:Cj2I.pic3.9.png
Файл:Cj2I.pic3.10.png
Неопределенные циклы
Цикл while обеспечивает выполнение выражения (или группы операторов, составляющих блок) до тех пор, пока условие равно true. Данный цикл записывается в следующем виде:
while (условие) оператор
Тело цикла while не будет выполнено ни разу, если его условие изначально равно false (рис. 3.11).
В листинге 3.3 показана программа, подсчитывающая, сколько лет надо вносить деньги на счет, чтобы накопить заданную сумму. Считается, что каждый год вносится одна и та же сумма и процентная ставка не изменяется.
Условие цикла while проверяется в самом начале. Следовательно, возможна ситуация, при которой код, содержащийся в блоке, не будет выполнен никогда. Если вы хотите, чтобы блок выполнялся хотя бы один раз, проверку условия нужно перенести в конец. Это можно сделать с помощью цикла do/while, который записывается следующим образом:
do оператор while (условие) ;
Условие проверяется лишь после выполнения тела цикла. Затем тело цикла повторяется, вновь проверяет условие и т.д. Например, код в листинге 3.4 вычисляет новый баланс вашего счета, а затем спрашивает вас, не собираетесь ли вы на заслуженный отдых.
do
{
balance += payment;
double interest = balance * interestRate / 100; balance += interest; year++;
// Вывести текущий баланс
// Спросить, не собирается ли пользователь на пенсию, // и получить ответ.
}
while (input.equals("N"));
Если пользователь отвечает N", цикл повторяется (рис. 3.12). Эта программа является хорошим примером применения циклов, которые нужно выполнить хотя бы один раз.
Листинг 3.3. Содержимое файла Retirement. java
/**
@version 1.20 2004-02-10
@author Cay Horstmann
*/
import java.util.*;
public class Retirement
{
public static void main(String[] args)
{
// read inputs
Scanner in = new Scanner(System.in);
System.out.print("How much money do you need to retire? ");
double goal = in.nextDouble();
System.out.print("How much money will you contribute every year? ");
double payment = in.nextDouble();
System.out.print("Interest rate in %: ");
double interestRate = in.nextDouble();
double balance = 0;
int years = 0;
// update account balance while goal isn't reached
while (balance < goal)
{
// add this year's payment and interest
balance += payment;
double interest = balance * interestRate / 100;
balance += interest;
years++;
}
System.out.println("You can retire in " + years + " years.");
}
}
Листинг 3.4. Содержимое файла Retirement2. java
/**
@version 1.20 2004-02-10
@author Cay Horstmann
*/
import java.util.*;
public class Retirement2
{
public static void main(String[] args)
{
Scanner in = new Scanner(System.in);
System.out.print("How much money will you contribute every year? ");
double payment = in.nextDouble();
System.out.print("Interest rate in %: ");
double interestRate = in.nextDouble();
double balance = 0;
int year = 0;
String input;
// update account balance while user isn't ready to retire
do
{
// add this year's payment and interest
balance += payment;
double interest = balance * interestRate / 100;
balance += interest;
year++;
// print current balance
System.out.printf("After year %d, your balance is %,.2f%n", year, balance);
// ask if ready to retire and get input
System.out.print("Ready to retire? (Y/N) ");
input = in.next();
}
while (input.equals("N"));
}
}
Определенные циклы
Цикл for — очень распространенная языковая конструкция. В ней число повторений контролируется переменной, выполняющей роль счетчика и обновляемой на каждой итерации. Приведенный ниже цикл выводит на экран числа от 1 до 10. Его выполнение показано на рис. 3.13.
for (int i = 1; i <= 10; i++) System.out.println(i);
Первый элемент оператора for обычно выполняет инициализацию счетчика, второй формулирует условие выполнения тела цикла, а третий определяет способ обновления счетчика.
Хотя в языке Java, как и в языке С++, элементами оператора цикла for могут быть практически любые операторы, существуют неписанные правила, согласно которым все три элемента оператора for должны только инициализировать, проверять и обновлять один и тот же счетчик. Если не придерживаться этих правил, полученный код станет совершенно непригоден для чтения.
Данные правила не слишком сковывают инициативу программиста. Даже придерживаясь их, можно сделать очень многое, например реализовать цикл с убывающим счетчиком.
for (int і = 10; і > 0; -і)
System.out.prinln("Обратный отсчет System.out.println("Старт!);
+ і);
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
Будьте осторожны, проверяя в цикле равенство двух чисел с плавающей точкой. Представленный ниже цикл может никогда не завершиться. for (double X = 0; X != 10; х += 0.1) ... |
При объявлении переменной в первой части оператора for ее область видимости простирается до конца тела цикла.
for (int i = 1; i <= 10; i++) {
}
// Здесь переменная i больше не определена.
Если переменная определена в операторе for, ее нельзя использовать вне этого цикла. Следовательно, если вы хотите использовать конечное значение счетчика вне цикла for, эту переменную нужно объявлять до начала цикла!
int i;
for (i = 1; i <= 10; f++) {
} // Здесь переменная i по-прежнему доступна.
С другой стороны, можно объявить переменные, имеющие одинаковое имя в разных циклах for.
for (int i = 1; i <= 10; i++) {
}
for (int i = 11; i <= 20; i++) // Переопределение переменной i. {
}
Действия, выполняемые посредством цикла for, можно реализовать с помощью цикла whilе. Приведенные ниже два фрагмента кода эквивалентны.
for (int i = 10; i > 0; i--)
System.out.printin("Обратный отсчет . . . " + i);
и
int i = 10; while (i > 0) {
System, out.printin ("Обратный отсчет . . . " + i) ;
i--;
}
В листинге 3.5 показан типичный пример использования цикла for.
Эта программа вычисляет вероятность выигрыша в лотерее. Например, если нужно угадать 6 номеров из 50, количество возможных вариантов равно (50x49x48x47x46x45)/ (1x2x3x4x5x6), так что шанс выиграть равен 1 из 15890700.Удачи!
В общем случае, если нужно угадать k номеров из n, количество возможных вариантов равно следующему выражению:
(nx(n-1)x(n-2)x...х (n-k+1) / (1Х2ХЗХ...xk)
Оно вычисляется с помощью следующего цикла for:
int lotteryOdds =1;
for (int i = 1; i <= k; i++)
lotteryOdds = lotteryOdds * (n-i+1) / i;
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
В конце данной главы будет рассмотрен "обобщенный цикл for", называемый также циклом "for each". Эта языковая конструкция была введена в JDK 5.0. |
Листинг 3.5. Содержимое файла LotteryOdds.java
/**
@version 1.20 2004-02-10
@author Cay Horstmann
*/
import java.util.*;
public class LotteryOdds
{
public static void main(String[] args)
{
Scanner in = new Scanner(System.in);
System.out.print("How many numbers do you need to draw? ");
int k = in.nextInt();
System.out.print("What is the highest number you can draw? ");
int n = in.nextInt();
/*
compute binomial coefficient
n * (n - 1) * (n - 2) * . . . * (n - k + 1)
-------------------------------------------
1 * 2 * 3 * . . . * k
*/
int lotteryOdds = 1;
for (int i = 1; i <= k; i++)
lotteryOdds = lotteryOdds * (n - i + 1) / i;
System.out.println("Your odds are 1 in " + lotteryOdds + ". Good luck!");
}
}
Многовариантное ветвление — оператор switch
Конструкция if /else может оказаться неудобной, если вам необходимо реализовать выбор из многих вариантов. В языке Java есть оператор switch, эквивалентный одноименному оператору, используемому в языках С и С++.
Например, программируя выбор из четырех альтернативных вариантов (рис. 3.14), можно использовать следующий код:
Scanner in = new Scanner(System.in);
System.out.print("Select an option (1, 2, 3, 4) ");
int choice = in.nextlnt();
switch (choice)
{
case 1:
break; case 2:
break;
case 3: '
break; case 4:
break; default:
// Неверный выбор.
break;
}
Выполнение начинается с метки case, соответствующей значению переменной choice, и продолжается до очередного оператора break или конца оператора switch. Если ни одна метка не совпадает со значением переменной, выполняется раздел default (если он предусмотрен).
Заметим, что метка case должна быть целочисленной. Нельзя проверять строки. Например, в следующем фрагменте кода сделана ошибка:
String input = ... ; switch (input) // ОШИБКА {
case "A": // ОШИБКА break;
}
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
Если вы забудете добавить в конце раздела case оператор break, возможно последовательное выполнение нескольких разделов case. Совершенно очевидно, что эта ситуация чревата ошибками, поэтому мы никогда не используем оператор switch в своих программах. |
Прерывание потока управления
Несмотря на то что разработчики языка Java сохранили зарезервированное слово goto, они решили не включать его в язык. В принципе применение операторов goto считается признаком плохого стиля программирования. Некоторые программисты считают, что борьба с использованием оператора goto ведется недостаточно активно (см., например, известную статью Дональда Кнута (Donald Knuth) "Structured Programming with goto statements"). Они считают, что применение операторов goto может приводить к ошибкам, однако в некоторых случаях нужно выполнять преждевременный выход из цикла. Создатели языка Java согласились с их аргументами и даже добавили в язык новый оператор для поддержки такого стиля программирования — оператор break с меткой.
Рассмотрим обычный оператор break, в котором не применяется метка. Для выхода из цикла можно применять тот же оператор, который использовался для выхода из тела оператора switch. Пример использования оператора break приведен ниже.
while (years <= 100) {
balance += payment;
double interest = balance * interestRate / 100;
balance += interest;
if(balance >= goal) break;
years++;
}
Теперь выход из цикла осуществляется при выполнении одного из двух условий: years > 100 — в начале цикла либо balance >= goal — внутри цикла. Разумеется, те же действия можно было бы вычислить и без применения оператора break.
while (years <= 100 && balance < goal) {
balance += payment;
double interest = balance * interestRate / 100; balance += interest;
if(balance < goal)
years++;
}
Заметим, однако, что проверка условия balance < goal в данном варианте программы повторяется дважды. Оператор break позволяет избежать этого.
В отличие от языка С++, язык Java содержит также оператор break сметкой, обеспечивающий выход из вложенных циклов. Если во вложенном цикле выполняется некоторое условие, возможно, потребуется выйти из всех вложенных циклов. Программировать дополнительные условия для проверки каждого вложенного цикла попросту неудобно.
Ниже приводится пример, иллюстрирующий работу оператора break с меткой. Заметим, что метка должна предшествовать тому внешнему циклу, из которого требуется выйти. Метка должна оканчиваться двоеточием.
Scanner in = new Scanner(System.in);
int n;
read_data:
while (. . .) // Этот цикл помечен
{
Поток управления 109
for (. . .) // Этот цикл не помечен
{
System.out.print("Введите число >= 0: ");
n = in.nextlnt();
if (n < 0) // Если это не произойдет, цикл продолжится break read„data;
// Прерывание цикла
}
// Данное выражение выполняется сразу же после оператора break с меткой if (п < 0) // Проверка на „наличие недопустимой ситуации
// Обработка недопустимой ситуации
else
{
// Действия в результате нормального выполнения программы
}
Если в программу было введено неверное число, оператор break с меткой выполнит переход в конец помеченного блока. В этом случае необходимо проверить, нормально ли осуществлен выход из цикла, или он произошел в результате выполнения оператора break.
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
Это может показаться странным, но метку можно связать с любым оператором, даже оператором if или блоком. Соответствующий пример приведен ниже.метка: { Итак, если вам крайне нужен оператор goto, вы можете поместить блок, из которого нужно выйти, и применить оператор break! Естественно, мы не рекомендуем использовать этот способ. Заметим также, что подобным образом можно выйти из блока, но невозможно войти в него. Итак, если вам крайне нужен оператор goto, вы можете поместить блок, из которого нужно выйти, и применить оператор break! Естественно, мы не рекомендуем использовать этот способ. Заметим также, что подобным образом можно выйти из блока, но невозможно войти в него. |
Существует также оператор continue, который, подобно break, прерывает нормальное выполнение программы. Оператор continue передает управление в начало текущего вложенного цикла. Пример использования данного оператора приведен ниже.
while (sum < goal) {
String input = JOptionPane.showInputDialog("Введите число"); n = integer.Parselnt(input) ,* if (n < 0) continua;
sum += n; //He выполняется, если n < 0.
}
Если n < 0, то оператор continue выполняет переход в начало цикла, пропуская оставшуюся часть текущей итерации.
Если оператор continue используется в цикле for, он передает управление оператору увеличения счетчика цикла.
for (count = 0; count < 100; count ++) {
String input = JOptionPane.showInputDialog ("Введите число");
n = Integer.parselnt(input);
if (n < 0) continue;
sum += n; // He выполняется, если n < 0.
}
Если n < 0, то оператор continue выполнит переход к оператору count++.
В языке Java есть также оператор continue с меткой, передающий управление заголовку оператора цикла, помеченного соответствующей меткой.
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
Многие программисты считают, что операторы break и continue неоправданно усложняют текст программы. Применять эти операторы вовсе не обязательно — те же действия можно реализовать, не прибегая к ним. В этой книге мы нигде не используем ни break, ни continue. |
Работа с большими числами
Если для решения задачи недостаточно точности встроенных целочисленных типов и чисел с плавающей точкой, можно обратиться к классам Biglnteger HBigDecimal, принадлежащим пакету java.matJi. Эти классы предназначены для выполнения действий с числами, состоящими из произвольного количества цифр. Классы Biglnteger и BigDecimal реализуют арифметические операции произвольной точности соответственно для целых и действительных чисел.
Для преобразования обычного числа в число с произвольной точностью (называемое также большим числом) используется статический метод valueOf ():
Biglnteger а = Biglnteger.valueof(100);
К сожалению, к большим числам нельзя применять обычные математические операторы, например + или *. Вместо этого надо использовать методы add () и multiply () из соответствующих классов.
Biglnteger с = a.add(b); // с = а + b
Biglnteger d = с.multiply(b.add(Biglnteger.value.Of(2))); // d = с * (b + 2)
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
В отличие от языка С++, язык Java не поддерживает перегрузку операторов. Поэтому разработчики класса Biglnteger были лишены возможности переопределить операторы + и * для методов add () и multiply () в классе Biglnteger. |
В листинге 3.6 показана модифицированная программа для подсчета шансов выиграть в лотерее (ее исходный вариант см. в листинге 3.5). Теперь эта программа может работать с большими числами. Например, если вам предложили сыграть в лотерее, в которой нужно угадать 60 чисел из 490 возможных, то эта программа сообщит вам, что шанс выиграть равен 1 из 7163958434619955574151162225400929334117176127892634934933510134594811 04668848. Удачи!
Программа, представленная в листинге 3.5, вычисляла следующее выражение:
lotteryOdds = lottery * (n - i + 1) / i;
При работе с большими числами соответствующая строка кода выглядит так:
lotteryOdds = lotteryOdds.multiply(Biglnteger.valueOf(n-i+1)
.divide(Biglnteger.valueOf(i));
Листинг 3.6. Содержимое файла BiglntegerTest .Java
/**
@version 1.20 2004-02-10
@author Cay Horstmann
*/
import java.math.*;
import java.util.*;
public class BigIntegerTest
{
public static void main(String[] args)
{
Scanner in = new Scanner(System.in);
System.out.print("How many numbers do you need to draw? ");
int k = in.nextInt();
System.out.print("What is the highest number you can draw? ");
int n = in.nextInt();
/*
compute binomial coefficient
n * (n - 1) * (n - 2) * . . . * (n - k + 1)
-------------------------------------------
1 * 2 * 3 * . . . * k
*/
BigInteger lotteryOdds = BigInteger.valueOf(1);
for (int i = 1; i <= k; i++)
lotteryOdds = lotteryOdds
.multiply(BigInteger.valueOf(n - i + 1))
.divide(BigInteger.valueOf(i));
System.out.println("Your odds are 1 in " + lotteryOdds + ". Good luck!");
}
}
Массивы
Массив — это структура данных, в которой хранятся величины одинакового типа. Доступ к отдельному элементу массива осуществляется с помощью целочисленного индекса. Например, если а — массив целых чисел, то значение выражения а [ i ] равно i-му целому числу в массиве.
Массив объявляется следующим образом: сначала указывается тип массива, т.е тип элементов, содержащихся нем, затем следует пара пустых квадратных скобок, а после них — имя переменной. Ниже приведено объявление массива, состоящего из целых чисел.
int [] а;
Однако этот оператор лишь объявляет переменную а, не инициализируя ее. Чтобы создать массив, нужно применить оператор new.
int[] а = new int[100];
Этот оператор создает массив, состоящий из 100 целых чисел.
{{
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
Объявить массив можно двумя способами:int[] а; Большинство программистов, работающих на Java, используют первый способ, так как при этом тип более явно отделяется от имени переменной. |
Элементы сформированного выше массива нумеруются от 0 до 99 (а не от 1 до 100). После создания массив можно заполнять конкретными значениями, в частности, это можно делать в цикле.
int [] а = new int [100] ;
for (int i = 0; i < 100; i++)
a [i] = i; // Заполняет массив числами от 0 до 99.
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
Если, создав массив, состоящий из 100 элементов, вы попытаетесь обратиться к а [100] (или любому другому элементу, индекс которого выходит за пределы диапазона от 0 до 99), программа прекратит работу, поскольку будет сгенерировано исключение, соответствующее выходу индекса массива за пределы допустимого диапазона. Чтобы подсчитать количество элементов в массиве, используйте выражение имя_массива. length. Например: for (int i = 0; i < a.length; i++) System,out.println(a[i]); |
После создания массива изменить его размер невозможно (хотя можно, конечно, изменять его отдельные элементы). Если'в ходе выполнения программы необходимо часто изменять размер массива, лучше использовать другую структуру данных, называемую списком.
Цикл "for each "
В JDK 5.0 был реализован новый цикл, позволяющий перебирать все элементы массива (а также любого другого набора данных), не применяя счетчик.
Новый вариант цикла for записывается следующим образом:
for (переменная : набор_данных) выражение
При обработке цикла переменной последовательно присваивается каждый элемент набора данных, после чего выполняется выражение (или блок). В качестве набора данных может использоваться массив либо экземпляр класса, реализующего интерфейс Iterable, например ArrayList. Списки будут обсуждаться в главе 5, а интерфейс Iterable — в главе 2 второго тома.
Ниже приведен пример использования рассматриваемого здесь цикла.
for (int element : а)
System.out.println(element);
В результате выполнения данного фрагмента кода каждый элемент массива будет выведен в отдельной строке.
Действия данного цикла можно кратко описать как "обработка каждого элемента из а". Разработчики языка Java рассматривали возможность применения в качестве идентификатора данного цикла ключевых слов f oreach и in. Однако данный тип цикла появился намного позже основных языковых средств Java, и введение нового ключевого слова привело бы к необходимости изменять исходный код некоторых готовых приложений, содержащих переменные или методы с такими именами.
Следует заметить, что действия, выполняемые с помощью нового цикла, можно реализовать с помощью традиционного цикла for.
for (int i = 0; i < a.length; i++) System.out.println(a[i]) ;
Однако при использовании нового цикла запись получается более краткой (необходимость в начальном выражении и условии завершения цикла отпадает), и вероятность возникновения ошибок уменьшается.
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
Переменная цикла в выражении "for each" перебирает не значения индекса, а элементы массива. |
Несмотря на появление нового цикла "for each", упрощающего во многих случаях составление программ, традиционный цикл for отнюдь не устарел. Без него нельзя обойтись, например, в тех случаях, когда вам надо обработать не весь набор данных, а лишь его часть, либо тогда, когда счетчик явно используется в теле цикла.
Инициализация массивов и анонимные массивы
В языке Java есть средство для одновременного создания массива и его инициализации. Пример такой синтаксической конструкции приведен ниже.
int[] smallPrimes = { 2, 3, 5, 7, 11, .13};
Заметьте, что в этом случае оператор new не нужен.
Кроме того, можно даже инициализировать массив, не имеющий имени, или анонимный массив.
new int[] {16, 19, 23, 29, 31, 37};
Данное выражение выделяет память для нового массива и заполняет его числами, указанными в фигурных скобках. При этом подсчитывается их количество и соответственно определяется размер массива. Такую синтаксическую конструкцию удобно применять для повторной инициализации массива без образования новой переменной.
Например, выражение
smallPrimes = new int{ 17, 19, 23, 29, 31, 37 };
представляет собой сокращенную запись выражения
int[] anonymous = { 17, 19, 23, 29, 31, 37 };
smallPrimes = anonymous;
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
При необходимости можно создать массив нулевого размера. Такой массив может оказаться полезным при написании метода, возвращающего массив, который в некоторых случаях оказывается пустым. Массив нулевой длины.объявляется следующим образом: new тип_элементов [ 0 ] |
Копирование массивов
По мере необходимости одну переменную массива можно скопировать в другую, но при этом обе переменные будут ссылаться на один и тот же массив.
int[] luckyNumbers = smallPrimes;
luckyNuimbers[5] =12; // Теперь элемент smallPrimes[5]также равен 12.
Результат копирования переменных массивов показан на рис. 3.15. Если необходимо скопировать все элементы одного массива в другой, следует использовать метод arrayсору () из класса System. Его вызов выглядит следующим образом:
System.arraycopy(from, fromlndex, to, tolndex, count);
Массив to должен иметь достаточный размер, чтобы в нем поместились все копируемые элементы.
Ниже приведен фрагмент кода, результаты выполнения которого показаны на рис. 3.16. Вначале создаются два массива, а затем последние четыре элемента первого массива копируются во второй. Копирование исходного массива начинается с элемента с номером 2, а в целевой массив копируемые данные помещаются, начиная с элемента с номером 3.
int[] smallPrimes = {2, 3, 5, 7, 11, 13};
int[] luckyNumbers = {1001, 1002, 1003, 1004, 1005, 1006, 1007};
System.arraycopy(smallPrimes, 2, luckyNumbers, 3, 4);
for { int i = 0; i < luckyNumbers.length; i++)
System.println(i + ": " + luckyNumbers[i]);
Выполнение данного фрагмента приводит к следующему результату:
1001
1002
1003
5
7
11
13
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
Массив в языке Java значительно отличается от массива в языке С++. Однако переменную массива можно условно сравнить с указателем на динамически созданный массив. Таким образом, выражение Javaint[] а = new int[100]; //Java можно сравнить с выражением c++ int* а = new int[100]; // С++ но оно существенно отличается от следующего: int а[100]; // С++ В языке Java оператор [ ] по умолчанию проверяет диапазон изменения индексов. Кроме того, в языке Java не поддерживается арифметика указателей, — нельзя увеличить указатель а, чтобы обратиться к следующему элементу массива. |
Параметры командной строки
В каждой из рассмотренных ранее программ на языке Java присутствовал метод main () с параметром String [] args. Этот параметр означает, что метод main () получает массив, элементами которого являются параметры, указанные в командной строке.
Рассмотрим следующую программу:
public class Message {
public static void main(String[] args) {
if (args[0].equals("-h");
System.out.print{"Hello, ");
else if (args[0].equals("-g"))
System.out.print("Goodbye, "); // Вывод остальных параметров командной строки.
for (int i = 1; i < args.length; i++)
System.out.print (" " + args[i]);
System.out.print("!");
}
}
При следующем вызове программы
java Message -g cruel world
массив args будет состоять из таких элементов:
args[0]: "-g"
args[1]: "cruel"
args[2]: "world"
Программа выведет сообщение
Goodbye, cruel world!
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
При запуске программы на языке Java ее имя не помещается в массиве args. Например, после запуска из командной строки программы Message с помощью командыjava Message -h world элемент args [0 ] будет равен "-h" , а не "Message" или "java". |
Сортировка массива
Если нужно упорядочить массив чисел, можно применить метод sort () из класса Arrays.
int[] а = new int[10000];
Arrays.sort(a);
Этот метод использует усовершенствованную версию алгоритма быстрой сортировки, которая считается наиболее эффективной для большинства множеств. Класс Arrays содержит несколько удобных методов, предназначенных для работы с массивами. Эти методы приведены в конце данного раздела.
Программа, код которой представлен в листинге 3.7, создает массив и генерирует случайную комбинацию чисел для лотереи. Например, если нужно выиграть "6 из 49", программа может вывести следующее сообщение:
Попробуйте такую комбинацию, и станете богатым!
4
7
8
19
30
44
Чтобы выбрать случайные числа, массив numbers сначала заполняется числами 1,2, ...,n.
int[] numbers = new int[n];
for (int i = 0; i < numbers.length; i++)
numbers[i] = i + 1;
Второй массив предназначен для хранения сгенерированных чисел.
int[] result = new int[k];
Теперь- сгенерируем к чисел. Метод Math. random () возвращает случайное число с плавающей точкой, лежащее в интервале от 0 (включительно) до 1 (это значение не принадлежит интервалу). Умножая результат на число п, получим случайное число, лежащее между 0 и n-1.
int г = (int)(Math.random() * n) ;
Присвоим i-e число i-му элементу массива. Сначала там будет помещено само число г, однако, как будет показано ниже, содержимое массива number будет изменяться после генерации каждого нового числа.
result[i] = numbers[r];
Теперь мы должны убедиться, что ни одно число не повторится — все номера должны быть разными. Следовательно, нужно поместить в элемент number [г] последнее число, содержащееся в массиве, и уменьшить n на единицу.
numbers[r] = numbers[n -1];
n--;
Обратите внимание на то, что при каждой генерации мы получаем индекс, а не само число. Этот индекс относится к массиву, содержащему числа, которые еще не были выбраны.
После генерации к номеров полученный массив сортируется, чтобы результат выглядел более элегантно.
Arrays.sort(result);
for (int i = 0; i < result.length; i++)
System.out.println(result[i]);
Листинг 3.7. Содержимое файла LotteryDrawing.Java
/**
@version 1.20 2004-02-10
@author Cay Horstmann
*/
import java.util.*;
public class LotteryDrawing
{
public static void main(String[] args)
{
Scanner in = new Scanner(System.in);
System.out.print("How many numbers do you need to draw? ");
int k = in.nextInt();
System.out.print("What is the highest number you can draw? ");
int n = in.nextInt();
// fill an array with numbers 1 2 3 . . . n
int[] numbers = new int[n];
for (int i = 0; i < numbers.length; i++)
numbers[i] = i + 1;
// draw k numbers and put them into a second array
int[] result = new int[k];
for (int i = 0; i < result.length; i++)
{
// make a random index between 0 and n - 1
int r = (int) (Math.random() * n);
// pick the element at the random location
result[i] = numbers[r];
// move the last element into the random location
numbers[r] = numbers[n - 1];
n--;
}
// print the sorted array
Arrays.sort(result);
System.out.println("Bet the following combination. It'll make you rich!");
for (int r : result)
System.out.println(r);
}
}
Многомерные массивы
Для доступа к элементам многомерного массива применяется несколько индексов. Такие массивы используются для хранения таблиц и более сложных упорядоченных структур данных. Если многомерные массивы в вашей работе не нужны, можете смело пропустить этот раздел.
Допустим, что вам нужно создать таблицу чисел, показывающих, как возрастет первоначальная инвестиция объемом 10000 долларов при разных процентных ставках, если прибыль ежегодно выплачивается и реинвестируется. Необходимые данные показаны в табл. 3.8.
Очевидно, что эту информацию лучше всего хранить в двухмерном массиве (или матрице), который мы назовем balance.
Объявить двухмерный массив в языке Java довольно просто. Например, это можно сделать следующим образом:
double[[]] balances;
Как обычно, мы не можем использовать массив, пока он не инициализирован с помощью оператора new.
balances = new double[NYEARS][NRATES];
В других случаях, если элементы массива известны заранее, можно использовать сокращенную запись для его инициализации, в которой не применяется оператор new.
int[][] magicSguare =
{
{16, 3, 2, 13},
{5, 10, 11, 8},
{9, 6, 7, 12},
{4, 15, 14, 1}
) ;
После инициализации массива к его отдельным элементам можно обращаться с помощью двух пар квадратных скобок, например balances [ i ] [ j ].
Программа, приведенная в качестве примера, сохраняет процентные ставки в одномерном массиве interest, а результаты подсчета баланса для каждого года и каждой процентной ставки— в двухмерном массиве balance. Первая строка массива инициализируется исходной суммой.
for (int j = 0; j < balances[0].length; j++)
balances[0][j] = 10000;
Затем мы подсчитываем содержимое остальных строк.
for {int i = 1; i < balances.length; i++ ,
{
for (int j = 0; j < balances[i].length; j++)
{
double oldBalance = balances[i - 1][j];
double interest = ... ;
balances[i][j] = oldBalance + interest;
}
}
В листинге 3.8 код программы показан полностью.
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
Цикл "for each" не обеспечивает автоматического перебора элементов двухмерного массива. Он лишь перебирает строки, которые, в свою очередь, являются одномерными массивами. Для обработки всех элементов двухмерного массива нужно два цикла.for (double[] row : balances) |
Листинг 3.8. Содержимое файла CompoundInterest.java
/**
@version 1.40 2004-02-10
@author Cay Horstmann
*/
public class CompoundInterest
{
public static void main(String[] args)
{
final double STARTRATE = 10;
final int NRATES = 6;
final int NYEARS = 10;
// set interest rates to 10 . . . 15%
double[] interestRate = new double[NRATES];
for (int j = 0; j < interestRate.length; j++)
interestRate[j] = (STARTRATE + j) / 100.0;
double[][] balances = new double[NYEARS][NRATES];
// set initial balances to 10000
for (int j = 0; j < balances[0].length; j++)
balances[0][j] = 10000;
// compute interest for future years
for (int i = 1; i < balances.length; i++)
{
for (int j = 0; j < balances[i].length; j++)
{
// get last year's balances from previous row
double oldBalance = balances[i - 1][j];
// compute interest
double interest = oldBalance * interestRate[j];
// compute this year's balances
balances[i][j] = oldBalance + interest;
}
}
// print one row of interest rates
for (int j = 0; j < interestRate.length; j++)
System.out.printf("%9.0f%%", 100 * interestRate[j]);
System.out.println();
// print balance table
for (double[] row : balances)
{
// print table row
for (double b : row)
System.out.printf("%10.2f", b);
System.out.println();
}
}
}
"Неровные" массивы
Все языковые конструкции, которые мы до сих пор рассматривали, мало отличались от других языков программирования. Однако механизм массивов в Java имеет особенность, предоставляющую совершенно новые возможности. В данном языке вообще нет многомерных массивов, только одномерные. Многомерные массивы — это "массивы массивов".
Например, массив balances в предыдущем примере фактически представляет собой массив, состоящий из 10 элементов, каждый из которых является массивом из шести элементов, представляющих, собой числа с плавающей точкой (рис 3.17).
Выражение balance[i] определяет i-й подмассив, т.е i-ю строку таблицы. Эта строка сама представляет собой массив, и выражение balance [ i ] [ j ] относится к его j -му элементу.
Поскольку строки массива доступны из программы, их можно легко переставлять!
double[] temp = balances[i];
balances[i] = balances[i+1];
balances[i=1] = temp;
Кроме того, в языке Java легко создавать "неровные" массивы, т.е массивы, у которых разные строки имеют разную длину. Приведем стандартный пример. Создадим массив, в котором элемент, стоящий на пересечении i-й строки и j-го столбца, равен количеству возможностей выбрать j чисел из i.
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 1
1 6 15 20 15 6 1
Поскольку число j не может превышать i, получается треугольная матрица. В i-й строке этой матрицы находится i+1 элементов. (Мы можем выбрать и 0 элементов; сделать это можно лишь одним-единственным способом.) Чтобы создать неровный массив, сначала разместим в памяти массив, хранящий его строки.
int[] [] odds = new int[NMAX+l] [];
Затем поместим туда сами строки.
for (int n=0; n<=NMAX; n++)
odds[n] = new int[n+1];
Теперь в памяти размещен весь массив, так что мы можем обращаться к его элементам, как обычно, при условии, что индексы не выходят за пределы допустимого диапазона.
for (int n=0; n<odds.length; n++)
for (int k=0; k<odds[n].length; k++)
{
// Вычисление вариантов.
odds[n][k] = lotteryOdds;
}
В листинге 3.9 приведен полный текст данной программы.
ВАЖНОЕ ЗАМЕЧАНИЕ ! | |
---|---|
В языке Java объявлениеdouble [] [] balance = new double [ 10 [ 6 ] ; // Java не эквивалентно объявлению double balance[10][6]; // С++ и даже не соответствует double (*balance)[6] = new double[10][6]; // С++ Вместо этого в памяти размещается массив, состоящий из десяти указателей. Средствами С++ это можно выразить следующим образом: double** balance = new double*[10]; // С++ Затем каждый элемент в массиве указателей заполняется массивом, состоящим из 6 чисел. for (i = 0; i < 10; i++) К счастью, этот цикл выполняется автоматически при объявлении массива с помощью оператора new double[10] [6]. |
Листинг 3.9. Содержимое файла LottaryArray.java
/**
@version 1.20 2004-02-10
@author Cay Horstmann
*/
public class LotteryArray
{
public static void main(String[] args)
{
final int NMAX = 10;
// allocate triangular array
int[][] odds = new int[NMAX + 1][];
for (int n = 0; n <= NMAX; n++)
odds[n] = new int[n + 1];
// fill triangular array
for (int n = 0; n < odds.length; n++)
for (int k = 0; k < odds[n].length; k++)
{
/*
compute binomial coefficient
n * (n - 1) * (n - 2) * . . . * (n - k + 1)
-------------------------------------------
1 * 2 * 3 * . . . * k
*/
int lotteryOdds = 1;
for (int i = 1; i <= k; i++)
lotteryOdds = lotteryOdds * (n - i + 1) / i;
odds[n][k] = lotteryOdds;
}
// print triangular array
for (int[] row : odds)
{
for (int odd : row)
System.out.printf("%4d", odd);
System.out.println();
}
}
}
Реклама
------------------------
ТРИО теплый пол отзыв
Заработок на сокращении ссылок
Earnings on reducing links
Код PHP на HTML сайты
Категория: Книги по Java
Комментарии |