Data Types
Java Data Type Categories
Java groups data into two families: primitive types (hold simple values) and reference types (hold references to objects on the heap). Unlike Python’s dynamic typing, Java is statically typed: every variable has a declared type, checked at compile time. If you’ve used Go, that idea will feel familiar.
Quick map (what to reach for)
- Whole numbers: int (default) → long for timestamps/big ranges
- Real numbers: double (default) → float if you really need to save memory
- True/false: boolean
- Single code unit: char (UTF-16 code unit)
- Text: String
- Fixed-size collections: arrays (int[], String[])
- Resizable collections: List, Map, Set (from java.util)
- Exact money/precision math: BigDecimal (reference type)
Primitive Data Types
Primitives are not objects and have no methods. They’re stored directly in variables (no reference indirection) and are fast.
| Type | Bits (fixed) | Range / Values | Default (for fields) | Typical use |
|---|---|---|---|---|
| boolean | JVM-defined (1 bit conceptually) | true / false | false | flags, conditions |
| char | 16 | U+0000 … U+FFFF (UTF-16 code unit) | U+0000 | single code unit (be careful with full Unicode) |
| byte | 8 | −128 … 127 | 0 | raw bytes, I/O, buffers |
| short | 16 | −32 768 … 32 767 | 0 | niche numeric needs |
| int | 32 | −2^31 … 2^31−1 | 0 | default whole number |
| long | 64 | −2^63 … 2^63−1 | 0L | timestamps, counters |
| float | 32 | IEEE-754 single precision | 0.0f | big numeric arrays where memory matters |
| double | 64 | IEEE-754 double precision | 0.0d | default real number |
Note: Boolean (uppercase B) is the wrapper class for primitive boolean.
Literals and readability
- Decimal, hex (0x2A), binary (0b101010), octal (052).
- Underscores for readability: 1_000_000.
- Suffixes: L for long (42L), f/F for float (3.0f). Double is default for floating-point.
- Char literals: 'A', 'λ'.
Numeric promotions and casting (core rules)
- Widening (safe, automatic): byte → short → int → long → float → double; and char → int → …
- Narrowing (explicit, may lose data): (byte) someInt, (int) someLong, etc.
- Binary numeric promotion: in + − * / %, byte/short/char become int; mixing with long/float/double promotes to the wider type.
- Integer division truncates: 5 / 2 is 2. Use 5 / 2.0 for 2.5.
- Overflow on integers wraps (no exception). For checked arithmetic, use Math.addExact, subtractExact, multiplyExact.
boolean (from easy to advanced)
- Values: true or false. Default for fields: false.
- Operators: && (and), || (or), ! (not). They short-circuit (right side may not run).
- No implicit numeric conversion (you can’t add true to a number).
- Advanced: in streams, filter predicates are boolean-valued lambdas.
boolean ok = (3 < 5) && !false; // true
if (ok) { System.out.println("go"); }
char (UTF-16 code unit)
- 16-bit unsigned code unit. Some Unicode characters need two char values (surrogate pair).
- Arithmetic with char promotes to int.
- For full Unicode correctness (emoji, many scripts), iterate by code points, not char.
// Basic char
char letter = 'A';
int code = letter; // 65
// Safer iteration over a String's Unicode code points
String s = "A😊Z";
s.codePoints().forEach(cp -> System.out.println(cp)); // prints code points as ints
byte, short, int, long (integers)
- All are signed, two’s complement.
- Division by zero throws ArithmeticException.
- Remainder has the same sign as the dividend.
- Bitwise ops: & (and), | (or), ^ (xor), ~ (not), << (left shift), >> (signed right shift), >>> (unsigned right shift).
- Unsigned helpers:
* Byte.toUnsignedInt, Short.toUnsignedInt * Integer.compareUnsigned, divideUnsigned, toUnsignedString * Long.compareUnsigned, divideUnsigned, toUnsignedString
// Literals and underscores
int mask = 0b1010_1100; // 172
long nanos = 12_345_678_901L;
// Shifts and masks
int flags = 0;
flags |= (1 << 2); // set bit 2
boolean hasBit2 = (flags & (1 << 2)) != 0;
// Checked arithmetic
int a = Integer.MAX_VALUE;
try {
int b = Math.addExact(a, 1); // throws ArithmeticException
} catch (ArithmeticException ex) {
System.out.println("overflow");
}
float, double (floating point)
- IEEE-754. Special values exist: NaN, positive/negative infinity, negative zero.
- Comparisons with NaN are always false (except Double.isNaN(x)).
- Decimal fractions like 0.1 are approximations in binary. For money, use BigDecimal.
- Rounding: Math.round, floor, ceil.
double x = 0.1 + 0.2; // 0.30000000000000004
System.out.println(x == 0.3); // false
System.out.println(Double.isNaN(Math.sqrt(-1))); // true
Wrapper classes and autoboxing
Wrappers make primitives usable where objects are required (collections, generics). Autoboxing/unboxing converts between them automatically.
Mapping: boolean↔Boolean, char↔Character, byte↔Byte, short↔Short, int↔Integer, long↔Long, float↔Float, double↔Double
Caveats:
- Unboxing null throws NullPointerException.
- Equality: use equals(...) for wrappers; == compares identity.
- Small values may be cached (commonly −128..127 for Integer/Long). Do not rely on ==.
Integer i = 128; // autoboxed
Integer j = 128; // a different object in general
System.out.println(i.equals(j)); // true
System.out.println(i == j); // often false
List<Integer> nums = new java.util.ArrayList<>();
nums.add(42); // autoboxed int → Integer
int v = nums.get(0); // unboxed
Reference (Non-Primitive) Types
These store references (addresses) to objects on the heap. All reference types ultimately inherit from java.lang.Object.
String (immutable, pooled)
- Immutable: operations create new strings.
- Literals may be interned (shared) by the VM.
- Compare content with equals(...). Use == only to test identity (same object).
- Efficient building: StringBuilder for many concatenations (StringBuffer is synchronized; rarely needed).
Useful APIs:
- length, charAt, substring, indexOf, startsWith, endsWith, replace, split, join, format, repeat
- bytes/encoding: getBytes(StandardCharsets.UTF_8)
String a = "Hello";
String b = "He" + "llo"; // often same interned object as a
System.out.println(a.equals(b)); // true
// Avoid repeated concatenation in loops; use StringBuilder
StringBuilder sb = new StringBuilder();
for (int k = 0; k < 3; k++) sb.append("Hi ");
System.out.println(sb.toString()); // "Hi Hi Hi "
Arrays (objects with length, fixed size)
- Homogeneous, zero-indexed, fixed length.
- Default initialization: numbers→0, boolean→false, references→null.
- Multidimensional arrays are arrays of arrays (jagged shapes allowed).
- Equality is by identity; use Arrays.equals / Arrays.deepEquals for content.
- Utilities in java.util.Arrays: sort, binarySearch, copyOf, fill, toString, deepToString
// 1D and 2D arrays
int[] a = {10, 20, 30};
System.out.println(a.length); // 3
int[][] grid = new int[2][];
grid[0] = new int[] {1, 2};
grid[1] = new int[] {3, 4, 5}; // jagged
// Content equality
int[] x = {1,2,3}, y = {1,2,3};
System.out.println(java.util.Arrays.equals(x, y)); // true
Classes, interfaces, and Object basics
- Your own types are classes and interfaces. Single inheritance for classes; multiple inheritance via interfaces.
- Key Object methods: toString, equals, hashCode.
- Contract: if you override equals, also override hashCode.
class Point {
final int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
@Override public boolean equals(Object o) {
if (!(o instanceof Point p)) return false;
return x == p.x && y == p.y;
}
@Override public int hashCode() { return java.util.Objects.hash(x, y); }
@Override public String toString() { return "Point(" + x + "," + y + ")"; }
}
Enums (type-safe constants)
- Define a fixed set of values with optional fields/behavior.
- Great for states, categories, days, etc.
enum Traffic { RED, YELLOW, GREEN }
Traffic t = Traffic.RED;
switch (t) {
case RED -> System.out.println("stop");
case GREEN -> System.out.println("go");
default -> System.out.println("wait");
}
Records (concise immutable data carriers, Java 16+)
- Auto-generated equals, hashCode, toString; fields are final.
public record User(String name, int age) {}
User u = new User("Ada", 37);
System.out.println(u.name()); // accessor
Optional (avoid null for “maybe” values)
- Wrap a possibly-present value; encourage explicit handling.
java.util.Optional<String> maybe = java.util.Optional.of("hi");
String result = maybe.map(s -> s + "!").orElse("empty");
BigInteger and BigDecimal (arbitrary precision)
- Immutable big number classes.
- For money, construct BigDecimal from String to avoid binary rounding surprises.
import java.math.BigDecimal;
import java.math.MathContext;
BigDecimal price = new BigDecimal("19.99");
BigDecimal tax = price.multiply(new BigDecimal("0.0775"), MathContext.DECIMAL64);
BigDecimal total = price.add(tax);
Type inference (var)
- Since Java 10: var for local variables with obvious initializers.
- Still statically typed; the compiler infers the concrete type.
- Not allowed for fields, parameters, or returns.
var list = new java.util.ArrayList<String>(); // inferred as ArrayList<String>
list.add("BFH");
Equality, identity, and null
- Primitives compare by value with ==.
- Objects: == checks identity (same reference), equals checks content.
- null means “no object”. Dereferencing null throws NullPointerException.
- For safe use, validate inputs, consider Optional, and leverage Objects.requireNonNull.
Control-flow with booleans (short-circuiting)
String s = null;
if (s != null && s.length() > 0) {
// length() evaluated only if s != null
}
Bits and bytes (advanced)
- Right shift >> keeps sign; >>> fills with zeros.
- Masking, packing, and unpacking are common when handling protocols/files.
// Pack RGBA bytes into an int
int r=10, g=20, b=30, a=255;
int packed = (r & 0xFF) << 24 | (g & 0xFF) << 16 | (b & 0xFF) << 8 | (a & 0xFF);
// Extract
int r2 = (packed >>> 24) & 0xFF;
Exceptions to watch for
- ArithmeticException: integer divide by zero, checked arithmetic methods
- ArrayIndexOutOfBoundsException: bad index
- NullPointerException: dereferencing null
- NumberFormatException: parsing a bad number string
Parsing and formatting numbers
int n = Integer.parseInt("42");
double d = Double.parseDouble("3.14");
String s1 = String.format("n=%d, d=%.2f", n, d);
Putting it all together (tiny program)
import java.util.*;
import java.math.*;
public class TypesTour {
public static void main(String[] args) {
// primitives and promotion
int n = 5;
double avg = (n + 2) / 2.0; // 3.5
boolean ok = n > 3;
// strings
String msg = "Count: " + n + ", ok=" + ok;
// arrays
int[] data = new int[3]; // {0,0,0}
data[0] = 42;
// collections (need wrappers)
List<Integer> numbers = new ArrayList<>();
numbers.add(10); numbers.add(20);
// equality examples
String a = "Hi", b = new String("Hi");
System.out.println(a.equals(b)); // true
// floating point caveat
System.out.println(0.1 + 0.2); // slightly off 0.3
// money with BigDecimal
BigDecimal price = new BigDecimal("19.99");
BigDecimal tax = price.multiply(new BigDecimal("0.08"));
System.out.println("Total: " + price.add(tax));
System.out.println(msg + " data0=" + data[0] + " size=" + numbers.size());
}
}
Cheat sheet: when to pick what
- int: default whole numbers. long for time in milliseconds or large counts.
- double: default real numbers; float if memory matters and small precision loss is ok.
- boolean: flags and conditions.
- char: single UTF-16 code unit (be careful with full Unicode).
- String: text; use equals for comparison; use StringBuilder for many appends.
- Arrays for fixed size; List/Map/Set for flexible collections.
- BigDecimal for money and precision-critical calculations.