16

16


Без команды break outer программе не удалось бы покинуть внешний цикл из внутреннего цикла, так как команда break сама по себе завершает выполнение только текущего цикла (это справедливо и для continue).



Конечно, если завершение цикла также приводит к завершению работы ме¬тода, можно просто применить команду return.



Теперь рассмотрим пример, в котором используются команды break и con¬tinue с метками в цикле while:



//: control/LabeledWhile java



// Цикл while с метками



import static net mindview.util.Print.*;



public class LabeledWhile {



public static void main(String[] args) { int i = 0; outer:



while(true) {



print("Внешний цикл while"); while(true) { i++;



printC'i = " + i); if(i == 1) {



print("continue"); continue;



}



if(i == 3) {



print("continue outer"); continue outer;



}



if(i == 5) {



printC'break"); break,



}



if(i == 7) {



printC'break outer"); break outer;



}



}



}



}



} /* Output: Внешний цикл while i = 1 continue i = 2 i = 3



continue outer Внешний цикл while i = 4 i = 5 break



Внешний цикл while i = 6



i = 7



break outer



*///-



Те же правила верны и для цикла while:



• Обычная команда continue переводит исполнение к началу текущего внутреннего цикла, программа продолжает работу.



• Команда continue с меткой вызывает переход к метке и повторный вход в цикл, следующий прямо за этой меткой.



• Команда break завершает выполнение текущего цикла.



• Команда break с меткой завершает выполнение внутреннего цикла и цик¬ла, который находится после указанной метки.



Важно помнить, что единственная причина для существования меток в Ja¬va — наличие вложенных циклов и необходимость выхода по break и продолже¬ния по continue не только для внутренних, но и для внешних циклов.



В статье Дейкстры особенно критикуются метки, а не сам оператор goto. Дейкстра отмечает, что, как правило, количество ошибок в программе растет с увеличением количества меток в этой программе. Метки затрудняют анализ программного кода. Заметьте, что метки Java не страдают этими пороками, по¬тому что их место расположения ограничено и они не могут использоваться для беспорядочной передачи управления. В данном случае от ограничения возмож¬ностей функциональность языка только выигрывает.



switch



Команду switch часто называют командой выбора. С помощью конструкции switch осуществляется выбор из нескольких альтернатив, в зависимости от зна¬чения целочисленного выражения. Форма команды выглядит так:



switch(целочисленное-выражение) {



case целое-значение1 команда; break; case целое-значение2 : команда; break, case целое-значениеЗ : команда; break; case целое-значение4 • команда; break; case целое-значениеб : команда; break; // ..



default: оператор;



}



Целочисленное-выражение — выражение, в результате вычисления которого по¬лучается целое число. Команда switch сравнивает результат целочисленного-вы- ражения с каждым последующим целым-значением. Если обнаруживается совпа¬дение, исполняется соответствующая команда (простая или составная). Если же совпадения не находится, исполняется команда после ключевого слова default.



Нетрудно заметить, что каждая секция case заканчивается командой break, которая передает управление к концу команды switch. Такой синтаксис по¬строения конструкции switch считается стандартным, но команда break не явля¬ется строго обязательной. Если она отсутствует, при выходе из секции будет вы¬полняться код следующих секций case, пока в программе не встретится очеред¬ная команда break. Необходимость в подобном поведении возникает довольно редко, но опытному программисту оно может пригодиться. Заметьте, что по¬следняя секция default не содержит команды break; выполнение продолжается в конце конструкции switch, то есть там, где оно оказалось бы после вызова break. Впрочем, вы можете использовать break и в предложении default, без практической пользы, просто ради «единства стиля».



Команда switch обеспечивает компактный синтаксис реализации множест¬венного выбора (то есть выбора из нескольких путей выполнения программы), но для нее необходимо управляющее выражение, результатом которого являет¬ся целочисленное значение, такое как int или char. Если, например, критерием выбора является строка или вещественное число, то команда switch не подой¬дет. Придется использовать серию команд if-else.



Следующий пример случайным образом генерирует английские буквы. Про¬грамма определяет, гласные они или согласные:



//: control/VowelsAndConsonants.java // Демонстрация конструкции switch, import java.util.*,



import static net.mindview.util.Print *;



public class VowelsAndConsonants {



public static void main(String[] args) { Random rand = new Random(47); for(int i = 0: i < 100; i++) {



int с = rand.nextInt(26) + 'a'; printnb((char)c + ", " + с + ": "); switch(c) {



case 'a': case 'e': case * i": case 'о':



case 'u': рппи"гласная"); break;



case 'y':



case V: print ("Условно гласная"); break;



default: рппи"согласная");



}



}



}



} /* Output: y, 121: Условно гласная n, 110: согласная z, 122: согласная



b, 98: согласная



г, 114: согласная n, 110: согласная



у. 121: Условно гласная



д. 103: согласная



c. 99: согласная f, 102: согласная о, 111: гласная



w, 119: Условно гласная z. 122: согласная



*///:-



Так как метод Random.nextlnt(26) генерирует значение между 0 и 26, для по¬лучения символа нижнего регистра остается прибавить смещение 'а'. Символы в апострофах в секциях case также представляют собой целочисленные значе¬ния, используемые для сравнения.



Обратите внимание на «стопки» секций case, обеспечивающие возможность множественного сравнения для одной части кода. Будьте начеку и не забывайте добавлять команду break после каждой секции case, иначе программа просто пе¬рейдет к выполнению следующей секции case. В команде



int с = rand.nextInt(26) + 'а',



метод rand.nextlnt() выдает случайное число int от 0 до 25, к которому затем прибавляется значение 'а'. Это означает, что символ а автоматически преобразу¬ется к типу int для выполнения сложения.



Чтобы вывести с в символьном виде, его необходимо преобразовать к типу char; в противном случае значение будет выведено в числовом виде.



Резюме



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




Инициализация и завершение

В
ходе компьютерной революции выяснилось, что основной причиной чрезмерных затрат в программировании является «небезопасное» программирование.



Основные проблемы с безопасностью относятся к инициализации и заверше¬нию. Очень многие ошибки при программировании на языке С обусловлены не¬верной инициализацией переменных. Это особенно часто происходит при рабо¬те с библиотеками, когда пользователи не знают, как нужно инициализировать компонент библиотеки, или забывают это сделать. Завершение — очень акту¬альная проблема; слишком легко забыть об элементе, когда вы закончили с ним работу и его дальнейшая судьба вас не волнует. В этом случае ресурсы, зани¬маемые элементом, не освобождаются, и в программе может возникнуть нехват¬ка ресурсов (прежде всего памяти).



В С++ появилось понятие конструктора — специального метода, который вызывается при создании нового объекта. Конструкторы используются и в Java; к тому же в Java есть сборщик мусора, который автоматически освобождает ре¬сурсы, когда объект перестает использоваться. В этой главе рассматриваются вопросы инициализации и завершения, а также их поддержка в Java.



Конструктор гарантирует инициализацию






Конечно, можно создать особый метод, назвать его initialize() и включить во все ваши классы. Имя метода подсказывает пользователю, что он должен вызвать этот метод, прежде чем работать с объектом. К сожалению, это означает, что пользователь должен постоянно помнить о необходимости вызова данного ме¬тода. В Java разработчик класса может в обязательном порядке выполнить инициализацию каждого объекта при помощи специального метода, называе¬мого конструктором. Если у класса имеется конструктор, Java автоматически



вызывает его при создании объекта, перед тем как пользователи смогут обра¬титься к этому объекту. Таким образом, инициализация объекта гарантирована.



Как должен называться конструктор? Здесь есть две тонкости. Во-первых, любое имя, которое вы используете, может быть задействовано при определе¬нии членов класса; так возникает потенциальный конфликт имен. Во-вторых, за вызов конструктора отвечает компилятор, поэтому он всегда должен знать, какой именно метод следует вызвать. Реализация конструктора в С++ кажется наиболее простым и логичным решением, поэтому оно использовано и в Java: имя конструктора совпадает с именем класса. Смысл такого решения очеви¬ден — именно такой метод способен автоматически вызываться при инициали¬зации.



Рассмотрим определение простого класса с конструктором:



//. initialization/SimpleConstructor.java



// Демонстрация простого конструктора



class Rock {



RockO { // Это и есть конструктор System.out print("Rock ");



}



}



public class SimpleConstructor {



public static void mainCString[] args) { for(int i = 0; i < 10. i++) new RockO,



}



}



} /* Output:



Rock Rock Rock Rock Rock Rock Rock Rock Rock Rock



*/// ~



Теперь при создании объекта:



new Rock( ),



выделяется память и вызывается конструктор. Тем самым гарантируется, tfro объект будет инициализирован, прежде чем программа сможет работать с ним.



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



Подобно любому методу, у конструктора могут быть аргументы, для того чтобы позволить вам указать, как создать объект. Предыдущий пример легко изменить так, чтобы конструктору при вызове передавался аргумент:



// initialization/SimpleConstructor2 java



// Конструкторы могут получать аргументы



class Rock2 {



Rock2(int i) {



System.out.println("Rock " + i + " ");



}



}



public class SimpleConstructor2 {



public static void main(String[] args) { for(int i = 0; i < 8; i++) new Rock2(i).



}



} /* Output:



Rock 0 Rock 1 Rock 2 Rock 3 Rock 4 Rock 5 Rock 6 Rock 7



*///:-



В аргументах конструктора передаются параметры для инициализации объ¬екта. Например, если у класса Tree (дерево) имеется конструктор, который по¬лучает в качестве аргумента целое число, обозначающее высоту дерева, то объ¬екты Tree будут создаваться следующим образом:



Tree t = new Tree(12), // 12-метровое дерево



Если Tree(int) является единственным конструктором класса, то компилятор не позволит создавать объекты Tree каким-либо другим способом.



Конструкторы устраняют большой пласт проблем и упрощают чтение кода. В предыдущем фрагменте кода не встречаются явные вызовы метода, подобно¬го initialize(), который концептуально отделен от создания. В Java создание и инициализация являются неразделимыми понятиями — одно без другого не¬возможно.



Конструктор — не совсем обычный метод, так как у него отсутствует возвра¬щаемое значение. Это ощутимо отличается даже от случая с возвратом значе¬ния void, когда метод ничего не возвращает, но при этом все же можно заста¬вить его вернуть что-нибудь другое. Конструкторы не возвращают никогда и ничего (оператор new возвращает ссылку на вновь созданный объект, но сами конструкторы не имеют выходного значения). Если бы у них существовало воз¬вращаемое значение и его можно было бы выбирать, то компилятору пришлось бы как-то объяснять, что же делать с этим значением.



Перегрузка методов



Одним из важнейших аспектов любого языка программирования является ис¬пользование имен. Создавая объект, вы фактически присваиваете имя области памяти. Метод — имя для действия. Использование имен при описании систе¬мы упрощает ее понимание и модификацию. Работа программиста сродни ра¬боте писателя; в обоих случаях задача состоит в том, чтобы донести свою мысль до читателя.



Проблемы возникают при перенесении нюансов человеческого языка в язы¬ки программирования. Часто одно и то же слово имеет несколько разных значе¬ний — оно перегружено. Это полезно, особенно в отношении простых различий. Вы говорите «вымыть посуду», «вымыть машину» и «вымыть собаку». Было бы глупо вместо этого говорить «посудоМыть посуду», «машиноМыть машину» и «собакоМыть собаку» только для того, чтобы слушатель не утруждал себя выявлением разницы между этими действиями. Большинство человеческих языков несет избыточность, и даже при пропуске некоторых слов определить смысл не так сложно. Уникальные имена не обязательны — сказанное можно понять из контекста.

Report Page