Skip to content

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 and interface 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 typeCorresponding reference type
booleanjava.lang.Boolean
bytejava.lang.Byte
shortjava.lang.Short
intjava.lang.Integer
longjava.lang.Long
floatjava.lang.Float
doublejava.lang.Double
charjava.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.

Wrapper Type has loaded