Java


This is a reference page for Java, designed to be kept open alongside the Java Tutorial. Use it to look up syntax, concepts, and comparisons while you work through the hands-on exercises.

New to Java? Start with the interactive tutorial first — it teaches these concepts through practice with immediate feedback. This page is a reference, not a teaching resource.

Basics

Entry Point and Syntax

Java forces everything into a class. There are no free functions. The entry point is a static method called main — the JVM looks for it by name:

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello, world!");
    }
}

Every word in the signature has a purpose:

Keyword Why
public The JVM must be able to call it from outside the class
static No instance of the class is created before main runs
void Returns nothing; use System.exit() for exit codes
String[] args Command-line arguments, like C++’s argv

Quick mapping from Python and C++:

Feature Python C++ Java
Entry point if __name__ == "__main__": int main() (free function) public static void main(String[] args) (class method)
Typing Dynamic (x = 42) Static (int x = 42;) Static (int x = 42;)
Memory GC + reference counting Manual (new/delete) or RAII GC (generational)
Free functions Yes Yes No — everything lives in a class
Multiple inheritance Yes (MRO) Yes No — single class inheritance + interfaces
// Variables — declare type like C++
int count = 10;
double pi = 3.14159;
String name = "Alice";     // String is a class, not a primitive
boolean done = false;      // not 'bool' (C++) or True/False (Python)

// Printing
System.out.println("Count: " + count);

// Arrays — fixed size, .length is a field (no parentheses)
int[] scores = {90, 85, 92};
System.out.println(scores.length);  // 3 — NOT .length() or len()

// Enhanced for — like Python's "for x in list"
for (int s : scores) {
    System.out.println(s);
}

Size inconsistency: Arrays use .length (field). Strings use .length() (method). Collections use .size() (method). This is a well-known Java wart.

The Dual Type System: Primitives and Wrappers

Java has 8 primitive types that live on the stack (like C++ value types), and corresponding wrapper classes that live on the heap:

Primitive Size Default Wrapper
byte 8-bit 0 Byte
short 16-bit 0 Short
int 32-bit 0 Integer
long 64-bit 0L Long
float 32-bit 0.0f Float
double 64-bit 0.0 Double
char 16-bit '\u0000' Character
boolean 1-bit false Boolean

Why wrappers exist: Java generics only work with objects, not primitives. You cannot write ArrayList<int> — you must write ArrayList<Integer>.

Autoboxing is the automatic conversion between primitive and wrapper:

ArrayList<Integer> numbers = new ArrayList<>();
numbers.add(42);              // autoboxing: int → Integer
int first = numbers.get(0);   // unboxing: Integer → int

Autoboxing Traps

Trap 1 — Null unboxing causes NullPointerException:

Integer count = null;
int n = count;    // NullPointerException! Can't unbox null.

Trap 2 — Boxing in loops is slow:

// BAD — creates a new Integer object on every iteration
Integer sum = 0;
for (int i = 0; i < 1_000_000; i++) {
    sum += i;  // unbox sum, add i, box result — every iteration!
}

// GOOD — use primitive type for accumulation
int sum = 0;
for (int i = 0; i < 1_000_000; i++) {
    sum += i;  // pure arithmetic, no boxing
}

The Identity Trap: == vs .equals()

⚠ False Friend: In Python, == compares values. In Java, == on objects compares identity (are these the exact same object in memory?), not value equality.

String c = new String("hello");
String d = new String("hello");
System.out.println(c == d);       // false — different objects in memory
System.out.println(c.equals(d));  // true  — same characters

String literals appear to work with == because Java interns them into a shared pool:

String a = "hello";
String b = "hello";
System.out.println(a == b);  // true — but only because both point to the interned literal!

Do not rely on this. Always use .equals() for string comparison.

The Integer cache trap: Java caches Integer objects for values −128 to 127, making == accidentally work for small numbers:

Integer x = 127;
Integer y = 127;
System.out.println(x == y);     // true (cached — same object)

Integer p = 128;
Integer q = 128;
System.out.println(p == q);     // false (not cached — different objects)
System.out.println(p.equals(q)); // true (always use .equals())

The golden rule:

  • Use == for primitives (int, double, boolean, char)
  • Use .equals() for everything else (objects, strings, wrapper types)

Object-Oriented Programming

Classes and Encapsulation

A Java class bundles private fields with public methods that control access. Unlike Python (where self.balance is always accessible) and C++ (where you control access at the class level), Java enforces encapsulation at compile time.

public class BankAccount {
    private String owner;    // private — only accessible within this class
    private double balance;

    public BankAccount(String owner, double initialBalance) {
        this.owner = owner;          // 'this' disambiguates field from parameter
        this.balance = initialBalance;
    }

    public void deposit(double amount) {
        if (amount > 0) {            // validation — callers can't bypass this
            balance += amount;
        }
    }

    public boolean withdraw(double amount) {
        if (amount > 0 && balance >= amount) {
            balance -= amount;
            return true;
        }
        return false;               // returns false instead of allowing overdraft
    }

    public double getBalance() { return balance; }
    public String getOwner()   { return owner; }

    // Called automatically by System.out.println(account) — like Python's __str__
    public String toString() {
        return "BankAccount[owner=" + owner + ", balance=" + balance + "]";
    }
}

Access Modifiers

Java has four access levels. The default (no keyword) is different from C++:

Modifier Class Package Subclass World
private
(none) = package-private
protected
public

⚠ False Friend from C++: In C++, the default access in a class is private. In Java, the default is package-private — accessible to any class in the same package. Always be explicit.

In UML class diagrams: - means private, + means public, # means protected, ~ means package-private.

Information Hiding

Encapsulation (using private fields) is a mechanism. Information hiding is a design principle.

A module hides its secrets — design decisions that are likely to change. When a secret is properly hidden, changing that decision modifies exactly one class. When a secret leaks, a single change cascades across many classes.

Secret to Hide Example Why
Data representation int[] vs ArrayList vs database Storage format may change
Algorithm Bubble sort vs quicksort Optimization may change
Business rules Grading thresholds, capacity limits Policy may change
Output format CSV vs JSON vs text Reporting needs may change
External dependency Which API or library to call Vendor may change

The Getter/Setter Fallacy

Fields can be private and yet still leak design decisions:

// Fully encapsulated — but leaking the "ISBN is an int" decision
class Book {
    private int isbn;
    public int getIsbn() { return isbn; }
    public void setIsbn(int isbn) { this.isbn = isbn; }
}

When the spec changes to support international ISBNs with hyphens (String), every caller of getIsbn() breaks. The module is encapsulated but hides nothing.

Better design — expose behavior, not data:

// Hides the representation; callers depend on behavior only
class GradeReport {
    private ArrayList<Integer> scores;  // hidden

    public String getLetterGrade(int score) { ... }  // hides the grading policy
    public double getAverage()             { ... }  // hides the data representation
    public String formatReport()           { ... }  // hides the output format
}

Test for information hiding: For each design decision, ask: “If this changes, how many classes must I edit?” If the answer is more than one, the secret has leaked.

Interfaces: Design by Contract

An interface defines what a class can do, without specifying how. Java’s philosophy:

Program to an interface, not an implementation.

// Defining an interface — method signatures only
public interface Shape {
    double getArea();
    double getPerimeter();
    String describe();
}

// Implementing an interface — must provide ALL methods
public class Circle implements Shape {
    private double radius;

    public Circle(double radius) { this.radius = radius; }

    public double getArea()      { return Math.PI * radius * radius; }
    public double getPerimeter() { return 2 * Math.PI * radius; }
    public String describe()     { return "Circle(r=" + radius + ")"; }
}

Declare variables as the interface type so you can swap implementations without changing calling code:

Shape s = new Circle(5.0);    // interface type on the left
Shape r = new Rectangle(3, 4);
// s and r can be used interchangeably anywhere Shape is expected

Compared to C++ and Python:

  C++ Python Java
Mechanism Pure virtual functions / abstract class Duck typing (no enforcement) interface keyword, compiler-enforced
Multiple inheritance Yes (virtual base classes) Yes (MRO) A class can implement multiple interfaces
Default methods No No Java 8+: default methods can have implementations

Inheritance and Polymorphism

Java supports single class inheritance with abstract classes for sharing both state and behavior:

// Abstract class — cannot be instantiated, may have concrete fields and methods
public abstract class Vehicle {
    private String make;
    private int year;

    public Vehicle(String make, int year) {  // abstract classes have constructors
        this.make = make;
        this.year = year;
    }

    public String getMake() { return make; }
    public int getYear()    { return year; }

    // Subclasses MUST implement these
    public abstract String describe();
    public abstract String startEngine();
}

public class Car extends Vehicle {
    private int numDoors;

    public Car(String make, int year, int numDoors) {
        super(make, year);  // MUST call parent constructor first — like C++ initializer lists
        this.numDoors = numDoors;
    }

    @Override  // optional but recommended — compiler verifies you're actually overriding
    public String describe() {
        return getYear() + " " + getMake() + " Car (" + numDoors + " doors)";
    }

    @Override
    public String startEngine() { return "Vroom!"; }
}

Polymorphism — a parent reference can point to any subclass:

Vehicle[] fleet = {
    new Car("Toyota", 2024, 4),
    new Motorcycle("Harley", 2023, true),
};

for (Vehicle v : fleet) {
    System.out.println(v.describe());  // calls Car.describe() or Motorcycle.describe()
    //                                    based on the actual runtime type — dynamic dispatch
}

Key differences from C++:

  • Java methods are virtual by default — no virtual keyword needed
  • @Override annotation is optional but the compiler validates it catches typos
  • super(args) must be the first statement in a constructor (C++ uses initializer lists)

When to use interface vs abstract class:

  Interface Abstract Class
Methods Abstract (+ default in Java 8+) Abstract AND concrete
Fields Only static final constants Instance fields allowed
Constructor No Yes
Inheritance implements (multiple OK) extends (single only)
Use when… Unrelated classes share behavior Related classes share state + behavior

Generics

Generics: Not C++ Templates

Java generics look like C++ templates but work completely differently:

Feature C++ Templates Java Generics
Mechanism Code generation (monomorphization) Type erasure (single shared implementation)
Runtime type info Yes — vector<int>vector<string> No — List<String> = List<Integer> at runtime
Primitive types Yes — vector<int> works No — must use List<Integer>
new T() Yes No — type is unknown at runtime
// A generic class — T is a type parameter
public class Box<T> {
    private T item;

    public Box(T item) { this.item = item; }
    public T getItem()  { return item; }
}

// The compiler ensures type safety — no casts needed
Box<String> nameBox = new Box<>("Alice");
String name = nameBox.getItem();  // compiler knows it's String

Box<Integer> numBox = new Box<>(42);
int num = numBox.getItem();       // unboxing Integer → int

Generic methods declare their own type parameters:

// <X, Y> before the return type — method's own type parameters
public static <X, Y> Pair<Y, X> swap(Pair<X, Y> pair) {
    return new Pair<>(pair.getSecond(), pair.getFirst());
}

Bounded type parameters — restrict what types are allowed:

// T must implement Comparable<T> — like C++20 concepts
public static <T extends Comparable<T>> T findMax(T a, T b) {
    return a.compareTo(b) >= 0 ? a : b;
}

Type Erasure

When Java 5 added generics (2004), billions of lines of pre-generics code already existed. To maintain binary compatibility, generic types are erased after compilation:

// What you write:
List<String> names = new ArrayList<>();
String first = names.get(0);

// What the compiler generates (roughly):
List names = new ArrayList();
String first = (String) names.get(0);  // cast inserted by compiler

Consequences:

  • ArrayList<int> is illegal — use ArrayList<Integer> instead
  • new T() is illegal — type is unknown at runtime
  • if (list instanceof List<String>) is illegal — generic type is erased

Collections Framework

Choosing the Right Collection

Java Collections are organized by interfaces. Declare variables as the interface type:

«interface» Collection «interface» List «interface» Set «interface» Map «resizable array=""» ArrayList «doubly-linked» LinkedList «unordered, fast» HashSet «sorted» TreeSet «unordered, fast» HashMap «sorted by="" key=""» TreeMap

Predict each output. Then explain why Line A and Line B differ — what does each operator actually check?

Integer x = 127;
Integer y = 127;
System.out.println(x == y);   // true

Integer p = 128;
Integer q = 128;
System.out.println(p == q);   // false

The only change is 127 → 128. What mechanism in the JVM causes this flip, and why is this dangerous in production code?

Integer count = null;
int n = count;  // what happens here?

Describe exactly what the JVM does on the second line and what error results.

// Version A
Integer sum = 0;
for (int i = 0; i < 1_000_000; i++) {
    sum += i;
}

// Version B
int sum = 0;
for (int i = 0; i < 1_000_000; i++) {
    sum += i;
}

Both produce the same final value. Analyze what the JVM does differently in Version A on every iteration. Which version should you use?

public class BankAccount {
    public String owner;
    public double balance;
    ...
}

The fields are public. Explain what specific harm this causes compared to making them private with a withdraw() method that validates before mutating.

class GradeReport {
    private ArrayList<Integer> scores;

    public ArrayList<Integer> getScores() { return scores; }
}

The field is private. A colleague says “information hiding is achieved.” Are they right? What would break if you later switch scores to int[]?

public interface Shape {
    double getArea();
    double getPerimeter();
}

public class Circle implements Shape {
    private double radius;
    public Circle(double r) { this.radius = r; }

    @Override
    public double getArea() { return Math.PI * radius * radius; }

    @Override
    public double getPerimeter() { return 2 * Math.PI * radius; }
}

Shape s = new Circle(5.0);
System.out.println(s.getArea());

Explain what @Override buys you here. Give an example of the specific bug it prevents.

abstract class Vehicle {
    private String make;
    public Vehicle(String make) { this.make = make; }
    public String getMake() { return make; }
    public abstract String describe();
}

class Car extends Vehicle {
    public Car(String make) {
        super(make);       // ← this line
    }
    @Override
    public String describe() { return getMake() + " Car"; }
}

Why must super(make) be the first statement in Car’s constructor? What would happen if it were moved after getMake()?

Vehicle[] fleet = {
    new Car("Toyota", 2024),
    new Motorcycle("Harley", 2023),
};
for (Vehicle v : fleet) {
    System.out.println(v.describe());
}

The reference type is Vehicle, but describe() is abstract. Describe precisely what happens at compile time and at runtime when v.describe() is called.

public class Pair<A, B> {
    private A first;
    private B second;

    public static <X, Y> Pair<Y, X> swap(Pair<X, Y> p) {
        return new Pair<>(p.getSecond(), p.getFirst());
    }
}

Why does swap declare its own type parameters <X, Y> instead of reusing the class’s <A, B>?

Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 95);
int grade = scores.get("Bob");  // Bob not in map

This compiles without warnings. Predict what happens at runtime and explain the chain of events.

public class SafeCalculator {
    public double divide(int a, int b) throws CalculatorException {
        if (b == 0) throw new CalculatorException("Division by zero");
        return (double) a / b;
    }
}

class CalculatorException extends Exception {
    public CalculatorException(String msg) { super(msg); }
}

CalculatorException extends Exception, not RuntimeException. What concrete difference does this choice produce for callers of divide()?

// Version A
public double average(ArrayList<Integer> scores) { ... }

// Version B
public double average(List<Integer> scores) { ... }

Both compile. Analyze the practical difference when other code calls average().

Set<String> submitted = new HashSet<>();
List<String> roster = new ArrayList<>();

submitted.add("Alice");
submitted.add("Alice");  // duplicate

roster.add("Alice");
roster.add("Alice");     // duplicate

System.out.println(submitted.size());  // ?
System.out.println(roster.size());     // ?

Predict each output and explain what design principle drives the difference between HashSet and ArrayList.

public class Course implements Enrollable {
    private ArrayList<Student> students = new ArrayList<>();

    public boolean isEnrolled(String name) {
        for (Student s : students) {
            if (s.getName().equals(name)) return true;
        }
        return false;
    }
}

This works correctly. Evaluate it for performance and explain what would change if you swapped ArrayList<Student> for HashMap<String, Student>.

Java — Write the Code

You are given a scenario or design problem. Write Java code that solves it. Questions target Apply, Evaluate, and Create levels — not just syntax recall.

[Apply] Two String variables input and stored may or may not point to the same object. Write a boolean expression that checks whether they contain the same characters, guaranteed to be correct regardless of how they were created.

[Evaluate + Apply] A HashMap lookup is crashing in production with a NullPointerException. The code is:

Map<String, Integer> grades = loadFromDB();
int g = grades.get(studentId);

Fix it in one line, defaulting to 0 for missing students.

[Create] Design a BankAccount class that:

  • Stores owner (String) and balance (double) — neither directly accessible from outside
  • Provides a constructor, getOwner(), getBalance()
  • deposit(double amount) — only accepts positive amounts
  • withdraw(double amount) — returns false if insufficient funds; true on success
  • toString() returns "BankAccount[owner=Alice, balance=100.0]"

[Evaluate + Create] This class has a design problem. Identify it, then rewrite GradeReport so that changing the grading thresholds (A ≥ 90, B ≥ 80…) requires editing only one method:

class GradeReport {
    private List<Integer> scores;
    public List<Integer> getScores() { return scores; }
}

// In main:
for (int s : report.getScores()) {
    if (s >= 90) System.out.println("A");
    else if (s >= 80) System.out.println("B");
}

[Apply] Define a Drawable interface with one method: String draw(). Then write a Square class that implements it — draw() returns "Square(side=5.0)".

[Create] Design an abstract class Animal with:

  • A private String name and a constructor
  • A concrete getName() getter
  • An abstract method makeSound() that returns a String

Then write a Dog subclass that calls the parent constructor and returns "Woof!" from makeSound().

[Apply] Write a generic class Box<T> that holds one item of any type. Include a constructor, a getItem() method, and a setItem() method.

[Apply + Analyze] Write a generic static method findMax that takes two arguments of any type and returns the larger one. The type must be constrained to types that can be compared.

[Create] Write a WordCounter class that takes a String[] in its constructor and provides:

  • int getCount(String word) — returns 0 for unknown words, no NPE
  • int getUniqueCount() — number of distinct words

Use the most appropriate collection for each responsibility.

[Apply] Define a checked exception EnrollmentException and a Course.enroll(Student s) method that throws it when the course is full (capacity exceeded). Write both the class definition and the calling code that handles the exception.

[Apply] Write a try-catch-finally block that: opens a file (throws IOException), reads its content, and prints an error if it fails. The finally block should always print "Done.".

[Evaluate + Apply] You need to store course enrollments. Two options:

  • List<Student> with a manual duplicate check in enroll()
  • LinkedHashSet<Student> that handles duplicates automatically

Implement enroll(Student s) using each approach, then state which is preferable and why.

[Apply] Write a method printAll(List<String> items) that iterates the list with an enhanced for-loop, printing each item. Then call it with an ArrayList<String> and a LinkedList<String>. Explain why both calls compile.

[Create] You are building a course registration system. Design the method signature (interface method + throws) for an Enrollable interface that:

  • Adds a student (can fail if course is full or duplicate)
  • Removes a student by name (returns whether it succeeded)
  • Checks enrollment by name
  • Returns a list of enrolled student names

[Evaluate + Create] A teammate wrote this accumulator. Find the performance issue, explain the root cause, and write the corrected version.

Integer total = 0;
for (String word : words) {
    if (word.length() > 5) total++;
}

Java Concepts Quiz

Test your deeper understanding of Java's type system, OOP model, and design idioms. Covers false friends with C++/Python, encapsulation vs information hiding, generics, collections, and exception handling. Includes Parsons problems, technique-selection questions, and spaced interleaving across all concepts.

Predict the output of this code:

String a = new String("hello");
String b = new String("hello");
System.out.println(a == b);
System.out.println(a.equals(b));
Correct Answer:

What does this code print?

Integer x = 200;
Integer y = 200;
System.out.println(x == y);
System.out.println(x.equals(y));
Correct Answer:

What happens at runtime when this code executes?

Integer count = null;
int n = count;
Correct Answer:

A teammate writes this in a hot loop:

Integer sum = 0;
for (int i = 0; i < 1_000_000; i++) {
    sum += i;
}

You suggest changing Integer sum to int sum. What is the precise reason?

Correct Answer:

In Java, what is the default access level when no access modifier is specified on a field or method?

Correct Answer:

A GradeReport class has private ArrayList<Integer> scores and exposes it like this:

public ArrayList<Integer> getScores() { return scores; }

All fields are private. Has information hiding (Parnas 1972) been achieved?

Correct Answer:

Dog, Car, and Printer each need a serialize() method. They share no fields or common behavior. Which Java construct is the right fit?

Correct Answer:

Why is ArrayList<int> illegal in Java, while vector<int> is valid in C++?

Correct Answer:

This code does not compile. Why?

public boolean isStringList(List<?> list) {
    return list instanceof List<String>;
}
Correct Answer:

[Technique Selection] Match each task to the best collection:

  • A: Track which students have submitted homework (no duplicates, O(1) lookup by name)
  • B: Map each student ID (int) to their final grade (double)
  • C: Maintain an ordered history of grade submissions (newest at the end, access by index)
Correct Answer:

What is the bug in this code?

Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 95);
int grade = scores.get("Bob");
Correct Answer:

Which exceptions does the Java compiler force you to explicitly catch or declare with throws?

Correct Answer:

In a Java constructor, where must super(args) appear, and what happens if you omit it?

Correct Answer:

Given:

Vehicle v = new Car("Toyota", 2024, 4);
System.out.println(v.describe());

Vehicle is abstract with abstract describe(). Car overrides it. Which describe() runs?

Correct Answer:

Arrange the lines to implement a generic Pair<A, B> class with a static swap method that returns a Pair<B, A>.

Drag lines into the solution area in the correct order (some items are distractors that should not be used):
↓ Drop here ↓
Correct order:
public class Pair<A, B> {
private A first;
private B second;
public Pair(A first, B second) { this.first = first; this.second = second; }
public A getFirst() { return first; }
public B getSecond() { return second; }
public static <X, Y> Pair<Y, X> swap(Pair<X, Y> p) {
return new Pair<>(p.getSecond(), p.getFirst());
}
}

Arrange the lines to define a Shape interface and a Circle class that correctly implements it.

Drag lines into the solution area in the correct order (some items are distractors that should not be used):
↓ Drop here ↓
Correct order:
public interface Shape {
double getArea();
double getPerimeter();
}
public class Circle implements Shape {
private double radius;
public Circle(double radius) { this.radius = radius; }
@Override
public double getArea() { return Math.PI * radius * radius; }
@Override
public double getPerimeter() { return 2 * Math.PI * radius; }
}

Arrange the lines to define a checked exception, declare it in a method, and handle it in calling code.

Drag lines into the solution area in the correct order (some items are distractors that should not be used):
↓ Drop here ↓
Correct order:
class InsufficientFundsException extends Exception {
public InsufficientFundsException(String msg) { super(msg); }
}
public boolean withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) { throw new InsufficientFundsException("Insufficient funds"); }
balance -= amount;
return true;
}
try {
account.withdraw(1000.0);
} catch (InsufficientFundsException e) {
System.out.println("Error: " + e.getMessage());
}

[Interleaving: Interfaces + Collections + OOP] You’re designing a Course class. It needs:

  • A way for other classes to enroll/drop students without knowing the internal storage
  • Fast O(1) lookup for isEnrolled(String name)
  • No duplicate enrollments

Which two decisions together best achieve these goals?

Correct Answer: