RKDEEP

Что такое boxing unboxing в java. IntegerCache

Kirill Rybkin2020-10-26

Boxing и unboxing появились в java 1.5 для автоматической конвертации примитивных типов в Wrapper class, autoboxing позволяет использовать примитивы и Wrapper объекты взаимозаменяемыми в присвоении, вызовах методов и т.д. До версии java 1.5 при использовании коллекций Hashmap или ArrayList мы не могли добавить в коллекцию приметив, вместо этого необходимо сначала конвертировать их в Object и только потом можно добавить в коллекцию. С появление autoboxing и unboxing преобразование примитивов в объекты происходит автоматически на этапе компиляции.

Что такое boxing/unboxing в Java?

Когда Java автоматически преобразует примитивный тип напр. int в соответствующий класс Integer тогда это преобразование называется autoboxing, потому что примитив оборачивается в класс обертку, противоположное преобразование называется unboxing, когда Integer object преобразуется в примитивный тип int.

Все примитивные типы byte, short, char, int, long, float, double и boolean имеют соответствующие классы обертки. т.к. весь процесс преобразования происходит автоматически без единой строчки кода – этот процесс называется autoboxing/autounboxing.

Когда происходит autoboxing и unboxing

Autoboxing и unboxing выполняется все время когда ожидается объект, а передается примитив например в вызове метода в сигнатуре ожидается Объект, а передается примитивный тип, java автоматически конвертирует примитив в соответствующий объект. Классика использования autoboxing – добавление примитивов в коллекции такие как ArrayList, HashSet …

ArrayList intList = new ArrayList(); 
intList.add(1); //autoboxing - primitive to object 
intList.add(2); //autoboxing
int number = intList.get(0); // unboxing 

Autoboxing при присвоении

//before autoboxing
Integer iObject = Integer.valueOf(3);
Int iPrimitive = iObject.intValue()

//after java5
Integer iObject = 3; //autobxing - primitive to wrapper conversion
int iPrimitive = iObject; //unboxing - object to primitive conversion

Autoboxing при вызове метода

public static Integer show(Integer iParam){
    System.out.println("autoboxing example - method invocation i: " + iParam); return iParam; 
} 
//autoboxing and unboxing in method invocation show(3); 
// autoboxing int result = show(3); //unboxing because return type of method is Integer

Вызываем метод show(Integer) который принимает Integer в сигнатуре, при вызове метода с примитивом, int сначала конвертится в Integer далее вызывается метод. На втором вызове метода происходит unboxing т.к. show() метод возвращает Integer, а присваивается результат в int.

Скрытое создание объекта обертки в Java

При autoboxing нужно следить за не преднамеренным созданием объектов в цикле.

Integer sum = 0;
for(int i=1000; i<5000; i++){
   sum+=i;
 }

Код выше sum+=i представляется каа sum = sum + i и так как оператор + на применяется к объектам Integer, сначала выполняется unboxing sum далее autoboxing результата, который будет сохранен в переменной sum как Integer.

Так как sum это Integer object это создаст 4000 безполезных объектов. Если это произойдет в большом масштабе это потенциально уменьшит производительность частыми вызовами GC. Для арифметических операций предпочитайте приметивы и следите за неявными преобразованием в Object (Boxed primitive)

Что нужно помнить при autoboxing в Java

Сравнение объектов оператором равенства (==)

Я соглашусь, что преобразование премитива в Object добавляет удобство и уменьшает многословность, но есть места где autoboxing может привести к ошибке. т .к оператор сравнения может применяться к примитивам и объектам это может вызвать к определенной путанице.

Когда сравниваем два объекта используя “==” оператор сравнивает ссылки объектов, а не значения и autoboxing не происходит.

Integer one = new Integer(1);
Integer anotherOne = new Integer(1);

if(one == anotherOne){
  System.out.println("both one are equal");

} else {
   System.out.println("Both one are not equal");
}

Выведет сообщение “Both ones are not equal” потому что не произошло автораспаковки. Ставится все более запутанным когда сравнение “==” применяется вместе с логическими операторами >< которые делают auto-unboxing перед сравнением.

Использование совместно объектов оберток и примитивов в логических выражениях

Если сравнивать примитив с объектом, выполнится распаковка объекта если объект не был инициализирован будет выброщено исключение NullPointerException.

private static Integer count;

//NullPointerException on unboxing
if( count <= 0){
  System.out.println("Count is not started yet");
}

Кешируемые объекты

Еще одна тонкость использования autoboxing состоит в кешируемых объектах. Java кеширует часто используемые объекты которые могут вести себя по разному в зависимости от значения, кешируются объекты со значения от -128 до 127.

IntergerCache выглядит:

/**
     * Cache to support the object identity semantics of autoboxing for values between
     * -128 and 127 (inclusive) as required by JLS.
     *
     * The cache is initialized on first usage.  The size of the cache
     * may be controlled by the {@code -XX:AutoBoxCacheMax=} option.
     * During VM initialization, java.lang.Integer.IntegerCache.high property
     * may be set and saved in the private system properties in the
     * sun.misc.VM class.
     */

    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

Цитата из java спецификации секции Boxing Conversion:

If the value p being boxed is an integer literal of type int between -128 and 127 inclusive (§3.10.1), or the boolean literal true or false (§3.10.3), or a character literal between '\u0000' and '\u007f' inclusive (§3.10.4), then let a and b be the results of any two boxing conversions of p. It is always the case that a == b.

Кеширование объектов применяется не только к Integer типам. Мы имеем реализацию кеша у таких объектов:

  • ByteCache
  • IntegerCache
  • ShortCache
  • LongCache
  • CharacterCache

Бесполесные объекты и накладные расходы GS

Создание бесполезных объектов при autoboxing (часто это более лимита кеширования) потенциально приведет к частым вызовам сборщика мусора, что скажется на производительности. Объект обертка Integer занимает 16 байт против примитива int в 4 байта.