Appearance
Wrapper Type
We already know that Java's data types are divided into two categories:
- Primitive types:
byte
,short
,int
,long
,boolean
,float
,double
,char
. - Reference types: all
class
andinterface
types.
Reference types can be assigned a value of null
, indicating 'empty', but primitive types cannot be assigned null
.
java
String s = null;
int n = null; // compile error!
So, how can we treat a primitive type as an object (reference type)?
For example, to convert the int primitive type into a reference type, we can define an Integer
class that contains a single instance field of type int
. Thus, the Integer
class can be viewed as the wrapper class for int
:
java
public class Integer {
private int value;
public Integer(int value) {
this.value = value;
}
public int intValue() {
return this.value;
}
}
After defining the Integer
class, we can convert int
and Integer
to each other:
java
Integer n = null;
Integer n2 = new Integer(99);
int n3 = n2.intValue();
In fact, because wrapper types are so useful, the Java core library provides a corresponding wrapper type for each basic type:
basic type | Corresponding reference type |
---|---|
boolean | java.lang.Boolean |
byte | java.lang.Byte |
short | java.lang.Short |
int | java.lang.Integer |
long | java.lang.Long |
float | java.lang.Float |
double | java.lang.Double |
char | java.lang.Character |
We can use it directly and don’t need to define it ourselves:
java
public class Main {
public static void main(String[] args) {
int i = 100;
// Not recommended, there will be compilation warnings:
Integer n1 = new Integer(i);
// Create an Integer instance through the static method valueOf(int):
Integer n2 = Integer.valueOf(i);
// Create an Integer instance through the static method valueOf(String):
Integer n3 = Integer.valueOf("100");
System.out.println(n3.intValue());
}
}
Auto Boxing
Because int
and Integer
can be converted to each other:
java
int i = 100;
Integer n = Integer.valueOf(i);
int x = n.intValue();
Therefore, the Java compiler can help us automatically convert between int
and Integer
:
java
Integer n = 100; // The compiler automatically uses Integer.valueOf(int)
int x = n; // The compiler automatically uses Integer.intValue()
This method of writing assignments that directly converts int
into Integer
is called Auto Boxing, and conversely, the method of writing assignments that directly converts Integer
into int
is called Auto Unboxing.
Notice
Autoboxing and auto-unboxing only occur during the compilation phase, in order to write less code.
Boxing and unboxing will affect the execution efficiency of the code, because the compiled class
code strictly distinguishes between basic types and reference types. Moreover, NullPointerException
may be reported when automatic unboxing is executed:
java
public class Main {
public static void main(String[] args) {
Integer n = null;
int i = n;
}
}
Immutable class
All wrapper types are immutable classes. If we look at the source code of Integer
, we can see that its core code is as follows:
java
public final class Integer {
private final int value;
}
Therefore, once an Integer
object is created, the object is immutable.
Special attention should be paid to comparing two Integer
instances: ==
comparison must not be used, because Integer
is a reference type and must be compared using equals()
:
java
// == or equals?
public class Main {
public static void main(String[] args) {
Integer x = 127;
Integer y = 127;
Integer m = 99999;
Integer n = 99999;
System.out.println("x == y: " + (x==y)); // true
System.out.println("m == n: " + (m==n)); // false
System.out.println("x.equals(y): " + x.equals(y)); // true
System.out.println("m.equals(n): " + m.equals(n)); // true
}
}
Children's shoes who carefully observe the results can find ==
comparison, the smaller two identical Integer
returns true
, and the larger two identical Integer
returns false
. This is because Integer
is an immutable class, and the compiler treats Integer x = 127;
automatically becomes Integer x = Integer.valueOf(127);
, in order to save memory, Integer.valueOf()
always returns the same instance for smaller numbers.
Therefore, ==
comparison "exactly" is true
, but we must not use ==
because there is caching optimization inside Integer
of the Java standard library. ==
comparison, you must use equals()
method to compare two Integer
.
Best practices
Program according to semantics, rather than "optimizing" for a specific underlying implementation.
Because Integer.valueOf()
may always return the same Integer
instance, when we create Integer
ourselves, there are two methods:
- Method 1:
Integer n = new Integer(100);
- Method 2:
Integer n = Integer.valueOf(100);
Method 2 is better because method 1 always creates a new Integer
instance. Method 2 leaves the internal optimization to the implementer of Integer
. Even if there is no optimization in the current version, it may be optimized in the next version.
We call static methods that can create "new" objects static factory methods. Integer.valueOf()
is a static factory method that returns cached instances as much as possible to save memory.
Best practices
When creating new objects, prefer static factory methods over the new operator.
If we examine the source code of the Byte.valueOf()
method, we can see that all Byte
instances returned by the standard library are cached instances, but the caller does not care about how the static factory method creates new instances or directly returns cached instances.
Base conversion
The Integer
class itself also provides a large number of methods. For example, the most commonly used static method parseInt()
can parse a string into an integer:
java
int x1 = Integer.parseInt("100"); // 100
int x2 = Integer.parseInt("100", 16); // 256,Because it is parsed in hexadecimal
Integer
can also format integers into strings in a specified base:
java
// Integer:
public class Main {
public static void main(String[] args) {
System.out.println(Integer.toString(100)); // "100",Expressed as decimal
System.out.println(Integer.toString(100, 36)); // "2s",Represented as hexadecimal
System.out.println(Integer.toHexString(100)); // "64",Represented as hexadecimal
System.out.println(Integer.toOctalString(100)); // "144",Expressed as octal
System.out.println(Integer.toBinaryString(100)); // "1100100",Expressed as binary
}
}
Note: The output of the above method is all String
. In the computer memory, it is only expressed in binary, and there is no decimal or hexadecimal representation. int n = 100
is always represented in memory as a 4-byte binary:
┌────────┬────────┬────────┬────────┐
│00000000│00000000│00000000│01100100│
└────────┴────────┴────────┴────────┘
System.out.println(n);
we often use relies on the core library to automatically format the integer into decimal output and display it on the screen. Using Integer.toHexString(n)
automatically formats the integer into decimal through the core library. Hexadecimal.
Here we notice an important principle of programming: data storage and display should be separated.
Java's wrapper types also define some useful static variables
java
// boolean has only two values, true/false, and its packaging type
// only needs to refer to the static fields provided by Boolean.:
Boolean t = Boolean.TRUE;
Boolean f = Boolean.FALSE;
// The maximum/minimum value that int can represent:
int max = Integer.MAX_VALUE; // 2147483647
int min = Integer.MIN_VALUE; // -2147483648
// The number of bits and bytes occupied by long type:
int sizeOfLong = Long.SIZE; // 64 (bits)
int bytesOfLong = Long.BYTES; // 8 (bytes)
Finally, all integer and floating-point wrapper types inherit from Number
, so it is very convenient to obtain various basic types directly through the wrapper type:
java
// Upcast to Number:
Number num = new Integer(999);
// Get byte, int, long, float, double:
byte b = num.byteValue();
int n = num.intValue();
long ln = num.longValue();
float f = num.floatValue();
double d = num.doubleValue();
Handling unsigned integers
In Java, there is no basic data type of unsigned integer (Unsigned). byte
, short
, int
and long
are all signed integer types, and the highest bit is the sign bit. The C language provides all data types supported by the CPU, including unsigned integers. The conversion between unsigned integer and signed integer needs to be completed in Java with the help of static methods of the packaging type.
For example, byte is a signed integer type with a range of -128
~ +127
, but if byte
is regarded as an unsigned integer type, its range is 0
~ 255
. We convert a negative byte
to int
unsigned integer:
java
// Byte
public class Main {
public static void main(String[] args) {
byte x = -1;
byte y = 127;
System.out.println(Byte.toUnsignedInt(x)); // 255
System.out.println(Byte.toUnsignedInt(y)); // 127
}
}
Because the binary representation of -1
in byte
is 11111111
, int
converted to unsigned integer is 255
.
Similarly, a short
can be unsigned to int
, and an int
can be unsigned to long
.
Summary
The packaging type provided by the Java core library can wrap basic types into class
;
Automatic boxing and automatic unboxing are completed during compilation (JDK>=1.5);
Boxing and unboxing will affect execution efficiency, and NullPointerException
may occur during unboxing;
Comparisons of wrapped types must use equals()
;
The packaging types of integers and floating point numbers are inherited from Number
;
Packaging types provide a number of useful methods.