Java Classes

Su Suriya Ravichandran Updated 14 Nov 2026
9 min read ·Lesson 42 of 133

A class defines a new type. It describes the data that objects of that type hold (fields) and the operations they support (methods), and it specifies how new objects are initialized (constructors). Once a class exists, you can create as many objects from it as you need, and the compiler checks that every use of those objects matches what the class declares.

Declaring a Class

public class Book {
    // fields: the data each Book object holds
    String title;
    String author;
    int pages;

    // method: behavior every Book object has
    void describe() {
        System.out.println(title + " by " + author + ", " + pages + " pages");
    }
}
PartMeaning
publicThe class can be used from any package
classDeclares a class
BookThe class name, in PascalCase; also the name of the new type
{ ... }The class body containing fields, methods, constructors, and nested types

A public class must be in a file of the same name: Book.java.

Declaring the class creates the type but no objects. Objects are created with new:

Book b = new Book();
b.title = "Clean Code";
b.author = "Robert C. Martin";
b.pages = 464;
b.describe();     // Clean Code by Robert C. Martin, 464 pages

This works but leaves the fields open to any code, and an object can exist with missing data. The rest of this lesson builds the class into a well-designed form.

Members of a Class

A class body can contain:

MemberPurposeExample
FieldsStore an object's stateprivate double balance;
MethodsDefine behaviorpublic void deposit(double amount)
ConstructorsInitialize new objectspublic Book(String title)
Static fieldsData shared by the whole classstatic int count;
Static methodsBehavior not tied to one objectstatic Book fromCsv(String line)
Initializer blocksCode run during class or object initializationstatic { ... }
Nested typesClasses, interfaces, enums, records declared insideenum Format { HARDCOVER, EBOOK }

Fields

Fields are variables declared directly in the class body. Each object gets its own copy of every non-static field:

public class Book {
    private String title;
    private String author;
    private int pages;
    private boolean available = true;   // field with an initial value
}

Fields that are not given a value start at their default: 0 for numbers, false for booleans, null for references. This differs from local variables, which have no default and must be assigned before use.

Fields are usually private: only the class's own code can read or write them directly. This is the first step of encapsulation.

Constructors

A constructor runs when an object is created and sets its initial state. It has the same name as the class and no return type:

public class Book {
    private final String title;
    private final String author;
    private final int pages;
    private boolean available = true;

    public Book(String title, String author, int pages) {
        if (pages <= 0) {
            throw new IllegalArgumentException("pages must be positive");
        }
        this.title = title;
        this.author = author;
        this.pages = pages;
    }
}
Book b = new Book("Clean Code", "Robert C. Martin", 464);

Now every Book is guaranteed to have a title, an author, and a valid page count from the moment it exists. this.title refers to the field; title alone refers to the parameter. Fields marked final must be assigned exactly once, in the constructor or at declaration, and can never change afterward.

If a class declares no constructor at all, the compiler adds a default constructor with no parameters. Declaring any constructor removes it. Constructors have their own lesson.

Methods

Methods give objects behavior. Instance methods can read and modify the object's own fields:

public class Book {
    private final String title;
    private final String author;
    private final int pages;
    private boolean available = true;

    public Book(String title, String author, int pages) {
        this.title = title;
        this.author = author;
        this.pages = pages;
    }

    public boolean checkOut() {
        if (!available) {
            return false;
        }
        available = false;
        return true;
    }

    public void returnBook() {
        available = true;
    }

    public boolean isAvailable() { return available; }
    public String getTitle() { return title; }
    public int getPages() { return pages; }

    public int readingMinutes(int pagesPerHour) {
        return pages * 60 / pagesPerHour;
    }
}
Book b = new Book("Clean Code", "Robert C. Martin", 464);

System.out.println(b.checkOut());          // true
System.out.println(b.checkOut());          // false: already out
b.returnBook();
System.out.println(b.isAvailable());       // true
System.out.println(b.readingMinutes(40));  // 696

The available field is private, so other code cannot set it directly; it must go through checkOut and returnBook, which apply the library's rules. Methods like getTitle (called getters or accessors) provide read access to private fields.

Overriding toString

Every class inherits toString() from Object. Its default output is the class name and a hash code, which is rarely useful:

System.out.println(b);   // Book@6d06d69c

Override it to describe the object meaningfully:

@Override
public String toString() {
    return "Book[" + title + ", " + author + ", " + pages + "p, "
         + (available ? "available" : "checked out") + "]";
}
System.out.println(b);   // Book[Clean Code, Robert C. Martin, 464p, available]

println, string concatenation, debuggers, and logging all call toString automatically. @Override asks the compiler to confirm that the method really overrides a parent method; a typo such as tostring then becomes a compile error instead of a silently unused method.

Static Members

A static field or method belongs to the class itself rather than to any object:

public class Book {
    private static int created = 0;          // one shared counter

    private final String title;

    public Book(String title) {
        this.title = title;
        created++;
    }

    public static int getCreatedCount() {    // called on the class
        return created;
    }
}
new Book("A");
new Book("B");
System.out.println(Book.getCreatedCount());   // 2

Use static members for data or behavior that does not depend on a particular object: counters, constants (static final), and factory or utility methods. The static keyword has its own lesson.

Classes as Types

A class name can be used anywhere a type is needed: variable declarations, parameters, return types, fields, and generic type arguments.

public class Library {
    private final List<Book> books = new ArrayList<>();

    public void add(Book book) {
        books.add(book);
    }

    public Book findByTitle(String title) {
        for (Book b : books) {
            if (b.getTitle().equalsIgnoreCase(title)) {
                return b;
            }
        }
        return null;
    }

    public int totalPages() {
        int sum = 0;
        for (Book b : books) {
            sum += b.getPages();
        }
        return sum;
    }
}

Library uses Book as a field's element type, a parameter type, and a return type. The compiler checks every use: passing a String to add, or calling a method Book does not have, is a compile error. Classes built from other classes this way (composition) are how real applications are structured.

Class Design Guidelines

Model one concept per class. Book represents a book; Library manages a collection of books. A class that does unrelated things is hard to name, a sign it should be split.

Make fields private. Expose behavior through methods. This lets the class enforce its rules and change its internal representation later without breaking other code.

Establish a valid state in the constructor. Require the data an object needs to be meaningful, and reject invalid values immediately.

Prefer immutability where possible. Fields that never change should be final. An object whose state cannot change after construction is simpler to reason about and safe to share between threads.

Avoid setters by reflex. Generating a setX for every field gives up the control encapsulation provides. Add a method only when there is a legitimate operation, and name it after that operation (checkOut, rename, applyDiscount).

Kinds of Classes

As you progress, you will meet several specialized forms, each with its own lesson:

FormDeclared withPurpose
Regular classclassGeneral-purpose type with state and behavior
Abstract classabstract classPartial implementation meant to be extended; cannot be instantiated
InterfaceinterfaceA contract of methods that classes implement
EnumenumA fixed set of named constants, which can have fields and methods
RecordrecordA concise, immutable data carrier
Nested / inner classclass inside another classHelper types tied to an outer class
Anonymous classnew Type() { ... }One-off implementation defined inline

For pure data with no behavior beyond accessing it, a record is often the best choice:

public record BookInfo(String title, String author, int pages) {}

This single line provides a constructor, private final fields, accessor methods (title(), author(), pages()), and correct equals, hashCode, and toString.

Practical Example: Product Inventory

public class Product {
    private final String sku;
    private final String name;
    private double price;
    private int stock;

    public Product(String sku, String name, double price, int stock) {
        if (price < 0 || stock < 0) {
            throw new IllegalArgumentException("price and stock must be non-negative");
        }
        this.sku = sku;
        this.name = name;
        this.price = price;
        this.stock = stock;
    }

    public void restock(int quantity) {
        if (quantity <= 0) throw new IllegalArgumentException("quantity must be positive");
        stock += quantity;
    }

    public boolean sell(int quantity) {
        if (quantity <= 0 || quantity > stock) return false;
        stock -= quantity;
        return true;
    }

    public void applyDiscount(double percent) {
        price = price * (100 - percent) / 100;
    }

    public double inventoryValue() {
        return price * stock;
    }

    public boolean isLowStock() {
        return stock < 5;
    }

    @Override
    public String toString() {
        return String.format("%s %-10s %8.2f x %3d", sku, name, price, stock);
    }

    public static void main(String[] args) {
        Product kb = new Product("KB-01", "Keyboard", 2499, 10);
        Product ms = new Product("MS-02", "Mouse", 799, 3);

        kb.sell(7);
        ms.restock(20);
        kb.applyDiscount(10);

        System.out.println(kb);
        System.out.println(ms);
        System.out.printf("Total value: %.2f%n", kb.inventoryValue() + ms.inventoryValue());
        System.out.println("Keyboard low stock? " + kb.isLowStock());
    }
}

Output:

KB-01 Keyboard    2249.10 x   3
MS-02 Mouse        799.00 x  23
Total value: 25124.30
Keyboard low stock? true

The constructor rejects invalid starting values. sku and name are final because they identify the product and should never change; price and stock change only through methods that express real business operations. sell returns false instead of allowing negative stock. inventoryValue and isLowStock compute answers from the object's own state, so callers never need to know how that state is stored. Each Product object maintains its own state independently: selling keyboards does not affect mice.

0 Comments

Reviewed before they appear

No comments yet.

Java
Java Introduction Java Features Java Syntax Java Comments Java Statements Java Output Java Variables Java Data Types Java Type Casting Java Operators Java Strings Java Numbers Java Math Java Booleans Java User Input Java Conditions Java If Else Java Switch Java While Loop Java For Loop Java Break Java Continue Java Arrays Java Methods Java Parameters Java Return Values Java Scope Java Recursion Install Java Install JDK Java IDEs Java JDK Java JRE Java JVM Java Compiler Java File Compile Java Run Java JShell Basics Java Packages Java OOP Java Classes Java Objects Class Methods Constructors Constructor Overload this Keyword static Keyword final Keyword Encapsulation Inheritance Polymorphism Abstraction Access Modifiers super Keyword Method Overload Method Override Instanceof Operator Nested Classes Inner Classes Anonymous Classes Enums Records Array Basics Array Length Loop Arrays Multidimensional Arrays Array Copy Array Sorting Array Search ArrayList LinkedList HashSet TreeSet HashMap TreeMap Queue Interface Deque Interface Stack Class Iterator List Interface Set Interface Map Interface Collections Class Comparable Comparator Generics Basics Generic Classes Generic Methods Wildcards String Basics String Length String Methods String Concatenation String Compare String Search String Replace String Split String Format StringBuilder StringBuffer Text Blocks Regular Expressions Number Types Integer Class Float Class Double Class BigInteger BigDecimal Math Class Random Numbers Number Format Date Basics LocalDate LocalTime LocalDateTime ZonedDateTime Date Formatting Date Parsing Period Class Duration Class Java Instant Class Java Exception Basics Java try...catch Java Multiple Catch Blocks Java finally Block Java throw Keyword Java throws Keyword Java Checked Exceptions Java Unchecked Exceptions Java Custom Exceptions Java try-with-resources Java Exception Chaining
Ask about this post
AI Ask about this post

Ask questions about Java Classes and get answers drawn from it.

Signed-in readers only.