Why this matters
- The
==versus.equals()question is the most common source of silent bugs for new Java developers, and autoboxing is why. - Integer overflow and floating-point imprecision cause money and timing bugs that no test catches unless you know to look.
- Enums replace a whole category of bug-prone integer constants, and most people under-use them.
Popped the moment a method returns.
Cleared by the garbage collector, not by returning.
The eight primitives
Size, range and default
byte— 8 bits, −128 to 127, default0. Used for raw binary data.short— 16 bits, about ±32 thousand, default0. Rarely used directly.int— 32 bits, about ±2.1 billion, default0. The default choice for whole numbers.long— 64 bits, about ±9.2 quintillion, default0L. Needed for timestamps in milliseconds.float— 32-bit decimal, default0.0f. Roughly 7 significant digits.double— 64-bit decimal, default0.0d. The default choice for decimals, about 15 digits.char— 16 bits, a single UTF-16 unit, default'\u0000'. Unsigned, unlike the rest.boolean—trueorfalse, defaultfalse. Its size is not specified.
Overflow is silent
Arithmetic wraps around rather than failing, which is the single most dangerous default in the language.
int max = Integer.MAX_VALUE; // 2_147_483_647
System.out.println(max + 1); // -2147483648 — wrapped, no warning
long millisPerDay = 24 * 60 * 60 * 1000; // fine
long millisPerYear = 24 * 60 * 60 * 1000 * 365; // wrong: int overflow before assignment
long correct = 24L * 60 * 60 * 1000 * 365; // one L fixes the whole expression
The third line is the classic trap. Every operand is an int, so the multiplication happens in
32 bits and overflows before the result is widened to long. Marking the first operand L
forces the whole expression into 64-bit arithmetic.
Floating point is approximate
System.out.println(0.1 + 0.2); // 0.30000000000000004
System.out.println(0.1 + 0.2 == 0.3); // false
// For money, use BigDecimal — and the String constructor, not the double one
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
System.out.println(a.add(b)); // 0.3
double stores binary fractions, and 0.1 has no exact binary representation, just as 1/3
has no exact decimal one. Never use double for currency. Either use BigDecimal constructed
from strings, or store whole cents in a long.
Wrappers and autoboxing
Each primitive has an object counterpart — int/Integer, double/Double and so on —
because collections and generics can only hold objects. The compiler converts automatically.
List<Integer> scores = new ArrayList<>();
scores.add(42); // autoboxing: int → Integer
int first = scores.get(0); // unboxing: Integer → int
That convenience has three sharp edges.
Integer a = 127, b = 127;
System.out.println(a == b); // true — both from the cache
Integer c = 128, d = 128;
System.out.println(c == d); // false — two distinct objects
System.out.println(c.equals(d)); // true — compares values
Integer absent = null;
int value = absent; // NullPointerException on unboxing
The three edges
- Cached range.
Integercaches instances from −128 to 127, so==accidentally works there and silently stops working above it. Always compare wrapper values with.equals(). - Unboxing null. A
nullwrapper unboxes by calling.intValue()onnull, producing aNullPointerExceptionat a line with no visible method call. - Boxing in loops. A
longaccumulator typed asLongallocates a new object on every iteration. In a hot loop this is a real cost.
Enums instead of constants
An enum is a class with a fixed set of instances. It gives you type safety, readable output, and a natural place to attach behaviour.
public enum Status {
PENDING("Awaiting review"),
APPROVED("Ready to ship"),
REJECTED("Returned to sender");
private final String description;
Status(String description) {
this.description = description;
}
public String description() {
return description;
}
public boolean isFinal() {
return this == APPROVED || this == REJECTED;
}
}
| Aspect | int constants | enum |
|---|---|---|
| Type safety | Any int is accepted, valid or not | Only declared values compile |
| Printing | Shows a meaningless number | Shows the constant name |
| Can hold data or methods | No | Yes — fields, constructors, methods |
| Works in switch | Yes | Yes, and the compiler can check exhaustiveness |
| Comparison | == | == is safe; instances are singletons |
Type safety
int constantsAny int is accepted, valid or notenumOnly declared values compilePrinting
int constantsShows a meaningless numberenumShows the constant nameCan hold data or methods
int constantsNoenumYes — fields, constructors, methodsWorks in switch
int constantsYesenumYes, and the compiler can check exhaustivenessComparison
int constants==enum== is safe; instances are singletons
Enum is one of the highest-value, lowest-effort upgrades in everyday Java.
Operators worth a second look
&&and||short-circuit;&and|do not.if (list != null && !list.isEmpty())is safe only because of short-circuiting.- Integer division truncates.
7 / 2is3. Write7 / 2.0for3.5. %keeps the sign of the left operand.-7 % 3is-1, not2.i++returns the old value;++ireturns the new one. Avoid using either inside a larger expression.>>>shifts in zeros regardless of sign, while>>preserves the sign bit.varinfers the type of a local variable from its initialiser. It is still statically typed; use it when the right side already names the type clearly.
Common misreadings
- "
varmakes Java dynamically typed." The type is fixed at compile time; only the writing is shorter. - "
charcan be negative." It is the one unsigned primitive, ranging from0to65535. - "
==works for Integer." It works accidentally inside the cache range, which makes the bug worse, not better. - "
floatsaves meaningful memory." On modern hardwaredoubleis the sensible default;floatmainly buys imprecision. - "Local variables default to zero." Fields do. Locals must be assigned before use, enforced at compile time.
Quick recall
Everything you need if you only revisit this box.
- Eight primitives hold values directly; everything else is a reference. Fields get defaults, locals do not.
- Integer arithmetic wraps silently. Use
Learly in long expressions, orMath.addExactwhen wrapping would be a bug. doublecannot represent0.1exactly. UseBigDecimalbuilt from strings, or integer cents, for money.Integercaches −128 to 127, so==works by accident there. Always use.equals()for wrapper values.- Unboxing a
nullwrapper throwsNullPointerExceptionat a line with no visible call. - Enums give type safety, readable names and attached behaviour;
==is the correct comparison for them. &&and||short-circuit;&and|evaluate both sides. Integer division truncates and%follows the left operand's sign.
Test yourself
Answer these before moving on — recall is what makes it stick.