Why do people still use primitive types in Java?

82,917

Solution 1

In Joshua Bloch's Effective Java, Item 5: "Avoid creating unnecessary objects", he posts the following code example:

public static void main(String[] args) {
    Long sum = 0L; // uses Long, not long
    for (long i = 0; i <= Integer.MAX_VALUE; i++) {
        sum += i;
    }
    System.out.println(sum);
}

and it takes 43 seconds to run. Taking the Long into the primitive brings it down to 6.8 seconds... If that's any indication why we use primitives.

The lack of native value equality is also a concern (.equals() is fairly verbose compared to ==)

for biziclop:

class Biziclop {

    public static void main(String[] args) {
        System.out.println(new Integer(5) == new Integer(5));
        System.out.println(new Integer(500) == new Integer(500));

        System.out.println(Integer.valueOf(5) == Integer.valueOf(5));
        System.out.println(Integer.valueOf(500) == Integer.valueOf(500));
    }
}

Results in:

false
false
true
false

EDIT Why does (3) return true and (4) return false?

Because they are two different objects. The 256 integers closest to zero [-128; 127] are cached by the JVM, so they return the same object for those. Beyond that range, though, they aren't cached, so a new object is created. To make things more complicated, the JLS demands that at least 256 flyweights be cached. JVM implementers may add more if they desire, meaning this could run on a system where the nearest 1024 are cached and all of them return true... #awkward

Solution 2

Autounboxing can lead to hard to spot NPEs

Integer in = null;
...
...
int i = in; // NPE at runtime

In most situations the null assignment to in is a lot less obvious than above.

Solution 3

Boxed types have poorer performance and require more memory.

Solution 4

Primitive types:

int x = 1000;
int y = 1000;

Now evaluate:

x == y

It's true. Hardly surprising. Now try the boxed types:

Integer x = 1000;
Integer y = 1000;

Now evaluate:

x == y

It's false. Probably. Depends on the runtime. Is that reason enough?

Solution 5

Besides performance and memory issues, I'd like to come up with another issue: The List interface would be broken without int.
The problem is the overloaded remove() method (remove(int) vs. remove(Object)). remove(Integer) would always resolve to calling the latter, so you could not remove an element by index.

On the other hand, there is a pitfall when trying to add and remove an int:

final int i = 42;
final List<Integer> list = new ArrayList<Integer>();
list.add(i); // add(Object)
list.remove(i); // remove(int) - Ouch!
Share:
82,917

Related videos on Youtube

Naftuli Kay
Author by

Naftuli Kay

Updated on June 13, 2020

Comments

  • Naftuli Kay
    Naftuli Kay almost 4 years

    Since Java 5, we've had boxing/unboxing of primitive types so that int is wrapped to be java.lang.Integer, and so and and so forth.

    I see a lot of new Java projects lately (that definitely require a JRE of at least version 5, if not 6) that are using int rather than java.lang.Integer, though it's much more convenient to use the latter, as it has a few helper methods for converting to long values et al.

    Why do some still use primitive types in Java? Is there any tangible benefit?

    • Tedil
      Tedil about 13 years
      ever thought about memory consumption and performance?
    • Naftuli Kay
      Naftuli Kay about 13 years
      @Tedil Ok, so why would they even introduce this feature, as it's essentially a "trap" according to you; perceived better features at the cost of memory and performance? I'm just saying.
    • corsiKa
      corsiKa about 13 years
      @TK Because putting primitives in collections is problematic. Basically they expect developers to be aware of the performance issues when they use features.
    • ColinD
      ColinD about 13 years
      @TK: They introduced it because sometimes you have to, or want to, use the wrapper objects (any time you want to store the value in a collection or pass it to a method requiring an object) and it makes doing so much more convenient.
    • Naftuli Kay
      Naftuli Kay about 13 years
      @glowcoder/ColinD Very interesting. I guess my subsequent question is "Why in the $@#$ haven't they optimized the JRE to essentially negate this performance impact by simply making the wrapper classes invisible and weightless? I always thought of "autoboxing" as something that only really affected devs when writing code as a benefit to ease of use.
    • biziclop
      biziclop about 13 years
      @TK Kocheran Mostly because new IntegeR(5) == new Integer(5) should by the rules, evaluate to false.
    • ColinD
      ColinD about 13 years
      @TK: Autoboxing is just syntactic sugar. Underneath, objects still have to be objects and primitives still have to be primitives. There are certain critical differences in how those function that, I'd guess, prevent them from making object wrappers something "invisible and weightless".
    • Oleh Prypin
      Oleh Prypin about 13 years
      Why don't they just allow to overload operators... This .equals() stuff really makes me think worse about Java.
    • Artem
      Artem about 13 years
      See GNU Trove or Mahout Collections or HPPC or ... for solutions to collections of primitive types. Those of us who care about speed spend our time using more primitive types, not less.
    • NullUserException
      NullUserException almost 10 years
      @corsiKa When you use the new operator it will always create a new object. You asked for a new object, and you will receive new object.
    • NullUserException
      NullUserException almost 10 years
      @OlehPrypin The good thing about not allowing operator overloading is that you can look at Java code and you will always know what it's doing. In a language like C++, you have no idea what the operators might have been overloaded to.
    • corsiKa
      corsiKa almost 10 years
      @NullUserException Yes, for sure. That's illustrated by my example, notably class biziclip, and in my hastily-made-before-the-five-minute-window-expires edit.
    • Paul Draper
      Paul Draper almost 10 years
      Unrelated, but this inconsistency in Java's type system is one of the best improvements Scala makes.
  • Eddie
    Eddie about 13 years
    I disagree. The performance aspect can be critical. Very little of this is probably inertial or force of habit.
  • Naftuli Kay
    Naftuli Kay about 13 years
    I would have assumed that the JRE would be "smart" enough to do this with Java wrapped primitives as well. Fail.
  • rlibby
    rlibby about 13 years
    Sorry, this is wrong. A smart JVM can do escape analysis on any object allocations and, if they cannot escape, allocate them on the stack.
  • Eddie
    Eddie about 13 years
    Yes, this is beginning to be a feature of modern JVMs. In five years, what you say will be true for most JVMs then in use. Today it is not. I almost commented about this, but decided to not comment on it. Perhaps I should have said something.
  • biziclop
    biziclop about 13 years
    @Eddie It can be, but it very rarely is. Trust me, for most people performance arguments are just an excuse.
  • ColinD
    ColinD about 13 years
    Now imagine if i were declared as Long as well!
  • Paŭlo Ebermann
    Paŭlo Ebermann about 13 years
    You don't want one variable to change if you do i++ on another variable, so Integer quite needs to be immutable to be able to do this (or at least this i++ would have to create a new Integer object anyway). (And the primitive values are immutable, too - you just don't remark this since they are no objects.)
  • Eddie
    Eddie about 13 years
    @ColinD: I tested it. 32.6 seconds to run the code above, and 59.9 seconds if i is also Long. Not quite double. Ouch.
  • Eddie
    Eddie about 13 years
    @Paŭlo: Saying that primitive values are immutable is kind of meaningless. When you reassign a primitive variable to a new value, you are not creating anything new. No memory allocation is involved. Peter's point stands: i++ for a primitive does no memory allocation, but for an object it necessarily does.
  • Paŭlo Ebermann
    Paŭlo Ebermann about 13 years
    @Eddie: (It does not necessarily need memory allocation, it could also return a cached value. For some small values it does, I think.) My point was that the immutability of Integers here is not the deciding point, you would anyway want to have another object, regardless of immutability.
  • Dave O.
    Dave O. about 13 years
    to clarify: Unfortunately there are no methods that allow arithmetic operations on Int, Long, etc. - so there is no other solution than (un)boxing. Even if there were, they would just use primitive types internally.
  • John K
    John K about 13 years
    In the class biziclop, aren't the third and fourth statements wrongly inconsistent in their result? Right now the code says Integer.valueOf(5) == Integer.valueOf(5) resolves true but Integer.valueOf(500) == Integer.valueOf(500) resolves false.
  • TREE
    TREE about 13 years
    @John: Nope. They're right. ;) It might depend on the specific JVM, but some JVMs optimize by creating flyweights for the autoboxed primitive numbers around zero.
  • corsiKa
    corsiKa about 13 years
    @John yeah it's crazy. Unfortunately, because people often do quick tests on 1, 5, or other small values, they mistaken believe that any valueOf will equate to true. I've seen it lead to more than a couple bugs from inexperienced programmers.
  • Peter Knego
    Peter Knego about 13 years
    @Paŭlo: my only point was that Integer is an order of magnitude slower then primitives. And this is due to the fact that boxed types are immutable and every time you have change a value a new object is created. I did not claim there is something wrong with them or the fact that they are immutable. Just that they are slower and that a coder should know that. Take a look at how Groovy fares without primitive types jroller.com/rants/entry/why_is_groovy_so_slow
  • Paŭlo Ebermann
    Paŭlo Ebermann about 13 years
    I should have said "some implementations", as this is not governed by the language specifications, I think. (And sadly I can't cite any sources here, it is only from what I heard somewhere.)
  • Daniel Earwicker
    Daniel Earwicker about 13 years
    @TREE - the spec actually requires VMs to create flyweights within a certain range. But sadly it allows them to extend that range, meaning that programs may behave differently on different VMs. So much for cross platform...
  • Pop Catalin
    Pop Catalin about 13 years
    Java has gone down the drain, with more and more bad design choices. Autoboxing is a complete failure, it's neither robust, predictable or portable. I really really wonder what they were thinking ... instead of fixing the dreaded primitive-object duality they managed to make it worse than in the first place.
  • corsiKa
    corsiKa about 13 years
    @Catalin I disagree with you that autoboxing is a complete failure. It has some flaws, which is no different than any other design that could have been used (including nothing.) They make very clear what you can and can't expect, and like any other design they expect developers to know and obey the contracts of those designs.
  • bestsss
    bestsss about 13 years
    ŭlo, JIT already keeps meta into in the pointer; incl, the pointer can keep GC info, or the Klass (optimizing Class is a lot better idea than optimizing Integers, for which I can care less). Changing the pointer would require, shift/cmp/jnz code (or something like that) before each pointer load. The branch probably won't be very well predicted by the hardware (since it can be both Value Type and normal Object) and it would lead to performance hit.
  • Daniel Earwicker
    Daniel Earwicker about 13 years
    Immutability and ++ is a red herring here. Imagine Java was enhanced to support operator overloading in a really simple way, such that if a class (such as Integer has a method plus, then you could write i + 1 instead of i.plus(1). And assume also that the compiler is smart enough to expand i++ into i = i + 1. Now you could say i++ and effectively "increment the variable i" without Integer being mutable.
  • CromTheDestroyer
    CromTheDestroyer about 13 years
    I'm surprised recent versions of Hotspot haven't narrowed the gap between the boxed type and primitive performance. On my machine the primitive version of the code above is 11 times faster.
  • corsiKa
    corsiKa about 13 years
    You would think it would be fairly straightforward to optimize out. I can't imagine what it could be preventing them from doing so.
  • NullUserException
    NullUserException almost 10 years
    @NaftuliTzviKay That's not a "failure." They make it VERY CLEAR that the == operator performs reference identity comparisons on Integer expressions and value equality comparisons on int expressions. Integer.equals() exists for this very reason. You should never use == to compare values in any non-primitive type. This is Java 101.
  • R.Moeller
    R.Moeller almost 10 years
    I did Smalltalk for some years. The optimization still was pretty expensive, as for each operation on an int they had to unmask and reapply them. Currently java is on par with C when manipulating primitive numbers. With unmask+mask it will likely be >30% slower.
  • Igor Čordaš
    Igor Čordaš almost 10 years
    I too would like to protect the performance argument. On Android with Dalvik each object you create will increase the "risk" of GC being called and the more objects you have the pauses will be longer. So creating Integers instead of int in a loop will probably cost you some dropped frames.
  • biziclop
    biziclop almost 10 years
    @PSIXO It's a fair point, I wrote it with purely server-side Java in mind. Mobile devices are a whole different animal. But my point was that even developers who otherwise write terrible code without any regard to performance will cite this as a reason, from them this sounds very much as an excuse.
  • corsiKa
    corsiKa over 9 years
    @NullUserException I think Naftuli means it's a failure on the part of the programmer who does something like this (and I bet it happens a lot!)
  • Chris Nevill
    Chris Nevill over 9 years
    The trouble lies it's all very well to blame the programmer and obviously it's very quick to understand the issue if you're looking for it but if you dip in and out of several different languages often months or even years apart each with different naming conventions etc. it's very easy to make mistakes like this and not realise for some time that there is even an issue there.
  • MrBackend
    MrBackend about 9 years
    It would be broken, yes. However, remove(int) is a design flaw IMO. Method names should never be overloaded if there is the slightest chance of a mix up.
  • xehpuk
    xehpuk about 9 years
    @MrBackend Fair enough. Interestingly, Vector had removeElementAt(int) from the beginning. remove(int) was introduced with the collections framework in Java 1.2.
  • Holger
    Holger almost 9 years
    @MrBackend: when the List API was designed, neither Generics nor Autoboxing existed, so there was no chance of mixing up remove(int) and remove(Object)
  • Holger
    Holger almost 8 years
    @Franklin Yu: sure, but when designing a new language/ version without compatibility constraints, you would not stop at changing that unfortunate overload. You would simply get rid of the distinction of primitives and boxed values altogether, so that the question which to use will never appear.
  • Geniy
    Geniy over 5 years
    i1 == i2; would be false only if i1 >= 128. So, current example is wrong
  • Teja MS
    Teja MS about 3 years
    Reason: If we use Wrapper classes to create variable , it will always create a new reference in the memory
  • Holger
    Holger almost 3 years
    I think, the equality issue should come first. Further, boxed types imply the possibility of being null. The performance differences can be devastating, but still, the semantic issues are worse.