RKDEEP

Что такое boxing unboxing в java. Внутренний кэш классов оберток

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 это 12 байт заголовка + 4 байта int значения.

kirill@kirill:~/$ java -jar jol-cli-latest.jar internals java.lang.Integer
# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# WARNING | Compressed references base/shifts are guessed by the experiment!
# WARNING | Therefore, computed addresses are just guesses, and ARE NOT RELIABLE.
# WARNING | Make sure to attach Serviceability Agent to get the reliable addresses.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

Instantiated the sample instance via public java.lang.Integer(int)

java.lang.Integer object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0xf80022be
 12   4    int Integer.value             0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

Поэтому для использования больших java.util коллекций следует рассмотреть применение коллекций примитивов напр. Eclipse Collections.