Hi! You are already well acquainted with primitive types, and have worked quite a bit with them.
In programming (and Java in particular), primitives have many advantages: they use little memory (and thus make the program more efficient) and have a clearly delineated range of values.
However, while learning Java, we've already often repeated the mantra "
everything in Java is an object".
But primitives directly contradict those words. They aren't objects. So, is our "everything is an object" principle false?
Actually, it's not.
In Java, every primitive type has a twin brother, a
wrapper class.
![Wrappers, unboxing, and boxing - 1]()
What's a wrapper?
A wrapper is a special class that stores a primitive internally.
But because it's a class, you can create instances of it. They store the primitive values internally, but are still real objects.
Wrapper class names are very similar to (or exactly the same as) the names of their corresponding primitives. So, they are easy to remember.
Wrapper Classes for Primitive Data Types
|
Primitive Data Types
|
Wrapper Classes
|
int
|
Integer
|
short
|
Short
|
long
|
Long
|
byte
|
Byte
|
float
|
Float
|
double
|
Double
|
char
|
Character
|
boolean
|
Boolean
|
Wrapper objects are created the same way as any other object:
public static void main(String[] args) {
Integer i = new Integer(682);
Double d = new Double(2.33);
Boolean b = new Boolean(false);
}
Wrapper classes let us mitigate the shortcomings of primitive types.
The most obvious is that
primitives don't have methods.
For example, they don't have a
toString()
method, so you can't, for instance, convert an
int
to a
String
.
But the
Integer
wrapper class makes this easy.
public static void main(String[] args) {
Integer i = new Integer(432);
String s = i.toString();
}
However, converting in the other direction can be trickier.
Let's say we have a
String
, which we know for sure contains a number.
Regardless, there's no native way to use a primitive
int
to extract the number from the
String
and convert it to a number.
But, we can with the
wrapper classes.
public static void main(String[] args) {
String s = "1166628";
Integer i = Integer.parseInt(s);
System.out.println(i);
}
Output:
1166628
We successfully extracted a number from the
String
and assigned it to the
Integer
reference variable
i
.
By the way, regarding references.
You already know that arguments are passed to methods in different ways: primitives by value, and objects by reference.
You can use this knowledge when creating your own methods: for example, if your method uses fractional numbers but you need logic to pass by reference, you can pass
Double
/
Float
arguments to the method instead of
double
/
float
.
In addition to
wrapper classes' methods, their static fields can also be very convenient.
For example, imagine you have the following task:
display the maximum possible int
value, followed by the minimum possible value.
This problem seems rather basic. But without Google, it's unlikely you could do it.
But
wrappers allow you to easily handle such "mundane tasks":
public class Main {
public static void main(String[] args) {
System.out.println(Integer.MAX_VALUE);
System.out.println(Integer.MIN_VALUE);
}
}
These fields keep you from getting distracted from accomplishing more serious tasks.
Not to mention the fact that typing in
2147483647 (which happens to be the value of MAX_VALUE) is no minor feat! :)
Moreover, in a previous lesson, we pointed out that
wrapper objects are immutable.
public static void main(String[] args) {
Integer a = new Integer(0);
Integer b = new Integer(0);
b = a;
a = 1;
System.out.println(b);
}
Output:
0
The state of the object originally pointed to by
a
didn't change (because the value of
b
would have also changed).
As with
String
s, instead of changing the
wrapper object's state, an entirely new object is created in memory.
So, why did Java's creators ultimately decide to leave primitive types in the language?
Since everything should be an object, and we've got
wrapper classes that can express everything that primitives express, why not only keep the
wrappers in the language and remove the primitives?
The answer is simple:
performance.
Primitive types are called primitive because they lack many of the "heavyweight" features of objects. Yes, objects have many convenient methods, but you don't always need them.
Sometimes, all you need is the number 33, or 2.62, or
true
/
false
. In situations where the advantages of objects don't matter and aren't required for the program to function, primitives are far better suited to the task.
Autoboxing/unboxing
In Java, a feature of primitives and their
wrappers is
autoboxing/unboxing.
![Wrappers, unboxing, and boxing - 2]()
Let's dig into this concept.
As we've already learned, Java is an object-oriented language. That means that all programs written in Java are made of objects.
Primitives are not objects.
But even so, a
wrapper variable can be assigned a primitive value. This process is called
autoboxing.
Similarly, a primitive variable can be assigned a
wrapper object.
This process is called unboxing.
For example:
public class Main {
public static void main(String[] args) {
int x = 7;
Integer y = 111;
x = y; // Unboxing
y = x * 123; // Autoboxing
}
}
In line 5, we assign y, which is an
Integer
object, to the primitive
x
.
As you can see, we don't have to take any additional steps:
the compiler knows that int
and Integer
are, essentially, the same thing. That's unboxing.
Something similar is happening with autoboxing on line 6: the primitive value (x * 123) is easily assigned to object
y
. This is an example of autoboxing.
That's why the term includes the word "auto":
because you don't have to do anything special to assign primitives to their corresponding wrapper objects (and vice versa). It all happens automatically.
Convenient, huh? :)
We see another example of the convenience of autoboxing/unboxing when working with methods. This is because
method arguments are also autoboxed and unboxed.
For example, if a method takes two
Integer
objects as inputs, we can easily pass ordinary
int
s instead!
public class Main {
public static void main(String[] args) {
printNumber(7);// A standard int, not even an int variable
}
public static void printNumber(Integer i) {
System.out.println("You entered the number " + i);
}
}
Output:
You entered the number 7
It works in the other direction, too:
public class Main {
public static void main(String[] args) {
printNumber(new Integer(632));
}
public static void printNumber(int i) {
System.out.println("You entered the number " + i);
}
}
An important point you need to remember is this:
autoboxing and unboxing don't work for arrays!
public class Main {
public static void main(String[] args) {
int[] i = {1,2,3,4,5};
printArray(i);// Error, this won't compile!
}
public static void printArray(Integer[] arr) {
System.out.println(Arrays.toString(arr));
}
}
Attempting to pass an array of primitives to a method that takes an array of objects will result in a compilation error.
In conclusion, let's briefly compare
primitives and
wrappers one more time.
Primitives:
- Have performance advantages
Wrappers:
- Allow us to not violate the "everything is an object" principle, which means that numbers, characters, and boolean values don't violate this concept
- Expand the possibilities for working with these values by providing convenient methods and fields
- Are necessary when a method only works with objects
GO TO FULL VERSION