Почему некоторые языки нуждаются в боксе и распаковке?

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

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

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

std::vector<double> dummy;

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

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

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

Ответ 1

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

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

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

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

Ответ 2

Бокс и распаковка - это необходимость, возникающая из-за того, что языки (например, С# и Java) реализуют стратегии распределения памяти.

Определенные типы выделяются в стеке и другие в куче. Чтобы обрабатывать тип выделенного стека как типа, выделенного кучей, необходим бокс для перемещения типа, выделенного стеком, в кучу. Unboxing - это обратные процессы.

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

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

Ответ 3

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

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

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

printer.print(5);

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

Ответ 4

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

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

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

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

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

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

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

Теория и практика Java: Generics gotchas

Ответ 5

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

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

 7.toString()

Несмотря на то, что toString - это метод объекта. Чтобы свести этот кивок к производительности, были созданы эквивалентные объекты. AutoBoxing удаляет код шаблона, который должен помещать примитив в его класс-оболочку и вынимать его снова, делая код более читаемым.

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

Ответ 6

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

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

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

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

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