29 апр. 2015 г.

Операторы for и foreach

Начиная с версии JDK 5, в Java существуют две формы цикла for. Первая — традиционная форма, используемая начиная с исходной версии Java. Вторая — новая форма “for-each”. Мы рассмотрим оба типа цикла for, начиная с традиционной формы.

Общая форма традиционного оператора for выглядит следующим образом:

F00001

Если в цикле будет повторяться только один оператор, фигурные скобки можно опустить.

Цикл for действует следующим образом. При первом запуске цикла программа выполняет инициализационную часть цикла. В общем случае это выражение, устанавливающее значение управляющей переменной цикла, которая действует в качестве счетчика, управляющего циклом. Важно понимать, что выражение инициализации выполняется только один раз. Затем программа вычисляет условие, которое должно быть булевским выражением. Как правило, выражение условия сравнивает значение управляющей переменной с целевым значением. Если это значение истинно, программа выполняет тело цикла. Если оно ложно, выполнение цикла прерывается. Затем программа выполняет тело цикла и только после этого выполняется часть повторение цикла. Повторение это обычно выражение, которое увеличивает или уменьшает значение управляющей переменной. Затем программа повторяет цикл, при каждом прохождении вначале вычисляя условное выражение, затем выполняя тело цикла и выполняя выражение повторения. Процесс повторяется до тех пор, пока значение выражения условия не станет ложным.

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

Приведем пару примеров поясняющих все вышесказанное:

F00002

В этом примере переменная i объявлена вне цикла (строка 7), поэтому она так же доступна после его завершения (строка 12).

Из вывода выполнения данной программы видно, что выражение повторения цикла, а именно префиксный инкремент (++i) переменной i выполняется после выполнения тела цикла, то есть после выполнения строки 10, которая выводит приветствие.

Этот момент очень важно понимать, чтобы иметь правильное представление о работе цикла for.

 

Теперь посмотрим на вывод этой программы с аргументами командной строки и без них:

F00003

Как видно из вывода данной программы приращение переменной i происходит после выполнения последней команды цикла, которая выводит приветствие (строка 10).

А теперь объявим переменную внутри цикла (оператора for):

F00004

Как видно Eclipse нам сразу же указал на ошибку, что переменная j, объявленная в строке 15, не видна вне цикла, так как ее область действия или область видимости распространяется только на тело цикла, в котором она была объявлена.

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

Вывод этого кода, аналогичен выводу, кода который мы только что рассмотрели, за исключением того, что вместо “Привет” выводится “Hello”. Ну и то что после цикла не возможно вывести значение переменной j.

При объявлении переменной внутри цикла for необходимо помнить о следующем важном обстоятельстве: область и время существования этой переменной полностью совпадают с областью и временем существования оператора for.

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

Например:

F00005

В этом примере в инициализационной части цикла мы устанавливаем начальные значения обеих управляющих переменных a и b. Оба разделенных запятой оператора в итерационной части выполняются при каждом повторении цикла.

Данный код генерирует следующий вывод:

F00006

Цикл for поддерживает несколько разновидностей, которые увеличивают его возможности и повышают применимость. Гибкость этого цикла обусловлена тем, что его три части: инициализацию, проверку условий и итерационную не обязательно использовать только по прямому назначению. Фактически каждый из разделов оператора for можно применять в любых целях. Например:

F00007

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

По существу эта программа делает то же приветствие аргументов, если они есть. Если их нет, то ни чего не выводит. Сразу же приведу пример ее вывода:

F00008

Как видно из вывода этой программы, итерационная часть выполняется, как уже и говорилось, после выполнения тела цикла. В данном случае это оператор println в строке 9. Оператор for в данном коде растянулся на две строки 9 и 10, поскольку он достаточно длинный. Я это сделал для демонстрации того, что каждая часть оператора for может быть применена в разных целях. Стоит еще заметить, что приращение переменной i происходит в строке 12 и там же задается условие для продолжения или выхода из цикла, которое проверяется в строке 9.

Еще один подобный пример, цикл for можно задействовать для прохождения по элементам связного списка:

F00009

Стоит, так же, отметить, что любую из частей цикла for (инициализацию, условие и итерационную) или даже все можно пропустить. Например, можно создать таким образом бесконечный цикл:

for (;;){
   
//бесконечный цикл
}

Инициализационное или итерационное выражения либо они оба могут отсутствовать:

F00010

В этом примере инициализационное и итерационное выражения вынесены за пределы определения оператора for. В результате соответствующие части оператора for пусты.

 

 

Чтобы последовательность выполнения частей оператора for стала более наглядный я приведу небольшой пример. Хотя методы мы еще не изучали, но я надеюсь идея этой программы будет вам понята. Смысл ее в том, чтобы наглядно показать последовательность выполнения всех частей оператора for.

for01

for02

Из вывода программы видно, что инициализационная часть программы (метод initTest()) выполняется только один раз.

Затем выполняется проверка условия, представленная методом condTest().

После проверки условия, выполняется тело цикла.

И уже после этого выполняется часть повторение, представленная методом recTest().

В методе condTest() выполняется проверка условия продолжения цикла. В данном случае переменная i сравнивается c 4, и пока переменная i меньше 4, то тело цикла выполняется.

Тело цикла выполняется четыре раза так как переменная i была по умолчанию проинициализирована нулем.

Оператор foreach

Начиная с версии JDK 5 в Java можно использовать вторую форму цикла for, реализующую цикл в стиле foreach (“для каждого”). Цикл в стиле foreach предназначен для строго последовательного выполнения повторяющихся действий по отношению к коллекциям объектов, например, таких как массивы. В Java возможность применения цикла foreach реализована за счет усовершенствования цикла for.  Общая форма версии foreach цикла for имеет следующий вид:

for (тип итерационная переменная : коллекция) блок-операторов

Тип это тип переменной, итерационная переменная — имя итерационной переменной, которая последовательно будет принимать значения из коллекции, от первого до последнего. Элемент коллекция указывает коллекцию, по которой должен выполняться цикл. С циклом for можно применять различные типы коллекций, но пока мы будем использовать только массивы, кстати которые тоже пока не проходили, но по крайней мере уже было много примеров с приветствиями из массива строк, куда попадают аргументы командной строки.

На заметку: оператор foreach применим к массивам и классам, реализующим интерфейс java.lang.Iterable.

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

Хотя повторение цикла for в стиле foreach выполняется до тех пор, пока не будут обработаны все элементы массива (коллекции), цикл можно прервать и раньше, используя оператор break.

Поскольку итерационная переменная получает значения из коллекции, ее тип должен совпадать (или быть совместимым) с типом элементов, хранящихся в коллекции. Таким образом, при выполнении цикла по массивам тип должен быть совместим с базовым типом массива.

Чтобы понять побудительные причины применения циклов в стиле foreach, рассмотрим тип цикла for, для замены которого предназначен этот стиль.

Возьмем опять наш пример с приветствием аргументов из командной строки:

F00011

Не правда ли, это куда элегантней, чем применение других операторов цикла, для этой цели?

Собственно у этой программы простой вывод:

F00012

Мы его уже много раз видели в разных вариантах, но повторенье – мать ученья.

 

Для полной ясности рассмотрим еще несколько примеров.

F00013

При каждом прохождении цикла переменной x автоматически присваивается значение, равное значению следующего элемента массива nums. Таким образом, на первой итерации x содержит 1, на второй — 2 и т.д. При этом упрощается синтаксис программы, и исключается возможность выхода за пределы массива.

Вывод этой части программы такой:

F00014

Хотя повторение цикла for в стиле foreach выполняется до тех пор, пока не будут обработаны все элементы массива, цикл можно прервать и раньше, используя оператор break. Например:

F00015

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

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

Данный код выведет следующее:

F00016

Любой метод, возвращающий массив, может использоваться с foreach. Например класс String содержит метод toCharArray, возвращающий массив char. Пример:

F00017Данный код просто выведет посимвольно строку Привет Мир!

 

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

На последок, приведу еще пару примеров продвинутого оператора for. По большому счету, это просто отрефактореный код того примера что я уже приводил.

F00018 Не правда ли, что этот код стал более читаем и понятен, чем тот, что я уже приводил? Или же не понятно? Ну тогда смотрим на другой пример кода, который делает тоже самое.

F00019

Неужели опять не понятно? Smile

Оба этих кода делают одинаковый вывод:

 

F00020

Конечно при условии что аргументами в командной строке были Вася и Петя.

На этом с оператором for и его тенью foreach закончим.

Оператор do while

Цикл do while очень похож на цикл while, за исключением того, что выражение цикла проверяется в конце цикла, а не в начале. Это значит, что тело цикла всегда выполняется как минимум один раз. Синтаксис цикла таков:

DW00001

При каждом повторении цикла do while программа вначале выполняет тело цикла, а затем вычисляет условное выражение. Если это выражение истинно, цикл повторяется. В противном случае выполнение цикла прерывается.

И опять будем приветствовать аргументы:

DW00002

Тут тоже все достаточно просто. Единственное – это проверка на то что есть аргументы, так как если ее не сделать, то тело цикла будет выполнятся даже когда нет аргументов, что приведет в свою очередь ко ошибке выполнения, так как будет обращение к несуществующему элементу массива строк.

Вывод у этой программы точно такой же как и у предыдущей, поэтому тут приводить его не буду.

 

И еще небольшой примерчик обратного отсчета от десяти:

DW00003

Данный код выведет:

--> 10
--> 9
--> 8
--> 7
--> 6
--> 5
--> 4
--> 3
--> 2
--> 1

Это пример того, что условное выражение может содержать другие выражения, но важно чтобы результатом вычисления этих выражений было значение типа boolean.

Оператор while

Оператор while — наиболее часто используемый оператор цикла Java. Он повторяет оператор или блок операторов до тех пор, пока значение его управляющего выражения истинно. Он имеет следующую общую форму:

W00001

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

Ну и сразу небольшой примерчик:

W00002

Данная программа – это модификация Hello World из этого поста. Только она начинает выводить приветствия с последнего введенного аргумента.

W00003

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

W00004

 

В последнем примере запуска нет аргументов, поэтому тело цикла не было выполнено ни разу.

Тело цикла while (или любого другого цикла Java) может быть пустым. Это обусловлено тем, что синтаксис Java допускает применение нулевого оператора (содержащего только символ точки с запятой). Например:

W00005

Данный код выведет следующую строку:  Среднее значение равно 150

Этот цикл while работает следующим образом. Значение i увеличивается, а значение j уменьшается на единицу. Затем программа сравнивает эти два значения. Если новое значение i по-прежнему меньше нового значения j , цикл повторяется. Если значение i равно или больше значения j , выполнение цикла прекращается. По выходу из цикла переменная i будет содержать среднее значение исходных значений i и j . (Конечно, эта процедура работает только в том случае, если в самом начале значение i меньше значения j). Как видите, никакой потребности в наличии тела цикла не существует. Все действия выполняются внутри самого условного выражения. В профессионально написанном Java-коде короткие циклы часто не содержат тела, если само по себе управляющее выражение может выполнять все необходимые действия.

28 апр. 2015 г.

Оператор switch

Оператор switch состоит из выражения и тела, которое содержит различные пронумерованные точки входа. Выражение вычисляется, и управление переходит на точку входа, определенную полученным значением. Выражение должно возвращать следующие тип данных: int, short, char, byte (или их обертки), String (с Java 7) или enum. Тип данных enum мы пока не проходили.

Общая форма оператора switch имеет следующий вид:

sw01

Часто он оказывается эффективнее применения длинных последовательностей операторов if-else-if.

Например, программу из предыдущего поста можно переписать таким образом:

sw02

И варианты исполнения программы:

sw03

При выполнении оператора switch интерпретатор вычисляет значение выражения в круглых скобках, а затем ищет метку case, соответствующую полученному значению. Если интерпретатор находит метку, он начинает выполнять блок программы с первого оператора после метки case. Если интерпретатор не находит метку case с соответствующим значением, он начинает выполнение блока с первого оператора после специальной метки default:. Или, если нет метки default:, интерпретатор выполняет только соответствующий метод case если он есть, либо не выполняет ни какой если нет подходящей метки.

Можете попробовать закомментировать в предыдущем примере выполнение метки default и посмотреть как будет выполнятся программа.

sw04

Обратите внимание на использование ключевого слова break в конце каждого case в предыдущем коде. Оператор break будет описан чуть позже; в данном случае он заставляет интерпретатор покинуть оператор switch. Метки case определяют только на чальную точку нужного кода. Отдельные варианты не являются независимыми блоками программы и не содержат никакой неявной точки окончания. Поэтому при помощи оператора break или другого подходящего оператора нужно явно определить окончание каждого варианта. Если нет оператора break, оператор switch начинает выполнять код с первого оператора после соответствующей метки case и продолжает выполнять операторы, пока не достигнет конца блока. Иногда удобно писать код с последовательным переходом от одной метки case к другой, однако в 99% случаев вам придется завершать каждый раздел case и default оператором, приводящим к завершению выполнения оператора switch. Обычно в таких случаях применяют оператор break, но также подходят return и throw. Как только программа доходит до оператора break (rerturn, throw), она продолжает выполнение с первой строки кода, следующей за всем оператором switch.

Оператор switch может содержать более одной метки case для одного и того же оператора. По существу это спользование нескольких операторов case без разделяющих их операторов break.

sw05

Есть несколько важных ограничений для оператора switch и его меток caseВо-первых типы с плавающей точкой и boolean не поддерживаются. То же самое относится к long, хотя long является целым типом. Во-вторых, значение, ассоциируемое с каждой меткой case, должно быть постоянным значением или выражением, которое может вычислить компилятор. Например, метка case не может содержать выражение, вычисляемое во время выполнения – с переменными и вызовами методов. В-третьих, значения меток case должны соответствовать типу данных выражения switch. И наконец, не разрешается создавать две и более метки case с одинаковым значением или больше одной метки default.

Вложенные операторы switch

Оператор switch можно использовать в последовательности операторов внешнего оператора switch. Такой оператор называютвложенным оператором switch. Поскольку оператор switch определяет собственный блок, каких-либо конфликтов между константами case внутреннего и внешнего операторов switch не происходит. Например, следующий фрагмент полностью допустим:

sw06

В данном случае оператор case1:  внутреннего оператора switch не конфликтует с оператором case1:  внешнего оператора switch. Программа сравнивает значение переменной count только со списком ветвей case внешнего уровня. Если значение count равно 1, программа сравнивает значение переменной target c внутренним списком ветвей case.

Ну и на последок можно сказать, что как правило, оператор switch эффективнее набора вложенных операторов if. Это свойство представляет особый интерес, поскольку позволяет понять работу компилятора Java. Компилируя оператор switch, компилятор Java будет проверять каждую из констант case и создавать “таблицу переходов”, которую будет использовать для выбора ветви программы в зависимости от значения выражения. Поэтому в тех случаях, когда требуется осуществлять выбор в большой группе значений, оператор switch будет выполняться значительно быстрее последовательности операторов if-else. Это обусловлено тем, что компилятору известно, что все константы case имеют один и тот же тип, и их нужно просто проверять на предмет равенства значению выражения switch. Компилятор не располагает подобными сведениями о длинном списке выражений оператора if.

Оператор if/else

Оператор if является основным оператором управления, который позволяет Java принимать решения или, точнее, выполнять операторы по условию. Оператор if содержит выражение и оператор. Если выражение равно true, то интерпретатор выполняет данный оператор. Однако если выражение равно false, то оператор пропускается. Выражение всегда должно возвращать тип boolean. Например:

if ( username == null) // Если имя пользователя равно null, то
    
username = "John Doe"; // определить его

Согласно синтаксису оператора if, выражение необходимо заключать в круглые скобки, хотя они и кажутся избыточными.

Как уже упоминалось, группа операторов, заключенная в фигурные скобки, сама является оператором, поэтому можно записать такой оператор if:

if (( address == null) || ( address.equals(""))) {
      address = "[undefined]";
     
System.out.println("WARNING: адрес не указан.");
}

Оператор if может содержать необязательное ключевое слово else, за которым следует второй операнд. При записи оператора в таком виде вычисляется выражение, а затем, если результат равен true, выполняется первый оператор. Иначе выполняется второй оператор. Например:

if ( username != null) System.out.println("Привет " + username);
  
else {
     
username = askQuestion("Как тебя зовут?");
     
System.out.println("Привет " + username + ". Welcome!");
  
}

При использовании вложенных операторов if/else необходимо убедиться в том, что оператору else соответствует подходящий оператор if. Рассмотрим следующие строки:

if ( i == j)
 
if ( j == k) System.out.println("i равно k");
else
 
System.out.println("i не равно j"); // ОШИБКА!!

В этом примере внутренний оператор if образует единый оператор, выполнение которого зависит от внешнего оператора if. К сожалению, не ясно (если связи не определяются отступами), к какому оператору if относится данный оператор else. В представленном примере отступы сбивают с толку. Согласно правилу, такой оператор else связан с ближайшим к нему оператором if. С правильно установленным отступом этот код выглядит следующим образом:

if ( i == j)
 
if ( j == k) System.out.println("i равно k");
     else
  
System.out.println("i не равно j"); // ОШИБКА!!

Это допустимый код, но он явно отличается от кода, который хотел записать программист. При работе с операторами if лучше использовать фигурные скобки. Это облегчит чтение кода. Лучше писать код таким образом:

if (i == j) {
  
if (j == k)
      
System.out.println("i равно k");
} else {
  
System.out.println("i не равно j");
}

Оператор else if

Оператор if/else используют при проверке состояния и при выборе одного из двух операторов или блоков программы. Но что делать, если нужно выбирать из нескольких блоков? В таком случае обычно применяется оператор else if, который на самом деле является идиоматическим вариантом стандартного оператора if/else, а не новой синтаксической конструкцией. Он выглядит так:

if ( n == 1) {
  
// Выполнить блок кода №1
}
else if ( n == 2) {
 
// Выполнить блок кода №2
}
else if ( n == 3) {
 
// Выполнить блок кода №3
}
else {
 
// Если ни одно условие не выполнилось, выполнить блок №4
}

Но вообще, в большинстве случаев, для такой конструкции лучше использовать оператор switch, который мы рассмотрим в следующем посте. А пока не большой пример:

if01

И несколько вариантов вывода этой программы на консоль:

if02

Так же, я думаю, тут еще разок стоит упомянуть об операторе ?: одним слайдом:

if03

Мы его рассматривали тут.

Java Statements

Операторы-выражения (Expression Statements)

Тут все просто. Мы это уже проходили, поэтому кратенько. Любое выражение можно использовать в качестве оператора, если поставить после него точку с запятой. Присваивание, инкременты и декременты, вызов метода и создание объекта являются разрешенными типами операторов. Например:

a = 1; // Присваивание
x *= 2; // Присваивание посредством операции
i++; // Постфиксная форма инкремента
--c; // Префиксная форма декремента
System.out.println("statement"); // Вызов метода

Составные операторы (Compound Statements)

Составным оператором (compound statement) называется любое количество операторов любого типа, заключенных в фигурные скобки. Составной оператор можно применить в любой части программы, где согласно синтаксису Java необходим оператор:

for( int i = 0; i < 10; i++) {
   a[i]++; // Тело цикла является составным оператором.  
   b[i]--; // Он состоит из двух операторов>выражений,
} // заключенных в фигурные скобки.

Пустой оператор (empty statement)

Пустой оператор (empty statement) в Java обозначается точкой с запятой. Пустой оператор ничего не делает, однако иногда такой синтаксис бывает полезен. Например, его можно применять для указания пустого тела цикла for:

for( int i = 0; i < 10; a[ i++]++) // Увеличить элементы массива
        /* empty */; // Тело цикла – пустой оператор

Оператор с меткой (labeled statement)

Оператор с меткой (labeled statement) – это оператор, которому было дано имя, записанное перед оператором и отделенное от него двоеточием. Метки используются операторами break и continue. Например:

rowLoop: for( int r = 0; r < rows.length; r++) { // Цикл с меткой
   
colLoop: for( int c = 0; c < columns.length; c++) { // Еще один
  
break rowLoop; // Использование метки
 
}
}

Оператор объявления локальной переменной (local variable)

Локальная переменная (local variable), обычно просто называемая переменной, является символическим именем места хранения значения, определяемым внутри метода или составного оператора. Перед использованием переменные нужно объявлять припомощи оператора объявления переменной. Поскольку язык Java строго типизирован, объявление переменной определяет тип переменной, а хранимые в ней значения могут быть только указанного типа.

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

Операторы-инструкции в Java

feofan03
— Был у нас толмач-немчин. Ему переводить, а он лыка не вяжет. Мы его… в кипятке и… сварили.
— Нельзя так с переводчиками обращаться.

к/ф  Иван Васильевич меняет профессию

В английском языке для обозначения обычных операторов, которые мы уже рассматривали, и инструкций существуют два различных термина: operator и statement соответственно. Однако в русском языке оба этих термина переводятся как «оператор».


Чаще всего операторы-инструкции, в руководствах и книгах, называют управляющими операторами или управляющими конструкциями Java и имеют в виду под ними операторы циклов, условные операторы и т.п. В большинстве случаев это так и есть, но оригинальная документация Oracle к операторам инструкциям (statement) относит так же и другие операторы, например – выражения и составные операторы.

И так, трудности перевода прояснены и далее для краткости будем использовать термин «оператор» как для операторов, так и для операторов-инструкций.

Как уже повелось, в этом, отдельном посте, приведу таблицы операторов, а в следующем описание этих таблиц, дабы было удобнее смотреть таблицы и их описание в разных вкладках браузера.

ST0001

И теперь тоже самое на русском

ST0004
В русской таблице отсутствует описание оператора foreach, но в английской оно есть. Это разновидность цикла for, что даже видно из таблицы на английском и было введено в Java 5. Применим к массивам и классам, реализующим интерфейс java.lang.Iterable.