18 Dec 2012

Primitive types, autoboxing, caching

Здесь мы обсудим детально работу с примитивами, их обертками, а также - их кешированием в Java.
Итак, во-первых, в Java есть примитивы, такие как int, double, а есть их аналоги - Integer, Double. Созданы они скорей всего для работы с generics (Map<Integer, Double>), а возможно и просто, чтоб можно было использовать примитивы как объекты, но это не суть важно. А важно то, что при работе с обертками есть несколько незаметных на первый взгляд нюансов.
Когда мы говорим про развертывание (unboxing):

Integer boxed = new Integer(10);
int primitive = boxed;
на самом деле происходит следующее: primitive = boxed.intValue(). Таким образом если наш boxed будет null, то выбросится NullPointerException (NPE). То же самое и с тем же Double, только там метод соответственно называется doubleValue()]
Когда происходит обертывание (boxing):
int primitive = 10;
Integer boxed = primitive;
на самом деле происходит следующее: Integer boxed = Integer.valueOf(10) то же самое будет и с любой другой оберткой, вызовется их valueOf(). И здесь важный нюанс, если вы заглянете в вышеприведенный метод, то окажется, что он не просто создает объект класса Integer, а смотрит является ли переданное в него число больше -127 или меньше какой-то верхней границы, если же число и взаправду входит в этот промежуток, то новый объект создаваться не будет! Это значит, что:
Integer one = 1;
Integer copyOfOne = 1;
one.equals(copyOfOne) == true
one == copyOfOne == true!
Ссылки равны, это произошло, потому как метод valueOf() вернул тот же объект. Однако:
Integer thousand = 1000;
Integer copyOfThousand = 1000;
thousand.equals(copyOfThousand) == true
thousand == copyOfThousand == false!
И это может привести к неразберихе. Запомните, что существует такой кеш, и, кстати, его можно изменять при запуске приложения. Задать верхнюю границу можно с помощью параметра: -XX:AutoBoxCacheMax=1000, который доступен в Sun JDK начиная с какого-то обновления 6й версии. Такой кеш существует так же и для Character. В классе Boolean все еще проще, там кеш постоянен и задается всегда одинаково, с помощью констант Boolean.TRUE, Boolean.FALSE. Ну и соответственно:
Boolean a = true;
Boolean b = true;
Boolean c = Boolean.TRUE;
Boolean d = new Boolean(true);
a == b == c != d == true
Остальные обертки (Double, Float) не работают с кешем, потому как они не точные и тут уже не ясно какие цифры используются чаще всего.
Также важно понимать, что все классы-обертки являются неизменяемыми (immutable), что значит, что их нельзя изменять после создания (у них нет setter’ов). Нужно заметить в таком случае, что это может привести к не эффективной работе с обертками, например:
Integer a = 10;
a = a + 1;
Во второй строке будет создан новый объект Integer, теперь объект не тот, что был раньше. То же самое произойдет и просто при:
a++. Каждый раз при инкременте, будет создан новый объект Integer и он будет присвоен ссылке а.

Теперь поговорим о чуть другом - о классе String. Он не является оберткой, однако несет с собой похожие нюансы. Итак, во-первых, строки в Java - тоже неизменяемы (для изменяемых нужно применять StringBuilder, StringBuffer). Таким образом:

String a = "a";
a += "b";
Вторая строка создаст уже новый объект и присвоит его ссылке а. Во-вторых, у строк также имеется свой кеш (его чаще называют пулом строк), который заполняется всеми строками-константами (в данном случае конснанта - это захардкодженная строка), то есть:
System.out.println("a"). Кроме того, что это создаст объект со значением “а”, этот объект еще и поместится в кеш. Из этого следует:
String a = "a";
Strinb b = "a";
String c = new String("a");
a.equals(b) == true
a.equals(c) == true
a == b == true!
a == c == false!
Также динамическую (не-константа) строку можно поместить в кеш с помощью интернирования (сейчас увидите откуда взялось такое слово):
String a = new String("a");
String b = "a";
String stringFromCache = a.intern();
b == stringFromCache == true
В данном случае не много толку было от интернирования, т.к. строка “а” и так была в константах, однако это просто демонстрация того, как можно положить строку в кеш.
Более потробно о строка и их эффективном использовании можно (и нужно, обязательно прочитайте!) прочитать в теме Правильные и неправильные примеры работы со String.