Beginner Reading time: ~10 min

Syntax and Basic Types

Primitive types, wrapper classes, variables, scope, lifecycle and type casting

Syntax and Basic Types

The building blocks of Java: primitive types, wrapper classes, variables, scope, lifecycle, and type casting.

1. Definition

What is it?

Java is a strongly typed language, meaning every variable must have its type declared (or inferred by the compiler via var). The language fundamentals are built on primitive types and their corresponding wrapper classes.

Why does it exist?

  • Type safety: the compiler catches type errors at compile-time
  • Performance: primitive types are stored directly on the stack, no heap allocation needed
  • Platform independence: type sizes are fixed across all platforms (e.g., int is always 32 bits)

Where does it fit?

This is the most fundamental layer of Java — everything else (OOP, Collections, Streams) builds on top of it.


2. Core Concepts

2.1 Primitive Types

Java has exactly 8 primitive types:

Type Size Default Range Usage
byte 8 bit 0 -128 to 127 Memory-efficient arrays, I/O
short 16 bit 0 -32,768 to 32,767 Rarely used
int 32 bit 0 -2ÂłÂč to 2ÂłÂč-1 Default integer type
long 64 bit 0L -2⁶³ to 2⁶³-1 Large numbers, timestamps
float 32 bit 0.0f ~±3.4×10³⁞ Rarely used, prefer double
double 64 bit 0.0d ~±1.7×10³⁰⁞ Default decimal type
char 16 bit '\u0000' 0 to 65,535 Unicode character
boolean ~1 bit* false true/false Logical value

*The boolean size is implementation-dependent: the JVM typically stores it as an int (32 bits) on the stack.

Key rules:

  • Primitives cannot be null
  • Primitives cannot be used in generics (List<int> ❌, List<Integer> ✅)
  • Integer literals default to int, decimal literals default to double

2.2 Wrapper Classes

Every primitive has a corresponding wrapper class:

Primitive Wrapper Cache Range
byte Byte -128 to 127 (full range)
short Short -128 to 127
int Integer -128 to 127
long Long -128 to 127
float Float no cache
double Double no cache
char Character 0 to 127
boolean Boolean TRUE / FALSE (both)

Autoboxing/Unboxing:

Integer x = 42;        // autoboxing: int → Integer
int y = x;             // unboxing: Integer → int
Integer z = null;
int w = z;             // ⚠ NullPointerException!

Integer Cache (-128 to 127):

Integer a = 127;
Integer b = 127;
System.out.println(a == b);    // true  (from cache)

Integer c = 128;
Integer d = 128;
System.out.println(c == d);    // false (new objects!)
System.out.println(c.equals(d)); // true  (value comparison)

2.3 Variables, Scope, Lifecycle

Variable types:

Type Declared Default Lifetime
Local variable Inside method ❌ None (must initialize) End of block
Instance variable In class (non-static) ✅ Yes Until object is GC'd
Class variable static ✅ Yes Program termination
Parameter Method signature Provided by caller End of method

Scope rules:

public void example() {
    int x = 10;            // scope: entire method
    if (x > 5) {
        int y = 20;        // scope: this if block only
        System.out.println(x + y); // OK
    }
    // System.out.println(y); // ❌ Compile error! y not visible here
}

2.4 Type Casting

Implicit (widening) — automatic, lossless:

byte → short → int → long → float → double
char → int

Explicit (narrowing) — manual, may lose data:

double d = 9.78;
int i = (int) d;    // i = 9 (truncation, NOT rounding!)

int big = 130;
byte b = (byte) big; // b = -126 (overflow!)

Special rules:

  • Conversion between float and double may lose precision
  • int → float conversion can also lose precision (float has only 24-bit mantissa)

3. Practical Usage

When to use what?

Situation Recommendation Why
General integers int Default, most common
Financial calculations BigDecimal ⚠ NOT double! Precision errors!
Numbers in collections Integer (wrapper) Generics requirement
Large dataset, memory matters primitive arrays Wrapper has 16+ bytes/element overhead
Boolean flag boolean primitive Simple, no null concerns
Nullable boolean (from DB) Boolean wrapper null = "we don't know"

When NOT to use?

  • ❌ double for financial calculations — 0.1 + 0.2 != 0.3
  • ❌ == for wrapper comparison — breaks outside cache range
  • ❌ float when not necessary — double is the default, float is rare

4. Code Examples

Basic Example — Types and Conversion

public class TypeBasics {
    public static void main(String[] args) {
        // Primitives
        int count = 42;
        double price = 19.99;
        boolean active = true;
        char grade = 'A';

        // Widening (automatic)
        long bigCount = count;          // int → long, OK
        double bigPrice = count;        // int → double, OK

        // Narrowing (explicit cast required)
        int rounded = (int) price;      // 19 (truncation!)
        byte small = (byte) count;      // 42 (fits)

        // Wrapper autoboxing
        Integer boxed = count;          // autoboxing
        int unboxed = boxed;            // unboxing

        // var (Java 10+) — type inferred by compiler
        var name = "Alice";             // String
        var number = 42;                // int (NOT Integer!)
        var list = List.of(1, 2, 3);    // List<Integer>
    }
}

Advanced Example — Financial Calculation Pitfall

public class MoneyPitfall {
    public static void main(String[] args) {
        // ❌ WRONG: double precision error
        double price = 0.1;
        double quantity = 3;
        System.out.println(price * quantity);
        // Output: 0.30000000000000004 (!)

        // ✅ CORRECT: use BigDecimal
        BigDecimal bdPrice = new BigDecimal("0.1");
        BigDecimal bdQuantity = new BigDecimal("3");
        System.out.println(bdPrice.multiply(bdQuantity));
        // Output: 0.3
    }
}

Common Pitfall — Integer Cache

public class IntegerCacheTrap {
    public static void main(String[] args) {
        // Within cache (-128..127): WORKS
        Integer a = 100;
        Integer b = 100;
        System.out.println(a == b);      // true ✅

        // Outside cache: DOES NOT WORK
        Integer c = 200;
        Integer d = 200;
        System.out.println(c == d);      // false ❌
        System.out.println(c.equals(d)); // true ✅

        // ⚠ Null unboxing
        Integer nullable = null;
        // int value = nullable;  // NullPointerException!
    }
}

5. Trade-offs

Aspect Primitive Wrapper
⚡ Performance Fast (stack, no allocation) Slower (heap allocation, GC)
đŸ’Ÿ Memory Minimal (e.g., int = 4 bytes) Significant (e.g., Integer = ~16 bytes)
🔧 Maintainability Simple but can't be null Requires null handling but more expressive
🔄 Flexibility Can't be used in generics Works with collections and generics

Concrete numbers:

  • int[1000] → ~4 KB memory
  • Integer[1000] → ~20 KB memory (5x more!)
  • One million Integer autoboxing operations → significant GC pressure

6. Common Mistakes

1. Using `==` with wrappers

// ❌ Wrong
if (value1 == value2) { ... }  // reference comparison!

// ✅ Correct
if (value1.equals(value2)) { ... }
// or
if (Objects.equals(value1, value2)) { ... }  // null-safe

2. Using `double` for financial calculations

// ❌ Never do this
double balance = 1000.00;
balance -= 999.99;
// balance = 0.010000000000047748 (NOT 0.01!)

// ✅ Use BigDecimal
BigDecimal balance = new BigDecimal("1000.00");
balance = balance.subtract(new BigDecimal("999.99"));
// balance = 0.01 ✅

3. Null unboxing

// ❌ NullPointerException thrown
Integer count = getCountFromDb(); // may return null
int total = count + 1;            // NPE!

// ✅ Null check
int total = (count != null) ? count + 1 : 1;
// or use Optional
int total = Optional.ofNullable(count).orElse(0) + 1;

4. Narrowing cast overflow

// ❌ Silently produces wrong result
int big = 128;
byte b = (byte) big;  // b = -128 (overflow, no exception!)

// ✅ Check before casting
if (big >= Byte.MIN_VALUE && big <= Byte.MAX_VALUE) {
    byte b = (byte) big;
} else {
    throw new ArithmeticException("Value out of byte range");
}
// or Java 8+:
byte b = Math.toIntExact(big); // ArithmeticException on overflow

7. Senior-level Insights

When does primitive vs wrapper choice matter?

  • Hot path (tight loops, real-time processing): always use primitives. Autoboxing in a million-iteration loop causes measurable slowdown.
  • Domain models: use wrapper if the value is nullable (e.g., Integer age — "we don't know the age" vs int age — "age is 0").
  • API design: Optional<Integer> is better than a nullable Integer.

Integer Cache Customization

The JVM's -XX:AutoBoxCacheMax=<size> flag can extend the Integer cache range. In high-frequency systems (e.g., IDs ranging 1–10,000), this can improve performance.

`var` usage — when yes, when no?

// ✅ Good: right-hand side makes type obvious
var users = new ArrayList<User>();
var response = httpClient.send(request, BodyHandlers.ofString());

// ❌ Bad: type is not obvious
var result = process();          // what type is this?
var x = flag ? getA() : getB(); // which type?

JIT Optimization

The JIT compiler can often eliminate autoboxing via escape analysis when the wrapper object doesn't "escape" the method. But don't rely on this — always measure in production code!

Floating Point Comparison

// ❌ Never use == with double
double a = 0.1 + 0.2;
if (a == 0.3) { ... }  // false!

// ✅ Epsilon-based comparison
private static final double EPSILON = 1e-10;
if (Math.abs(a - 0.3) < EPSILON) { ... }  // true

8. Glossary

Term Definition
Autoboxing Automatic conversion of a primitive type to its wrapper class (int → Integer)
Unboxing Automatic conversion of a wrapper class to its primitive (Integer → int)
Widening Implicit type conversion from a smaller type to a larger one (lossless)
Narrowing Explicit type conversion from a larger type to a smaller one (may lose data)
Integer Cache The JVM caches Integer objects for values -128 to 127 via Integer.valueOf()
Stack Memory area where local primitive variables and references are stored
String Pool → See: String Handling
Escape Analysis JIT technique: determines if an object "escapes" its method scope

9. Cheatsheet

  • 🔱 8 primitive types: byte, short, int, long, float, double, char, boolean
  • 📩 Wrapper = primitive + null + generics compatibility (but more expensive)
  • ⚠ == on wrappers compares references, use .equals()!
  • 💰 For money, ALWAYS use BigDecimal, never double
  • 🎯 Integer cache: -128 to 127 (extendable via JVM flag)
  • 📏 Widening is automatic, narrowing requires explicit cast
  • đŸš« Null unboxing = NPE — always check for null!
  • 🔍 var (Java 10+) — only when type is obvious from context
  • ⚡ Hot path = primitive, domain model = wrapper if nullable needed
  • 🧼 float ≈ 7 decimal digits, double ≈ 15 decimal digits precision

🎼 Games

8 questions