Home/Learn/Java A–Z/Classes & Objects

Classes & Objects

Beginner
Java Fundamentals

Define classes, create objects, write constructors, apply access modifiers, and understand how the this keyword and object lifecycle work.

Overview

A class is a blueprint; an object is an instance of that blueprint created at runtime on the heap. Classes define state (fields) and behaviour (methods). Constructors are special methods called at object creation — if you don't write one, the compiler adds a no-arg default. Access modifiers (public, private, protected, package-private) control visibility and are the foundation of encapsulation. The this keyword refers to the current object and is used to disambiguate field names and chain constructors.

Class Definition & Constructors

A constructor has the same name as the class and no return type. If you define any constructor, the compiler stops generating the default no-arg one. Constructor overloading (multiple constructors with different parameters) is common. Constructor chaining with this() calls another constructor in the same class — this() must be the first statement.

Java initialises fields before the constructor body runs. Instance initialisers (blocks without a name) also run before the constructor body.

BankAccount.java
public class BankAccount {
    // Fields (state)
    private String owner;
    private double balance;
    private static int totalAccounts = 0; // shared across all instances

    // No-arg constructor
    public BankAccount() {
        this("Unknown", 0.0);  // delegates to 2-arg constructor
    }

    // Parameterised constructor
    public BankAccount(String owner, double initialBalance) {
        if (initialBalance < 0) throw new IllegalArgumentException("Balance cannot be negative");
        this.owner   = owner;        // 'this' disambiguates field from param
        this.balance = initialBalance;
        totalAccounts++;
    }

    // Copy constructor
    public BankAccount(BankAccount other) {
        this(other.owner, other.balance);
    }

    public void deposit(double amount) {
        if (amount <= 0) throw new IllegalArgumentException("Amount must be positive");
        balance += amount;
    }

    public boolean withdraw(double amount) {
        if (amount > balance) return false;
        balance -= amount;
        return true;
    }

    // Getters
    public String getOwner()   { return owner; }
    public double getBalance() { return balance; }
    public static int getTotalAccounts() { return totalAccounts; }

    @Override
    public String toString() {
        return "BankAccount[owner=" + owner + ", balance=" + balance + "]";
    }

    public static void main(String[] args) {
        BankAccount acc1 = new BankAccount("Alice", 1000.0);
        BankAccount acc2 = new BankAccount("Bob",   500.0);
        BankAccount acc3 = new BankAccount(acc1); // copy constructor

        acc1.deposit(200);
        acc1.withdraw(50);
        System.out.println(acc1);  // BankAccount[owner=Alice, balance=1150.0]
        System.out.println(BankAccount.getTotalAccounts()); // 3
    }
}

Access Modifiers

Java has four access levels, from most to least restrictive:

private — accessible only within the same class package-private (default, no keyword) — accessible within the same package protected — package-private + accessible in subclasses (even in other packages) public — accessible from anywhere

The golden rule of encapsulation: make fields private, expose behaviour through public methods. This lets you change the internal representation without breaking callers.

Person.java
package com.example;

public class Person {
    private String name;       // only this class
    int age;                   // package-private
    protected String country;  // this package + subclasses
    public String id;          // everywhere (avoid for mutable fields)

    public Person(String name, int age) {
        this.name = name;
        this.age  = age;
    }

    // Getter — controlled read access
    public String getName() { return name; }

    // Setter — can add validation
    public void setName(String name) {
        if (name == null || name.isBlank())
            throw new IllegalArgumentException("Name cannot be blank");
        this.name = name;
    }

    @Override
    public String toString() { return name + " (age " + age + ")"; }
}

Object Lifecycle & toString / equals

Objects are created with new, which allocates memory on the heap and calls the constructor. Objects become eligible for garbage collection when no live references point to them — you don't need to free memory manually.

Every class implicitly extends Object, which provides default implementations of toString() (prints class name + hashCode in hex), equals() (reference equality), and hashCode(). Override these in your own classes for meaningful behaviour.

Point.java
import java.util.Objects;

public class Point {
    private final int x;
    private final int y;

    public Point(int x, int y) { this.x = x; this.y = y; }

    // Override equals — value-based equality
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;           // same reference
        if (!(o instanceof Point p)) return false; // null or wrong type
        return x == p.x && y == p.y;
    }

    // Override hashCode — must be consistent with equals
    @Override
    public int hashCode() {
        return Objects.hash(x, y);  // use Objects.hash for simplicity
    }

    // Override toString — readable representation
    @Override
    public String toString() { return "Point(" + x + ", " + y + ")"; }

    public static void main(String[] args) {
        Point p1 = new Point(3, 4);
        Point p2 = new Point(3, 4);
        Point p3 = p1;

        System.out.println(p1 == p2);         // false — different objects
        System.out.println(p1.equals(p2));    // true  — same values
        System.out.println(p1 == p3);         // true  — same reference
        System.out.println(p1);               // Point(3, 4)
    }
}

Interactive Visualization

C = ClassA = AbstractI = Interface
CObject
Object is the root of the Java class hierarchy. Every class extends Object.
1 / 5

Key Points to Remember

  • If you define any constructor, the compiler no longer generates a default no-arg constructor
  • this() to chain constructors must be the first statement in the constructor body
  • Make fields private; expose state through public getters and validated setters
  • All classes extend Object implicitly — override toString(), equals(), hashCode() for proper behaviour
  • equals() and hashCode() must be overridden together — inconsistency breaks HashMap/HashSet
  • Objects become eligible for GC when all references to them go out of scope

Practice Classes & Objects in the Playground

Run and modify code directly in your browser - no setup needed.

Interview Questions

Sign in to ask Aria
1

What is the difference between a class and an object?

EasyTCS
2

What happens if you don't define a constructor in a Java class?

EasyWipro
3

What is constructor chaining? How is it achieved?

EasyInfosys
4

Why should you override hashCode() whenever you override equals()?

MediumAmazon
5

What are the four access modifiers in Java? When would you use each?

EasyGoogle

Ask Aria about Classes & Objects

Your personal AI tutor — ask anything about this concept