Are primitive types different in Java and C#?

五迷三道 提交于 2019-11-30 04:57:34

Both C# and Java have primitive (or "value") types: int, double, float, etc...

However, after this C# and Java tend to divide.

Java has wrapper Class types for all of the primitive types (which is a small finite set in Java) which allows them to be treated as Object. double/Double, int/Integer, bool/Boolean, etc. These wrapper types are reference-types (read: Classes) and, as such, null is a valid value to assign to such typed expressions/variables. Recent versions of Java (1.5/5+) add in implicit coercions from primitives to their corresponding wrapper.

// 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# doesn't provide a such a direct wrapping1 - in part, because C# supports an infinite set of value types via structures; rather, C# handles "nullable value types" by introduction of a Nullable<T> wrapper type. In addition C#, like Java, has implicit conversions from the value type T to Nullable<T>, with the restriction that T is "not a nullable type" itself.

// 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

Note that Nullable<T> is also a value type and thus follows the standard structure rules for when/if a value is "on the stack" or not.

In response to the comment:

Absolutely correct, Nullable being a value-type does allow it to have a more compact memory footprint in certain cases as it can avoid the memory overhead of a reference type: What is the memory footprint of a Nullable<T>. However it still requires more memory than the non-Nullable type because it has to remember if the value is, well, null, or not. Depending upon alignment issues and VM implementation, this may or may not be significantly less than a "full" object. Also, since values in C#/CLR are reified, consider any lifting operations that must be performed:

// C#
object x = null;
x = (bool?)true;
(x as bool?).Value // true

The article Java Tip 130: Do you know your data size? talks about reference type memory consumption (in Java). One thing to note is that the JVM has specialized versions of Arrays internally, one for each primitive type and for Objects (however, please note that this article contains some misleading statements). Note how the Objects (vs. primitives) incur extra memory overhead and the byte alignment issues. C# however, can extend the optimized-array case for Nullable<T> types vs. the the limited special-cases the JVM has because Nullable<T> is itself just a structure type (or "primitive").

However, an Object, only requires a small fixed size to maintain a "reference" to it in a variable slot. A variable slot of type Nullable<LargeStruct> on the other hand, must have space for LargeStruct+Nullable (the slot itself may be on the heap). See C# Concepts: Value vs Reference Types. Note how in the "lifting" example above the variable is of type object: object is the "root type" in C# (parent of both reference types and value types) and not a specialized value type.


1 The C# language supports a fixed set of aliases for primitive/common types that allow access to "friendly lowercase" type names. For instance, double is an alias for System.Double and int is an alias for System.Int32. Unless a different Double type is imported in scope, double and Double will refer to the same type in C#. I recommend using the aliases unless there is a reason to do otherwise.

Nullable<double> (aka double?) in C# is not the same as a Double in Java.

Before Java had autoboxing/unboxing, you had to manually convert between primitives and first-class objects:

Double dblObj = new Double(2.0);
double dblPrim = dblObj.doubleValue();

In Java 1.5 that changed, so you could just do:

Double dblObj = 2.0;
double dblPrim = dblObj;

And Java would insert code to mirror the above example automatically.

C# is different because there is an unlimited number of "primitive" types (what the CLR calls value types). These behave mostly like Java's primitives, using value semantics. You can create new value types using the struct keyword. C# has autoboxing/unboxing for all value types, and also makes all value types derive from Object.

So you can use a value type (like double) where you would use any object reference (e.g. as a key in a Dictionary) and it will be boxed if necessary, or else just used directly. (C#'s Generics implementation is good enough to avoid boxing in most circumstances.)

In C#, the best way to separate objects are by "Value Types" which are kinda like primitives - ints, bools, etc and "Reference Types" - classes etc.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!