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
classisprivate. 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
virtualkeyword needed @Overrideannotation is optional but the compiler validates it catches typossuper(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 — useArrayList<Integer>insteadnew T()is illegal — type is unknown at runtimeif (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:
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) andbalance(double) — neither directly accessible from outside - Provides a constructor,
getOwner(),getBalance() deposit(double amount)— only accepts positive amountswithdraw(double amount)— returnsfalseif insufficient funds;trueon successtoString()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 nameand a constructor - A concrete
getName()getter - An abstract method
makeSound()that returns aString
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 NPEint 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 inenroll()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));
What does this code print?
Integer x = 200;
Integer y = 200;
System.out.println(x == y);
System.out.println(x.equals(y));
What happens at runtime when this code executes?
Integer count = null;
int n = count;
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?
In Java, what is the default access level when no access modifier is specified on a field or method?
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?
Dog, Car, and Printer each need a serialize() method. They share no fields or common behavior. Which Java construct is the right fit?
Why is ArrayList<int> illegal in Java, while vector<int> is valid in C++?
This code does not compile. Why?
public boolean isStringList(List<?> list) {
return list instanceof List<String>;
}
[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)
What is the bug in this code?
Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 95);
int grade = scores.get("Bob");
Which exceptions does the Java compiler force you to explicitly catch or declare with throws?
In a Java constructor, where must super(args) appear, and what happens if you omit it?
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?
Arrange the lines to implement a generic Pair<A, B> class with a static swap method that returns a Pair<B, A>.
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.
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.
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?