Как известно, в Java существуют примитивные типы для чисел (byte, short, int, long, float, double) и объектные обёртки над ними (Byte, Short, Integer, Long, Float, Double). В различных статьях можно встретить диаметрально противоположные рекомендации о том, чем пользоваться. С одной стороны объектные обёртки универсальны: их можно использовать со стандартными коллекциями, которые удобны, инкапсулированы и вообще прекрасны. Но боксинг убивает производительность и ест кучу памяти. Примитивные типы быстры и компактны, но их можно поместить только в массивы, которые и от записи не защитишь, и абстракция на нуле. Если же вам нужно что-то типа Map, для отображения чего-нибудь на числа, то придётся либо мириться с потерей производительности и памяти, либо использовать сторонние библиотеки, реализующие нестандартный интерфейс. Однако в некоторых случаях вам помогут изменяемые (mutable) числа.
Представьте себе, что вам нужно подсчитывать количества разных строк, которые откуда-то поступают. Часто пишут примерно такой код:
public Map<String, Integer> countStrings() {
Map<String, Integer> counts = new HashMap<String, Integer>();
while(true) {
String next = getNextString();
if(next == null) break;
Integer val = counts.get(next);
if(val == null) counts.put(next, 1);
else counts.put(next, val+1);
}
return counts;
}
Если типов строк не так много, а повторов хватает, то боксинг будет создавать миллионы временных Integer-объектов, которые потом будет чистить сборщик мусора. Представить страшно. Нет, это не катастрофически медленно, но всё же несложно ускорить процедуру 2-3 раза. Для этого мы и используем MutableInteger.
Такой класс есть в некоторых библиотеках (например, в org.apache.commons.lang), но его несложно и написать самому. Простая реализация может выглядеть примерно так:
public class MutableInteger {
private int value;
public MutableInteger(int value) {
this.value = value;
}
public int intValue() {
return value;
}
public void set(int value) {
this.value = value;
}
public void increment() {
value++;
}
public String toString() {
return String.valueOf(value);
}
}
Далее можно добавить простые методы для арифметических действий. Ещё удобно унаследовать Number, реализовать интерфейс Comparable, а также не забыть про equals и hashCode (в hashCode можно просто вернуть value). Но для текущей задачи нам хватит того, что написано. Теперь countStrings() можно переписать следующим образом:
public Map<String, MutableInteger> countStrings() {
Map<String, MutableInteger> counts = new HashMap<String, MutableInteger>();
while(true) {
String next = getNextString();
if(next == null) break;
MutableInteger val = counts.get(next);
if(val == null) counts.put(next, new MutableInteger(1));
else val.increment();
}
return counts;
}
Не очень хорошо, конечно, раскрывать детали реализации, возвращая Map<String, MutableInteger>, но если мы унаследуем java.lang.Number, то можно вернуть Map<String, Number>. Ну или в крайнем случае после подсчёта скопировать всё в новую Map. Аналогично можно собирать не только количество, но и другую статистику по набору объектов.
Заметим также, что java.util.concurrent.atomic.AtomicInteger по сути тоже MutableInteger, однако накладные расходы на атомарность могут даже превысить расходы на создание объектов и сборку мусора из первого примера, поэтому отдельный класс MutableInteger всё же нужен.
Автор: lany