I just started my APCSA course and am slightly confused on why a person might user Wrapper Classes instead of the more simple primitive data types.
Could someone give an example of a situation where you would want to use primitives and one where you should use a Wrapper class?
The way I understand wrapper classes is they are used to convert primitive data types into objects so you can store them in Collection classes. I also believe that this happens automatically on backend by the compiler through a process called autoboxing.
To me, someone with no real world programming experience, I find myself wondering why use primitives at all?
The wrapper classes are much more interesting because you get methods to do things, where primitives are boring. Why not just use wrapper classes for everything? Is that even possible?
My gut tells me that someone is going to say primitives are faster, but is that a valid argument considering the speed of computers today?
Videos
The real difference is that primitive types are not reference types.
In Java type system, reference types have a common root, while primitive types do not: All reference types are subtypes of Object. In comparison, primitive types do not have a common root; each primitive type is its own special unicorn.
This means that a variable declared with Object type can hold a value of any reference type, but it cannot hold a value of any primitive type.
This is a problem if you want to create data structures that are generally useful. Consider how you would implement a "dynamic array" data structure like ArrayList or Vector. You could use an array Object[] to store the elements, and that would work for all reference types. But since primitive types don't have a common root you would have to create a separate implementation for each primitive type.
To solve this problem, wrapper classes were created. Now, instead of needing 8 separate implementations of dynamic array (1 for reference types and 7 for primitive types), you could wrap each primitive value in an object, and just use the implementation for reference types.
Primitive types and wrapper classes were created at the same time, when the first design and implementation of Java 1.0 were made. The designers did not "reuse what was already available" because nothing was available: they built everything from scratch.
Could the designers have solved the problem some other way, maybe by creating a type system that had a common root for both primitive and reference types? Yes, but they didn't, and they probably had good reasons: implementation complexity, ease of understanding, time to market, ...
Actually, this question is pretty insightful. The answers are good and correct, but the underlying question that I think is worth asking is: should primitive types exist at all? We had a lot of discussion about this at the time and I think the reason things ended up the way they did had to do with the fact that Oak (later Java) was designed as an embedded language for the IOT (internet of things). If it had been originally designed as a server language, it might have been very different. I think primitive types are a legacy idea that is actually very harmful for exactly the reasons that Joni mentions. It's possible and desirable to have everything in the system extend Object.
How could you eliminate primitives? With a smarter JVM. HotSpot could turn special primitive objects into appropriate machine code wherever possible and there wouldn't be a need for autoboxing, wrappers or difficulties with the type system being split. The only limitation that I think would need to be placed on Integer, Float, Double, etc., is that they would have to be final. This would allow these classes to be optimized anywhere possible as if they were primitives without actually having primitives in the language. You could even have syntactic abbreviations like 'int', 'float' and 'double' for these special classes, but the important thing is that the type system would be better if it was fully unified.
But you don't start trying to build such a thing when you're thinking about smart toasters. What happened to Java in terms of its wild success was a pure accident. It happened to be a solution to a lot of web-related issues just sitting there at the right time. But it wasn't planned that way, so there are a lot of things people would like to have back. I think a lot of people at Sun/Oracle would eliminate primitives in hindsight.
Iโm coming from C (and know a bit of C++), and this kindof confuses me. What is happening on a memory level when an int is converted to an Integer? Do all objects have some memory overhead or something? It seems like if an ArrayList uses arrays internally, then it should be able to use an array of ints.
My best guess is so that everything stored in the collection is guaranteed to have some set of functions available to it needed by the collection implementation, but in that case, why does the programmer have to worry about wrapping the types? That seems like something that should be handled under the hood.
Others have mentioned that certain constructs such as Collections require objects and that objects have more overhead than their primitive counterparts (memory & boxing).
Another consideration is:
It can be handy to initialize Objects to null or send null parameters into a method/constructor to indicate state or function. This can't be done with primitives.
Many programmers initialize numbers to 0 (default) or -1 to signify this, but depending on the scenario, this may be incorrect or misleading.
This will also set the scene for a NullPointerException when something is being used incorrectly, which is much more programmer-friendly than some arbitrary bug down the line.
Generally, you should use primitive types unless you need an object for some reason (e.g. to put in a collection). Even then, consider a different approach that doesn't require a object if you want to maximize numeric performance. This is advised by the documentation, and this article demonstrates how auto-boxing can cause a large performance difference.
In Item 5, of Effective Java, Joshua Bloch says
The lesson is clear: prefer primitives to boxed primitives, and watch out for unintentional autoboxing.
One good use for classes is when using them as generic types (including Collection classes, such as lists and maps) or when you want to transform them to other type without implicit casting (for example Integer class has methods doubleValue() or byteValue().
Edit: Joshua Bloch's reason is:
// Hideously slow program! Can you spot the object creation? public static void main(String[] args) { Long sum = 0L; for (long i = 0; i < Integer.MAX_VALUE; i++) { sum += i; } System.out.println(sum); }This program gets the right answer, but it is much slower than it should be, due to a one-character typographical error. The variable
sumis declared as aLonginstead of along, which means that the program constructs about 2^31 unnecessaryLonginstances (roughly one for each time thelong iis added to theLong sum). Changing the declaration of sum fromLongtolongreduces the runtime from 43 seconds to 6.8 seconds on my machine.
The standard practice is to go with the primitives, unless you're dealing with generics (make sure you are aware of autoboxing & unboxing!).
There are a number of good reasons to follow the convention:
1. You avoid simple mistakes:
There are some subtle, non-intuitive cases which often catch out beginners. Even experienced coders slip up and make these mistakes sometimes (hopefully this will be followed by swearing when they debug the code and find the error!).
The most commmon mistake is using a == b instead of a.equals(b). People are used to doing a == b with primitives so it's easily done when you're using the Object wrappers.
Integer a = new Integer(2);
Integer b = new Integer(2);
if (a == b) { // Should be a.equals(b)
// This never gets executed.
}
Integer c = Integer.valueOf(2);
Integer d = Integer.valueOf(2);
if (c == d) { // Should be a.equals(b), but happens to work with these particular values!
// This will get executed
}
Integer e = 1000;
Integer f = 1000;
if (e == f) { // Should be a.equals(b)
// Whether this gets executed depends on which compiler you use!
}
2. Readability:
Consider the following two examples. Most people would say the second is more readable.
Integer a = 2;
Integer b = 2;
if (!a.equals(b)) {
// ...
}
int c = 2;
int d = 2;
if (c != d) {
// ...
}
3. Performance:
The fact is it is slower to use the Object wrappers for primitives than just using the primitives. You're adding the cost of object instantiation, method calls, etc. to things that you use all over the place.
Knuth's "...say about 97% of the time: premature optimization is the root of all evil" quote doesn't really apply here. He was talking about optimisations which make the code (or system) more complicated - if you agree with point #2, this is an optimization which makes the code less complicated!
4. It's the convention:
If you make different stylistic choices to 99% of the other Java programmers out there, there are 2 downsides:
- You will find other people's code harder to read. 99% of the examples/tutorials/etc out there will use primitives. Whenever you read one you'll have the extra cognitive overhead of thinking about how it would look in the style that you're used to.
- Other people will find your code harder to read. Whenever you ask questions on Stack Overflow you'll have to sift through answers/comments asking "why aren't you using primitives?". If you don't believe me, just look at the battles people have over things like bracket placement, which doesn't even affect the generated code!
Normally I'd list some counter-points, but I honestly can't think of any good reasons to not go with the convention here!
I was following a tutorial, and at some point a Student class was made like so:
public class Student {
private Long id;
private String name;
private LocalDate dob;
private Integer age;
I was typing along, and just out of habit I used long and int.
Why would you use Classes instead of primitives?
Further in the tutorial, instances were created by just passing something like this:
new Student( 1L, "John", LocalDate.of(....), 40);