Core Java · Lesson 7
Types of Class
Understanding Different Types of Classes
Java provides several class types beyond regular classes, each designed for specific purposes. Static classes, abstract classes, inner classes, anonymous classes, and records (Java 16+) solve different problems in different contexts. Knowing when and how to use each type makes your code more organized and expressive. Most code uses regular classes, but understanding these variations helps you write more elegant, efficient solutions.
Static Nested Classes: Classes Within Classes
A static nested class is defined inside another class but behaves like a regular class with an important difference: it can be accessed without creating an instance of the outer class. Use static nested classes to logically group related classes and improve code organization. Static nested classes can access only static members of the outer class, not instance variables.
public class OuterClass {
private static String outerStaticMessage = "Shared message";
private String outerInstanceMessage = "Instance-specific";
// Static nested class
public static class StaticNestedClass {
public void display() {
System.out.println("Static nested class");
System.out.println(outerStaticMessage);
// System.out.println(outerInstanceMessage); // ERROR: can't access instance members
}
}
public static void main(String[] args) {
// Create static nested class without outer instance
OuterClass.StaticNestedClass nested = new OuterClass.StaticNestedClass();
nested.display();
}
}
Inner Classes: Nested with Access to Outer Scope
An inner class (non-static nested class) has full access to the outer class's members, including private ones. Inner classes are tightly coupled to an outer class instance — you must create an outer instance first. Use inner classes when the class is tightly related to the outer class and needs access to its private data.
public class OuterClass {
private String outerMessage = "Hello from outer";
public class InnerClass {
private String innerMessage = "Hello from inner";
public void display() {
System.out.println(innerMessage);
System.out.println(outerMessage); // Can access outer's private members
}
}
public static void main(String[] args) {
// Must create outer instance first
OuterClass outer = new OuterClass();
OuterClass.InnerClass inner = outer.new InnerClass();
inner.display();
}
}
Anonymous Classes: Quick Implementations on the Fly
An anonymous class is a class without a name, defined and instantiated in a single expression. Use anonymous classes for simple, one-off implementations, especially when implementing interfaces or extending classes with minimal customization. They're common in event listeners, callbacks, and testing.
public interface Greeting {
void greet(String name);
}
public class AnonymousClassExample {
public static void main(String[] args) {
// Anonymous class implementing interface
Greeting formalGreeting = new Greeting() {
@Override
public void greet(String name) {
System.out.println("Good day, " + name);
}
};
formalGreeting.greet("Alice"); // Good day, Alice
// Another anonymous class with different behavior
Greeting casualGreeting = new Greeting() {
@Override
public void greet(String name) {
System.out.println("Hey " + name + "!");
}
};
casualGreeting.greet("Bob"); // Hey Bob!
}
}
Records: Immutable Data Carriers (Java 16+)
Records are a concise way to create immutable classes that primarily carry data. A record automatically generates constructor, getters, equals(), hashCode(), and toString() methods. Use records for simple data holders like DTOs (Data Transfer Objects), configuration objects, or return types. Records are final and cannot be extended.
// Record: compact syntax for immutable data class
public record Person(String name, int age) {
// Automatically generates:
// - constructor: Person(String name, int age)
// - getters: name() and age()
// - equals(), hashCode(), toString()
}
public class RecordExample {
public static void main(String[] args) {
Person person = new Person("Charlie", 28);
System.out.println(person.name()); // Charlie
System.out.println(person.age()); // 28
System.out.println(person); // Person[name=Charlie, age=28]
Person person2 = new Person("Charlie", 28);
System.out.println(person.equals(person2)); // true (auto-generated equals)
}
}
Enum Classes: Representing Fixed Sets of Constants
An enum (enumeration) is a special class representing a fixed set of constants. Enums are type-safe — the compiler catches invalid values. Use enums for fixed sets of related values like status codes, directions, colors, or days of the week. Enums can have attributes and methods.
public enum Color {
RED("FF0000"),
GREEN("00FF00"),
BLUE("0000FF"),
YELLOW("FFFF00");
private final String hexCode;
// Constructor
Color(String hexCode) {
this.hexCode = hexCode;
}
public String getHexCode() {
return hexCode;
}
}
public class EnumExample {
public static void main(String[] args) {
Color selectedColor = Color.RED;
System.out.println("Color: " + selectedColor);
System.out.println("Hex code: " + selectedColor.getHexCode());
// Iterate all values
for (Color color : Color.values()) {
System.out.println(color + " -> " + color.getHexCode());
}
// Type-safe: Color color = Color.PURPLE; // ERROR: PURPLE doesn't exist
}
}
| Class Type | Definition | Access | Use Case | Example |
|---|---|---|---|---|
| Regular Class | Top-level, standalone | Via instance | General purpose | class Dog { } |
| Static Nested | Inside another class, static | Without outer instance | Logical grouping | OuterClass.NestedClass nested = new OuterClass.NestedClass() |
| Inner Class | Inside another class, non-static | Requires outer instance | Tight coupling needed | Outer o = new Outer(); Outer.Inner i = o.new Inner() |
| Anonymous Class | Unnamed, one-off | Inline instantiation | Listeners, callbacks | new Interface() { @Override ... } |
| Record | Immutable data carrier | Via auto-generated accessors | DTOs, data holders | record Point(int x, int y) |
| Enum | Fixed set of constants | Static constants | Status, fixed options | enum Status { ACTIVE, INACTIVE } |
Common Mistake: Using inner classes when static nested classes would work. Inner classes create unnecessary dependencies on outer class instances and can cause memory leaks if the outer instance is long-lived. If your nested class doesn't need outer instance access, make it
static. This decouples the classes and improves garbage collection.
Best Practices for Different Class Types
- Prefer regular classes for most code; they're straightforward and flexible
- Use static nested classes to organize related classes without creating instance dependencies
- Use inner classes only when necessary — they increase coupling and memory usage
- Replace anonymous classes with lambdas (Java 8+) for simpler, cleaner code
- Use records for data holders: they're type-safe and reduce boilerplate
- Use enums for fixed sets of values: they're safer than string/int constants
- Avoid nested classes getting too deep: 2-3 levels is usually the limit for readability
Real-World Example: Game Development with Mixed Class Types
A game engine uses an outer class Game managing the game state, an inner class Player tightly coupled to game logic with direct access to game variables, an enum Direction for movement (UP, DOWN, LEFT, RIGHT), and records Position(int x, int y) and GameEvent(String type, long timestamp) for immutable data. Anonymous classes handle button click listeners in the UI. A static nested class GameConfig holds configuration constants like difficulty levels. By choosing the right class type for each responsibility, the codebase is organized, type-safe, and maintainable.
Quiz Check