☕ Java

Java Class & Methods — Declaration, Fields, Constructors & Best Practices

Everything you need to know about Java Classes and their Methods — class declaration, fields, constructors, instance & static members, getter/setter, the 'this' keyword, inner classes, anti-patterns, and real-world production code examples.

📅

Last Updated

March 2026

⏱️

Read Time

26 min

🎯

Level

Beginner

🏷️

Chapter

14 of 35

What is a Class in Java?

A class in Java is the most fundamental building block of object-oriented programming. It is a blueprint or template that defines: (1) the state an object holds — represented by fields (variables), and (2) the behaviour an object can perform — represented by methods. A class itself occupies no memory for data — it is only when you create an object from the class that memory is allocated.

In Java, everything lives inside a class. There are no standalone variables or functions at the top level of a file. Every field, every method, every constructor must be declared inside a class body. This is what makes Java a purely object-oriented language at the structural level — the class is the mandatory container for all code.

Real-world analogy: think of a class as an architectural blueprint for a house. The blueprint defines how many rooms there are (fields), what each room is for, and what actions can be performed in the house (methods). Using the blueprint, you can construct many actual houses (objects) — each one is a separate physical structure with its own rooms, but all follow the same blueprint design.

Class Declaration & Syntax — Anatomy of a Java Class

A Java class declaration follows a specific structure. Understanding each part is essential before writing any class.

📌
Full Syntax

[accessModifier] [modifiers] class ClassName [extends ParentClass] [implements Interface1, Interface2] { // fields // constructors // methods // inner classes } Minimum valid class: class Dog { }

📋
Class Naming Rules

• Must start with a letter, $ or _ (not a digit) • PascalCase convention: every word capitalised — BankAccount, OrderService, HttpRequest • Should be a NOUN or noun phrase — represents a thing, not an action • Match the filename exactly (case-sensitive) for public classes • Avoid: Manager, Helper, Utils as standalone names — too vague

⚙️
Class Modifiers

Access modifiers: public (any package), default/package-private (same package only). Non-access modifiers: • final — class cannot be subclassed (e.g., String, Integer) • abstract — class cannot be instantiated; may have abstract methods • strictfp — floating-point calculations use strict IEEE 754 Top-level classes cannot be private or protected.

☕ JavaClassDeclarationDemo.java
// File: ClassDeclarationDemo.java
// One public class per file — filename must match

// Basic class with all structural elements labelled
public class BankAccount {

    // ── FIELDS (State) ──────────────────────────────
    private static int totalAccounts = 0;      // static field — shared
    private final  String accountNumber;       // instance field — immutable
    private        String ownerName;           // instance field — mutable
    private        double balance;             // instance field — mutable

    // ── CONSTANTS ────────────────────────────────────
    public static final double MIN_BALANCE = 500.0;

    // ── CONSTRUCTOR ──────────────────────────────────
    public BankAccount(String accountNumber, String ownerName, double initialDeposit) {
        this.accountNumber = accountNumber;
        this.ownerName     = ownerName;
        this.balance       = initialDeposit;
        totalAccounts++;   // Update class-level counter
    }

    // ── INSTANCE METHODS (Behaviour) ─────────────────
    public void deposit(double amount) {
        if (amount <= 0) throw new IllegalArgumentException("Amount must be positive");
        this.balance += amount;
    }

    public boolean withdraw(double amount) {
        if (amount <= 0 || amount > balance) return false;
        this.balance -= amount;
        return true;
    }

    // ── GETTERS ──────────────────────────────────────
    public String getAccountNumber() { return accountNumber; }
    public String getOwnerName()     { return ownerName; }
    public double getBalance()       { return balance; }

    // ── STATIC METHOD (Class-level) ───────────────────
    public static int getTotalAccounts() { return totalAccounts; }

    // ── toString ─────────────────────────────────────
    @Override
    public String toString() {
        return String.format("BankAccount[%s | %s | ₹%.2f]",
                            accountNumber, ownerName, balance);
    }
}

// Second class in same file — must NOT be public
class AccountDemo {
    public static void main(String[] args) {
        BankAccount acc1 = new BankAccount("ACC001", "Ravi",  10000);
        BankAccount acc2 = new BankAccount("ACC002", "Priya", 25000);

        acc1.deposit(5000);
        acc1.withdraw(2000);

        System.out.println(acc1);  // BankAccount[ACC001 | Ravi | ₹13000.00]
        System.out.println(acc2);  // BankAccount[ACC002 | Priya | ₹25000.00]
        System.out.println("Total accounts: " + BankAccount.getTotalAccounts()); // 2
    }
}

Class vs Object — Blueprint vs Instance

The distinction between a class and an object is the cornerstone of object-oriented programming. Confusing the two leads to some of the most common Java mistakes — especially the infamous 'non-static field cannot be referenced from a static context' compile error.

AspectClassObject
DefinitionBlueprint / TemplateInstance of the blueprint
MemoryClass metadata loaded once in Method AreaEach object allocated separately on the Heap
Creation keywordclass keywordnew keyword
How manyOne class definitionAs many objects as needed
Field storageNo data storage (only structure definition)Each object stores its own field values
Static membersBelong to the class — one shared copyAccessed through class name, not object
Instance membersDefined in classEach object has its own copy
Real-world analogyArchitectural blueprint of a houseThe actual house built from the blueprint
Another analogyCookie cutter mouldActual cookies cut from the mould
☕ JavaClassVsObject.java
public class Student {
    // Fields defined in CLASS — each OBJECT gets its own copy
    String name;
    int    rollNumber;
    double cgpa;

    // Constructor
    public Student(String name, int rollNumber, double cgpa) {
        this.name       = name;
        this.rollNumber = rollNumber;
        this.cgpa       = cgpa;
    }

    public void introduce() {
        System.out.printf("Hi, I am %s (Roll: %d), CGPA: %.1f%n",
                          name, rollNumber, cgpa);
    }
}

public class ClassVsObjectDemo {
    public static void main(String[] args) {

        // ✅ Creating OBJECTS from the Student CLASS
        Student s1 = new Student("Ravi",   101, 8.5);
        Student s2 = new Student("Priya",  102, 9.2);
        Student s3 = new Student("Arjun",  103, 7.8);

        // Each object has its OWN separate copy of fields
        s1.introduce(); // Hi, I am Ravi (Roll: 101), CGPA: 8.5
        s2.introduce(); // Hi, I am Priya (Roll: 102), CGPA: 9.2
        s3.introduce(); // Hi, I am Arjun (Roll: 103), CGPA: 7.8

        // Modifying one object does NOT affect others
        s1.cgpa = 8.9;
        System.out.println(s1.cgpa); // 8.9 — only s1 changed
        System.out.println(s2.cgpa); // 9.2 — s2 unchanged

        // null reference — variable declared but no object created
        Student s4 = null;
        // s4.introduce(); // ❌ NullPointerException — no object behind s4
    }
}

Fields — Instance Variables & Static Variables

Fields (also called member variables or class variables) represent the state of a class. Every object gets its own copy of instance fields, while static fields are shared across all objects of the class. Field design is one of the most important decisions in class design — the wrong choice here leads to fragile, unmaintainable objects.

🔷
Instance Fields

Declared without 'static'. Each object gets its own separate copy. Hold the unique state for that particular object. Example: in a Student class, 'String name' and 'double cgpa' are instance fields — every student has their own name and cgpa. Accessed through an object reference: student.name. Best practice: always private — expose through getter/setter.

🔶
Static Fields

Declared with 'static'. Only ONE copy exists per class — shared by ALL objects. Changes to a static field through any object are visible to all other objects. Used for: counters (totalStudents), constants (MAX_RETRIES), shared configuration. Accessed via class name: Student.totalStudents. Best practice: static mutable fields are risky in multi-threaded programs — prefer static final constants.

🔒
final Fields

'final' means the field can only be assigned ONCE — either at declaration or in the constructor. For primitives: the value is immutable. For objects: the reference cannot be reassigned (but the object's internal state can still be mutated). 'private final String id;' set in constructor = effectively immutable ID. Static final = a constant: 'public static final double PI = 3.14159;'. Immutable fields make objects thread-safe and easier to reason about.

📊
Field Initialisation Order

Fields are initialised in this order: (1) Static fields and static initialiser blocks (when the class is first loaded — once only). (2) Instance fields set to their default values (0, false, null). (3) Instance field declarations with initialiser expressions. (4) Constructor body executes. This order matters when one field's default value depends on another — always initialise in constructor for clarity.

☕ JavaFieldsDemo.java
public class Employee {

    // ── STATIC FIELDS ─────────────────────────────────
    private static int     employeeCount = 0;           // Shared counter
    public  static final String COMPANY  = "TechCorp";  // Constant

    // ── INSTANCE FIELDS ───────────────────────────────
    private final  int    employeeId;    // Immutable after construction
    private        String name;
    private        String department;
    private        double salary;
    private        boolean active;

    // ── STATIC INITIALISER BLOCK ──────────────────────
    // Runs ONCE when class is first loaded
    static {
        System.out.println("Employee class loaded for: " + COMPANY);
    }

    // ── INSTANCE INITIALISER BLOCK ────────────────────
    // Runs every time an object is created, BEFORE constructor
    {
        this.active = true;     // Default — every new employee is active
        employeeCount++;        // Increment shared counter
        this.employeeId = employeeCount; // Assign unique ID
    }

    // Constructor
    public Employee(String name, String department, double salary) {
        this.name       = name;
        this.department = department;
        this.salary     = salary;
    }

    // Static method — accesses only static field
    public static int getEmployeeCount() { return employeeCount; }

    // Instance methods
    public int    getEmployeeId()  { return employeeId; }
    public String getName()        { return name; }
    public String getDepartment()  { return department; }
    public double getSalary()      { return salary; }
    public boolean isActive()      { return active; }

    public void setSalary(double salary) {
        if (salary < 0) throw new IllegalArgumentException("Salary cannot be negative");
        this.salary = salary;
    }

    @Override
    public String toString() {
        return String.format("[%d] %s | %s | ₹%.0f | %s",
               employeeId, name, department, salary, active ? "Active" : "Inactive");
    }
}

// Usage:
// Employee e1 = new Employee("Ravi",  "Engineering", 85000);
// Employee e2 = new Employee("Priya", "Design",      72000);
// System.out.println(e1); // [1] Ravi | Engineering | ₹85000 | Active
// System.out.println(e2); // [2] Priya | Design | ₹72000 | Active
// System.out.println(Employee.getEmployeeCount()); // 2
// System.out.println(Employee.COMPANY);            // TechCorp

Constructors — Initialising Objects Correctly

A constructor is a special block of code that is automatically called when an object is created using the new keyword. Its sole purpose is to initialise the object's state — setting field values, validating inputs, and setting up any initial conditions. A properly written constructor guarantees that an object is always in a valid, consistent state from the moment of creation.

📌
Constructor Rules

1. Name MUST exactly match the class name (case-sensitive) 2. NO return type — not even void 3. Can have any access modifier (public, private, protected, default) 4. Can be overloaded (multiple constructors with different params) 5. If no constructor is written, Java provides a default no-arg constructor 6. If ANY constructor is written, the default is NOT provided automatically

🔗
Constructor Chaining — this()

'this(args)' inside a constructor calls ANOTHER constructor in the same class. Must be the FIRST statement in the constructor. Used to avoid code duplication when multiple constructors share setup logic. Example: a no-arg constructor can call a full constructor with default values: 'this("Unknown", 0)'. This is the DRY principle applied to constructors.

⬆️
super() — Parent Constructor

'super(args)' calls the PARENT class constructor. Must be the FIRST statement in a subclass constructor. If not explicitly written, Java automatically inserts 'super()' (no-arg parent constructor). If the parent has no no-arg constructor, the subclass MUST explicitly call super(args) — otherwise compile error. Used in inheritance to initialise the parent portion of the object.

🏭
private Constructor — Control Creation

A private constructor prevents external instantiation. Used in: (1) Singleton pattern — only one instance ever exists. (2) Utility class — all static methods, no instantiation needed. (3) Factory method pattern — object creation controlled through a static factory method that validates inputs before constructing. Example: 'LocalDate.of(2026, 3, 20)' is a factory method that validates the date before constructing.

☕ JavaConstructorDemo.java
public class Product {

    private final String productId;
    private       String name;
    private       double price;
    private       int    stock;
    private       String category;

    // ── CONSTRUCTOR 1: Full constructor ───────────────
    public Product(String productId, String name, double price,
                   int stock, String category) {
        // Validation in constructor — object can never be invalid
        if (productId == null || productId.isBlank())
            throw new IllegalArgumentException("Product ID required");
        if (price < 0)
            throw new IllegalArgumentException("Price cannot be negative");
        if (stock < 0)
            throw new IllegalArgumentException("Stock cannot be negative");

        this.productId = productId;
        this.name      = name;
        this.price     = price;
        this.stock     = stock;
        this.category  = category;
    }

    // ── CONSTRUCTOR 2: Without category (chaining to constructor 1) ──
    public Product(String productId, String name, double price, int stock) {
        this(productId, name, price, stock, "General"); // ✅ Constructor chaining
    }

    // ── CONSTRUCTOR 3: Minimum — just ID and name ─────
    public Product(String productId, String name) {
        this(productId, name, 0.0, 0); // ✅ Chains to constructor 2
    }

    // Getters
    public String getProductId() { return productId; }
    public String getName()      { return name; }
    public double getPrice()     { return price; }
    public int    getStock()     { return stock; }
    public String getCategory()  { return category; }

    // Business methods
    public boolean isAvailable()          { return stock > 0; }
    public void    restock(int quantity)  { this.stock += quantity; }
    public boolean sell(int quantity) {
        if (quantity > stock) return false;
        this.stock -= quantity;
        return true;
    }

    @Override
    public String toString() {
        return String.format("%s: %s | ₹%.2f | Stock: %d | %s",
               productId, name, price, stock, category);
    }
}

// Usage — three ways to create a Product:
// Product p1 = new Product("P001", "Laptop", 65000.0, 10, "Electronics");
// Product p2 = new Product("P002", "Mouse",  499.0, 50);  // category = General
// Product p3 = new Product("P003", "Keyboard");           // price=0, stock=0

Instance Methods in a Class — Behaviour of Objects

An instance method defines the behaviour of an object. It operates on the object's own state (instance fields) and can read or modify them. Instance methods represent the verbs of a class — what objects of that type can do. Every instance method implicitly receives a this reference — a pointer to the current object — allowing it to access the calling object's fields and other methods.

🔧
Command Methods (Mutators)

Instance methods that CHANGE the object's state. Examples: deposit(double amount), withdraw(double amount), addItem(Item item), deactivate(). They typically return void or a status value (boolean for success/failure). Should validate inputs before modifying state. Never expose raw field mutation — always wrap it in a method with business rules.

🔍
Query Methods (Accessors)

Instance methods that READ and return information about the object's state without modifying it. Examples: getBalance(), isActive(), calculateTax(), toString(). Should have NO side effects — calling a query method multiple times should give the same result. Boolean queries should be named with 'is', 'has', or 'can': isAvailable(), hasPermission(), canWithdraw(amount).

🤝
Collaboration Methods

Instance methods that coordinate between objects — one object calls methods on another. Example: orderService.placeOrder(cart, payment) — the OrderService object collaborates with Cart and Payment objects. These methods are the 'glue' of object-oriented systems. They should delegate to other objects' methods rather than reaching into their internals (Law of Demeter).

☕ JavaInstanceMethodsDemo.java
public class ShoppingCart {

    private final String cartId;
    private final java.util.List<CartItem> items;
    private       String couponCode;
    private       double discountPercent;

    public ShoppingCart(String cartId) {
        this.cartId           = cartId;
        this.items            = new java.util.ArrayList<>();
        this.discountPercent  = 0.0;
    }

    // ── COMMAND METHODS (Mutators) ────────────────────

    public void addItem(CartItem item) {
        if (item == null) throw new IllegalArgumentException("Item cannot be null");
        // If item already exists, increase quantity
        items.stream()
             .filter(i -> i.getProductId().equals(item.getProductId()))
             .findFirst()
             .ifPresentOrElse(
                 i -> i.increaseQuantity(item.getQuantity()),
                 () -> items.add(item)
             );
    }

    public boolean removeItem(String productId) {
        return items.removeIf(i -> i.getProductId().equals(productId));
    }

    public void applyCoupon(String code, double discountPercent) {
        if (code == null || code.isBlank())
            throw new IllegalArgumentException("Coupon code required");
        if (discountPercent <= 0 || discountPercent > 100)
            throw new IllegalArgumentException("Invalid discount");
        this.couponCode       = code;
        this.discountPercent  = discountPercent;
    }

    public void clear() {
        items.clear();
        couponCode      = null;
        discountPercent = 0.0;
    }

    // ── QUERY METHODS (Accessors) ─────────────────────

    public boolean isEmpty()   { return items.isEmpty(); }
    public int     getItemCount() { return items.size(); }
    public String  getCartId() { return cartId; }

    public double getSubtotal() {
        return items.stream()
                    .mapToDouble(i -> i.getPrice() * i.getQuantity())
                    .sum();
    }

    public double getDiscountAmount() {
        return getSubtotal() * discountPercent / 100;
    }

    public double getTotal() {
        return getSubtotal() - getDiscountAmount();
    }

    public boolean hasCoupon() { return couponCode != null; }

    @Override
    public String toString() {
        return String.format("Cart[%s] Items: %d | Subtotal: ₹%.2f | " +
               "Discount: ₹%.2f | Total: ₹%.2f",
               cartId, items.size(), getSubtotal(),
               getDiscountAmount(), getTotal());
    }
}

Static Fields & Static Methods — Class-Level Members

The static keyword on a field or method makes it belong to the class itself rather than to any individual object. Static members exist independently of any instance — they are created when the class is loaded and exist for the lifetime of the application. Understanding when to use static — and when NOT to — is a key design skill.

📊
Static Fields — Shared State

One copy shared by ALL objects. Changes through any object are visible to all. Common uses: object counters (totalCreated++), shared configuration, caches, constants (static final). Risk: static mutable fields cause subtle bugs in multi-threaded environments and make testing harder (state persists between tests). Prefer static FINAL (constants) over mutable static fields.

🛠️
Static Methods — Class-Level Operations

No 'this' reference — no access to instance fields. Used for: pure utility functions (Math.sqrt), factory methods (LocalDate.of), helper operations that don't need object state. Cannot be overridden in subclasses (method hiding, not overriding). Can be called without creating an object — ClassName.method().

🧱
Static Initialiser Block

A 'static { ... }' block runs ONCE when the class is first loaded by the JVM — before any constructor or static method call. Used for: complex static field initialisation, loading configuration files, setting up static caches. Multiple static blocks run in the order they appear in the class. Cannot throw checked exceptions.

☕ JavaStaticMembersDemo.java
import java.util.HashMap;
import java.util.Map;

public class CurrencyConverter {

    // ── STATIC CONSTANT ───────────────────────────────
    public static final String BASE_CURRENCY = "INR";

    // ── STATIC MUTABLE FIELD (rate table) ─────────────
    private static final Map<String, Double> EXCHANGE_RATES = new HashMap<>();

    // ── STATIC INITIALISER BLOCK ──────────────────────
    // Runs once when the class is first loaded
    static {
        EXCHANGE_RATES.put("USD", 0.012);
        EXCHANGE_RATES.put("EUR", 0.011);
        EXCHANGE_RATES.put("GBP", 0.0095);
        EXCHANGE_RATES.put("JPY", 1.78);
        System.out.println("CurrencyConverter loaded — rates initialised");
    }

    // ── INSTANCE FIELDS ───────────────────────────────
    private final String fromCurrency;
    private       double amount;

    public CurrencyConverter(String fromCurrency, double amount) {
        this.fromCurrency = fromCurrency;
        this.amount       = amount;
    }

    // ── STATIC METHODS ────────────────────────────────

    // Factory method — validates currency before constructing
    public static CurrencyConverter of(String currency, double amount) {
        if (!EXCHANGE_RATES.containsKey(currency) && !currency.equals(BASE_CURRENCY))
            throw new IllegalArgumentException("Unsupported currency: " + currency);
        if (amount < 0)
            throw new IllegalArgumentException("Amount cannot be negative");
        return new CurrencyConverter(currency, amount);
    }

    // Static utility — no instance needed
    public static boolean isSupportedCurrency(String currency) {
        return EXCHANGE_RATES.containsKey(currency) || currency.equals(BASE_CURRENCY);
    }

    public static void updateRate(String currency, double rate) {
        if (rate <= 0) throw new IllegalArgumentException("Rate must be positive");
        EXCHANGE_RATES.put(currency, rate);
    }

    // ── INSTANCE METHOD ───────────────────────────────
    public double convertTo(String targetCurrency) {
        if (fromCurrency.equals(BASE_CURRENCY)) {
            return amount * EXCHANGE_RATES.getOrDefault(targetCurrency, 1.0);
        }
        // Convert to INR first, then to target
        double inrAmount = amount / EXCHANGE_RATES.getOrDefault(fromCurrency, 1.0);
        return inrAmount * EXCHANGE_RATES.getOrDefault(targetCurrency, 1.0);
    }
}

// Usage:
// CurrencyConverter.isSupportedCurrency("USD") → true
// CurrencyConverter conv = CurrencyConverter.of("INR", 83000);
// conv.convertTo("USD") → ~996.0

The 'this' Keyword — Referring to the Current Object

The this keyword in Java is a reference to the current object — the specific instance on which the method or constructor is currently executing. It is one of the most frequently used and most misunderstood keywords in Java, essential for writing clear class code.

1️⃣
Use 1 — Resolve Name Conflicts (Most Common)

'this.name = name;' — when a constructor or setter parameter has the same name as the instance field, 'this.fieldName' refers to the field and 'paramName' (without this) refers to the parameter. Without 'this', both refer to the parameter — the field is never set. This is the most common use of 'this' in Java code.

2️⃣
Use 2 — Constructor Chaining (this())

'this(args)' inside a constructor calls another constructor in the SAME class. Must be the VERY FIRST statement in the constructor — before any other code. Used to avoid duplicating initialisation logic across multiple constructors. The chain of constructors ultimately leads to the one that does all the real initialisation work.

3️⃣
Use 3 — Pass Current Object as Argument

'someMethod(this)' passes the current object as an argument to another method. Common in event handling: 'button.addListener(this)'. Also used in builder and fluent interface patterns: 'return this;' at the end of a setter returns the current object, enabling method chaining: 'builder.setName("X").setAge(25).build()'.

4️⃣
'this' Cannot Be Used in static Context

Static methods belong to the class, not to any object — there is NO current object. Therefore 'this' cannot be used inside a static method or static initialiser block. Attempting to do so is a compile error: 'non-static variable this cannot be referenced from a static context'. This is the same reason you cannot access instance fields from a static method.

☕ JavaThisKeywordDemo.java
public class Person {

    private String name;
    private int    age;
    private String city;

    // ── Use 1: Resolve field vs parameter name conflict ──
    public Person(String name, int age, String city) {
        this.name = name;  // 'this.name' = field; 'name' = parameter
        this.age  = age;
        this.city = city;
    }

    // ── Use 2: Constructor chaining with this() ──────────
    public Person(String name, int age) {
        this(name, age, "Unknown"); // Calls the 3-param constructor
    }

    public Person(String name) {
        this(name, 0); // Calls the 2-param constructor
    }

    // ── Use 3: Return 'this' for method chaining (Fluent API) ──
    public Person setName(String name) {
        this.name = name;
        return this; // Returns current object for chaining
    }

    public Person setAge(int age) {
        if (age < 0 || age > 150) throw new IllegalArgumentException("Invalid age");
        this.age = age;
        return this;
    }

    public Person setCity(String city) {
        this.city = city;
        return this;
    }

    // ── Use 3b: Pass 'this' as argument to another method ──
    public void register(PersonRegistry registry) {
        registry.add(this); // Passes current Person object
    }

    // Getters
    public String getName() { return name; }
    public int    getAge()  { return age; }
    public String getCity() { return city; }

    @Override
    public String toString() {
        return String.format("Person{name='%s', age=%d, city='%s'}",
                            name, age, city);
    }
}

// Usage — method chaining via 'return this':
// Person p = new Person("Ravi")
//     .setAge(28)
//     .setCity("Bengaluru");
// System.out.println(p);
// → Person{name='Ravi', age=28, city='Bengaluru'}

// Constructor chaining:
// Person p2 = new Person("Priya"); // name=Priya, age=0, city=Unknown
// Person p3 = new Person("Arjun", 25); // name=Arjun, age=25, city=Unknown

Getter & Setter Methods — Controlled Field Access

Getter and setter methods are the standard mechanism for providing controlled external access to private fields. They are the practical implementation of encapsulation: keep fields private, expose them through methods that can enforce validation, add logging, trigger events, or compute derived values.

📖
Getter — Read Access

Returns the value of a private field. Naming: 'get' + FieldName in PascalCase for most types: getName(), getBalance(), getCategory(). Boolean fields: 'is' + FieldName: isActive(), isVerified(), isPremium(). Getters should be simple and NEVER have side effects — they should only read and return, never modify state.

✏️
Setter — Write Access with Validation

Sets a private field value — the KEY advantage over public fields is built-in validation. Naming: 'set' + FieldName: setName(), setBalance(). Should validate before assigning: null checks, range checks, format checks. Can throw exceptions for invalid input. Can trigger side effects: update a timestamp, fire a change event, recalculate a derived value.

🔒
Read-Only Fields — Getter Without Setter

Some fields should be immutable after construction — provide a getter but NO setter. Example: userId set in constructor should never change. Making a field final + getter-only enforces immutability at the language level. This is safer than just 'forgetting' to write a setter — final prevents accidental reassignment even inside the class.

Computed Properties — Getter Without Stored Field

Not every getter needs a corresponding stored field. Some values can be COMPUTED on demand: 'getFullName() { return firstName + " " + lastName; }'. Or 'getAge() { return Period.between(birthDate, LocalDate.now()).getYears(); }'. Computed getters eliminate synchronisation issues — there is no stored age to become stale. Calculate from source data, not from a cached copy.

☕ JavaGetterSetterDemo.java
import java.time.LocalDate;
import java.time.Period;
import java.util.regex.Pattern;

public class UserProfile {

    private static final Pattern EMAIL_REGEX =
        Pattern.compile("^[\\w._%+\\-]+@[\\w.\\-]+\\.[a-zA-Z]{2,}$");

    // ── Read-only (no setter) ─────────────────────────
    private final String userId;

    // ── Regular fields with validation setters ────────
    private String    displayName;
    private String    email;
    private LocalDate birthDate;
    private String    phoneNumber;
    private boolean   emailVerified;

    public UserProfile(String userId, String displayName, String email) {
        this.userId  = userId;
        setDisplayName(displayName); // Reuse setter validation
        setEmail(email);             // Reuse setter validation
        this.emailVerified = false;
    }

    // ── GETTER: Read-only field ────────────────────────
    public String getUserId() { return userId; } // No setter

    // ── GETTER + SETTER with validation ───────────────
    public String getDisplayName() { return displayName; }
    public void setDisplayName(String displayName) {
        if (displayName == null || displayName.isBlank())
            throw new IllegalArgumentException("Display name cannot be blank");
        if (displayName.length() > 50)
            throw new IllegalArgumentException("Display name max 50 chars");
        this.displayName = displayName.trim();
    }

    public String getEmail() { return email; }
    public void setEmail(String email) {
        if (email == null || !EMAIL_REGEX.matcher(email).matches())
            throw new IllegalArgumentException("Invalid email: " + email);
        this.email        = email.toLowerCase();
        this.emailVerified = false; // Changing email resets verification
    }

    public LocalDate getBirthDate() { return birthDate; }
    public void setBirthDate(LocalDate birthDate) {
        if (birthDate != null && birthDate.isAfter(LocalDate.now()))
            throw new IllegalArgumentException("Birth date cannot be in the future");
        this.birthDate = birthDate;
    }

    // ── BOOLEAN GETTER: 'is' prefix ────────────────────
    public boolean isEmailVerified() { return emailVerified; }
    public void verifyEmail()        { this.emailVerified = true; } // Business method, not raw setter

    // ── COMPUTED GETTER — no stored field ─────────────
    public int getAge() {
        if (birthDate == null) return -1;
        return Period.between(birthDate, LocalDate.now()).getYears();
    }

    public boolean isAdult() {
        return getAge() >= 18;
    }

    @Override
    public String toString() {
        return String.format("UserProfile{id='%s', name='%s', email='%s', verified=%b}",
                            userId, displayName, email, emailVerified);
    }
}

All Method Types in a Class — A Complete Overview

A well-designed Java class typically contains several types of methods, each serving a distinct purpose. Understanding these roles helps you write classes that are cohesive, readable, and maintainable.

Method TypePurposeNaming ConventionExample
ConstructorInitialise object stateSame as class namepublic BankAccount(String id)
GetterRead a private field valueget + FieldName / is + FieldNamegetBalance() / isActive()
SetterValidate and write a private fieldset + FieldNamesetBalance(double amount)
Command / MutatorPerform an action, change stateVerb phrasedeposit(), withdraw(), close()
Query / AccessorReturn computed info, no state changeVerb or noun phrasecalculateInterest(), getTotal()
Factory (static)Create and return a new objectof(), from(), create()Product.of("P001", "Laptop")
Utility (static)Stateless helper computationVerb phraseStringUtils.isBlank(s)
toString()Human-readable object description@Override toStringreturn "User[" + name + "]";
equals() + hashCode()Object equality and hashing@Override both togetherBased on unique fields like ID
Callback / HandlerRespond to events or lifecycle hookson + Event / handle + EventonPaymentSuccess(PaymentEvent e)
☕ JavaAllMethodTypesInClass.java
import java.util.Objects;

public class Ticket {

    private static int nextId = 1000;

    private final int    ticketId;
    private final String event;
    private final String seat;
    private       double price;
    private       TicketStatus status;

    // ── CONSTRUCTOR ───────────────────────────────────
    public Ticket(String event, String seat, double price) {
        this.ticketId = nextId++;
        this.event    = event;
        this.seat     = seat;
        this.price    = price;
        this.status   = TicketStatus.AVAILABLE;
    }

    // ── STATIC FACTORY METHOD ─────────────────────────
    public static Ticket of(String event, String seat, double price) {
        if (price < 0) throw new IllegalArgumentException("Price cannot be negative");
        return new Ticket(event, seat, price);
    }

    // ── GETTERS ───────────────────────────────────────
    public int          getTicketId() { return ticketId; }
    public String       getEvent()    { return event; }
    public String       getSeat()     { return seat; }
    public double       getPrice()    { return price; }
    public TicketStatus getStatus()   { return status; }

    // ── BOOLEAN QUERIES ───────────────────────────────
    public boolean isAvailable() { return status == TicketStatus.AVAILABLE; }
    public boolean isBooked()    { return status == TicketStatus.BOOKED; }
    public boolean isCancelled() { return status == TicketStatus.CANCELLED; }

    // ── COMMAND METHODS ───────────────────────────────
    public void book() {
        if (!isAvailable()) throw new IllegalStateException("Ticket not available");
        this.status = TicketStatus.BOOKED;
    }

    public void cancel() {
        if (isCancelled()) throw new IllegalStateException("Already cancelled");
        this.status = TicketStatus.CANCELLED;
    }

    // ── QUERY / COMPUTED VALUE ────────────────────────
    public double getPriceWithTax() {
        return price * 1.18; // 18% GST
    }

    // ── toString ─────────────────────────────────────
    @Override
    public String toString() {
        return String.format("Ticket[#%d | %s | Seat:%s | ₹%.0f | %s]",
               ticketId, event, seat, price, status);
    }

    // ── equals & hashCode ─────────────────────────────
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Ticket t)) return false;
        return ticketId == t.ticketId;
    }

    @Override
    public int hashCode() {
        return Objects.hash(ticketId);
    }
}

enum TicketStatus { AVAILABLE, BOOKED, CANCELLED }

Inner Classes (Nested Classes) — Classes Inside Classes

Java allows declaring a class inside another class. These are called nested classes or inner classes. They are used to logically group classes that are only used in one place, increase encapsulation, and produce more readable and maintainable code. Java has four types of nested classes, each with distinct characteristics.

🏠
Non-static Inner Class

Declared inside a class without 'static'. Has access to ALL members of the outer class — including private fields. Requires an outer class object to be created: 'Outer.Inner inner = outer.new Inner()'. Use for: iterator implementations, event listeners tightly coupled to their outer class. Avoid if the inner class doesn't truly need access to the outer class instance.

🔲
Static Nested Class

Declared inside a class WITH 'static'. Does NOT have access to instance members of the outer class — only static members. Created without an outer class object: 'Outer.StaticNested obj = new Outer.StaticNested()'. Preferred over non-static inner class when the nested class doesn't need outer instance access. Used for: builder classes (Person.Builder), grouped helper classes, namespace organisation.

📄
Local Class

Declared INSIDE a method body. Visible only within that method. Can access local variables of the enclosing method if they are effectively final. Rarely used in modern Java — lambdas and anonymous classes typically serve the same purpose more concisely.

Anonymous Class

A class with no name — declared and instantiated in the same expression. Commonly used to implement interfaces or extend abstract classes with a one-off implementation: 'Runnable r = new Runnable() { public void run() { ... } };'. In modern Java (8+), lambdas replace anonymous classes for functional interfaces. Still used for abstract classes and multi-method interfaces.

☕ JavaInnerClassesDemo.java
// ── STATIC NESTED CLASS: Builder Pattern ──────────────
public class DatabaseConfig {

    private final String host;
    private final int    port;
    private final String database;
    private final String username;
    private final int    poolSize;

    // private constructor — use Builder to create
    private DatabaseConfig(Builder builder) {
        this.host     = builder.host;
        this.port     = builder.port;
        this.database = builder.database;
        this.username = builder.username;
        this.poolSize = builder.poolSize;
    }

    public String getHost()     { return host; }
    public int    getPort()     { return port; }
    public String getDatabase() { return database; }

    @Override
    public String toString() {
        return String.format("DB[%s:%d/%s user=%s pool=%d]",
                            host, port, database, username, poolSize);
    }

    // ✅ STATIC NESTED CLASS — Builder
    public static class Builder {
        // Required params
        private final String host;
        private final String database;
        // Optional params with defaults
        private int    port     = 5432;
        private String username = "root";
        private int    poolSize = 10;

        public Builder(String host, String database) {
            this.host     = host;
            this.database = database;
        }

        public Builder port(int port)         { this.port = port;         return this; }
        public Builder username(String user)  { this.username = user;     return this; }
        public Builder poolSize(int size)     { this.poolSize = size;     return this; }

        public DatabaseConfig build() {
            if (host == null || host.isBlank())
                throw new IllegalStateException("Host is required");
            return new DatabaseConfig(this);
        }
    }
}

// Usage — Builder pattern:
// DatabaseConfig config = new DatabaseConfig.Builder("localhost", "mydb")
//     .port(3306)
//     .username("admin")
//     .poolSize(20)
//     .build();
// System.out.println(config);
// → DB[localhost:3306/mydb user=admin pool=20]

// ── NON-STATIC INNER CLASS example ───────────────────
class LinkedList<T> {
    // Inner class — has access to LinkedList's private state
    private class Node {
        T    data;
        Node next;
        Node(T data) { this.data = data; }
    }
    private Node head;
    // Node is an implementation detail — hidden from outside world
}

Common Mistakes & Pitfalls — Bugs That Trip Everyone Up

These class-and-method mistakes are consistently found in Java beginner and intermediate code. Each one either causes a compile-time error or a subtle runtime bug that is hard to diagnose.

☕ JavaClassMethodMistakes.java
// ❌ MISTAKE 1: Forgetting 'this' — field never assigned
public class Person {
    private String name;
    public Person(String name) {
        name = name; // ❌ Assigns parameter to itself! Field 'this.name' stays null
    }
}
// ✅ Fix:
public Person(String name) {
    this.name = name; // ✅ Field assigned correctly
}

// ❌ MISTAKE 2: Accessing instance member from static context
public class Counter {
    int count = 0;
    public static void main(String[] args) {
        System.out.println(count); // ❌ Compile error — no 'this' in static
    }
}
// ✅ Fix: create an object
public static void main(String[] args) {
    Counter c = new Counter();
    System.out.println(c.count); // ✅
}

// ❌ MISTAKE 3: Calling this() NOT as first statement
public class Rectangle {
    int width, height;
    public Rectangle(int width, int height) {
        this.width = width;
        this(width, height, "blue"); // ❌ Compile error — must be FIRST
    }
}

// ❌ MISTAKE 4: Writing return type on constructor
public class Circle {
    // void Circle(double radius) { } // ❌ This is a METHOD named 'Circle', not a constructor
    // It will never be called by 'new Circle(...)' — silent logical error
    // ✅ Correct constructor — no return type:
    public Circle(double radius) { }
}

// ❌ MISTAKE 5: Default constructor disappears after writing one
public class Config {
    private String env;
    public Config(String env) { this.env = env; }
}
// Config c = new Config(); // ❌ Compile error — no-arg constructor gone
// ✅ Fix: explicitly add no-arg constructor if still needed
public Config() { this("development"); }

// ❌ MISTAKE 6: Returning mutable internal collection from getter
public class Team {
    private List<String> members = new ArrayList<>();
    public List<String> getMembers() {
        return members; // ❌ Caller gets direct reference — can modify!
    }
}
// ✅ Fix: return defensive copy
public List<String> getMembers() {
    return Collections.unmodifiableList(members);
}

// ❌ MISTAKE 7: NullPointerException from uninitialized object field
public class Order {
    private List<String> items; // ❌ Null — not initialised
    public void addItem(String item) {
        items.add(item); // ❌ NPE — items is null
    }
}
// ✅ Fix: initialise at declaration
private List<String> items = new ArrayList<>();

Bad Practices & Anti-Patterns — What Senior Developers Reject

These class design anti-patterns are the most common causes of failed code reviews in professional Java teams. Each violates fundamental OOP principles and creates long-term maintenance nightmares.

🚫
God Class — Does Everything

A single class with 50+ fields, 100+ methods, handling every concern of the application. Violates Single Responsibility Principle. Signs: class name ends in Manager, Service, Handler, Processor doing unrelated things. Fix: split by responsibility — UserRegistrationService, UserAuthenticationService, UserProfileService. Each class should have ONE reason to change.

🚫
Anemic Domain Model

A class that is just a data bag — only private fields and public getters/setters, no business logic. The business logic is scattered across 'service' classes that directly manipulate the domain object's state. Fix: move behaviour INTO the domain class. An 'Account' class should have deposit(), withdraw(), calculateInterest() — not just getBalance()/setBalance().

🚫
Mutable Object Without Thread Safety

A class with mutable state accessed by multiple threads without synchronisation. 'counter++' is NOT atomic — it is read-modify-write in three steps. Two threads can both read 5, both increment to 6, and both write 6 — losing one increment. Fix: use immutable objects where possible, AtomicInteger for counters, or synchronised/volatile for shared mutable state.

🚫
Long Parameter List in Constructor

A constructor with 7+ parameters is a code smell. Hard to call correctly, easy to swap adjacent arguments of the same type (price and stock both int — very easy to mix up). Fix: use the Builder pattern for classes with many optional parameters. Or group related parameters into dedicated value objects: 'Address address' instead of 'String street, String city, String state, String pincode'.

🚫
No equals()/hashCode() on Value Objects

Using objects as Map keys or Set elements without overriding equals() and hashCode() means Java uses REFERENCE equality (identity) — two 'equal' objects are treated as different. Example: two ProductId objects with the same id String will not be found as equal without overriding these methods. Rule: whenever you override equals(), you MUST override hashCode() with the same fields — contracts require this.

🚫
Calling Overridable Methods in Constructor

Calling a non-final, non-private (i.e., overridable) method inside a constructor is dangerous in an inheritance hierarchy. If a subclass overrides that method and the subclass's constructor hasn't run yet, the overridden version runs before the subclass fields are initialised — accessing uninitialised fields. Fix: only call private or final methods from constructors.

☕ JavaClassAntiPatterns.java
// ❌ ANTI-PATTERN: Calling overridable method in constructor
public class Base {
    public Base() {
        initialise(); // ❌ Dangerous if overridden in subclass
    }
    public void initialise() { System.out.println("Base init"); }
}
class Derived extends Base {
    private String config;
    public Derived() {
        super(); // Base() calls initialise() before Derived() sets config
        this.config = "loaded";
    }
    @Override
    public void initialise() {
        // ❌ config is null here! Derived() hasn't run yet
        System.out.println("Config: " + config.toUpperCase()); // NPE!
    }
}
// ✅ Fix: make initialise() private or final in Base

// ❌ ANTI-PATTERN: No equals/hashCode — broken Set/Map behaviour
class ProductId {
    private final String id;
    public ProductId(String id) { this.id = id; }
    // Missing equals() and hashCode()
}
// var set = new HashSet<ProductId>();
// set.add(new ProductId("P001"));
// set.contains(new ProductId("P001")); // ❌ Returns false! Different object

// ✅ Fix: override both equals and hashCode
class ProductId {
    private final String id;
    public ProductId(String id) { this.id = id; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof ProductId p)) return false;
        return java.util.Objects.equals(id, p.id);
    }

    @Override
    public int hashCode() {
        return java.util.Objects.hash(id);
    }
}

Real-World Production Code Examples — Class Design in Context

The following examples demonstrate complete, production-quality Java class designs — showing how fields, constructors, instance methods, static members, and inner classes work together in real enterprise code.

☕ JavaMoney.java — Immutable Value Object
package com.techsustainify.domain.value;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Currency;
import java.util.Objects;

/**
 * Immutable value object representing a monetary amount.
 * No setters — once created, a Money object never changes.
 * Operations return NEW Money objects (like BigDecimal, String).
 */
public final class Money {

    // Constants
    public static final Money ZERO_INR = new Money(BigDecimal.ZERO, "INR");

    // Private final fields — immutable after construction
    private final BigDecimal amount;
    private final String     currencyCode;

    // ── PRIVATE CONSTRUCTOR — use factory methods ─────
    private Money(BigDecimal amount, String currencyCode) {
        this.amount       = amount.setScale(2, RoundingMode.HALF_UP);
        this.currencyCode = currencyCode.toUpperCase();
    }

    // ── STATIC FACTORY METHODS ────────────────────────
    public static Money of(double amount, String currency) {
        if (amount < 0) throw new IllegalArgumentException("Amount cannot be negative");
        validateCurrency(currency);
        return new Money(BigDecimal.valueOf(amount), currency);
    }

    public static Money of(BigDecimal amount, String currency) {
        Objects.requireNonNull(amount, "Amount cannot be null");
        if (amount.compareTo(BigDecimal.ZERO) < 0)
            throw new IllegalArgumentException("Amount cannot be negative");
        validateCurrency(currency);
        return new Money(amount, currency);
    }

    // ── GETTERS (no setters — immutable) ──────────────
    public BigDecimal getAmount()       { return amount; }
    public String     getCurrencyCode() { return currencyCode; }

    // ── BUSINESS METHODS — return new Money objects ───
    public Money add(Money other) {
        assertSameCurrency(other);
        return new Money(this.amount.add(other.amount), currencyCode);
    }

    public Money subtract(Money other) {
        assertSameCurrency(other);
        BigDecimal result = this.amount.subtract(other.amount);
        if (result.compareTo(BigDecimal.ZERO) < 0)
            throw new ArithmeticException("Subtraction results in negative money");
        return new Money(result, currencyCode);
    }

    public Money multiply(double factor) {
        if (factor < 0) throw new IllegalArgumentException("Factor cannot be negative");
        return new Money(amount.multiply(BigDecimal.valueOf(factor)), currencyCode);
    }

    public Money applyDiscount(double discountPercent) {
        if (discountPercent < 0 || discountPercent > 100)
            throw new IllegalArgumentException("Invalid discount percent");
        BigDecimal discount = amount.multiply(BigDecimal.valueOf(discountPercent / 100));
        return new Money(amount.subtract(discount), currencyCode);
    }

    public boolean isGreaterThan(Money other) {
        assertSameCurrency(other);
        return amount.compareTo(other.amount) > 0;
    }

    public boolean isZero() {
        return amount.compareTo(BigDecimal.ZERO) == 0;
    }

    // ── PRIVATE HELPERS ───────────────────────────────
    private void assertSameCurrency(Money other) {
        if (!currencyCode.equals(other.currencyCode))
            throw new IllegalArgumentException(
                "Currency mismatch: " + currencyCode + " vs " + other.currencyCode);
    }

    private static void validateCurrency(String code) {
        try { Currency.getInstance(code); }
        catch (IllegalArgumentException e) {
            throw new IllegalArgumentException("Invalid currency code: " + code);
        }
    }

    // ── Object contract ───────────────────────────────
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Money m)) return false;
        return amount.compareTo(m.amount) == 0
            && currencyCode.equals(m.currencyCode);
    }

    @Override
    public int hashCode() {
        return Objects.hash(amount.stripTrailingZeros(), currencyCode);
    }

    @Override
    public String toString() {
        return currencyCode + " " + amount.toPlainString();
    }
}

// Usage:
// Money price    = Money.of(1000.00, "INR");
// Money shipping = Money.of(50.00,   "INR");
// Money total    = price.add(shipping);              // INR 1050.00
// Money final_   = total.applyDiscount(10);          // INR 945.00
// System.out.println(final_.isGreaterThan(Money.ZERO_INR)); // true
☕ JavaNotification.java — Builder Pattern with Static Nested Class
package com.techsustainify.notification;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public final class Notification {

    // ── ENUM nested inside class ──────────────────────
    public enum Type { EMAIL, SMS, PUSH, IN_APP }
    public enum Priority { LOW, NORMAL, HIGH, CRITICAL }

    // ── IMMUTABLE FIELDS ──────────────────────────────
    private final String            id;
    private final String            recipientId;
    private final Type              type;
    private final Priority          priority;
    private final String            subject;
    private final String            body;
    private final List<String>      tags;
    private final LocalDateTime     createdAt;
    private final LocalDateTime     scheduledFor;

    // ── PRIVATE CONSTRUCTOR — use Builder ─────────────
    private Notification(Builder b) {
        this.id           = java.util.UUID.randomUUID().toString();
        this.recipientId  = b.recipientId;
        this.type         = b.type;
        this.priority     = b.priority;
        this.subject      = b.subject;
        this.body         = b.body;
        this.tags         = Collections.unmodifiableList(new ArrayList<>(b.tags));
        this.createdAt    = LocalDateTime.now();
        this.scheduledFor = b.scheduledFor != null ? b.scheduledFor : this.createdAt;
    }

    // ── GETTERS ───────────────────────────────────────
    public String        getId()          { return id; }
    public String        getRecipientId() { return recipientId; }
    public Type          getType()        { return type; }
    public Priority      getPriority()    { return priority; }
    public String        getSubject()     { return subject; }
    public String        getBody()        { return body; }
    public List<String>  getTags()        { return tags; }
    public LocalDateTime getCreatedAt()   { return createdAt; }
    public LocalDateTime getScheduledFor(){ return scheduledFor; }

    // ── QUERY METHODS ─────────────────────────────────
    public boolean isUrgent()    { return priority == Priority.HIGH || priority == Priority.CRITICAL; }
    public boolean isScheduled() { return scheduledFor.isAfter(createdAt); }
    public boolean hasTag(String tag) { return tags.contains(tag); }

    @Override
    public String toString() {
        return String.format("Notification[%s | %s | %s | %s | '%s']",
               id.substring(0,8), type, priority, recipientId, subject);
    }

    // ── STATIC NESTED BUILDER CLASS ───────────────────
    public static class Builder {
        // Required
        private final String   recipientId;
        private final Type     type;
        private final String   body;
        // Optional with defaults
        private Priority       priority     = Priority.NORMAL;
        private String         subject      = "";
        private List<String>   tags         = new ArrayList<>();
        private LocalDateTime  scheduledFor = null;

        public Builder(String recipientId, Type type, String body) {
            if (recipientId == null || recipientId.isBlank())
                throw new IllegalArgumentException("Recipient ID required");
            if (body == null || body.isBlank())
                throw new IllegalArgumentException("Body required");
            this.recipientId = recipientId;
            this.type        = type;
            this.body        = body;
        }

        public Builder priority(Priority p)      { this.priority = p;     return this; }
        public Builder subject(String s)         { this.subject  = s;     return this; }
        public Builder tag(String tag)           { this.tags.add(tag);    return this; }
        public Builder scheduledFor(LocalDateTime t){ this.scheduledFor=t;return this; }

        public Notification build() {
            return new Notification(this);
        }
    }
}

// Usage:
// Notification n = new Notification.Builder("user-123", Notification.Type.EMAIL, "Your order is confirmed!")
//     .subject("Order Confirmation — #ORD-5521")
//     .priority(Notification.Priority.HIGH)
//     .tag("order")
//     .tag("transactional")
//     .build();

Class Structure Flowchart — Anatomy of a Java Class

This flowchart shows the complete structural anatomy of a Java class — from declaration to all possible members.

☕ Java Classpublic class ClassName { }
contains
📊 Static Fieldsstatic int count; static final PI = 3.14
🔷 Instance Fieldsprivate String name; private double salary
⚙️ Static Initialiserstatic { ... } — runs once on class load
🏗️ Constructorspublic ClassName(params) { ... }
🔧 Instance Methodspublic void method() — uses this
🛠️ Static Methodspublic static method() — no this
📖 Getters / SettersgetX() / setX(val) — encapsulation
🏠 Inner Classesstatic nested / non-static inner
✅ Complete ClassCompile → Load → Instantiate → Use

Code Execution Flow — from source to output

Java Class & Methods Interview Questions — Beginner to Advanced

These questions are consistently asked in Java fresher and experienced developer interviews, campus placements, and OCPJP/OCA certification exams.

Practice Questions — Test Your Class & Methods Knowledge

Attempt each question independently before reading the answer — active recall significantly improves retention and understanding.

1. What is the output? public class Box { static int count = 0; int id; public Box() { count++; this.id = count; } public static void main(String[] args) { Box b1 = new Box(); Box b2 = new Box(); Box b3 = new Box(); System.out.println(b1.id + " " + b2.id + " " + b3.id); System.out.println(Box.count); } }

Easy

2. Identify the bug: public class Circle { private double radius; public Circle(double radius) { radius = radius; } public double getArea() { return Math.PI * radius * radius; } }

Easy

3. Will this compile? Explain. public class Vehicle { private String brand; public Vehicle(String brand) { this.brand = brand; } } public class Car extends Vehicle { private int doors; public Car(int doors) { this.doors = doors; } }

Medium

4. Design a class 'Rectangle' with: length and width (private), constructor, getArea(), getPerimeter(), isSquare(), and toString(). All fields must be positive.

Medium

5. What is the output? public class Parent { static { System.out.println("Parent static block"); } { System.out.println("Parent instance block"); } public Parent() { System.out.println("Parent constructor"); } } public class Child extends Parent { static { System.out.println("Child static block"); } { System.out.println("Child instance block"); } public Child() { System.out.println("Child constructor"); } public static void main(String[] args) { new Child(); new Child(); } }

Hard

6. Implement a Thread-safe Singleton class in Java.

Hard

7. What is wrong with this equals() implementation? Fix it. public class Student { private int id; private String name; @Override public boolean equals(Object obj) { Student other = (Student) obj; return this.id == other.id; } }

Medium

8. Implement a Builder pattern for a class 'EmailMessage' with: to (required), from (required), subject (optional), body (optional), isHtml (optional, default false), priority (optional, default NORMAL).

Hard

Conclusion — Classes & Methods: The Heart of Java OOP

The class is the fundamental unit of object-oriented programming in Java. Every field, every constructor, every method — all live inside a class. Mastering class design is not just about knowing the syntax — it is about understanding how to model real-world entities with appropriate state, behaviour, and encapsulation.

The hallmarks of a well-designed Java class are clear: private fields with controlled access, constructors that guarantee valid state, methods that each do one thing, proper equals/hashCode for value objects, and meaningful toString for debugging. The Builder pattern for complex objects, factory methods for controlled creation, and the static nested class for grouped helpers — these patterns turn average Java code into production-grade software.

ConceptKey RuleExample
Class DeclarationBlueprint — no memory for data until 'new'public class BankAccount { }
Instance FieldsEach object gets its own copy — always privateprivate double balance;
Static FieldsOne shared copy per class — use for constants/countersprivate static int totalAccounts;
ConstructorSame name as class, no return type, initialise statepublic BankAccount(String id) { }
this keywordCurrent object — resolves name conflicts, chains constructorsthis.name = name; this(args);
Instance MethodOperates on object state — requires an object to callpublic void deposit(double amt) { }
Static MethodClass-level — no this, no instance accesspublic static int getCount() { }
Getter/SetterControlled encapsulated access to private fieldsgetName() / setName(String n)
Static Nested ClassBelongs to outer class — no outer instance neededpublic static class Builder { }
equals() + hashCode()Override together — required for Map/Set correctnessObjects.hash(id)

Your next step: Java Arrays — where you will learn to work with collections of data, how to pass arrays to methods, use the Arrays utility class, and understand the relationship between arrays and objects in Java. The class design principles you have learned here apply directly to how you design methods that work with arrays. ☕

Frequently Asked Questions — Java Class & Methods