Appearance
Lesson 02 · Numeric Values — Wrappers, Math & BigDecimal
Objectives
After this lesson you will be able to:
- Use the wrapper classes and explain autoboxing/unboxing — including the
null-unbox NPE and theIntegercache==trap. - Parse and format numbers (
parseInt,valueOf, radix,String.format). - Predict integer division, modulo sign, and silent overflow, and use
Mathcorrectly. - Choose
BigInteger/BigDecimalfor exactness, and avoid theBigDecimalequals/doubleconstructor traps.
Wrapper classes
Each primitive has an immutable wrapper class in java.lang that lets a number live as an object (so it can go into collections, generics, and null):
| Primitive | Wrapper | Primitive | Wrapper | |
|---|---|---|---|---|
byte | Byte | long | Long | |
short | Short | float | Float | |
int | Integer | double | Double | |
char | Character | boolean | Boolean |
Wrappers expose useful constants and statics: Integer.MAX_VALUE, Integer.MIN_VALUE, Long.BYTES, Double.NaN, Integer.parseInt(...), Integer.compare(a, b).
Prefer the static valueOf over the deprecated constructors (new Integer(1) was deprecated for removal in Java 9) — valueOf can reuse cached objects, new never does.
Autoboxing & the Integer cache
Autoboxing converts a primitive to its wrapper automatically; unboxing does the reverse.
java
Integer boxed = 42; // autobox: int → Integer (Integer.valueOf(42))
int back = boxed; // unbox: Integer → int
List<Integer> xs = new ArrayList<>();
xs.add(7); // autoboxedExam trap
Unboxing null throws NullPointerException — it tries to call .intValue() on null:
java
Integer i = null;
int x = i; // NullPointerException at runtime (compiles fine)Exam trap
Integer.valueOf caches boxed values from −128 to 127, so == (reference compare) is true in range and false outside it. Always compare values with .equals(), not ==.
java
Integer a = 127, b = 127;
a == b; // true — same cached object
Integer c = 128, d = 128;
c == d; // false — distinct objects, outside the cache
c.equals(d); // true — compare valuesBut if either side is a primitive, the wrapper unboxes and == compares numeric values:
java
Integer big = 1000;
big == 1000; // true — right side is int, so big is unboxedWhat prints? — mixed ternary
java
Object o = true ? Integer.valueOf(1) : Double.valueOf(2.0);
System.out.println(o);Answer: 1.0. A conditional with Integer and Double branches applies binary numeric promotion: both are unboxed and widened to double, so even the taken branch becomes 1.0 (re-boxed to a Double). The chosen value matters, but its type is decided by both branches.
Parsing & formatting
parseXxx returns a primitive; valueOf returns a wrapper. Bad input throws the unchecked NumberFormatException.
java
int n = Integer.parseInt("42"); // 42 (primitive int)
Integer w = Integer.valueOf("42"); // 42 (Integer)
int hex = Integer.parseInt("ff", 16); // 255 (radix/base argument)
double d = Double.parseDouble("3.14"); // 3.14
Integer.parseInt("4.0"); // NumberFormatException (not an int)Format the other way with String.format / printf patterns, or the radix helpers:
java
String.format("%d", 42); // "42"
String.format("%05d", 42); // "00042" (zero-padded width 5)
String.format("%,d", 1_000_000); // "1,000,000" (grouping)
String.format("%.2f", 3.14159); // "3.14" (2 decimals, rounded)
Integer.toBinaryString(10); // "1010"
Integer.toHexString(255); // "ff"Integer arithmetic: division, modulo, overflow
Integer division truncates toward zero (drops the fraction); % is the remainder, and its sign follows the left operand (the dividend):
java
7 / 2; // 3 (not 3.5 — integer division)
-7 / 2; // -3 (truncates toward zero, not -4)
7 % 3; // 1
-7 % 3; // -1 (sign matches the dividend, -7)
7 % -3; // 1
1 / 0; // ArithmeticException: / by zero (integer only)Exam trap
int/long math overflows silently — it wraps around with no error:
java
int max = Integer.MAX_VALUE; // 2_147_483_647
max + 1; // -2147483648 (Integer.MIN_VALUE) — wraps!
Math.abs(Integer.MIN_VALUE); // still MIN_VALUE — no positive counterpartUse the Math.*Exact methods to fail fast instead of wrapping:
java
Math.addExact(Integer.MAX_VALUE, 1); // throws ArithmeticExceptionMath you should know: abs, max, min, pow (returns double), sqrt, ceil, floor, and round. Math.round returns a long for a double argument and rounds half up toward positive infinity:
java
Math.round(2.5); // 3
Math.round(-2.5); // -2 (half rounds toward +∞, so -2.5 → -2)
Math.floorDiv(-7, 2);// -4 (rounds toward -∞, unlike / )
Math.floorMod(-7, 2);// 1 (sign follows the divisor, unlike % )Gotcha
Floating-point division does not throw on divide-by-zero. 1.0 / 0 is Infinity, -1.0 / 0 is -Infinity, and 0.0 / 0.0 is NaN. NaN is not equal to anything — even itself (Double.NaN == Double.NaN is false); test with Double.isNaN(x).
BigInteger & BigDecimal
When long overflows or you need exact decimals (money!), reach for the arbitrary-precision types in java.math. Both are immutable — every operation returns a new object.
java
BigInteger huge = BigInteger.valueOf(2).pow(100); // 1267650600228229401496703205376
BigDecimal price = new BigDecimal("0.10")
.add(new BigDecimal("0.20")); // exactly 0.30
0.10 + 0.20; // 0.30000000000000004 (double!)Exam trap
Use the String BigDecimal constructor, not the double one — new BigDecimal(0.1) captures the binary approximation (0.1000000000000000055…), while new BigDecimal("0.1") is exact.
Exam trap
BigDecimal.equals compares value and scale, so different scales are unequal. Use compareTo for numeric equality:
java
new BigDecimal("1.0").equals(new BigDecimal("1.00")); // false — scales 1 vs 2
new BigDecimal("1.0").compareTo(new BigDecimal("1.00")); // 0 — equal valueGotcha
divide throws ArithmeticException when the result is a non-terminating decimal — you must supply a scale and a RoundingMode: a.divide(b, 2, RoundingMode.HALF_UP).
SDET note
For money and any business assertion, compare with BigDecimal (via compareTo) and never with double — floating-point drift makes tests flaky and wrong by cents. When a test must round, state the RoundingMode explicitly so the expected value is unambiguous.
Key Takeaways
- Each primitive has an immutable wrapper; prefer
valueOfovernew. Autoboxing is automatic, but unboxingnullis an NPE. - The
Integercache (−128…127) makes==trueonly in range; compare wrapper values with.equals(). If either operand is primitive, the wrapper unboxes and==compares values. parseInt→ primitive,valueOf→ wrapper; bad input →NumberFormatException. Format withString.format/radix helpers.- Integer division truncates toward zero;
%sign follows the dividend;intmath overflows silently (useMath.addExact); integer/0throws, but float/0givesInfinity/NaN. Math.roundreturnslongand rounds half toward +∞;floorDiv/floorModdiffer from//%for negatives.- Use
BigInteger/BigDecimalfor range/exactness; buildBigDecimalfrom aString, and compare withcompareTo(notequals, which is scale-sensitive).
Lesson Quiz
What happens at runtime?
Integer i = null; int x = i;
Which comparisons are TRUE? (select all) (select all that apply)
Integer a = 127, b = 127; Integer c = 128, d = 128; Integer e = 1000;
What is the value of -7 % 3 ?
What does this print?
int max = Integer.MAX_VALUE; System.out.println(max + 1);
What is Math.round(-2.5) ?
What is the result of 7 / 2 ?
Which throws an exception?
What does Integer.parseInt("4.0") do?
What does this evaluate to?
new BigDecimal("1.0").equals(new BigDecimal("1.00"))Which expression gives EXACTLY 0.3?
Next: Boolean & Conditions. Run the matching code in labs/src/main/java/com/jse21/m01_values/Numeric.java.