Зачем некоторым языкам нужен бокс и распаковка? - PullRequest
29 голосов
/ 24 июня 2009

Это не вопрос того, что такое бокс и распаковка, это скорее , почему это нужно таким языкам, как Java и C #?

Я хорошо знаком с C ++, STL и Boost.

В C ++ я мог бы написать что-то вроде этого очень легко,

std::vector<double> dummy;

У меня есть некоторый опыт работы с Java, но я был очень удивлен, потому что мне пришлось написать что-то вроде этого,

ArrayList<Double> dummy = new ArrayList<Double>();

Мой вопрос, почему это должен быть Объект, что технически так сложно включить примитивные типы, когда речь идет об Обобщениях?

Ответы [ 6 ]

47 голосов
/ 24 июня 2009

Что технически сложно включить примитивные типы, когда речь идет о дженериках?

В случае с Java это связано с тем, как работают дженерики. В Java дженерики - это трюк во время компиляции, который не позволяет помещать объект Image в ArrayList<String>. Тем не менее, универсальные шаблоны Java реализованы с стиранием типов: универсальная информация о типах теряется во время выполнения. Это было сделано из соображений совместимости, потому что дженерики были добавлены довольно поздно в жизни Java. Это означает, что во время выполнения ArrayList<String> фактически является ArrayList<Object> (или лучше: просто ArrayList, который ожидает и возвращает Object во всех его методах), который автоматически приводит к String при извлечении значение.

Но поскольку int не является производным от Object, вы не можете поместить его в ArrayList, который ожидает (во время выполнения) Object, и вы также не можете привести Object к int , Это означает, что примитив int должен быть заключен в тип, который наследуется от Object, например Integer.

C # например, работает по-другому. Обобщения в C # также применяются во время выполнения, и для List<int> не требуется никакой упаковки. Упаковка в C # происходит только при попытке сохранить тип значения, например int, в переменной ссылочного типа, например object. Поскольку int в C # наследуется от Object в C #, запись object obj = 2 совершенно допустима, однако int будет упакован, что выполняется автоматически компилятором (ссылочный тип Integer не предоставляется пользователю или чему-либо еще). ).

12 голосов
/ 24 июня 2009

Упаковка и распаковка являются необходимостью, обусловленной тем, как языки (такие как C # и Java) реализуют свои стратегии выделения памяти.

Некоторые типы размещаются в стеке, а другие - в куче. Чтобы рассматривать тип, выделенный из стека, как тип, выделенный из кучи, бокс должен перемещать тип, выделенный из стека, в кучу. Распаковка происходит в обратном порядке.

В C # типы, выделенные стеком, называются типами значений (например, System.Int32 и System.DateTime), а типы, выделенные в куче, называются ссылочными типами (например, System.Stream System.String).

В некоторых случаях выгодно иметь возможность обрабатывать тип значения как ссылочный тип (отражение является одним из примеров), но в большинстве случаев лучше избегать упаковки и распаковки.

2 голосов
/ 25 июня 2009

Я могу только сказать вам для Java, почему он не поддерживает типы примитивов в дженериках.

Сначала возникла проблема, заключающаяся в том, что вопрос о том, чтобы поддерживать это каждый раз, вызывал дискуссию о том, должны ли java иметь примитивные типы. Что, конечно, мешало обсуждению актуального вопроса.

Во-вторых, главная причина не включать это в том, что они хотели двоичной обратной совместимости, чтобы она работала без изменений на виртуальной машине, не знающей обобщений. Эта причина обратной совместимости / совместимости миграции также является причиной того, что теперь API-интерфейс Collections поддерживает дженерики и остался прежним, и нет (как в C #, когда они ввели дженерики) полного нового набора универсального API-интерфейса Collection.

Совместимость была выполнена с использованием ersure (общая информация о параметрах типа, удаленная во время компиляции), что также является причиной того, что вы получаете так много непроверенных предупреждений о приведении типов в java.

Вы все еще можете добавить переработанные дженерики, но это не так просто. Простое добавление типа info add runtime вместо удаления не будет работать, так как это нарушает совместимость с исходным кодом и бинарностью (вы не можете продолжать использовать необработанные типы и не можете вызвать существующий скомпилированный код, потому что у них нет соответствующих методов ).

Другой подход - тот, который выбрал C #: см. Выше

А для этого случая использования автоматическая автобокс / распаковка не поддерживалась, поскольку автобокс стоит слишком дорого.

Теория и практика Java: общие сведения получили

2 голосов
/ 24 июня 2009

Я считаю, что это также потому, что примитивы не наследуются от Object. Предположим, у вас есть метод, который хочет иметь возможность принимать что-либо вообще в качестве параметра, например.

class Printer {
    public void print(Object o) {
        ...
    }
}

Возможно, вам потребуется передать простое примитивное значение этому методу, например:

printer.print(5);

Вы могли бы сделать это без упаковки / распаковки, потому что 5 является примитивом, а не объектом. Вы можете перегрузить метод печати для каждого типа примитива, чтобы включить такую ​​функциональность, но это неприятно.

1 голос
/ 09 июня 2012

Каждый нестроковый нестроковый объект, хранящийся в куче, содержит 8- или 16-байтовый заголовок (размеры для 32/64-битных систем), за которым следует содержимое открытых и закрытых полей этого объекта. Массивы и строки имеют вышеуказанный заголовок, плюс еще несколько байтов, определяющих длину массива и размер каждого элемента (и, возможно, количество измерений, длину каждого дополнительного измерения и т. Д.), За которыми следуют все поля первого элемент, затем все поля второго и т. д. При наличии ссылки на объект система может легко проверить заголовок и определить его тип.

Места хранения ссылочного типа содержат значение в четыре или восемь байтов, которое однозначно идентифицирует объект, хранящийся в куче. В существующих реализациях это значение является указателем, но его проще (и семантически эквивалентно) представить как «идентификатор объекта».

Места хранения типа значения содержат содержимое полей типа значения, но не имеют никакого связанного заголовка. Если код объявляет переменную типа Int32, нет необходимости хранить информацию с этим Int32, говоря, что это такое. Тот факт, что это местоположение содержит Int32, эффективно сохраняется как часть программы, поэтому его не нужно хранить в самом местоположении. Это представляет большую экономию, если, например, один имеет миллион объектов, каждый из которых имеет поле типа Int32. Каждый из объектов, содержащих Int32, имеет заголовок, который определяет класс, который может с ним работать. Поскольку одна копия кода этого класса может работать с любым из миллиона экземпляров, наличие того факта, что поле является Int32 частью кода, гораздо более эффективно, чем хранение в каждом из этих полей информации о том, что это так.

Упаковка необходима, когда делается запрос на передачу содержимого места хранения типа значения в код, который не знает, ожидать этого конкретного типа значения. Код, который ожидает объекты неизвестного типа, может принимать ссылку на объект, хранящийся в куче. Поскольку у каждого объекта, хранящегося в куче, есть заголовок, указывающий, какой это тип объекта, код может использовать этот заголовок всякий раз, когда необходимо использовать объект таким способом, который потребует знания его типа.

Обратите внимание, что в .net можно объявлять так называемые универсальные классы и методы. Каждое такое объявление автоматически генерирует семейство классов или методов, которые идентичны, за исключением того типа объекта, на который они рассчитывают воздействовать. Если передать Int32 подпрограмме DoSomething<T>(T param), это автоматически сгенерирует версию подпрограммы, в которой каждый экземпляр типа T будет эффективно заменен на Int32. Эта версия подпрограммы будет знать, что каждое место хранения, объявленное как тип T, содержит Int32, так что, как и в случае, когда подпрограмма жестко запрограммирована на использование места хранения Int32, в этом нет необходимости. хранить информацию о типе в самих этих местах.

1 голос
/ 24 июня 2009

В Java и C # (в отличие от C ++) все расширяет Object, поэтому классы коллекций, такие как ArrayList, могут содержать Object или любого из его потомков (в основном что угодно).

Однако из соображений производительности примитивам в Java или типам значений в C # был присвоен особый статус. Они не объект. Вы не можете сделать что-то вроде (на Java):

 7.toString()

Хотя toString - это метод для Object. Для того, чтобы связать этот узел с производительностью, были созданы эквивалентные объекты. AutoBoxing удаляет стандартный код необходимости вставлять примитив в его класс-обертку и извлекать его снова, делая код более читабельным.

Разница между типами значений и объектами в C # более серая. См. здесь о том, как они отличаются.

...