Java Academy

Master Java Development

Menu

Core Java

Servlets

JDBC

Microservices

Build Tools

APIs

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 TypeDefinitionAccessUse CaseExample
Regular ClassTop-level, standaloneVia instanceGeneral purposeclass Dog { }
Static NestedInside another class, staticWithout outer instanceLogical groupingOuterClass.NestedClass nested = new OuterClass.NestedClass()
Inner ClassInside another class, non-staticRequires outer instanceTight coupling neededOuter o = new Outer(); Outer.Inner i = o.new Inner()
Anonymous ClassUnnamed, one-offInline instantiationListeners, callbacksnew Interface() { @Override ... }
RecordImmutable data carrierVia auto-generated accessorsDTOs, data holdersrecord Point(int x, int y)
EnumFixed set of constantsStatic constantsStatus, fixed optionsenum 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

Quick Knowledge Test

Loading quiz questions...