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.

24 апр. 2015 г.

Операторы и выражения в Java. Часть 2 – описание таблицы приоритетов операторов

Здесь мы подробно рассмотрим таблицу приоритетов операторов приведенную в прошлом посте.

Приоритет

В колонке P таблицы приоритетов операторов определен приоритет (precedence) для каждого оператора. Приоритет определяет порядок выполнения операторов. Рассмотрим следующее выражение:

a + b * c

Оператор умножения имеет более высокий приоритет, чем оператор сложения, поэтому a прибавляется к произведению b и c. Приоритет операторов можно считать показателем степени связанности операторов с их операндами. Чем больше число, тем сильнее они связаны.

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

(a + b) * c

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

// Приведение класса, объединенное с доступом к члену
(( Integer) o).intValue();

// Присваивание в совокупности со сравнением
while(( line = in.readLine()) != null) { ... }

// Побитовые операторы в совокупности со сравнением
if (( flags & (PUBLIC | PROTECTED)) != 0) { ... }

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

Ассоциативность

Если выражение включает несколько операторов с одинаковым приоритетом, то порядком выполнения операций управляет ассоциативность операторов. Большинство операторов ассоциативны слева направо, то есть операции выполняются слева направо. Однако операторы присваивания и унарные операторы обратно ассоциативны (справа налево). В колонке А таблицы приоритетов операторов определена ассоциативность для каждого оператора или группы операторов. Значение L означает ассоциативность слева направо, а R означает обратную ассоциативность.

Аддитивные операторы ассоциативны слева направо, то есть выражение a+b-c вычисляется слева направо: (a+b)-c. Унарные операторы и операторы присваивания вычисляются справа налево. Рассмотрим более сложное выражение:

a = b += c = ~d

Оно вычисляется следующим образом:

a = ( b += ( c = (~d)))

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

Количество и типы операндов

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

-n // Оператор «унарный минус»

Однако большинство операторов являются бинарными; они работают с двумя операндами. Оператор (минус) может выступать в обеих формах:

a-b // Оператор вычитания является бинарным

В Java также определен один тернарный (ternary) оператор (работает с тремя операндами), часто называемый условным оператором. Он похож на условный оператор if, но стоит внутри выражения. Эти три операнда разделяются знаком вопроса и двоеточием; второй и третий операнды должны относиться к одному типу.

x > y ? x : y // Тернарное выражение, возвращает большее число из x и y.

Тернарный оператор мы рассмотрим поподробнее чуть позже.

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

  • число
    Целое число, значение с плавающей точкой или символ (то есть любой примитивный тип, кроме boolean).
  • целое число
    Значения: byte, short, int, long или char (значения long не допускаются в операторе доступа к массиву []).
  • ссылка
    Объект или массив.
  • переменная
    Переменная или любая другая величина (например, элемент массива), которой
    можно присвоить значение.

Возвращаемый тип

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

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

Побочные эффекты

GrabliКаждый оператор вычисляет значение на основе одного или нескольких операндов. Однако некоторые операторы, в дополнение к основному вычислению, приводят к побочным эффектам. Если выражение подразумевает побочные эффекты, то при его вычислении состояние Java программы изменяется настолько, что повторное вычисление выражения может привести к результату, отличному от первого. Например, оператор инкремента ++ имеет побочный эффект приращения переменной. Выражение ++a увеличивает значение переменной a и возвращает новое, увеличенное значение. При следующем вычислении этого выражения получится уже другое значение. Различные операторы присваивания также имеют побочные эффекты. Например, выражение a*=2 можно записать в виде a=a*2. Значением выражения является значение a, умноженное на 2, но выражение имеет побочный эффект сохранения нового значения в a. Оператор вызова метода ()  имеет побочные эффекты, если побочные эффекты есть у вызываемого метода. Некоторые методы, например Math.sqrt(), просто вычисляют и возвращают значение без каких-либо побочных эффектов. Однако обычно методы всетаки имеют побочные эффекты. И наконец, оператор new имеет существенный побочный эффект, выраженный в создании нового объекта.

Порядок вычислений

Вычисляя выражение, интерпретатор Java выполняет различные операции в последовательности, заданной круглыми скобоками, а также приоритетом и ассоциативностью операторов. Однако перед началом операции интерпретатор вычисляет операнды оператора (исключение составляют операторы &&, ||  и ?:, которые не всегда вычисляют все свои операнды). Интерпретатор всегда вычисляет операнды слева направо. Это имеет значение, если какойлибо из операндов является выражением с побочными эффектами. В качестве примера рассмотрим следующий код:

int a = 2;
int v = ++a + ++a * ++a;

Хотя умножение выполняется перед сложением, первыми вычисляются операнды оператора ++. Таким образом, выражение равно 3+4*5, или 23.

Хотя большинство операторов мы уже рассмотрели совместно с примитивными типами данных, но там был акцент именно на примитивных типах данных и операторы мы использовали чтобы понять что это за типы :-). Теперь же рассмотрим операторы чуть подробнее, так как там тоже могут быть грабельки.

Арифметические операторы

Поскольку большинство программ работает, в первую очередь, с числами, наиболее часто используются операторы, выполняющие арифметические операции. Арифметические операторы можно задавать с целыми числами, числами с плавающей точкой и даже с символами (то есть их можно применять с любым примитивным типом данных, кроме boolean). Арифметические операции с плавающей точкой применяются, если один из операндов является числом с плавающей точкой; в противном случае задействуется целочисленная арифметика. Это важно, так как арифметика для чисел с плавающей точкой отличается от целочисленной арифметики – например, по способу деления и способу обработки переполнения и округления. Существуют следующие арифметические операторы:

Сложение (+)

GrabliОператор + складывает два числа. Как вы уже возможно замечали, оператор + можно применять для сцепления строк. Если один из операндов сложения является строкой, то другой операнд также преобразуется в строку. Если вы хотите сочетать сложение со сцеплением, убедитесь в наличии круглых скобок. Например:

System.out.println("Total: " + 3 + 4); // Отображает "Total: 34", не 7!

 

Вычитание (−)

Если оператор является бинарным, то он вычитает второй операнд из первого. Например, 7−3 равняется 4. Оператор «» может выполнять унарную инверсию.

Умножение (*)

Оператор * умножает два числа. Например, 7*3 равняется 21.

Деление (/)

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

7/3       // Равно 2
7/3.0f   // Равно 2.333333f
7/0       // Генерируется ArithmeticException
7/0.0    // Равно плюс бесконечности
0.0/0.0 // Равно NaN

Деление по модулю (%)

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

Оператор % вычисляет первый операнд по модулю второго (то есть он возвращает целый остаток от деления первого операнда на второй). Например, 7%3 дает 1. Знак результата совпадает со знаком первого операнда. Хотя оператор взятия по модулю обычно используют с целыми числами, он также подходит для значений с плавающей точкой. Например, 4.3%2.1 равняется 0.1. Вычисление значений по модулю нуль для целых чисел приводит к ArithmeticException. Для значений с плавающей точкой любое значение по модулю 0.0 и бесконечность по любому модулю дают NaN.

Унарный минус (−)

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

Оператор сцепления строк

Кроме сложения чисел, оператор + и родственный оператор += могут сцеплять, или соединять, строки. Если один из складываемых операндов является строкой, оператор преобразовывает другой операнд в строку. Например:

// Отображает "Quotient: 2.3333333"
System.out.println("Quotient: " + 7/3.0f);

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

В интерпретатор Java встроены строковые преобразования для всех примитивных типов данных. Объект можно преобразовать в строку посредством метода toString(). В некоторых классах определены специальные методы toString(), чтобы с их помощью легко преобразовывать объекты данного класса в строки. Массив преобразуется в строку при вызове встроенного метода toString() , который, к сожалению, не возвращает удобное строковое представление содержимого массива.

Операторы инкремента и декремента

Оператор ++ увеличивает на единицу операнд, который является переменной, элементом массива или полем объекта. Поведение данного оператора зависит от его положения относительно операнда. Если оператор находится перед операндом, то он называется оператором префиксной формы инкремента (preincrement). Он увеличивает значение операнда на единицу и возвращает вычисленное значение. Если же оператор находится после операнда, то он называется оператором постфиксной формы инкремента (postincrement). Такой оператор увеличивает значение операнда на единицу, но возвращает значение операнда до увеличения.

Например, в следующем коде обе величины i и j  получают значение 2:

i = 1;
j = ++i;

Однако в следующем примере i получает значение 2, a j  – значение 1:

i = 1;
j = i++;

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

Выражения x++ и x-- эквивалентны выражениям x=x+1 и x=x-1  соответственно; x вычисляется один раз, за исключением случаев использования операторов инкремента и декремента. Если x является выражением с побочными эффектами, это существенно меняет дело. Например, следующие два выражения не идентичны:

a[ i++]++;  // Увеличивает элемент массива
a
[ i++] = a[ i++] + 1; // Прибавляет единицу к одному элементу массива, а сохраняет его в другом

Чаще всего эти операторы – как в префиксной, так и в постфиксной форме – применяются для увеличения и уменьшения значения счетчика цикла.

Операторы сравнения и булевы операторы мы подробно рассмотрели с примитивным типом boolean тут. А побитовые операторы и операторы побитового сдвига рассмотрели с целочисленными типами тут.

Операторы присваивания

Операторы присваивания сохраняют, или присваивают, значение какой-либо переменной. Левый операнд должен быть локальной переменной, элементом массива или полем объекта. Справа может находиться любое значение типа, совместимого с переменной. В отличие от всех остальных операторов, операторы присваивания обратно ассоциативны, то есть присваивания в a=b=c выполняются справа налево: a=(b=c) .

Условный оператор

Условный оператор ?:  является тернарным (три операнда) оператором, унаследованным из языка С. Он позволяет внедрять условие в само выражение. Его можно представить как версию оператора if/else. Знак вопроса (?) разделяет первый и второй операнды условного оператора, а двоеточие (:) – второй и третий. Первый операнд должен иметь boolean значение. Второй и третий операнды могут быть любого типа, но они должны приводиться к одному и тому же типу. Вначале условный оператор вычисляет первый операнд. Если он истинен, оператор вычисляет второй операнд, а результат представляет как значение выражения. В противном случае, если первый операнд ложен, условный оператор вычисляет и возвращает третий операнд. Условный оператор никогда не вычисляет оба операнда (второй и третий), поэтому следует осторожно использовать выражения с побочными эффектами. Ниже приведены примеры оператора:

int max = (x > y) ? x : y;
String name = (name != null) ? name : "unknown";

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

Оператор instanceof

Этот оператор более подробно рассмотрим уже при изучении классов и объектов, а пока мотаем на ус. Хотя что-то возможно кому-то и не будет понятно.

Для оператора instanceof левым операндом должно быть значение объекта или массива, а правым операндом – имя ссылочного типа. Он возвращает true, если объект или массив является экземпляром указанного типа, иначе возвращается false. Оператор instanceof всегда возвращает false, если левый операнд является null. Если выражение instanceof равно true, можно без риска присваивать левый операнд переменной типа правого операнда.

Оператор instanceof можно использовать только с типами и значениями массивов и объектов, а не с примитивными типами и значениями.

Ниже приведены примеры использования instanceof (не все из них можно скомпилировать, это просто примеры для понимания):

"string" instanceof String // Истинно: все строки являются экземплярами String
"" instanceof Object       // Истинно: строки также являются экземплярами Object
null instanceof String     // Ложно: null не является чьим>либо экземпляром

Object o = new int[] {1,2,3};
o
instanceof int[]    // Истинно: значение массива является int>массивом
o instanceof byte[] // Ложно: значение массива не является byte>массивом
o instanceof Object // Истинно: все массивы являются экземплярами Object

// Используйте instanceof, чтобы убедиться в безопасности приведения объекта
if ( object instanceof Point) {
Point p = (Point) object;
}

Специальные операторы

В Java определены 5 языковых конструкций, которые иногда считаются операторами, а иногда – просто частью основного синтаксиса языка. Эти операторы перечислены в таблице приоритетов операторов, чтобы показать их приоритет по отношению к другим операторам. Далее эти языковые конструкции будут описаны более подробно. Здесь они изложены вкратце (так как многое мы еще пока не изучили), чтобы вы могли распознать их в примерах кода:

Доступ к члену объекта (.)

Объектом (object) называется множество данных и методы, работающие с этими данными, а поля данных и методы объекта являются его членами (members). Оператор точка (.) обеспечивает доступ к этим членам. Если о – это выражение, представляющее собой объектную ссылку, а f является именем поля объекта, то о.f равно значению данного поля. Если m – имя метода, то o.m относится к этому методу и позволяет вызывать его при помощи оператора (), описанного ниже.

Доступ к элементу массива ([])

Массивом (array) называется нумерованный список значений. Можно обратиться к каждому элементу массива через его номер, или индекс. Оператор [] позволяет обращаться к отдельным элементам массива. Если b является массивом, а i – выражением с типом int, то выражение b[i]  ссылается на один из элементов b. В отличие от других операторов, работающих с целыми значениями, данный оператор ограничивает типы значения индекса до int и более узких.

Вызов метода (())

Метод (method) представляет собой именованный фрагмент Java кода, который можно запускать, или вызывать, сопровождая имя метода несколькими выражениями, заключенными в круглые скобки и разделенными запятыми (выражений может и не быть). Значения этих выражений являются аргументами метода. Метод обрабатывает аргументы и может возвращать значение, которое становится значением выражения вызова метода. Если o.m является методом, не ожидающим аргументов, то его можно вызвать при помощи o.m(). Например, если метод требует наличия трех аргументов, то его можно вызвать выражением o.m(x,y,z). Перед вызовом метода интерпретатор Java вычисляет каждый передаваемый методу аргумент. Эти выражения обязательно вычисляются слева направо, что имеет значение только в случае побочных эффектов какоголибо из аргументов.

Создание объекта (new)

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

new ArrayList();
new Point(1,2);

Преобразование или приведение типа (())

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

(byte) 28 // Целый литерал приводится к типу byte
(int) (x + 3.14f) // Значение суммы с плавающей точкой приводится к целому
(String) h.get(k) // Базовый объект приводится к более узкому типу

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

Ну и на последок небольшой примерчик, чтобы не было скучно:

O0001

Немного объясню что делает данная программа.

До строки 15 все просто, даже объяснять не надо.

В 15 строке проверяется если есть аргументы командной строки, то строковой переменной isArgs задается значение переменной sYes, если нет аргументов, то переменной sNo.

Далее в 20 строке, опять же, проверяется наличие аргументов в командной строке, и если они есть то выводится на печать первый аргумент переданный в командной строке при запуске программы.

 

Ну и теперь пример работы этой программы в консоли:

O0002

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

Результат, как говорится, на лицо.