0

Java is designed in a very OO approach, and somewhat even 'forces' programmers to program within the OO paradigm (which can be considered good or bad, a matter of opinion).

However while almost everything in Java is a class or an object, primitive data types (int, double, etc) are not.

While I see no advantages to this, there are disadvantages. For example, when a method wants to take in as a parameter 'any value', it usually declares the parameter of type Object, the supertype of all classes in Java. However if the programmer wants to input an int to the method - a perfectly valid value - he/she has to 'box' the value in an instance of the Integer class, created specifically for these kinds of needs (as far as I understand).

My question is: why aren't primitives in Java classes, like everything else?

2
  • 2
    Consider one of the performance enhancements in lmax - "It took a bit more cleverness to go up another order of magnitude. There are several things that the LMAX team found helpful to get there. One was to write custom implementations of the java collections that were designed to be cache-friendly and careful with garbage. An example of this is using primitive java longs as hashmap keys with a specially written array backed Map implementation (LongToObjectHashMap)" – user40980 May 17 '14 at 22:16
  • From C2 Is Java Object Oriented: "Lastly, it is a hybrid to provide performance gains. Even though Smalltalk has an advanced virtual machine, its inability (when I was using it) to provide a machine-correlated bytecode for integer math placed a significant performance impact on its entire environment. Being a hybrid, Java cannot be called a true Object-Oriented language. But then, why does it matter? ;-) Use the right tool for the job and life will be happy!" – user40980 May 17 '14 at 22:19
8

There are trade offs to be had in every design choice.

When you have a "pure" OO language (see if you can get anyone to agree on what that is), when you do addition, rather than having the system get two numbers and add them, you are instead first invoking the add method on the object and getting back a new one (making mutable Number Objects can make for difficult times).

An example of this would be the BigInteger class. Instead of int fourtyTwo = 6 * 7; you've got BigInteger fourtyTwo = new BigInteger(6).multiply(new BigInteger(7)); You've created a total of three objects and done some math in the background. This is also a little bit of a mess, but thats just the semantics - you can hide that with operators, but its still doing all that work behind it.

From C2 Is Java Object Oriented the reason becomes clear:

Lastly, it is a hybrid to provide performance gains. Even though Smalltalk has an advanced virtual machine, its inability (when I was using it) to provide a machine-correlated bytecode for integer math placed a significant performance impact on its entire environment. Being a hybrid, Java cannot be called a true Object-Oriented language. But then, why does it matter? ;-) Use the right tool for the job and life will be happy!

You can see what is being mentioned there in some docs for SmallTalk:

Thus, math is not a special case in Smalltalk; it is done, exactly like everything else, by creating objects, and sending them messages. This may seem odd to the Smalltalk novice, but this regularity turns out to be quite a boon: once you've mastered just a few paradigms, all of the language “falls into place”. Before you go on to the next chapter, make sure you try math involving * (multiplication), - (subtraction), and / (division) also. These examples should get you started:

While yes, it does men that some things can be more elegant in the language design it also means that its going to be slower. From a talk on SmallTalk and Objective C:

What makes things slow?

  • Small integer arithmetic
  • Boxing
  • Dynamic message lookup
  • Memory management operations

Another example of this performance can be seen at the Language Benchmarks Game for Ruby vs Java - where Ruby implements the "pure" OO style math - messages are sent to the object to add rather than working with primitives. Note that in many cases Ruby is one or two orders of magnitude slower than Java. These benchmarks are all math heavy, and when looking at the Java code, they're all using primitives.

There are things you can do to speed that up, but its still less than optimal. Early Java was plagued by performance concerns. Making math that much slower would have been awful.

Lets look at a high performance Java architecture: LMAX. Every iota of performance was pulled out of this, in some cases rewriting some rather basic things.

It took a bit more cleverness to go up another order of magnitude. There are several things that the LMAX team found helpful to get there. One was to write custom implementations of the java collections that were designed to be cache-friendly and careful with garbage. An example of this is using primitive java longs as hashmap keys with a specially written array backed Map implementation (LongToObjectHashMap). In general they've found that choice of data structures often makes a big difference, Most programmers just grab whatever List they used last time rather than thinking which implementation is the right one for this context.

Writing a HashMap that could work on <long, Object> rather than <Long, Object> allowed for some significant performance gains by not having to do object based equality for two objects (could just use ==) and avoiding a number of issues with garbage collection (look at your heap some time and count how many Long objects are sitting around when you're heavily using HashMaps).

Lastly consider that Java's hotspot optimizer will compile code to native when it needs to. The code produce by int fourtyTwo = 6 * 7 is much simpler and faster than the corresponding BigInteger fourtyTwo = new BigInteger(6).multiply(new BigInteger(7));

Its all about trade offs, and this was one where performance won out over "pure" elegance.

5
  • >>Benchmarks Game for Ruby vs Java<< In the benchmarks game Ruby is also slower than Smalltalk benchmarksgame.alioth.debian.org/u32/… – igouy May 18 '14 at 3:45
  • Do you think interpreted Java was faster or slower than Smalltalk? – igouy May 18 '14 at 3:47
  • 1
    You might wish to look at the JRuby vs Smalltalk comparison which isn't too far off from each other. However, Java still beats JRuby hands down. Realize that that is a particularly apt comparison because they are both running in the same virtual machine (and compiled) - the difference is how the objects are treated - particularly numbers in those tests. – user40980 May 18 '14 at 15:34
  • Yes, look at JRuby vs Smalltalk and note the faster Smalltalk math programs and faster JRuby string programs -- that's the same pattern as Ruby vs Smalltalk. – igouy May 20 '14 at 16:41
  • The point I am trying to make is that languages that do pure OO math (where you can do things like foo = 4.div(2) (valid ruby code)) will always take a penalty compared to math with primitives. Furthermore, HotSpot (JIT optimizer) will have a more difficult time compiling that code to something optimal. These were trade offs that Java made early on - allow primitives to make math faster. – user40980 May 20 '14 at 16:55
2

Quoting an answer of mine in another question.

In some cases language designers make compromises for usability's sake.

Numbers are used too often. Having to instantiate them all the time would make the language too verbose.

There a few other examples of special exceptions made for the sake of usability:

Objects of type String can be instantiated without new, event when String is not a primitive:

String s = "Hello";

String being a non-primitive, should be instanciated like this:

String s = new String("Hello");          // this also works 

But the compiler allows for the shorter, less OO option, because String is by far the most widely used class in the API.

Also arrays can be initialized in a non-OO way:

int i[] = {1,2,3};

Weird enough, an object is either an instance of a class or an array. Meaning arrays are a completely separate type of class.

Arrays have a lenght public field which is not a constant. Also there's no documentation on the class arrays are an instance of. ( not to confuse with Arrays class or java.reflect.Array ).

int a = myArray.length;    // not length()
5
  • 1
    The documentation for .length in the array can be found in JLS 10.7 which lists the fields and methods that arrays must have. – user40980 May 17 '14 at 22:28
  • 7
    I don't think the syntax is what makes it less OO - what matters is the semantics. In Python, for example, you can use numbers as objects without explicitly calling the int() constructor, e.g. (3).__eq__(4) == False. I don't think OO itself necessitates that everything must be explicitly created with constructor syntax. – user76704 May 17 '14 at 22:35
  • If we have the option to do String s = "hi", why not have Int as a class and have the option to do Int num = 3? – Aviv Cohn May 17 '14 at 22:35
  • @Prog look at the Integer class, – ratchet freak May 17 '14 at 23:57
  • Confusing syntax with runtime semantics... String s = "Hello"; has no instantiation at all at runtime, since "Hello" is a compile-time constant embedded in the constant pool of the Java classfile. – Rogério May 7 '19 at 18:58
2

One core principle of OOP is that values (not just variables) have types, which is a requirement for dynamic dispatch. Each object is tagged with a type. This tag is usually a pointer to a structure representing a class. In Java, objects have further overhead to facilitate synchronization, but lets be aware that each object has to at least carry a pointer (4 or 8 bytes) around together with the actual data. This alone doubles or quadruples the size of a primitive like int! Having primitives that aren't real objects is therefore very desirable from a performance standpoint.

Having primitives is not a problem – C# is very similar in that respect, except that real autoboxing makes explicit companion classes like Integer unnecessary.

In Java there is also some connection between primitives and generics. Due to Java's history, generics have been implemented using type erasure: at runtime, there is no difference between a List<Foo> and a List<Integer>, as the generic types are only checked during compilation. In reality, such generic collections just contain Objects. Generics can also be implemented efficiently using specialization (which is how templates in C++ work). As we can now figure out whether we have a List<Foo> or List<int>, we can use compact storage for primitive types (or in principle other sealed/final classes). Java can't do that, so we need full blown objects when storing primitives in collections – here, an Integer wrapper.

Such issues with generics were not on the horizon when Java was first released. Gradually, more and more autoboxing behaviour has been added to make this divide less visible.

1

I think the only reason is that it is complicated for the compiler writer.

I mean, you could have a system where every primitive type was used in the language as if it were any object with all the "elegance" and convenience that entails. So a call to string s = i.ToString(); would become a call to the equivalent static function string s = ToString(i); as a simplistic example.

However, you'd have to tell which types were primitive and which were not; you'd also have to ban inheriting from these (or you could get in all kinds of a mess); and even then some people would create their own pseudo-primitive types and expect them to perform with the speed of primitives.

But I think some aspects of objects would prove to be too difficult to implement in such a fashion for primitives - garbage collection requires all objects be created on the heap and referenced and the interfaces that are expected by method calls wouldn't necessarily work if there wasn't a real interface to call through (ie each object needs a vtable which our primitives-as-objects wouldn't have)

So boxing is a workaround that was added. I think the C++ way is better - no boxing required there and you can mix primitives and objects easily. Java went for a higher level abstraction and this way of having to work with primitives differently is just another example of real world impacting on our desire for perfection.

2
  • you'd have to tell which types were primitive and which were not” – that's not a problem, because that would be a property of the type. The actual problem is which values are primitives (see your point about vtables). “you'd also have to ban inheriting from these” – again, not a problem: many languages incl. Java feature “final” or “sealed” classes that can't be inherited. “garbage collection” – primitives usually have immutable value type semantics, which means copies can be made at any occasion (like function calls). Remember: Java doesn't have pointers. – amon May 17 '14 at 23:14
  • sure, but the compiler would to take account of all these factors, and all the areas where they might interact, and the overall scope of such a change would increase the complexity of the compiler significantly. – gbjbaanb May 17 '14 at 23:21

Not the answer you're looking for? Browse other questions tagged or ask your own question.