13 апр. 2015 г.

Примитивные целочисленные типы Java - byte, short, int, long, char

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

  Тип   Содержит   По умолчанию   Размер   Диапазон   Обертки
  char   целое беззнаковое   \u0000   16 bits   от \u0000  до \uFFFF (от 0 до 65535 десятичное)   Charaster
  byte   целое знаковое   0   8 bits   от -128 до 127   Byte
  short   целое знаковое   0   16 bits   от -32768 до 32767   Short
  int   целое знаковое   0   32 bits   от -2147483648 до 2147483647   Integer
  long   целое знаковое   0   64 bits   от -9223372036854775808 до 9223372036854775807   Long

Целочисленные литералы

Значения целочисленным переменным и константам задаются при помощи следующих литералов:

108 – десятичный литерал
0154 – восьмеричный литерал (в начале стоит ноль)
0x6c – шестнадцатеричный литерал
0b01101100 – двоичный литерал (с Java 7)

Так же в числовых литералах можно использовать символ подчеркивания _ для разграничения разрядов, это сделано только для удобства написания и понимания и ни как не влияет на значение (с Java 7). Например:

0110_1100, 0x7fff_ffff, 0177_7777_7777

Для обозначения литерала типа long можно использовать суффикс L (можно использовать и маленькую l, но ее легко попутать с единицей 1). Например:

3_000_000_000L

В связи с этим есть один интересный момент, что переменной типа long не возможно задать значения больше 2147483647 следующим оператором присваивания:

long iLong = 2147483648;

Хотя значение 2147483648 является допустимым для типа long, но компилятор считает это число 2147483648 литералом типа int, поскольку у него нет суффикса L, а для типа int число 2147483648 является не допустимым значением и поэтому компилятор выдаст ошибку. Чтобы этого не было надо использовать суффикс L. Например:

long iLong = 2147483648L;

Такое выражение компилятор уже примет как правильное.

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

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

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

I00001

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

 

Grabli

Результат арифметической операции имеет тип int, кроме того случая, когда один из
операндов типа long. В этом случае результат будет типа long
. Перед выполнением арифметической операции всегда происходит повышение (promotion) типов byte, short, char. Они преобразуются в тип int, а может быть, и в тип long, если другой операнд типа long. Операнд типа int повышается до типа long, если другой операнд типа long. Конечно, числовое значение операнда при этом не меняется. Это правило приводит иногда к неожиданным результатам.

Все это мы подробно рассмотрим на практических примерах.

Стоит упомянуть, что при делении на ноль возникает исключительная ситуация, которая при отсутствии обработки этой исключительной ситуации (ArithmeticException) приводит к аварийному завершению работы программы.

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

Операция деление по модулю (или остаток) определяется так:
a % b = a — (a / b) * b

То есть 5%3=2, потому что 5-(5/3)*3=2, имейте в виду что 5/3 в данном случае это целочисленное деление и результат этой операции равен 1.

Все операции кроме инкремента и декремента имеют короткую форму записи, представленную в левой колонке. С подобным мы уже сталкивались в типе boolean.

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

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

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

Если не понятно то в двух словах объясню. Максимальное положительное значение переменной типа byte равно 127. Что мы и задали в строке 32. Затем чтобы обмануть компилятор мы два раза сделали инкремент этого значения, то есть по идее должны были получить число 129. Если бы мы просто попытались сделать c=c+2, то компилятор и IDE показали бы нам ошибку приведения типов (можете попробовать и убедиться).  Далее мы вывели получившиеся значение, которое стало равно –127. Это произошло потому, что в старший разряд (первый слева) была помещена единица. Числу 129 соответствует двоичное значение 1000 0001.

Но для типа byte, максимальным положительным значением является 127, что соответствует двоичному числу 0111 1111. Когда в старший разряд помещается единица, то это число начинает интерпретироваться как отрицательное. То есть если двоичное число 1000 0001 интерпретировать как отрицательное, то его десятичное значение будет равно –127. Почему так?

А потому-что надо было учить информатику Smile

Теперь приведу вывод этой программы:

I00003

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

Побитовые операции в ряде случаев вещь чрезвычайно полезная и нужная.

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

Про операцию деления по модулю еще стоит отметить одно правило:

a == (a / b) * b + (a % b)

Это так на заметку. Ну и поперли дальше!

Побитовые операции

I00004

Побитовые операторы и операторы сдвига манипулируют отдельными битами целого числа. Чаще всего побитовые операторы применяются для проверки и установки отдельных флаговых битов. Чтобы понять их поведение, необходимо понимать двоичные числа и формат дополнения, используемый для представления отрицательных чисел. Эти операторы нельзя использовать с массивами, объектами или с плавающей точкой. Операторы побитового сдвига и оператор NOT (~) так же нельзя использовать с операндами типа boolean. Все побитовые операторы за NOT, имеют совмещенные операторы присваивания. Если один из аргументов побитового оператора имеет значение long, то в результате тоже получится long. Если нет, то результат получается int. Если левый операнд оператора сдвига имеет значение long, то в результате тоже будет long. Если нет, то в результате получится int.

I00005

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

Арифметический сдвиг влево бывает только один, так как там только один вариант чем заполнять младшие освободившиеся биты – это ноль.

Grabli

Побитовые операции выполняются поразрядно, после того как оба операнда будут приведены к одному типу int или long, так же как и для арифметических операций. Это обстоятельство надо учитывать, если вы применяете побитовые операции к типам byte, short и возможно char, поскольку тут тоже могут быть неожиданные эффекты.

Особенно осторожным надо быть с логическим сдвигом вправо >>>.

 

Теперь рассмотрим все эти операторы более подробно.

Побитовое НЕ (~)

Унарный оператор ~ является оператором побитового отрицания, или побитового НЕ. Он инвертирует каждый бит своего единственного операнда, преобразовывая единицы в нули и нули в единицы. Например:

I00006

Побитовое И (&)

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

I00007

Побитовое ИЛИ (|)

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

I00008

Побитовое исключающее ИЛИ (^)

Этот оператор объединяет два целых операнда посредством логической операции
исключающего ИЛИ с их отдельными битами. В результате биты установлены в единицу только там, где соответствующие биты в двух операндах различны. Если оба бита – нули или единицы, то результат будет содержать нулевой бит. Например:

I00009

Побитовый сдвиг влево (<<)

Оператор << сдвигает биты левого операнда влево на количество позиций, обозначенное правым операндом. Старшие биты левого операнда теряются, а справа добавляются нулевые биты. Сдвиг целого числа влево на n позиций равносилен умножению этого числа на 2n. Например:

I00010

Если левый операнд представляет тип long, то значение правого операнда должно находиться в диапазоне между 0 и 63. В противном случае левый операнд считается int, а значение правого операнда должно располагаться между 0 и 31.

Побитовый арифметический сдвиг вправо со знаком (>>)

Оператор >> сдвигает биты левого операнда вправо на количество позиций, обозначенное правым операндом. Младшие биты левого операнда сдвигаются «наружу» и теряются. Старшие биты, сдвигаемые «внутрь», идентичны изначальному старшему биту левого операнда, то есть если в старшем бите была единица, то освободившиеся старшие биты заполняются единицами, а если ноль, то нолями.  Данный прием называется расширением знака. Он применяется для сохранения знака левого операнда. Если левый операнд положительный, а правый имеет значение n, то оператор >> равносилен целочисленному делению на 2n.
Например:

I00011

Побитовый логический сдвиг вправо без знака (>>>)

Этот оператор аналогичен оператору >>, но он всегда заполняет нулями старшие биты, каким бы ни был знак левого операнда. Данный прием называется дополнением нулями. Его применяют, если левый операнд интерпретируется как значение без знака (несмотря на то что в Java все типы со знаком). Например:

I00012

Как я уже говорил, с этим оператором надо быть очень осторожным, так как надо учитывать, что его операнды автоматически приводятся к типу int (или long), то есть их разрядность увеличивается. Приведенный пример справедлив для типа данных int, а вот для byte и short, он уже может подглючить. Поэтому пример с логическим сдвигом вправо мы рассмотрим более подробно, отдельно от остальных.

И еще несколько небольших замечаний:

  • В побитовых операциях используется по умолчанию тип int, то есть компилятор java всегда воспринимает правый операнд как int, поэтому при операциях с byte и short необходимо пользоваться операцией приведения типов. Мы это еще пока не проходили, но скоро пройдем, а пока мотаем на ус.
  • Это же справедливо и для арифметических операций, как я уже говорил, происходит автоматическое приведение всех операндов к типу int или long.

Ну а теперь немножко практики.

В библиотеку ProJava.jar я добавил еще парочку статических методов printlnByte и printByte для вывода значений типа byte в двоичном виде как строку.

И далее то что это программа выводит

I00013

Тут все более или менее просто и понятно. Далее будем разбираться с оператором логического сдвига вправо.

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

I00014

Как видим для типа int все идет так как написано, то есть старшие биты заполняются нужным количеством нулей. А вот для типа byte, уже начинаются глюки.
Строка 32 выводит нам десятичное значение переменной типа byte b1. И это значение равно 1073741811. Не хило так для типа byte, у которого максимальное положительное значение это 127. Это произошло потому, что, как уже говорилось, при арифметических и побитовых операциях происходит автоматическое приведение типов byte и short до типа int (или long). Вот на это мы и напоролись в 32 строке.

39 строка, которая выводит двоичное значение переменной типа byte b1 тоже преподнесла нам сюрприз. По идее в двух старших битах должны быть два нуля, но мы их там не наблюдаем. Там пара единиц. Это опять же произошло все по той же причине автоматического расширение byte и short до int или long, если long присутствует в выражении.

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

И так в строке 39 мы просто получили обрубок значения int, старшие разряды которого были выкинуты, вместе с двумя нулями в начале (смотрите внимательно скриншот вывода).

Теперь поправим эту ситуацию, чтобы все работало правильно. Для этого надо применить маску 0xff, которая превратит все старшие 24 разряда типа int в нули.

Исправленный вариант:

и далее вывод программы (только новая часть):

I00015

Ну и напоследок приведу табличку побитовых операций (не сдвиговых, т.к. это не имеет смысла)

I00016

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

Grabli

Хотя про еще одни грабли все таки стоит упомянуть. Это опять же касается операций сдвига. Перед тем как совершать любой побитовый сдвиг (например, x>>y) виртуальная машина java вычисляет остаток от деления y на ширину типа данных (для int 32 (y%32), для long 64 (y%64) и т.д.) и производит сдвиг на величину этого остатка. Поэтому, например, если мы задумаем сдвинуть значение типа int на 32 бита или 64 бита и т.п., каким либо оператором сдвига, то значение x останется прежним, потому что 32%32=0 (64%32=0).

А теперь, чтобы стало понятнее рассмотрим это на примере:

I00017

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

I00018

Как видим, там где мы сдвигали на 32 разряда, ни какого сдвига на самом деле не произошло по описанной выше причине.

Кроме того, при сдвиге на 48 позиций вправо, сдвиг на самом деле произошел на 16 позиций, по той же причине.

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

10 апр. 2015 г.

Примитивные типы Java - char

Хоть тип char и относится к целочисленным типам, но все же он стоит несколько особнячком, так как предназначен для представления отдельных символов Unicode. Поэтому его рассмотрим отдельно от целочисленных типов, хотя над ним можно совершать все те же самые операции, что и над целочисленными типами (сложение, вычитание и т.д. и т.п.). Но в тоже время им можно задавать значения при помощи символьных литералов, например ‘A’ – то есть любым символом заключенным в одинарные кавычки, но не стоит это путать со строкой “А”, состоящей из одного символа.

Как уже упоминалось размер этого типа равен 16 битам и поэтому может содержать значения от 0 до 65535 в десятичном исчислении. Отрицательных значений для типа char не существует. Это единственное без знаковый целочисленный тип данных в Java.

Задавать значения типа char можно при помощи следующих литералов или управляющих символов, например:

Символьные литералы

‘A’ – символьный литерал, заданный напрямую любым отображаемым символом Unicode
‘\uxxxx’ – символ Unicode, где xxxx цифровой код символа Unicode в шестнадцатеричной форме
‘\xxx’ – символ кодовой таблицы Latin-1, где xxx восьмеричный код символа Latin-1
1046 – код символа Unicode в десятичном исчислении
0x0950 – код символа Unicode в шестнадцатеричном формате

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

Управляющие символы (так же должны быть заключены в одинарные кавычки):

\b – backspase BS – забой (\u0008 в кодировке Unicode и 8 в десятичной)
\t – horizontal tab HT – табуляция (\u0009 в кодировке Unicode и 9 в десятичной)
\n – line feed LF – конец строки (\u000a в кодировке Unicode и 10 в десятичной)
\f – form feed FF – конец страницы (\u000с в кодировке Unicode и 12 в десятичной)
\r – carriage return CR – возврат каретки (\u000d в кодировке Unicode и 13 в десятичной)
\” – двойная кавычка (\u0022 в кодировке Unicode и 34 в десятичной)
\’ – одинарная кавычка (\u0027 в кодировке Unicode и 39 в десятичной)
\\ – backslash \ – обратная косая черта (\u005c в кодировке Unicode и 92 в десятичной)

Для справки про запись символов в восьмеричной системе счисления: код любого символа с десятичной кодировкой от 0 до 255 можно задать, записав его не более чем тремя цифрами в восьмеричной системе счисления в апострофах после обратной наклонной черты: ' \123' — буква S, ' \346' — буква Ж в кодировке CP1251. Особого смысла нет использовать эту форму записи для печатных и управляющих символов, перечисленных в предыдущем пункте, поскольку компилятор сразу же переведет восьмеричную запись в шестнадцатеричную. Наибольший восьмеричный код
' \377' — десятичное число 255.

ПРИМЕЧАНИЕ
Прописные русские буквы в кодировке Unicode занимают диапазон от '\u0410' — заглавная буква А, до '\u042F' — заглавная Я, строчные буквы от '\u0430' — а, до '\u044F' — я.

Из этого ряда выпала только буква Ё. Smile Ну ё маё Smile Но все же она представлена в Unicode.

Заглавная Ё – '\u0401', и строчная ё – '\u0451'.

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

C0001

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

Ради прикола я создал переменную с идентификатором ё, который вполне допустив в Java, так как Java использует символы Unicode даже для записи кода самой программы.

Вывод программы в консоли Eclipse такой:

C0002

Как видим отработали наши символы табуляции, которые мы использовали, а так же оператор print(), который печатает без перевода на новую строку. Кроме того фразу Hello World мы заключили в двойные кавычки и немного ее разбили символом перевода строки и двумя символами табуляции.

Затем, в строке 22, мы увеличили значение переменной ё на единицу и снова вывели результат.

Таким образом видно, что примитивный тип char это целочисленный тип, над которым возможны все те же самые операции, что и над другими целочисленными типами.

В Eclipse эта программа запускается нормально, а вот в консоли это уже совсем другой кордебалет.

C0003Ну что? Потанцуем?

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

java -Dfile.encoding=UTF-8 CharType

Это необходимо делать, так как при выводе на консоль виртуальная машина java делает обратное преобразование из кодировки UTF8 в кодировку операционной системы, для Windows это 1251 или 866 в консоли.

CharType – это не параметр, это программа так называется, вернее класс.

Надо отметить, что если у вас нет каких-то специальных символов и вы просто используете русский язык под Windows, то все эти танцы с бубнами не нужны. Они так же не нужны если вы пишете под Mac OS X на Java, поскольку консоль там нативно поддерживает Unicode.

Стоит так же упомянуть, что на нынешний момент количество символов Unicode уже превышает 65536, поэтому с Java 5 для работы с этими символами было введено понятие суррогатной пары, поскольку 16 разрядного типа char уже стало не хватать. Для работы с этими символами используется две переменные типа char. Первый символ в паре обозначающих один символ Unicode называется high surrogate, а второй – low surrogate, а вместе они называются суррогатной парой и их обоих так же можно хранить в переменной типа int.

На практике, использование суррогатной пары, встречается чрезвычайно редко, но знать об этом следует. Более подробно об Unicode в Java и кодовых точках можно почитать тут и тут. И еще тут.

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

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

В данном случае выводится символ ракеты представленной кодом UTF-16BE 0xD83D и 0xDE80, что соответствует кодовой точке 128640.

Как видно в примере переменным типа char значения заданы при помощи шестнадцатеричных цифровых литералов.

Символ ракеты в консоли Eclipse и Windows выводится достаточно убого, но все же его можно узнать Smile.

Так же следует обратить внимание на то что я подсветил желтым

C0005

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

C0006

И в консоли Windows

C0007

В принципе ракету можно узнать в этом символе Smile. Сразу же хочу отметить, что консоли Eclipse и Windows могут отобразить далеко не все дополнительные символы Unicode.

На этом с char пока все. И еще пара ссылок по теме Unicode тут и тут.

8 апр. 2015 г.

Примитивные типы Java - boolean

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

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

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

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

B0001

Операторы сравнения состоят из операторов равенства, которые проверяют равенство или неравенство значений (== , !=), и операторов отношения (<,>,<=,>=), используемых с упорядоченными типами (числами и символами) при проверке соотношения больше/меньше. Операторы обоих типов возвращают значение типа boolean, поэтому их обычно используют с условными операторами if и циклами while и for для выбора ветви или проверки условия выполнения цикла.

В Java предусмотрены следующие операторы равенства:

Равно (==)

Оператор == возвращает true (истина), если оба его операнда равны; если нет, то возвращается false (ложь).

В случае примитивных операндов он проверяет идентичность самих значений операндов, однако в случае операндов ссылочных типов проверяется, ссылаются ли операнды на один и тот же объект или массив. Другими словами, оператор не проверяет равенство двух разных объектов или массивов. По средством этого оператора не удастся проверить равенство двух различных строк.
Если оператор == сравнивает два числовых или символьных операнда различных типов, то до начала сравнения более узкий операнд преобразуется к типу более широкого. Например, при сравнении short и float величина типа short преобразуется во float до начала сравнения. Для чисел с плавающей точкой специальное отрицательное нулевое значение считается равным обычному положительному нулевому значению. Кроме того, специальное значение NaN (нечисловое) не равно ни одному другому числу, включая само себя. Чтобы проверить, является ли значение с плавающей точкой значением NaN, используйте метод Float.isNan()  или Double.isNan() .

Не равно (!=)

Оператор != прямо противоположен оператору ==. Он возвращает true, если два примитивных операнда имеют разные значения либо если два ссылочных операнда относятся к различным объектам или массивам. В противном случае он возвращает false.

Операторы отношения можно использовать с числами и символами, но нельзя применять со значениями типа boolean, объектами или массивами, так как данные типы не упорядочены. В Java предусмотрены следующие операторы отношения:

Меньше (<)
Возвращает true, если первый операнд меньше второго.

Меньше или равно (<=)
Возвращает true, если первый операнд меньше или равен второму.

Больше (>)
Возвращает true, если первый операнд больше второго.

Больше или равно (>=)
Возвращает true, если первый операнд больше или равен второму.

Булевы операторы

B0002

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

Условное И (&&)

Данный оператор выполняет логическую операцию И над операндами. Он возвращает true тогда и только тогда, когда оба операнда истинны. Если один или оба операнда ложны, он возвращает false. Например:

if ( x < 10 && y > 3) // Если оба сравнения истинны

Данный оператор (и все другие логические операторы кроме унарного оператора ! ) имеет меньший приоритет, чем операторы сравнения. Таким образом, вышеприведенная запись кода вполне допустима. Однако некоторые программисты прибегают к помощи круглых скобок, чтобы явно обозначить порядок вычислений:

if (( x < 10) && ( y > 3)) ...

Следует выбрать тот стиль, который вы считаете более удобным для чтения.

Этот оператор называется условным И, потому что он не всегда оценивает второй операнд. Если первый операнд равен false, значение выражения также будет false, каким бы ни было значение второго операнда. Поэтому, для большей эффективности, интерпретатор Java пропускает анализ второго операнда. В выражениях с побочными эффектами этот оператор следует применять осторожно, так как нет гарантии, что будет вычислен второй операнд. С другой стороны, этот оператор позволяет писать такие выражения:

if ( data != null && i < data.length && data[i] != -1) ...

Второе и третье сравнения в данном выражении могут привести к ошибкам, если первое или второе сравнение возвращают false. К счастью, это не проблема, так как поведение оператора && условно.

Условное ИЛИ (||)

Данный оператор выполняет логическую операцию ИЛИ на двух операндах типа boolean. Он возвращает true, если один или оба операнда истинны. Если оба операнда ложны, он возвращает false. Подобно оператору &&, оператор ||  не всегда вычисляет второй операнд. Если первый операнд равен true, значение выражения тоже будет true, каким бы ни было значение второго операнда. В этом случае оператор просто пропускает второй операнд.

Логическое НЕ (!)

Этот унарный оператор меняет boolean значение операнда. Он возвращает false, если применяется к true значению, и true, если задано false значение. Данный оператор можно использовать в таких выражениях:

if (!found)// found является булевой переменной, объявленной где-то ранее

while(!c.isEmpty())// Метод isEmpty() возвращает булево значение

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

if (!( x > y && y > z))

Логическое И (&)

С операндами типа boolean поведение оператора & аналогично поведению оператора &&, но он всегда вычисляет оба операнда, каким бы ни было значение первого операнда. Однако данный оператор практически всегда используют с целыми числами как побитовый оператор.

Логическое ИЛИ (|)

Данный оператор выполняет логическую операцию ИЛИ над двумя операндами типа boolean. Он аналогичен оператору ||, но всегда вычисляет оба операнда, даже если первый операнд является true. Оператор |  почти всегда используется как побитовый оператор для целых чисел, а с операндами типа boolean его применяют достаточно редко.

Логическое исключающее ИЛИ (^)

Для операндов типа boolean данный оператор вычисляет исключающее ИЛИ. Он возвращает true, если только один из двух операндов истинен. Другими словами, он возвращает false, если оба операнда ложны либо истинны. В отличие от операторов && и ||  он всегда вычисляет оба операнда. Оператор ^ намного чаще применяется как побитовый оператор для целых чисел. С операндами типа boolean поведение данного оператора аналогично поведению оператора !=.

Логический оператор присваивания И (&=)

Данные оператор совмещает две операции: логическое И и операцию присваивания. Сперва, над двумя операндами данного оператор совершается операция логического И, а затем происходит присвоение значения этой операции левому операнду.

Например запись x&=y, аналогична записи x=x&y.

Логический оператор присваивания ИЛИ (|=)

Данный оператор аналогичен оператору &=, но только перед присваиванием совершает операцию логического ИЛИ над операндами.

Например запись x|=y, аналогична записи x=x|y.

Логический оператор присваивания исключающее ИЛИ (^=)

Аналогично двум предыдущим, только перед присваиванием совершает над операндами операцию исключающего логического ИЛИ.

Например запись x^=y, аналогична записи x=x^y.

Результаты выполнения булевских логических операций можно представить таблицей:

B0003

Ну а теперь немного попрактикуемся на простой программке:

B0004

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

B0005

В этой программе все просто и не нужно что-то объяснять дополнительно. Если что-то не понятно, то читаем еще раз матчасть. А вот для следующей программы нужно будет немного разъяснений.

B0006

Методы compare1 и compare2 сравнивают переданное им значение на то меньше оно семи или больше и возвращают соответствующий логический результат – true или false.

В строке 22 мы присваиваем boolean переменной a результат сравнения логическим И значения возвращаемые методами compare1 и compare2, которым были переданы значения 5 и 3 соответственно. В данном случаем compare1 вернет true, поскольку 5 меньше 7, а compare2 вернет false, поскольку 3 меньше 7. И в данном случае выполнятся оба метода – compare1 и compare2, потому что первый оператор условного И вернул true. Если бы он вернул false, то метод compare2 не выполнялся бы, что и происходит в 26 строке.

Условное ИЛИ уж разбирать не буду там все тоже самое с точностью до наоборот Smile 

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

B0007

Надеюсь, что все с этой темой понятно. Если нет, то внимательно вкуриваем мануал и медитируем… медитируем… медитируем…. омммммм…

7 апр. 2015 г.

Типы данных Java – Примитивные типы

Теперь более подробно рассмотрим примитивные типы данных Java. Как уже упоминалось их восемь. В Java размеры всех примитивных типов жестко фиксированы. Они не меняются с переходом на иную машинную архитектуру, как это происходит во многих других языках. Незыблемость размера — одна из причин улучшенной переносимости Java-npoгpaмм.

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

  Тип   Содержит   По умолчанию   Размер   Диапазон   Обертки
  boolean  true или false    flase  1 bit  не применимо  Boolean
  char  целое без знаковое    \u0000  16 bits  от \u0000  до \uFFFF  Charaster
  byte  целое знаковое   0  8 bits  от -128 до 127  Byte
  short  целое знаковое   0  16 bits  от -32768 до 32767  Short
  int  целое знаковое   0  32 bits  от -2147483648 до 2147483647  Integer
  long  целое знаковое   0  64 bits  от -9223372036854775808 до 9223372036854775807  Long
  float  вещественное знаковое   0.0  32 bits  от 1.4E−45 до 3.4028235E+38  Float
  double  вещественное знаковое   0.0  64 bits  от 4.9E−324 до 1.7976931348623157E+308  Double

О типе данных char надо отметить, что он содержит код симола Unicode.

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

Сперва приведу пример с ошибкой попытки присвоения значения по умолчанию локальной переменной:

PT0002

Здесь в 16 строке мы попытались вывести на консоль значение локальной переменной localInt, которую предварительно не инициализировали (не присвоили) ни каким допустимым значением. На что нам была выдана ошибка компиляции. В то время как для переменной defInt, такой ошибки выдано не было.

На копипасту внимания не обращаем, лень было переделывать скриншот Smile

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

PT0003

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

PT0004

Как говорится: “Что и требовалось доказать”. Smile

Для типа char выведено пустое значение так как код \u0000 относится к непечатным символам.

Так же стоит упомянуть и взять на заметку, что для любого объекта и строки (String) значением по умолчанию является null. Ну это так на заметку.

Далее уже будем рассматривать подробно каждый примитивный тип и операции которые можно над ним совершать.

6 апр. 2015 г.

Типы данных Java – Введение

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

Типы данных в языке Java, делятся на две группы: примитивные типы (primitive types) и ссылочные типы (reference types).

Существует так же специальный тип данныхnull. Переменную этого типа создать не возможно, но можно присвоить значение null (только) ссылочному типу данных.

Примитивные типы данных Java
Существует восемь примитивных типов данных в Java:
Примитивные типы хранят значение.

Ссылочные типы данных Java
Существует четыре типа ссылочных данных в Java:
Ссылочные типы хранят ссылку на объект, или же тип данных null, то есть нулевую (пустую) ссылку.

Так же существует очень много программистских мифов и фольклорных данных, которые по разному классифицируют принадлежность всех вышеперечисленных типов. Иногда, к примеру, тип char выделают в отельный тип, так как он представляет символы, но как вы можете убедится сами из оригинальной документации по Java – это не так. Char (символьный тип), в оригинальной документации, относят к целым типам данных, потому что над ними можно выполнять все те же самые операции, что и над обычными числовыми типами. Так же, почему то, очень часто пропускают типизированные переменные и в ссылочных типах указывать только три оставшихся. Или выделяют enum в отдельный тип ссылочных данных, хотя он относится к классам, так же поступают иногда и с классом String – выделяют его в отельный вид ссылочных данных, хотя это тоже класс. Все это тайна покрытая мраком, но свет изначальной документации выведет нас к истине!

На этом лирическое отступление закончим и зафиксируем знания о типах данных Java запомнив простую диаграмму приведенную ниже.

Java_data_types

Если вы были внимательны, то заметили, что я подсветил желтым, чем отличаются примитивные типы данных от ссылочных. Это конечно не единственное различие, но самое основное. Еще раз повторюсь, что В Java все является объектом, за исключением примитивных типов. Так вот, примитивные типы передаются по значению, а ссылочные по ссылке. Масло масленное.

Чтобы разобраться с этим надо рассмотреть пример. И тут без объектов и классов опять не обойтись. Но сейчас мы упомянем ссылочные типы, только чтобы на их примере понять чем от них отличаются примитивные типы.

Приведу простую программу для демонстрации это принципиальной разницы:

JT0001

А теперь приведу вывод этой программы в консоли Eclipse

JT0002

Ключевые строки в листинге это 10, 12, 20 и 23. Как видим в строке 10 мы присвоили j то же значение что и i. И далее инкрементировали j. Затем вывели результат. Как видим i и j имеют разные значения. А вот с классом Dog, который является ссылочным типом данных все уже не так. В 20 строке мы присвоили экземпляру thatDog значение экземпляра thisDog, затем вывели результат, который показал одинаковые значения для обоих экземпляров, что не удивительно. Затем для экземпляра thatDog в 23 строке мы поменяли имя и снова вывели результат. И опять нам показали одинаковые значения. Это произошло потому, что ссылочные типы данных хранят не значение, а ссылку на объект и в данном случае происходит присвоение ссылки, а не значения. Таким образом оба экземпляра thisDog и thatDog указывают на один и тот же объект в памяти.

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

JT0003

Я добавил строки 22 и 28, которые будут выводить значение того что реально хранится в объектах thisDog и thatDog. И вот вывод на консоль Eclipse:

JT0004

Как видим и thisDog и thatDog хранят один и тот же указатель на область в памяти где располагается экземпляр класса Dog.

Ну и чтобы все стало еще чуть более понятней приведу исходник класса Dog:

JT0005

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

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

3 апр. 2015 г.

Разбираемся с classpath в Java. Часть 2.

Теперь немого усложним задачу. Для экспериментов будем использовать проект 00005E_AdvancedHello. Давайте теперь сами скомпилируем этот файл без Eclipse.

Перейдем в каталог с исходником и дадим команду:

javac AdvancedHello.java

И увидим вот такую россыпь ошибок:

CP0021

Это произошло потому, что javac не знает где искать библиотеку в которой находится класс Print. Это можно указать ему в командной строке, как мы делали до этого, а можно и через системную переменную CLASSPATH. Давайте зададим ей нужное значение и попробуем скомпилировать еще раз.

CP0022

К сожалению все значение переменной не вошло, но вообще она выглядит так:

CLASSPATH=.;C:\Git\StudyingJava\ProJavaJarLibs\ProJava.jar

GrabliОбратите внимание на .; это обязательно должно быть. Так как мы переопределяем значение этой переменной, то необходимо указать и текущий каталог на что указывает точка. Точка с запятой это обычный разделитель. Чтобы все было еще более понятно и вы могли проверить что все нормально, значение этой переменной можно посмотреть через консоль командой

 

ECHO %CLASSPATH%

CP0023

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

Теперь снова попробуем скомпилировать наш класс AdvancedHello.jar

CP0024

Он удачно скомпилировался и запустился и все это только благодаря тому что мы создали системную переменную CLASSPATH.

Если бы мы не указали в ней точку, то программа бы не заработала, так как она бы не знала где искать метод main и искала бы его в ProJava.jar, но там его нет.

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

После этих экспериментов, если вы их делали, лучше удалите системную переменную CLASSPATH.

Во первых в нашем варианте компиляции мы получили просто класс AdvancedHello.class и если вы его переместите на другой комп и там не будет библиотеки ProJava.jar или же к ней не правильно будет прописан путь, то этот класс не запуститься. Поэтому обычно библиотеки встраивают в исполняемый jar файл. Но об этом чуть позже.

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