Pull to refresh

Изменяемые числовые объекты

Reading time 2 min
Views 14K
Как известно, в 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 всё же нужен.
Tags:
Hubs:
+13
Comments 54
Comments Comments 54

Articles