Primitive Data Types and Wrapper Classes: The Binary Reality
Java is a strictly typed language. Every variable must have a declared type before it can be used. While Java is an Object-Oriented language, it maintains 8 Primitive Types that are not objects. These exist to maximize performance—primitives are stored directly in the stack frame (if local) or within the object's heap memory (if fields), avoiding the overhead of object headers and pointer indirection.
1. The Eight Primitive Types
| Type | Size | Default | Range |
|---|---|---|---|
byte |
1 Byte | 0 | -128 to 127 |
short |
2 Bytes | 0 | -32,768 to 32,767 |
int |
4 Bytes | 0 | -2³¹ to 2³¹-1 |
long |
8 Bytes | 0L | -2⁶³ to 2⁶³-1 |
float |
4 Bytes | 0.0f | IEEE 754 Single Precision |
double |
8 Bytes | 0.0d | IEEE 754 Double Precision |
char |
2 Bytes | '\u0000' | 0 to 65,535 (Unicode) |
boolean |
Implementation Dependent | false | true / false |
The Range Anomaly: Why is the negative range larger by 1 than the positive range? To answer this, we must look at how integers are stored in bits: Two's Complement.
2. Integer Representation: Two's Complement
To represent negative numbers, computer scientists evolved through three stages of encoding.
2.1 Sign-Magnitude (The Intuitive Fail)
The highest bit is the sign (0 for +, 1 for -), and the rest is the absolute value.
- Problem 1: Two zeros (
+0as0000and-0as1000). - Problem 2: Logic circuits need separate adders and subtractors, as
1 + (-1)in sign-magnitude (0001 + 1001) results in1010(-2), which is incorrect.
2.2 Ones' Complement (The Transition)
Negative numbers are formed by flipping every bit of the positive number.
- Improvement: Addition and subtraction can use the same circuit with an "end-around carry."
- Problem: Still has two representations for zero (
0000and1111).
2.3 Two's Complement (The Standard)
Rule: Positive is same as binary. Negative = (Invert all bits) + 1.
Example for byte (-5):
- Start with
5:00000101 - Invert:
11111010 - Add 1:
11111011(This is-5)
Why it wins:
- Single Zero:
-0in Ones' Complement (11111111) becomes00000000in Two's Complement after adding 1 and discarding the overflow bit. - Unified Arithmetic: The CPU only needs an Adder. Subtraction is just addition with a negative number. Overflow bits are simply discarded.
- The Extra Slot: Since
0only has one representation (00000000), the "released" bit pattern10000000is mapped to-128.
3. Floating-Point: The IEEE 754 Logic
Floating-point numbers (float, double) use a "scientific notation" format:
$$(-1)^{sign} \times 1.mantissa \times 2^{(exponent - bias)}$$
The Anatomy of a Float (32-bit):
- Sign (1 bit): 0 (+) or 1 (-).
- Exponent (8 bits): Stored with a Bias of 127. To store $2^2$, the field holds $2 + 127 = 129$. This allows for negative exponents without a sign bit in the field.
- Mantissa (23 bits): The fractional part. The leading "1." is implicit and not stored to save space.
Why 0.1 + 0.2 != 0.3?
In base-10, $1/3$ is infinite ($0.333...$). In base-2 (binary), $0.1$ is an infinite repeating sequence ($0.0001100110011...$).
Since a double only has 52 bits of mantissa, it must truncate this infinite sequence. This tiny truncation creates the rounding error seen in financial calculations.
Rule: Never use
floatordoublefor money. UseBigDecimal.
4. Wrapper Classes and Autoboxing
Because Java's generic collections (like ArrayList<T>) only accept objects, Java provides a corresponding class for every primitive.
Bytecode Analysis of Autoboxing
Autoboxing is not "magic"; it is compile-time sugar.
Integer a = 10; // Becomes Integer.valueOf(10)
int b = a; // Becomes a.intValue()
The NPE Trap
Because unboxing calls a method (intValue()), if the wrapper object is null, an automatic unboxing attempt will throw a NullPointerException. This is a common bug in Ternary Operators:
Integer a = null;
int result = (true) ? a : 2; // Throws NPE because 'a' is unboxed to match 'int'
5. The Cache Pool Strategy
To save memory and improve performance, Java caches small wrapper objects in an internal pool.
| Class | Cache Range |
|---|---|
Byte, Short, Long |
-128 to 127 |
Integer |
-128 to 127 (Max is tunable via JVM args) |
Character |
0 to 127 |
Boolean |
Always reuses TRUE and FALSE |
Float, Double |
No Cache (Floating point values are too diverse) |
The Identity Trap:
Integer i1 = 127;
Integer i2 = 127;
System.out.println(i1 == i2); // true (Same object from cache)
Integer i3 = 128;
Integer i4 = 128;
System.out.println(i3 == i4); // false (New objects created)
The == operator on objects compares memory addresses. For cached values, addresses match; for non-cached values, they don't. Always use .equals() for value comparison.
6. Type Conversions
- Widening (Implicit): Moving from a small range to a larger range (e.g.,
bytetoint). Note thatlong(64-bit) can implicitly convert tofloat(32-bit). Java prioritizes Range over Precision in this case. - Narrowing (Explicit): Moving from large to small (e.g.,
inttobyte). This requires a cast(byte) myIntand involves "clipping" the higher bits, which can lead to negative numbers if the 8th bit of the result is 1.