Операторы и выражения в Java. Часть 2 – описание таблицы приоритетов операторов
Здесь мы подробно рассмотрим таблицу приоритетов операторов приведенную в прошлом посте.
Приоритет
В колонке P таблицы приоритетов операторов определен приоритет (precedence) для каждого оператора. Приоритет определяет порядок выполнения операторов. Рассмотрим следующее выражение:
a + b * c
Оператор умножения имеет более высокий приоритет, чем оператор сложения, поэтому a прибавляется к произведению b и c. Приоритет операторов можно считать показателем степени связанности операторов с их операндами. Чем больше число, тем сильнее они связаны.
Приоритет операторов по умолчанию можно изменить с помощью круглых скобок, которые явно укажут порядок операций. Чтобы сложение выполнялось перед умножением, предыдущее выражение можно переписать таким образом:
(a + b) * c
В Java приоритет операторов был выбран с намерением обеспечить совместимость с С; разработчики языка С выбрали такой приоритет, чтобы писать большую часть выражений естественно, без круглых скобок. В Java существует несколько общеупотребительных идиом, требующих наличия круглых скобок. Среди них:
// Приведение класса, объединенное с доступом к члену
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 не допускаются в операторе доступа к массиву []). - ссылка
Объект или массив. - переменная
Переменная или любая другая величина (например, элемент массива), которой
можно присвоить значение.
Возвращаемый тип
Подобно тому как каждый оператор ожидает операнды определенных типов, любой оператор выдает значение определенного типа. И тут действует правило приведения типов, которое мы уже обсуждали.
Операторы сравнения, равенства и булевые операторы всегда возвращают булевые значения. Все операторы присваивания возвращают присваиваемое значение, тип которого должен быть совместим с переменной, находящейся в левой части выражения. Условный оператор возвращает значение второго или третьего аргумента, которые должны относиться к одному типу.
Побочные эффекты
Каждый оператор вычисляет значение на основе одного или нескольких операндов. Однако некоторые операторы, в дополнение к основному вычислению, приводят к побочным эффектам. Если выражение подразумевает побочные эффекты, то при его вычислении состояние 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). Арифметические операции с плавающей точкой применяются, если один из операндов является числом с плавающей точкой; в противном случае задействуется целочисленная арифметика. Это важно, так как арифметика для чисел с плавающей точкой отличается от целочисленной арифметики – например, по способу деления и способу обработки переполнения и округления. Существуют следующие арифметические операторы:
Сложение (+)
Оператор + складывает два числа. Как вы уже возможно замечали, оператор + можно применять для сцепления строк. Если один из операндов сложения является строкой, то другой операнд также преобразуется в строку. Если вы хотите сочетать сложение со сцеплением, убедитесь в наличии круглых скобок. Например:
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»
Не забывайте заключать операции сложения в круглые скобки, если сочетаете их со сцеплением строк. В противном случае оператор сложения будет интерпретирован как оператор сцепления.
В интерпретатор 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.
Ну и на последок небольшой примерчик, чтобы не было скучно:
Немного объясню что делает данная программа.
До строки 15 все просто, даже объяснять не надо.
В 15 строке проверяется если есть аргументы командной строки, то строковой переменной isArgs задается значение переменной sYes, если нет аргументов, то переменной sNo.
Далее в 20 строке, опять же, проверяется наличие аргументов в командной строке, и если они есть то выводится на печать первый аргумент переданный в командной строке при запуске программы.
Ну и теперь пример работы этой программы в консоли:
Стрелочками указаны два запуска данной прогарммы: без аргументов в командной строке и с ними.
Результат, как говорится, на лицо.
НОУ ИНТУИТ | Лекция | Основные конструкции языков Java и C#
< Лекция 16 || Лекция 10: 12345678910
Аннотация: Рассматриваются базовые элементы технологий Java и .NET и основные конструкции языков Java и С#. Рассказывается о лексике, базовых типах, выражениях и инструкциях обоих языков, а также о правилах описания пользовательских типов.
Ключевые слова: ПО, Java, .NET, Web, программное обеспечение, интерфейс, SUN, IBM, Oracle, SAP, J2SE, J2EE, J2ME, очередь, язык программирования, JVM, интерпретация, приложение, доступ, пользователь, аплет, значение, виртуальная машина, MSIL, класс, class, library, промежуточный язык, транслятор, visual, Basic, net, C#, JIT, компонентная технология, выход, автоматическое управление памятью, тип значений, делегатный тип, инструкция, выражение, Unicode, бит, UTF-16, yield, литерал, объект, обратный, перевод строки, возврат каретки, строковый, операторы, условная компиляция, программа, операции, пространство имен, пакет, массив, командная строка, Visual Studio 2005, beta, компилятор, development kit, Windows, ссылочный тип, примитивный тип, класс-обертка, длина конъюнкции, minvalue, MaxValue, операция дополнения, знаковый бит, исключение, uncheck, IEEE 754, унарный оператор, инициализатор, бинарный оператор, операция конъюнкции, типовые параметры, SIO, unsafe, перечислимый тип, отладочное сообщение, структура данных, поле, список, параметры операций, abstract, тип данных, структурный тип, консоль, список операций
Платформы Java и .
NETНа данный момент наиболее активно развиваются две конкурирующие линии технологий создания ПО на основе компонентов — технологии Java и .NET. В этой и следующих лекциях мы рассмотрим несколько элементов этих технологий, являющихся ключевыми в создании широко востребованного в настоящее время и достаточно сложного вида приложений. Это Web-приложения, т.е. распределенное программное обеспечение, использующее базовую инфраструктуру Интернета для связи между различными своими компонентами, а стандартные инструменты для навигации по Web — браузеры — как основу для своего пользовательского интерфейса.
Технологии Java представляют собой набор стандартов, инструментов и библиотек, предназначенных для разработки приложений разных типов и связанных друг с другом использованием языка программирования Java. Торговая марка Java принадлежит компании Sun Microsystems, и эта компания во многом определяет развитие технологий Java, но в нем активно участвуют и другие игроки — IBM, Intel, Oracle, Hewlett-Packard, SAP, Bea и пр.
В этот набор входят следующие основные элементы:
- Платформа Java Platform Standard Edition (J2SE ) [1].
Предназначена для разработки обычных, в основном, однопользовательских приложений.
- Платформа Java Platform Enterprise Edition (J2EE ) [2].
Предназначена для разработки распределенных Web-приложений уровня предприятия.
Платформа Java Platform Micro Edition (J2ME) [3].Предназначена для разработки встроенных приложений, работающих на ограниченных ресурсах, в основном, в мобильных телефонах и компьютеризированных бытовых устройствах.
- Платформа Java Card [4].
Предназначена для разработки ПО, управляющего функционированием цифровых карт. Ресурсы, имеющиеся в распоряжении такого ПО, ограничены в наибольшей степени.
C некоторыми оговорками можно считать, что J2ME является подмножеством J2SE, а та, в свою очередь, подмножеством J2EE. Java Card представляет собой, по существу, особый набор средств разработки, связанный с остальными платформами только поддержкой (в сильно урезанном виде) языка Java.
Язык Java — это объектно-ориентированный язык программирования, который транслируется не непосредственно в машинно-зависимый код, а в так называемый байт-код, исполняемый специальным интерпретатором, виртуальной Java — машиной ( Java Virtual Machine, JVM). Такая организация работы Java -программ позволяет им быть переносимыми без изменений и одинаково работать на разных платформах, если на этих платформах есть реализация JVM, соответствующая опубликованным спецификациям виртуальной машины.
Кроме того, интерпретация кода позволяет реализовывать различные политики безопасности для одних и тех же приложений, выполняемых в разных средах, — к каким ресурсам (файлам, устройствам и пр. ) приложение может иметь доступ, а к каким нет, можно определять при запуске виртуальной машины. Таким способом можно обеспечить запускаемое пользователем вручную приложение (за вред, причиненный которым, будет отвечать этот пользователь) большими правами, чем аплет, загруженный автоматически с какого-то сайта в Интернете.
Режим интерпретации приводит обычно к более низкой производительности программ по сравнению с программами, оттранслированными в машинно-специфический код. Для преодоления этой проблемы JVM может работать в режиме динамической компиляции (just-in-time-compilation, JIT), в котором байт-код на лету компилируется в машинно-зависимый, а часто исполняемые участки кода подвергаются дополнительной оптимизации.
В настоящем курсе мы рассмотрим ряд элементов платформ J2EE и J2SE, имеющих большое значение для разработки Web-приложений. Читателей, интересующихся деталями устройства и использования J2ME и Java Card, мы отсылаем к документации по этим платформам ([3] и [4]).
.NET [5] представляет собой похожий набор стандартов, инструментов и библиотек, но разработка приложений в рамках .NET возможна с использованием различных языков программирования. Основой .NET являются виртуальная машина для промежуточного языка ( Intermediate Language, IL, иногда встречается сокращение MSIL, Microsoft IL), в который транслируются все .NET программы, также называемая
Промежуточный язык является полноценным языком программирования, но он не предназначен для использования людьми. Разработка в рамках .NET ведется на одном из языков, для которых имеется транслятор в промежуточный язык —Visual Basic.NET, С++, C#, Java (транслятор Java в .NET называется J#, и он не обеспечивает одинаковой работы программ на Java, оттранслированных в . NET и выполняемых на JVM) и пр. Однако разные языки достаточно сильно отличаются друг от друга, и чтобы гарантировать возможность из одного языка работать с компонентами, написанными на другом языке, необходимо при разработке этих компонентов придерживаться
Некоторым отличием от Java является то, что код на промежуточном языке в .NET не интерпретируется, а всегда выполняется в режиме динамической компиляции (JIT).
Компания Microsoft инициировала разработку платформы .NET и принятие стандартов, описывающих ее отдельные элементы (к сожалению, пока не все), и она же является основным поставщиком реализаций этой платформы и инструментов разработки. Благодаря наличию стандартов возможна независимая реализация .NET (например, такая реализация разработана в рамках проекта Mono [6]), но, в силу молодости платформы и опасений по поводу монопольного влияния Microsoft на ее дальнейшее развитие, реализации .NET не от Microsoft используются достаточно редко.
Прежде чем перейти к более детальному рассмотрению компонентных технологий Java и .NET, ознакомимся с языками, на которых создаются компоненты в их рамках.
Для Java -технологий базовым языком является Java, а при изучении правил построения компонентов для .NET мы будем использовать язык C#. Он наиболее удобен при работе в этой среде и наиболее похож на Java.
Оба этих языка сейчас активно развиваются — в сентябре 2004 года вышла версия 1.5, она же была объявлена версией 5 платформы J2SE и языка Java. При переходе между версиями 1.4 и 1.5 Java претерпела наиболее серьезные изменения за всю свою историю (достаточно полное описание этих изменений стало доступно только в середине 2005 года).
Существенное обновление C# также должно произойти в версии 2.0, выходящей в ноябре 2005 года. Поэтому мы сопоставляем характеристики уже имеющейся на момент написания версии Java 5 с готовящейся к выходу версией C# 2.0. Это представляется обоснованным еще и потому, что эти версии двух языков достаточно близки по набору поддерживаемых ими конструкций.
Данная лекция дает лишь базовую информацию о языках Java и C#, которая достаточна для понимания приводимого далее кода компонентов и общих правил, регламентирующих их разработку, и может служить основой для дальнейшего их изучения. Оба языка достаточно сложны, и всем их деталям просто невозможно уделить внимание в рамках двух лекций. Для более глубокого изучения этих языков (особенно необходимого при разработке инструментов для работы с ними) рекомендуется обратиться к соответствующим стандартам [7] и [8] (ссылки приведены на последние версии стандартов на момент написания этой лекции, кроме того, некоторые элементы C# 2.
Оба языка имеют мощные выразительные возможности объектно-ориентированных языков последнего поколения, поддерживающих автоматическое управление памятью и работу в многопоточном режиме. Они весьма похожи, но имеют большое число мелких отличий в деталях. Наиболее существенны для построения программ различия, касающиеся наличия в C# невиртуальных методов, возможности объявления и использования пользовательских типов значений и делегатных типов в C# и возможности передачи значений параметров в C# по ссылке. Обсуждение и одновременно сравнение характеристик языков мы будем проводить по следующему плану:
- Лексика.
- Общая структура программ.
- Базовые типы и операции над ними.
- Инструкции и выражения.
- Пользовательские типы.
- Средства создания многопоточных программ.
Общие черты Java и C# описываются далее обычным текстом, а особенности — в колонках.
В левой колонке будут описываться особенности Java. В правой колонке будут описываться особенности C#.
Дальше >>
< Лекция 16 || Лекция 10: 12345678910
Глубокое погружение в операторы Java и их использование
Концепции машинного обученияКонцепции машинного обучения
Подборка основанных на данных статей по социальным, экономическим и политическим вопросам.
Опубликовано 3 марта 2023 г.
+ Подписаться
Java — это язык программирования, который широко используется при разработке программного обеспечения, и понимание операторов является важной частью обучения написанию эффективного кода Java. В этой статье мы объясним операторы в Java эксперту в 2000 слов.
В языке Java операторы — это символы или ключевые слова, используемые для выполнения операций над одним или несколькими операндами. Операторы используются для манипулирования данными и выполнения различных вычислений в программе. Операторы Java можно разделить на несколько типов, таких как арифметические операторы, операторы присваивания, логические операторы, операторы сравнения и побитовые операторы.
Арифметические операторы используются для выполнения основных арифметических операций, таких как сложение, вычитание, умножение и деление. Оператор сложения (+) используется для сложения двух значений, оператор вычитания (-) используется для вычитания одного значения из другого, оператор умножения (*) используется для умножения двух значений вместе, а оператор деления (/) используется для деления одного значения на другое. В дополнение к этим основным арифметическим операторам в Java также есть оператор по модулю (%), который используется для вычисления остатка от операции деления.
Операторы присваивания используются для присвоения значения переменной. Наиболее распространенным оператором присваивания является оператор равенства (=), который присваивает значение переменной. Например, код «int x = 5;» присваивает значение 5 переменной x.
Логические операторы используются для выполнения логических операций над логическими значениями. Логические значения либо истинны, либо ложны. К логическим операторам относятся оператор И (&&), оператор ИЛИ (||) и оператор НЕ (!). Оператор И возвращает истину, если оба операнда истинны, оператор ИЛИ возвращает истину, если хотя бы один из операндов истинен, а оператор НЕ возвращает значение, противоположное логическому значению операнда.
Операторы сравнения используются для сравнения двух значений и возврата логического значения. Операторы сравнения включают оператор равенства (==), оператор не равно (!=), оператор больше (>), оператор меньше (<), оператор больше или равно оператор (>=) и оператор «меньше или равно» (<=). Оператор равенства возвращает истину, если оба операнда равны, оператор не равно возвращает истину, если оба операнда не равны, оператор больше возвращает истину, если первый операнд больше второго операнда, оператор меньше возвращает истину если первый операнд меньше второго операнда, оператор «больше или равно» возвращает значение true, если первый операнд больше или равен второму операнду, а оператор «меньше или равно» возвращает true, если первый операнд меньше или равен второму операнду. 9), оператор сдвига влево (<<) и оператор сдвига вправо (>>). Побитовый оператор И возвращает целочисленное значение с битом, установленным в 1, только если оба операнда имеют этот бит, установленный в 1, побитовый оператор ИЛИ возвращает целочисленное значение с битом, установленным в 1, если любой из операндов имеет этот бит, установленный в 1 , побитовый оператор XOR возвращает целочисленное значение с битом, установленным в 1, только если в одном из операндов этот бит установлен в 1, оператор сдвига влево сдвигает биты первого операнда влево на число битов, указанное в второй операнд, а оператор сдвига вправо сдвигает биты первого операнда вправо на число битов, указанное вторым операндом.
В дополнение к этим основным операторам, Java также включает ряд сокращенных операторов, которые объединяют арифметические операторы и операторы присваивания. Эти сокращенные операторы включают оператор присваивания сложения (+=), оператор присваивания вычитания (-=) и присваивание умножения. Если вам нужно более краткое объяснение этой темы, прочитайте нашу оригинальную статью об операторах в Java
. В заключение статьи я хотел бы поблагодарить вас за чтение этого исчерпывающего руководства по операторам Java. Я надеюсь, что эта статья дала вам четкое представление о различных типах операторов в Java и их использовании.
Если у вас есть вопросы или сомнения, задавайте их в разделе комментариев ниже. Я был бы рад прояснить любую путаницу или предоставить дополнительную информацию по этой теме. Еще раз, спасибо, что нашли время, чтобы прочитать эту статью.
Ссылки
Операторы в Java
W3schools.com. (2022). Java-операторы.
мл-concepts.com
259последователи
+ Подписаться
Операторы
8.1. Оператор Spread
Оператор Spread-dot ( *.
), часто сокращенно просто Spread Operator, используется для вызова действия над всеми элементами.
агрегатного объекта. Это эквивалентно вызову действия над каждым элементом и сбору результата в список:
class Car { Строка сделать Струнная модель } деф автомобили = [ новый автомобиль(марка: "Пежо", модель: "508"), новый автомобиль(марка: 'Renault', модель: 'Clio')] (1) def makes = автомобили*.make (2) assert makes == ['Peugeot', 'Renault'] (3)
1 | построить список из автомобилей элементов. Список представляет собой совокупность объектов. |
2 | вызвать оператора спреда из списка, обратившись к свойству make каждого элемента |
3 | возвращает список строк, соответствующих набору make элементов |
Выражение cars*.make
эквивалентно cars.collect{ it.make }
.
Нотация Groovy GPath позволяет сократить путь, когда указанное свойство
не является свойством содержащего списка, в этом случае он автоматически
распространение. В ранее упомянутом случае выражение автомобили.марка
банка
можно использовать, хотя часто рекомендуется сохранять явный оператор с расставленными точками.
Оператор распространения является нулевым, что означает, что если элемент коллекции имеет значение null,
он вернет null вместо того, чтобы выдать NullPointerException
:
cars = [ новый автомобиль(марка: "Пежо", модель: "508"), ноль, (1) новый автомобиль(марка: 'Renault', модель: 'Clio')] assert cars*. make == ['Peugeot', null, 'Renault'] (2) утверждать null*.make == null (3)
1 | построить список, для которого один из элементов нулевой |
2 | с использованием оператора распространения вызовет , а не исключение NullPointerException |
3 | получатель также может быть нулевым, и в этом случае возвращаемое значение равно ноль |
Оператор распространения можно использовать в любом классе, реализующем интерфейс Iterable
:
class Component { Целочисленный идентификатор Имя строки } класс CompositeObject реализует Iterable{ Компоненты защиты = [ новый компонент (id: 1, имя: «Foo»), новый компонент (id: 2, имя: «Бар»)] @Override Итератор<Компонент> итератор() { компоненты. iterator() } } def составной = новый составной объект () утверждать составной*.id == [1,2] утверждать составное*.имя == ['Foo','Bar']
Использовать несколько вызовов оператора растровой точки (здесь cars*.models*.name
), когда
работа с агрегатами структур данных, которые сами содержат агрегаты:
class Make { Имя строки Список моделей} @канонический модель класса { Имя строки } деф автомобили = [ новая марка (название: «Пежо», модели: [новая модель('408'), новая модель('508')]), новый Марка (название: «Рено», модели: [новая модель('Clio'), новая модель('Captur')]) ] def makes = автомобили*.название assert делает == ['Peugeot', 'Renault'] def модели = автомобили*.модели*.название утверждать модели == [['408', '508'], ['Clio', 'Captur']] assert models.sum() == ['408', '508', 'Clio', 'Captur'] // выравнивание на один уровень assert models.flatten() == ['408', '508', 'Clio', 'Captur'] // выравниваем все уровни (в данном случае один)
Рассмотрите возможность использования метода DGM collectNested
вместо оператора с расставленными точками для коллекций коллекций:
class Car { Строка сделать Струнная модель } деф автомобили = [ [ новый автомобиль(марка: 'Peugeot', модель: '408'), новый автомобиль(марка: 'Peugeot', модель: '508') ], [ новый автомобиль(марка: 'Renault', модель: 'Clio'), новый автомобиль(марка: 'Renault', модель: 'Captur') ] ] def models = cars. collectNested{ it.model } утверждать модели == [['408', '508'], ['Clio', 'Captur']]
8.1.1. Распространение аргументов метода
Возможны ситуации, когда аргументы вызова метода можно найти в списке, который необходимо адаптировать к методу аргументы. В таких ситуациях вы можете использовать оператор распространения для вызова метода. Например, представьте, что у вас есть следующая подпись метода:
int function (int x, int y, int z) { х*у+г }
тогда, если у вас есть следующий список:
def args = [4,5,6]
вы можете вызвать метод без определения промежуточных переменных:
assert function(*args) == 26
Можно даже смешивать обычные аргументы с расширенными:
args = [4] функция утверждения(*args,5,6) == 26
8.1.2. Элементы списка распространения
При использовании внутри литерала списка оператор расширения действует так, как если бы содержимое элемента расширения было встроено в список:
def items = [4,5] (1) список по умолчанию = [1,2,3,*элементы,6] (2) список утверждений == [1,2,3,4,5,6] (3)
1 | шт. список |
2 | мы хотим вставить содержимое списка элементов непосредственно в список без вызова addAll |
3 | содержимое элементов было встроено в список |
8.1.3. Элементы карты распространения
Оператор карты распространения работает аналогично оператору списка распространения, но для карт. Это позволяет вам встроить содержимое карты в другой литерал карты, как в следующем примере:
def m1 = [c:3, d:4] (1) карта защиты = [a:1, b:2, *:m1] (2) assert map == [a:1, b:2, c:3, d:4] (3)
1 | m1 это карта, которую мы хотим встроить |
2 | мы используем нотацию *:m1 для распространения содержимого m1 на карту |
3 | карта содержит все элементы m1 |
Должность оператора карты спреда имеет значение, как показано в следующем примере:
по умолчанию m1 = [c:3, d:4] (1) карта защиты = [a:1, b:2, *:m1, d: 8] (2) assert map == [a:1, b:2, c:3, d:8] (3)
1 | m1 это карта, которую мы хотим встроить |
2 | мы используем нотацию *:m1 для распространения содержимого m1 на карту , но переопределить ключ d после разброс |
3 | карта содержит все ожидаемые ключи, но d был переопределен |
8.
2. Оператор диапазона Groovy поддерживает концепцию диапазонов и предоставляет нотацию ( ..
) для создания диапазонов объектов:
def range = 0..5 (1) утверждать (0..5).collect() == [0, 1, 2, 3, 4, 5] (2) утверждать (0..<5).collect() == [0, 1, 2, 3, 4] (3) утверждать (0<..5).collect() == [1, 2, 3, 4, 5] (4) утверждать (0<..<5).collect() == [1, 2, 3, 4] (5) assert (0..5) instanceof List (6) утверждать (0..5).size() == 6 (7)
1 | простой диапазон целых чисел, сохраненный в локальной переменной |
2 | и IntRange с включенными границами |
3 | и IntRange с исключительной верхней границей |
4 | и IntRange с эксклюзивной нижней границей |
5 | и IntRange , с исключительными нижними и верхними границами |
6 | a groovy. lang.Range реализует интерфейс List |
7 | означает, что вы можете вызвать для него метод размера |
Реализация диапазонов упрощена, т. е. сохраняются только нижняя и верхняя границы. Вы можете создать диапазон
из любых Сопоставимый объект
, который имеет методы next()
и previous()
для определения следующего/предыдущего элемента в диапазоне.
Например, вы можете создать диапазон символов следующим образом:
assert('a'..'d').collect() == ['a','b','c','d']
8.3. Оператор космического корабля
Оператор космического корабля ( <=>
) делегирует метод compareTo
:
assert (1 <=> 1) == 0 утверждать (1 <=> 2) == -1 утверждать (2 <=> 1) == 1 утверждать ('a' <=> 'z') == -1
8.4. Оператор нижнего индекса
Оператор нижнего индекса представляет собой сокращенную запись для getAt
или putAt
, в зависимости от того, найдете ли вы его на
левая или правая часть задания:
def list = [0,1,2,3,4] список утверждений [2] == 2 (1) список[2] = 4 (2) утвердить список[0. .2] == [0,1,4] (3) список[0..2] = [6,6,6] (4) список утверждений == [6,6,6,3,4] (5)
1 | [2] можно использовать вместо getAt(2) |
2 | , если слева от задания, вызовет putAt |
3 | getAt также поддерживает диапазоны |
4 | так же как и putAt |
5 | список изменен |
Оператор нижнего индекса в сочетании с пользовательской реализацией getAt
/ putAt
является удобным способом деструктурирования
объекты:
класс пользователя { Длинный идентификатор Имя строки защита getAt(int i) { (1) переключатель (я) { случай 0: возвращаемый идентификатор случай 1: вернуть имя } выбросить новое исключение IllegalArgumentException("Нет такого элемента $i") } void putAt (int i, значение по умолчанию) { (2) переключатель (я) { случай 0: идентификатор = значение; возвращаться случай 1: имя = значение; возвращаться } выбросить новое исключение IllegalArgumentException("Нет такого элемента $i") } } def user = новый пользователь (id: 1, имя: «Алекс») (3) утверждать пользователя [0] == 1 (4) утвердить пользователя [1] == 'Алекс' (5) пользователь [1] = 'Боб' (6) утверждать user. name == 'Боб' (7)
1 | класс User определяет пользовательский getAt реализация |
2 | класс User определяет пользовательскую реализацию putAt |
3 | создать образец пользователя |
4 | с помощью оператора индекса с индексом 0 позволяет получить идентификатор пользователя |
5 | с помощью оператора нижнего индекса с индексом 1 позволяет получить имя пользователя |
6 | мы можем использовать оператор нижнего индекса для записи свойства благодаря делегированию putAt |
7 | и проверьте, действительно ли это свойство имя было изменено |
8.
5. Оператор безопасного индексирования Groovy 3.0.0 представляет оператор безопасного индексирования, т. е. ?[]
, что аналогично ?.
. Например:
Строка[] массив = ['a', 'b'] assert 'b' == array?[1] // получаем с использованием индекса обычного массива array?[1] = 'c' // устанавливаем с использованием индекса обычного массива утверждать 'c' == массив?[1] массив = ноль assert null == array?[1] // возвращаем null для всех значений индекса array?[1] = 'c' // тихо игнорируем попытку установить значение утверждать массив null ==?[1] def personInfo = [имя: 'Daniel.Sun', местоположение: 'Шанхай'] assert 'Daniel.Sun' == personInfo?['name'] // используем индекс карты нормалей personInfo?['name'] = 'sunlan' // устанавливается с использованием индекса карты нормалей утверждать 'sunlan' == personInfo?['name'] информация о человеке = ноль assert null == personInfo?['name'] // возвращаем null для всех значений карты personInfo?['name'] = 'sunlan' // незаметно игнорируем попытку установить значение утверждать null == personInfo?['name']
8.
6. Оператор принадлежности Оператор принадлежности ( в
) эквивалентен вызову метода isCase
. В контексте списка
это эквивалентно
для вызова содержится
, как в следующем примере:
def list = ['Grace','Rob','Emmy'] утверждать («Эмми» в списке) (1) утверждать ('Алекс'! в списке) (2)
1 | эквивалентно вызову list.contains('Эмми') или list.isCase('Эмми') |
2 | отрицание членства эквивалентно вызову !list.contains('Emmy') или !list.isCase('Emmy') |
8.7. Оператор идентификации
В Groovy использование ==
для проверки равенства отличается от использования того же оператора в Java. В Groovy это вызов равно
. Если вы хотите сравнить равенство ссылок, вы должны использовать равно
, как в следующем примере:
def list1 = ['Groovy 1.8','Groovy 2.0','Groovy 2.3'] (1) def list2 = ['Groovy 1.8','Groovy 2.0','Groovy 2.3'] (2) утверждать список1 == список2 (3) утверждать !list1.is(list2) (4) утверждать список1 !== список2 (5)
1 | Создать список строк |
2 | Создайте еще один список строк, содержащих те же элементы |
3 | , используя == , мы проверяем равенство объектов, что эквивалентно list1.equals(list2) в Java |
4 | с использованием равно , мы можем проверить, что ссылки различны, что эквивалентно list1 == list2 в Java |
5 | , используя === или !== (поддерживается и рекомендуется, начиная с Groovy 3. 0.0), мы также можем проверить, являются ли ссылки различными или нет, что эквивалентно list1 == list2 и list1 != list2 на Яве |
8.8. Оператор приведения
Оператор приведения ( как
) — вариант литья. Приведение преобразует объект из одного типа в другой без их
быть совместимым для назначения. Возьмем пример:
Целое число x = 123 Строка s = (Строка) x (1)
1 | Целое число не может быть присвоено String , поэтому оно создаст ClassCastException во время выполнения |
Это можно исправить, используя вместо этого приведение :
Целое число x = 123 Строка s = x as Строка (1)
1 | Целое число не может быть присвоено строке , но использование в качестве приведет к принудительному преобразованию в строку |
Когда объект принудительно преобразуется в другой, если целевой тип не совпадает с исходным типом, приведение возвращает новый объект . Правила приведения различаются в зависимости от исходного и целевого типов, и приведение может завершиться ошибкой, если не выполняется преобразование.
правила найдены. Пользовательские правила преобразования могут быть реализованы благодаря методу asType
:
класс Идентифицируемый { Имя строки } класс Пользователь { Длинный идентификатор Имя строки def asType (Цель класса) { (1) если (цель == Идентифицируемый) { вернуть новый идентифицируемый (имя: имя) } выбросить новое исключение ClassCastException («Пользователь не может быть принужден к $ target») } } def u = новый пользователь (имя: «Ксавье») (2) def p = u как идентифицируемый (3) утверждать p instanceof Идентифицируемый (4) assert !(p instanceof User) (5)
1 | класс User определяет пользовательское правило преобразования с User на Идентифицируемый |
2 | создаем экземпляр пользователя |
3 | мы принуждаем экземпляр пользователя к идентифицируемому |
4 | цель является экземпляром Идентифицируемый |
5 | цель больше не является экземпляром Пользователь |
8.
9. Алмазный оператор Алмазный оператор ( <>
) является синтаксическим сахарным оператором, добавленным для поддержки совместимости с оператором
то же имя в Java 7. Оно используется для указания того, что универсальные типы должны выводиться из объявления:
Liststrings = new LinkedList<>()
В динамическом Groovy это совершенно не используется. В Groovy со статической проверкой типов это также необязательно, так как Groovy средство проверки типов выполняет вывод типа независимо от того, присутствует этот оператор или нет.
8.10. Оператор вызова
Оператор вызова ()
используется для неявного вызова метода с именем call
. Для любого объекта, который определяет метод вызова
,
вы можете опустить часть .call
и вместо этого использовать оператор вызова:
class MyCallable { внутренний вызов (int x) { (1) 2*х } } def mc = новый MyCallable() утверждать mc.