Ответ 1
Оба С# и Java имеют примитивные (или "значения" ) типы: int, double, float и т.д.
Однако после этого С# и Java имеют тенденцию к делению.
Java имеет типы классов-оболочек для всех примитивных типов (который представляет собой небольшой конечный набор в Java), который позволяет им обрабатываться как Object.
double/Double
, int/Integer
, bool/Boolean
и т.д. Эти типы-оболочки являются ссылочными типами (read: Classes) и, как таковые, null
является допустимым значением для назначения таким типизированным выражениям/переменным. Последние версии Java (1.5/5 +) добавляют неявные принуждения от примитивов к их соответствующей оболочке.
// Java
Boolean b = true; // implicit conversion boolean -> Boolean (Java 5+)
Boolean b = null; // okay, can assign null to a reference type
boolean n = null; // WRONG - null is not a boolean!
С# не обеспечивает такую прямую упаковку 1 - частично, потому что С# поддерживает бесконечный набор типов значений через структуры; скорее, С# обрабатывает "типы допустимых значений" путем введения типа оболочки Nullable<T>
. Кроме того, С#, как и Java, имеет неявные преобразования от типа значения T
до Nullable<T>
, с ограничением на то, что T сам по себе не является нулевым типом.
// C#
Nullable<bool> b = true; // implicit conversion bool -> bool?
bool? b = true; // short type syntax, implicit conversion
bool? b = null; // okay, can assign null as a Nullable-type
bool b = null; // WRONG - null is not a bool
Обратите внимание, что Nullable<T>
также является типом значения и, следовательно, соответствует стандартным правилам структуры, когда/если значение "находится в стеке" или нет.
В ответ на комментарий:
Абсолютно корректный, Nullable, являющийся значением типа, позволяет в некоторых случаях иметь более компактный размер памяти, поскольку он может избежать издержек памяти ссылочного типа: Что такое объем памяти Nullable <T> . Однако для него все еще требуется больше памяти, чем тип, отличный от Nullable, потому что он должен помнить, имеет ли значение значение нуль или нет. В зависимости от проблем с выравниванием и реализации виртуальной машины это может быть или не быть значительно меньше, чем "полный" объект. Кроме того, поскольку значения в С#/CLR подтверждены, рассмотрите любые операции по подъему, которые должны выполняться:
// C#
object x = null;
x = (bool?)true;
(x as bool?).Value // true
Статья Java Tip 130: Вы знаете свой размер данных? говорит о потреблении памяти типа ссылки (в Java). Следует отметить, что JVM имеет специализированные версии массивов внутри, один для каждого примитивного типа и для объектов (однако учтите, что эта статья содержит некоторые вводящие в заблуждение утверждения). Обратите внимание, что объекты (против примитивов) несут дополнительные служебные данные памяти и проблемы выравнивания байтов. Однако С# может расширить случай оптимизированного массива для типов Nullable<T>
по сравнению с ограниченными специальными случаями, которые JVM имеет, поскольку Nullable<T>
сам по себе является просто структурным типом (или "примитивным" ).
Однако для объекта "Object" требуется только небольшой фиксированный размер для поддержки "ссылки" на него в переменном слоте. С другой стороны, переменный слот типа Nullable<LargeStruct>
должен иметь место для LargeStruct+Nullable
(сам слот может находиться в куче). См. Концепции С#: значения и типы ссылок. Обратите внимание, что в примере "подъема" над переменной есть тип object
: object
- это "корневой тип" в С# (родительский и ссылочный тип и типы значений), а не специализированный тип значения.
1 Язык С# поддерживает фиксированный набор псевдонимов для примитивных/общих типов, которые позволяют получить доступ к именам типа "дружественный нижний регистр". Например, double
является псевдонимом для System.Double
, а int
является псевдонимом для System.Int32
. Если другой тип double
не импортируется в область видимости, double
и double
будут ссылаться на один и тот же тип в С#. Я рекомендую использовать псевдонимы, если нет причин для этого.