Различаются ли примитивные типы в Java и C#?
-
22-09-2019 - |
Вопрос
Я вручную конвертирую код из Java в C# и борюсь с (то, что я называю) примитивными типами (см., например, Автоупаковка и распаковка ведут себя по-разному в Java и C#).Из ответов я понимаю, что double
(С#) и Double
(C#) эквивалентны и double
(C#) также можно использовать в контейнерах, например.как ключ в словаре.Однако, double
(Java) нельзя использовать в таких контейнерах, как HashMap, поэтому он автоматически упаковывается в Double
(Джава).
- Является
double
(С#) примитив или объект? - Если это примитив, то что заставляет его вести себя иначе, чем
double
(Джава)?
double
(C#) не может быть установлено значение null, если это не сделано nullable
.
- Является
double?
(C#) эквивалентноDouble
(Джава)?Оба ли они называются объектами?
(Полезно ли в этом обсуждении использование термина «объект первого класса»?)
Решение
И C#, и Java имеют примитивные типы (или «значения»):int, double, float и т.д.
Однако после этого C# и Java имеют тенденцию разделяться.
В Java есть типы классов-оболочек для все примитивные типы (который представляет собой небольшой конечный набор в Java), что позволяет рассматривать их как объект.double/Double
, int/Integer
, bool/Boolean
, и т. д.Эти типы-оболочки являются ссылочными типами (читай:Классы) и, как таковые, 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!
C# не предоставляет такой прямой упаковки.1 - отчасти потому, что C# поддерживает бесконечный набор типов значений с помощью структуры;скорее, C# обрабатывает «типы значений, допускающие значение NULL», путем введения Nullable<T>
тип обертки.Кроме того, C#, как и Java, имеет неявные преобразования типа значения. T
к Nullable<T>
, с тем ограничением, что T сам по себе не является типом, допускающим значение NULL.
// 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>.Однако для него по-прежнему требуется больше памяти, чем для типа, не допускающего значения NULL, поскольку он должен помнить, является ли значение нулевым или нет.В зависимости от проблем с выравниванием и реализации виртуальной машины это может быть или не быть значительно меньше, чем «полный» объект.Кроме того, поскольку значения в C#/CLR конкретизированы, рассмотрите любые операции подъема, которые необходимо выполнить:
// C#
object x = null;
x = (bool?)true;
(x as bool?).Value // true
Статья Java-совет 130:Знаете ли вы размер своих данных? рассказывает о потреблении памяти ссылочного типа (в Java).Следует отметить, что внутри JVM имеются специализированные версии массивов, по одной для каждого примитивного типа и для объектов (однако обратите внимание, что эта статья содержит некоторые вводящие в заблуждение заявления).Обратите внимание, как объекты (vs.примитивы) требуют дополнительных затрат памяти и проблем с выравниванием байтов.Однако C# может расширить вариант оптимизированного массива для Nullable<T>
типы против.ограниченные особые случаи, которые имеет JVM, потому что Nullable<T>
сам по себе является просто типом структуры (или «примитивом»).
Однако объекту требуется только небольшой фиксированный размер, чтобы поддерживать «ссылку» на него в слоте переменных.Переменный слот типа Nullable<LargeStruct>
с другой стороны, должно быть место для LargeStruct+Nullable
(сам слот может находиться в куче).Видеть Концепции С#:Значение и ссылочные типы.Обратите внимание, что в приведенном выше примере «подъема» переменная имеет тип object
: object
является «корневым типом» в C# (родительским как для ссылочных типов, так и для типов значений) и нет специализированный тип значения.
1 Язык C# поддерживает фиксированный набор псевдонимы для примитивных/общих типов, которые обеспечивают доступ к именам типов в «дружественном нижнем регистре».Например, double
это псевдоним для System.Double
и int
это псевдоним для System.Int32
.Если только не другое Double
тип импортируется в область видимости, double
и Double
будет относиться к тому же типу в C#.Я рекомендую использовать псевдонимы, если нет причин поступать иначе.
Другие советы
Nullable<double>
(он же double?
) в C# нет то же самое, что Double
на Яве.
До того, как в Java появилась автоупаковка/распаковка, вам приходилось вручную конвертировать примитивы в первоклассные объекты:
Double dblObj = new Double(2.0);
double dblPrim = dblObj.doubleValue();
В Java 1.5 это изменилось, поэтому вы можете просто сделать:
Double dblObj = 2.0;
double dblPrim = dblObj;
И Java автоматически вставит код для отражения приведенного выше примера.
C# отличается тем, что существует неограниченное количество «примитивных» типов (то, что CLR называет типы значений).Они ведут себя в основном как примитивы Java, используя семантика значений.Вы можете создавать новые типы значений, используя struct
ключевое слово.В C# есть автоупаковка/распаковка для все типы значений, а также делает все типы значений производными от Object
.
Таким образом, вы можете использовать тип значения (например, double
), где вы бы использовали любую ссылку на объект (например.как ключ в Dictionary
), и при необходимости он будет упакован или просто использован напрямую.(Реализация Generics в C# достаточно хороша, чтобы в большинстве случаев избежать упаковки.)
В C# лучший способ разделить объекты — это «Типы значений», которые похожи на примитивы — int
с, bool
s и т. д. и «Ссылочные типы» — классы и т. д.