A Modern Primitive Discussion

A lot can be said — and a lot has been said — about Java's inclusion of primitive types.

A lot can be said — and a lot has been said — about Java's inclusion of primitive types. You know, types like int, long, byte, char, and so on (eight of them in total). And in some cases, people like to throw String in there because it acts a little like a primitive since it's immutable. However, the String class isn't a primitive since it's truly a class and hence renders true objects when instantiated. But I digress.

Why are they there, when Java is purportedly an object-oriented language? Here are the main reasons:

However, the fact that Java also includes Object-ified versions of primitives (i.e. Integer, Boolean, and so on) can lead to some confusion. So, beyond the confusion to beginners as to the differences between int and Integer, why is having primitives in Java problematic? In fact, the auto boxing and unboxing features of Java (introduced in Java 1.5) help to eliminate or at least "smooth out" these differences. For example:

Before autoboxing: int meaning=42; Integer theMeaning = new Integer(meaning);

...after: int meaning=42; Integer theMeaning = meaning;

...or just: Integer theMeaning = 42;

Before unboxing (which is really just the reverse of auto boxing): Boolean ready = new Boolean( true ); if ( ready.booleanValue() == true ) { /* … */ }

...after: Boolean theCondition = new Boolean( true ); if ( ready ) { /* … */ }

However, a lot of confusion remains. For example, you create objects using the new keyword, and use the dot-notation to pass messages (or call methods on) the resulting objects. Neither apply to primitives. This means that operators such as ++ work directly on the values of primitives, while they (kind of) result in method calls on objects. This isn't much of a problem, really, except that with objects, you never work directly with the data, hence the variable itself doesn't change. This means that you have a chance to bounds check or otherwise perform some error checking before the value is modified. With primitives, you do not.

Things get a little more confusing, and error-prone, when you tell a new Java programmer that you can compare two primitive types as such: int i = … int x = … if ( i == x ) { /* … */ }

But you cannot do this with String (or other) objects, because it compares the object references themselves, not necessarily the data they represent. For example, the following will yield unintended results: String root = "Eric"; … String user = "Eric"; if ( user == root ) { /*…*/ } // Not what was intended

Instead, you need to do the following: if ( user.equals( root ) ) { /*…*/ } // Good!

With other objects, this gets even more ambiguous and error-prone: Boolean conditionOne = new Boolean(true); Boolean conditionTwo = new Boolean(true); if ( conditionOne == conditionTwo ) { /*…*/ } // Error!!!

Mixing primitives with objects gets more confusing when you consider how Java passes parameters in methods. In general, when passing objects to methods, they're always passed by value. (By the way, this tends to be a matter of much debate amongst even seasoned Java developers.) As a result, the code in the invoked method cannot change the parameters themselves. Don't believe me? Try this: public class ParamTest { public static void main(String[] args) { ParamTest obj = new ParamTest(); String s = "Eric"; obj.addLastName(s); System.out.println(s); } public void addLastName(String p) { p = p + " Bruno"; } }

The result is that even after the call to addLastName(), String s is still equal to "Eric". But you do get a reference to the actual object, so even though you cannot change the object itself, you can call methods on it to affect its state. For instance, if the object passed as a parameter has getter and setter methods, you can alter the object's state by calling the setter methods. A common example is passing a collection class object, such as a Vector, List, or Map. To summarize the confusion, many objects act as pass-by-reference, but primitives can never be altered in any way, as they don't accept method calls.

Although I won't go into great detail on these, other issues include the ugliness of putting and getting primitives into collection classes, and the inconsistency of Class objects in respect to Objects and primitives. For instance, the following generates an error on the last line: int val = 128; Vector vect = new Vector(); vect.add(val); val = (int)vect.elementAt(0); // Compiler error!!

But why no error on the call to add()? The inconsistency here is that while Vector accepts a primitive to add to the collection, it will only return an instance of an Object.

In regards to the Class object, the following will not compile: int i = 0; i.getClass(); // Error!

However, you can do this and get a Class object for a primitive type int (!): Class c = int.class;

and you can do this: class SomeClass { // …. int i = 0; }; SomeClass obj = new SomeClass(); Class c = obj.getClass(); Field f = c.getField(i); Class ci = f.getType();

But don't ever try this: Class c1 = Class.forName("java.lang.String"); // Good! Class c2 = Class.forName("int"); // Compiler error!

Needless to say, the confusion, ambiguity, and the inconsistencies associated with mixing primitive types and objects in Java has led many to dislike primitives altogether. Never mind all the thread-safety issues associated with primitives not being objects. What is a programmer to do?

JDK 9 (or 10) To the Rescue!

Some proposals to resolve this include either removing primitives altogether, or creating primitives that are really objects but still act like primitives in many ways. To do so, each variable in a Java program would need to have metadata associated with it, perhaps preceding it in memory. The JVM could then check this information to help remove the inconsistencies mentioned above.

Plans for JDK 9, and I've read JDK 10 in some cases, promise to repair the Java disjoint type system and eliminate primitives altogether. At JavaOne this past year, it was mentioned that the solution would be an all-object type system, but time will tell if Oracle sticks with that. You can read more about it in Simon Ritter's slides. Of course, there are issues like performance and backward compatibility that need to be maintained, so it will be interesting to see how the Java language and platform designers at Oracle plan to solve this one. Where do you stand on primitives in the Java language?

References