Programming
January 2009 19

Познаём Java. Третья чашка: примитивные типы, и объекты. Базовые конструкции

Типа реальные типы


Думаю, после краткого экскурса в возможности Java и прочитывания пары десятков строк кода примеров, вам захотелось узнать, чем должен уметь оперировать каждый Java-программист. Что ж, давайте поговорим о примитивных типах, классах (в том числе нескольких основных), сравнении, передаче параметров и простых структурах в Java.

Базовые типы


Их в Java 8:
  • boolean;
  • цыферки: byte, char, short, int, long;
  • нецелые цыферки: float, double.

Кроме того, есть ключевое слово void — в java оно типом не является и может использоваться только перед именем функции, указывая, что она ничего не возвращает.

boolean, что логично предположить, может быть true/false, byte — -128..127 (1 байт), char — 0..65536 (2 байта), short — -32768..32767 (2 байта).
В 4 байта int можно запихать числа до 2 с небольшим миллиардов, в 8 байт long — до 9*1018.
Нецелые float и double состоят из 4 и 8 байт соответственно (подробнее — тут).
Как вы видите, единственный беззнаковый тип — char. В нём хранятся символы, причём сразу в Unicode.
Все эти типы передаются исключительно по значению, и никаких «ссылок на int» у вас нет и не будет, если в механизм Reflection не уползёте.
Каждый из типов чётко ограничен по размеру, что обеспечивает нам возможность одинаково использовать их на любой Java-платформе. Единственное исключение — в первых спецификациях J2ME не было нецелых чисел, но сейчас с этим всё в порядке.
В общем случае, вы можете быть уверены, что написав
byte x = 100;
byte y = 100;
int i = x + y;


* This source code was highlighted with Source Code Highlighter.

вы получите именно 200, а не что-то ещё — переменные будут автоматически приведены к нужному типу.
Есть несколько способов задавать значение целочисленным переменным, например:

byte a;
a = 1;
a = 07;//восьмеричненько
a = 0x7;//hex
a = 'a';//будет взят код символа
a = 1.0;//упадёт с ошибкой - так можно делать только с float/double

* This source code was highlighted with Source Code Highlighter.

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

Операции с базовыми типами


Что мы можем с ними делать?
  • присваивать значение через =;
  • сравнивать через == и != (не-равно).

Для всех кроме boolean (но даже для char):
  • инкремент/декремент через ++ / --;
  • увеличвать/уменьшать/умножать/делить очевидными способами (деление — /);
  • находить остаток от деления через %;
  • (кроме float/double) сдвигать используя >> и <<;
  • (кроме float/double) применять двоичную логику через &, |, ^ (xor), ~ (не) .

Для boolean — &&, ||, ^,! (не).

Классы


Иерархия классов в Java начинается с Object. Что умеет каждый объект по мнению разработчиков Java?
  1. Объект умеет возвращать свой класс по команде getClass.
  2. Ради возможности сравнения существуют методы equals и hashCode.
  3. Объект может уметь клонировать себя через метод clone.
  4. Объект может переопределить метод toString и возвращать умную информацию о себе для дебага.


Кроме того, есть ещё метод finalize, который стоит переопределить, если перед смертью ваш объект должен «написать завещание» и набор методов для работы с потоками, которые могут усыпить/разбудить текущий поток.

Как с этим обращаться?


Итак, у нас есть набор базовых типов и класс, от которого наследуется всё, кроме этих типов. Что нам ещё нужно для счастья?
Нам нужно то, что их объединяет. Знакомьтесь — классы-обёртки.
Для того, чтобы иметь возможность оперировать с простыми числами (и boolean) как с объектами были придуманы классы-обёртки.
Из названия: Byte, Short, Integer, Long, Float, Double, Boolean, Character (единственный тип, который «переименовали»).
В новых версиях Java вы можете использовать их параллельно с примитивными типами, абсолютно прозрачно:
Integer x = 1;
Boolean a = false;


* This source code was highlighted with Source Code Highlighter.

В старых версиях приходилось «оборачивать» примитивные типы в обёртки, а затем «разворачивать» их оттуда. Например так:
Integer x = new Integer(1);
int r = x.intValue();

* This source code was highlighted with Source Code Highlighter.

Сравнение


Сравнение на больше-меньше есть только для чисел, и механизм его работы очевиден. С равенством интереснее.
В Java есть два способа сравнить объекты на равенство, == и метод equals.
== используется для примитивных типов. За его использование с объектами умные люди либо бьют по ушам либо всячески благодарят — для объектов == это исключительно сравнение по ссылке, т.е. объекты должны быть одним объектом, а не разными (даже если у них одинаковые поля и прочее — они всё равно не являются одним объектом).
Для остального надо использовать метод .equals.
Кроме того, метод hashCode служит (теоретически) для той же цели. Хорошим тоном считается переопределять его, если вы переопределили equals.
Дело в том, что переопределяя equals вы придумываете свои правила сравнения. Вы можете, например, учесть лишь часть полей класса, и использовать только их для сравнения.
Золотое правило сравнения:
Если после инициализации неких объектов a и b выражение a.equals(b) вернёт true, то a.hashCode() должен быть равен b.hashCode().

Почему нельзя писать == для объектов


Единственное, с чем == прокатывает — числа и строки, потому что они «кешируются». И то не все. Каждый Integer rrr = 1; это для Java одна и та же переменная, но это работает на ограниченном круге значений. Если я верно помню, значения больше 127 и меньше -128 — не кешируются.
Хотите покажу шутку в стиле «а в PHP 0 равно '0'»?
Integer a = new Integer(1);
Integer b = new Integer(1);
System.out.println(a>b);
System.out.println(a<b);
System.out.println(a==b);


* This source code was highlighted with Source Code Highlighter.

Это чудо вернёт вам 3 раза false. Потому что мы явно указали создание новых объектов с одним значением, и сравнение по ссылке вернуло false.
А в первых двух случаях произошло развёртывание, так как операция сравнения определена только для примитивных типов.
Мораль: == только для примитивных типов.

«Кстати, строки»


О чём я ещё не упоминал, но вы уже догадались: в Java есть класс String.
Простое использование:
String a = "hi!";
String b = new String("hi!");

* This source code was highlighted with Source Code Highlighter.

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

Кроме того, для строк есть операция соединения (конкатенации):
String r = «Привет » + «мир»;

Конструкторы, ссылки и значения: куда что идёт, и куда оно уходит.


Когда вы создаёте новый объект, вы начинаете его жизненный цикл.
Начинается он с вызванного вами конструктора. Да? Нет.

При первом упоминании класса вызываются по порядку все его статик-блоки вида static {...} лежащие в классе.
Статик-блоки родителей вызываются по мере их упоминания.
Затем вызываются по порядку все блоки вида {...} верхнего родителя.
Далее — вызывается конструктор этого же родителя.
Затем последние 2 шага повторяются для каждого класса иерархии.
В последнюю очередь вызываются {}-блоки и конструктор вашего класса.

Далее — ваш объект живёт до тех пор, пока на него есть ссылки.
Как только ссылки на объект теряются — он подхватывается сборщиком мусора и уничтожается. Никаких a = null чтобы «стереть ссылку» писать не нужно — java и так знает, когда вы перестали использовать объект.
Перед уничтожением вызывается метод finalize вашего милого объекта.

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

Как выбираются конструкторы.


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

public HelloWorld(){
super();
}

super-конструктор — это конструктор родителя. Вы можете использовать любой, из тех что у него есть.
На самом деле, если вы его не напишете — за вас это сделает компилятор. А что будет если у класса-родителя нет конструктора без параметров?
В этом случае вы должны явно вызывать super-конструктор всегда, и передавать ему параметры как для конструктора класса-родителя.

Что у вас там про кеширование?


Я упоминал кеширование объектов. Немного поясню. Дело в том, что упоминание чисел/строк — рутина, которая сваливается на нас постоянно. Чтобы не хранить в памяти тысячу интеджеров с 1 в качестве значения был сделан механизм кеширования. Он гарантирует вам, что на каждую вашу еденичку или строку будет создан ровно один объект в памяти, а при автоматическом развёртывании он и будет использован. Шутка в том, что разработчики платформы ограничили количество кешируемых чисел пределами, упомянутыми выше.

«Кстати, строки», часть 2 — будьте аккуратны.


Помните, каждая строка — неизменяемый объект.
Делая так: String x = «ads» + «dsada»; вы сжираете не 8*2 а 16*2 байт. Сначала происходит создание первых двух строк, затем третьей.
Чтобы избежать этого были придуманы StringBuffer и StringBuilder. Если вам нужно строить длинные строки — используйте эти штуки. Они быстрее и менее требовательны к памяти (так как не копируют строки), а StringBuffer ещё и потоко-безопасен (зато чуть больше тормозит).
Пример:
StringBuilder hi = new StringBuilder;
hi.append("Привет, мир");
hi.append(". ");
hi.append("Как твои дела?");
System.out.println(hi.toString());

* This source code was highlighted with Source Code Highlighter.

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

Массивы


Массивы в Java во многом напоминают массивы в C, но при этом они не оперируют арифметикой указателей.
Примеры массивов:
char[] asd = new char[5];//пустой массив на 5 символов
char[] ghj = new char[]{'a','n'};//массив на 2 символа


* This source code was highlighted with Source Code Highlighter.


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

Базовые конструкции Java


if(любое boolean-выражение или переменная){}else{}
for(действия до начала цикла; условие продолжения цикла; действие после каждого шага){}
while(условие продолжения цикла){}
do{}while(условие продолжения цикла)
switch(переменная числового или enum){case значение:… break; case значение:… break; default:}
for(Тип имяПеременной: массив){}

На последнем хотел бы остановиться подробнее. В Java так выглядит цикл for-each. Он перебирает каждый элемент массива, например так:
char[] chars = new char[]{'a','n'};
for(char a : chars){
 System.out.println(a);
}


* This source code was highlighted with Source Code Highlighter.


Кроме того, for-each применим для любой коллекции, и не только для них. Об этом расскажу потом.

P.S. Чем можно нормально подсветить Java-код? Source Code Highlighter для Java, к сожалению, не прездназначен.
+24
215.5k 232
Comments 109