Object-Oriented Programming (Java)

Chapter 2
Objects, Classes, and Object Life Cycles

Bachelors Program in IT
Object-Oriented Programming (Java)   •   Bachelor

Objects, Classes, and Object Life Cycles

Introduction

Object-oriented programming (OOP) is a way of designing software around entities that combine data with the operations permitted on that data. Java is strongly shaped by this model: classes define types, objects are runtime instances of those types, fields represent state, and methods represent behavior. Understanding these ideas is essential before studying inheritance, interfaces, collections, exceptions, and larger application architectures.

A well-designed object model does more than translate nouns from a problem statement into classes. It identifies responsibilities, protects internal state, controls how objects are created and used, and enables objects to collaborate without unnecessary dependence. This chapter examines the relationship among classes, objects, references, identity, state, behavior, scope, memory, constructors, and object life cycles.

Learning Objectives

By the end of this chapter, you should be able to:

  • distinguish a class from an object and a reference;
  • model entities using fields, methods, constructors, and access control;
  • explain object identity, state, and behavior;
  • trace object creation, use, reachability, and garbage collection;
  • distinguish local, instance, and class-level scope;
  • analyze aliasing, null references, immutability, and encapsulation;
  • design collaborating Java classes for a realistic problem;
  • evaluate object-oriented designs for maintainability, safety, and clarity.

1. The Object-Oriented Model

1.1 Classes and objects

A class is a programmer-defined type. It describes the data an object may contain and the operations that may be performed on it. An object is a concrete runtime instance of a class.

For example, a university system may define a Student class. Individual students such as Amina and Daniel are objects of that class. The class is a general specification; each object has its own identity and state.

public class Student {
    private String studentId;
    private String name;
    private int completedCredits;

    public Student(String studentId, String name) {
        this.studentId = studentId;
        this.name = name;
        this.completedCredits = 0;
    }

    public void completeCredits(int credits) {
        if (credits < 0) {
            throw new IllegalArgumentException("Credits cannot be negative");
        }
        completedCredits += credits;
    }

    public int getCompletedCredits() {
        return completedCredits;
    }
}

The class declares three fields and two methods. The constructor establishes an initial valid state. Each object created from the class receives its own instance fields.

1.2 State, behavior, and identity

An object can be understood through three related properties:

  • State: the values held by its fields at a particular time.
  • Behavior: the operations it provides through methods.
  • Identity: the fact that it is a distinct object, even if another object has identical field values.

If two Student objects both have the name “Amina” and zero completed credits, their states may be equal in content, but they remain separate objects. Identity matters when objects represent different real-world entities.

A useful design principle is that an object should protect the rules governing its state. For example, a student should not be able to acquire negative credits merely because another class directly changed a field. The method completeCredits provides controlled behavior and validates input.

1.3 Abstraction and encapsulation

Abstraction focuses attention on the essential characteristics of an entity while ignoring irrelevant implementation detail. A bank account may expose deposit and withdraw without exposing the internal storage mechanism.

Encapsulation combines data and operations and restricts direct access to implementation details. In Java, access modifiers support encapsulation:

ModifierAccessibilityTypical use
privateDeclaring class onlyInternal fields and helper methods
No modifierSame packagePackage-level collaboration
protectedPackage and subclassesCarefully controlled inheritance support
publicAny accessible codePublic API

Declaring fields private is common, but encapsulation is not achieved merely by generating unrestricted getters and setters. A good public method should express a meaningful operation and preserve invariants—conditions that must remain true for a valid object.

2. Declaring Classes in Java

A class may contain fields, constructors, methods, nested types, and initialization logic. A basic declaration is:

public class Rectangle {
    private final double width;
    private final double height;

    public Rectangle(double width, double height) {
        if (width <= 0 || height <= 0) {
            throw new IllegalArgumentException("Dimensions must be positive");
        }
        this.width = width;
        this.height = height;
    }

    public double area() {
        return width * height;
    }
}

The keyword this refers to the current object. In this.width = width, the left side identifies the instance field and the right side identifies the constructor parameter.

The fields are final, meaning that each field is assigned once for a given object. This supports immutability when the field refers to an immutable value or when mutable state is safely protected. String is immutable, while many collection classes are mutable.

2.1 Fields

An instance field belongs separately to each object:

private int quantity;

A static field belongs to the class rather than to individual objects:

private static int numberOfAccounts;

Static fields should represent genuinely class-wide information. They are not substitutes for instance state. A static counter, for example, may count constructed objects, but it must be designed carefully in concurrent programs and should not be assumed to be a permanent database record.

Fields receive default values when Java initializes an object: numeric primitive fields become zero, boolean fields become false, and reference fields become null. Local variables do not receive these automatic defaults; they must be definitely assigned before use.

2.2 Methods

A method defines behavior. Its declaration includes a visibility modifier, return type, name, parameter list, and body.

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

A method may change state, return information, or both. A method with return type void does not return a value. Parameters are local variables initialized from argument values when the method is called.

Java is pass-by-value. For a primitive argument, the value itself is copied. For an object argument, the value copied is a reference to an object; the method can use that reference to mutate the object, but assigning a new object to the parameter does not replace the caller’s reference.

public static void replace(Student student) {
    student = new Student("X", "New student");
}

Calling replace(original) does not make the caller’s variable refer to the new student. The parameter is a separate reference variable, although initially it refers to the same object as original.

2.3 Method overloading

A class may have several methods with the same name if their parameter lists differ. This is overloading.

public void enroll(String courseCode) { }
public void enroll(String courseCode, int credits) { }

Return type alone cannot distinguish overloaded methods. Overloading is resolved at compile time according to the method name and argument types.

3. Object Creation and Constructors

An object is normally created with new:

Student s = new Student("S101", "Amina");

This statement has two conceptual parts. The expression new Student(...) creates and initializes an object. The variable s stores a reference to that object.

A constructor has the same name as its class and has no return type. It establishes the initial state and may reject invalid arguments.

public class Course {
    private final String code;
    private final int credits;

    public Course(String code, int credits) {
        if (code == null || code.isBlank()) {
            throw new IllegalArgumentException("Course code is required");
        }
        if (credits <= 0) {
            throw new IllegalArgumentException("Credits must be positive");
        }
        this.code = code;
        this.credits = credits;
    }
}

If a class declares no constructor, Java supplies a no-argument default constructor. Once a class declares any constructor, Java does not automatically supply that constructor. A no-argument constructor must then be written explicitly if it is required.

A constructor should leave the object usable. Publishing this from a constructor—for example, by registering the unfinished object in a global collection—can expose partially initialized state and should generally be avoided.

Worked example: constructor invariants

Suppose a TemperatureReading must contain a Celsius value from −273.15 or higher. The constructor can enforce the invariant:

public final class TemperatureReading {
    private final double celsius;

    public TemperatureReading(double celsius) {
        if (celsius < -273.15) {
            throw new IllegalArgumentException("Below absolute zero");
        }
        this.celsius = celsius;
    }

    public double celsius() {
        return celsius;
    }
}

After successful construction, every instance satisfies the stated condition. This is safer than allowing arbitrary assignment followed by repeated validation throughout the program.

4. References, Identity, and Equality

A variable whose type is a class does not contain the object itself. It contains a reference value that may identify an object or may be null.

Student first = new Student("S101", "Amina");
Student second = first;

Now first and second refer to the same object. A change through either reference is visible through the other. This situation is called aliasing.

second.completeCredits(15);
System.out.println(first.getCompletedCredits()); // 15

The equality operator == compares primitive values or, for references, whether two references identify the same object. The equals method is intended to compare logical value equality when a class defines such a meaning. A class that overrides equals should normally also override hashCode, so equal objects produce equal hash codes when used in hash-based collections.

For example, two CourseCode objects containing “CS201” might be logically equal even though they are different objects. By contrast, two bank-account objects with the same balance are not necessarily the same account. Whether equality is identity-based or value-based is a domain and design decision.

Null references

null means that a reference does not currently identify an object. Calling an instance method through null causes a NullPointerException.

Student student = null;
// student.getCompletedCredits(); // failure at runtime

Null may represent “not known,” “not supplied,” or “not applicable,” but these meanings should not be confused. Good designs validate required references at boundaries, use meaningful alternatives where appropriate, and document whether null is permitted.

5. Scope, Lifetime, and Memory

Scope is the region of source code in which a name can be used. Lifetime is the period during program execution during which a value or object exists or remains usable. Scope and lifetime are related but not identical.

  • A local variable is declared inside a method or block and is usable only there.
  • A parameter is usable within the method invocation.
  • An instance field is associated with an object and is accessible according to its modifier.
  • A static field is associated with the class and generally exists while the class is loaded.

Consider:

public class Counter {
    private int value;                 // instance field
    private static int totalCounters;  // class field

    public void increase(int amount) {
        int change = amount;           // local variable
        value += change;
    }
}

The name change is unavailable after increase returns. The object’s value remains as long as the Counter object remains reachable. A variable going out of scope does not by itself immediately destroy an object: another reference may still identify it.

Java programs commonly use a managed heap for objects. The Java Virtual Machine (JVM) automatically reclaims memory occupied by objects that are no longer reachable from active references and other GC roots. This process is garbage collection. The exact algorithm and timing are implementation-dependent; programmers should not rely on a predictable collection time.

Garbage collection does not close files, database connections, sockets, or other external resources automatically at the moment an object becomes unreachable. Such resources must be closed explicitly, commonly through try-with-resources. The finally mechanism or resource-management abstractions may also be relevant. The obsolete finalize approach should not be used for reliable resource management.

A useful conceptual life cycle is:

  1. Allocation: memory for a new object is obtained.
  2. Initialization: fields and constructor logic establish state.
  3. Use: references enable method calls and collaboration.
  4. Loss of reachability: no active path can reach the object.
  5. Reclamation: the garbage collector may recover its memory.

The last two stages are not necessarily immediate or observable.

6. Collaboration Among Objects

Real applications are built from objects that collaborate. A Library may contain books, a Loan may refer to a member and a book, and a notification service may send messages. Collaboration is expressed through method calls and references.

public class ShoppingCart {
    private final List<CartItem> items = new ArrayList<>();

    public void add(Product product, int quantity) {
        if (product == null || quantity <= 0) {
            throw new IllegalArgumentException("Invalid item");
        }
        items.add(new CartItem(product, quantity));
    }

    public double total() {
        double result = 0.0;
        for (CartItem item : items) {
            result += item.subtotal();
        }
        return result;
    }
}

Here, ShoppingCart delegates item-specific calculation to CartItem. This separation supports the single-responsibility principle: a class should have a focused reason to change. It also reduces duplicated logic.

Association, aggregation, and composition

An association is a general relationship in which one object knows or uses another. An aggregation describes a whole-part relationship in which parts may exist independently. Composition is a stronger whole-part relationship in which the whole controls the meaningful life cycle of its parts.

These terms are useful design concepts, but Java does not automatically enforce them. A field such as private Address address; expresses a reference; the intended ownership semantics come from constructors, methods, visibility, and application rules.

Prefer collaboration through narrow interfaces and meaningful methods rather than exposing mutable internal collections. If a method returns an internal mutable list directly, callers may bypass validation. Returning an unmodifiable view or a defensive copy can preserve the class invariant.

7. Immutability and Defensive Design

An immutable object cannot change its observable state after construction. Immutability can simplify reasoning, improve thread safety, and prevent accidental aliasing.

Typical practices include:

  • declare the class final when subclassing could undermine guarantees;
  • make fields private final;
  • validate constructor arguments;
  • provide no mutating methods;
  • defensively copy mutable inputs and outputs.
public final class Meeting {
    private final String title;
    private final List<String> attendees;

    public Meeting(String title, List<String> attendees) {
        this.title = Objects.requireNonNull(title);
        this.attendees = List.copyOf(attendees);
    }

    public List<String> attendees() {
        return attendees;
    }
}

List.copyOf creates an unmodifiable list based on the supplied elements. The elements themselves must also be considered: an unmodifiable list does not make mutable element objects immutable.

Immutability is not always appropriate. A large mutable domain object may be efficient and natural when its state changes frequently. The important point is to make mutability deliberate and controlled.

8. Applied Case: University Course Registration

The following is an illustrative case.

Background

A university wants a registration component that records students, courses, and enrollments. It must prevent invalid registrations and calculate a student’s enrolled credits.

Problem

An initial procedural design stores parallel arrays of student identifiers, course identifiers, and credit totals. This design duplicates validation, makes relationships difficult to maintain, and permits inconsistent updates.

Analysis

The domain contains distinct entities:

  • Student, which has an identity and enrollment behavior;
  • Course, which has a code and credit value;
  • Registration, which represents a relationship between a student and a course;
  • RegistrationService, which coordinates policies such as duplicate prevention.

A course should validate its own credit value. A student should manage its registrations rather than exposing a mutable list. The service should coordinate operations involving multiple entities.

Approach

public final class Student {
    private final String id;
    private final Set<Course> courses = new HashSet<>();

    public Student(String id) {
        this.id = Objects.requireNonNull(id);
    }

    public boolean register(Course course) {
        return courses.add(Objects.requireNonNull(course));
    }

    public int totalCredits() {
        return courses.stream()
                .mapToInt(Course::credits)
                .sum();
    }
}

public record Course(String code, int credits) {
    public Course {
        if (code == null || code.isBlank() || credits <= 0) {
            throw new IllegalArgumentException("Invalid course");
        }
    }
}

The Student object owns the registration set and exposes a controlled operation. A Set prevents duplicate course objects according to the equality rules of Course. Because a record’s generated equality is component-based, two courses with the same code and credits compare logically equal. In a production system, the university would decide whether course code alone defines identity and might implement that policy explicitly.

Outcome

Invalid courses are rejected during construction. Duplicate registrations are naturally rejected by the set. Credit calculation is delegated to the Course objects and does not require parallel arrays. The design remains extensible: prerequisites, capacity, timetable conflicts, and registration deadlines can be added as policies without placing every responsibility in one class.

Learning points

  1. Domain entities should own behavior related to their state.
  2. Constructors are appropriate places for basic validity rules.
  3. Collections should not be exposed in ways that permit uncontrolled mutation.
  4. Equality policy must reflect the meaning of identity in the domain.
  5. Collaboration produces simpler classes than one large procedural controller.

9. Common Errors and Design Limitations

Excessive public state

Public fields allow any caller to violate invariants. Prefer private fields and operations that express valid state transitions.

Confusing references with objects

Assigning one reference to another does not copy the object. If independent state is required, create a copy deliberately, using a copy constructor, factory, or other documented mechanism.

Shallow copying

A shallow copy duplicates the outer object but may retain references to the same nested mutable objects. A deep copy duplicates the relevant object graph, but it is more expensive and may be semantically incorrect. Copying strategy must be defined according to the domain.

Overusing inheritance

Inheritance creates a strong relationship and can expose implementation assumptions. Composition—holding another object and delegating to it—often provides more flexible collaboration. The question is not whether inheritance is available, but whether the subtype genuinely satisfies the behavioral contract of the supertype.

Large, unfocused classes

A “god class” accumulates unrelated responsibilities and becomes difficult to test. Separate persistence, validation, presentation, and domain behavior when their reasons to change differ.

Premature object creation

Objects should be created when their responsibilities and lifetime are understood. Excessive temporary objects can complicate ownership, while avoiding objects merely to reduce allocations can produce tangled code. Modern JVMs optimize many allocations, but clarity and correct design remain primary concerns.

10. Professional and Ethical Considerations

Object models influence privacy, security, and accountability. A User object may contain sensitive personal information; exposing fields through unrestricted methods can create a data-protection risk. Apply least privilege, validate inputs, avoid logging confidential values, and define retention and deletion behavior.

Object identity also affects authorization. A method such as updateAccount(Account account) must ensure that the caller is permitted to update that particular account, not merely that the object exists. Equality checks must not replace authorization checks.

For systems processing health, financial, or educational data, design choices should align with applicable laws, institutional policies, audit requirements, and secure coding standards. AI-assisted code generation can suggest classes and methods, but developers remain responsible for checking constructor validation, reference safety, resource handling, licensing, and security consequences.

11. Best Practices

  • Give each class a clear responsibility and meaningful name.
  • Keep fields private unless a stronger reason justifies exposure.
  • Establish valid state in constructors or factories.
  • Prefer immutable value objects where practical.
  • Validate required references with explicit contracts.
  • Document whether methods accept null.
  • Use == for primitive comparison and deliberate identity checks; use equals for logical equality.
  • Override hashCode whenever equals is overridden.
  • Avoid returning internal mutable collections directly.
  • Close external resources explicitly.
  • Favor composition when it reduces coupling.
  • Test normal behavior, invalid inputs, boundary conditions, aliasing, and object collaboration.

Chapter Summary

A class is a definition of a type, whereas an object is a concrete runtime instance. Objects possess identity, state, and behavior. Fields represent state, methods provide behavior, and constructors establish valid initial conditions. Encapsulation protects invariants by controlling access to state; abstraction exposes what clients need while hiding unnecessary implementation detail.

Reference variables identify objects rather than containing the objects themselves. Assignment can create aliases, and null identifies no object. Java is pass-by-value, including when the copied value is an object reference. Scope determines where a name can be used, while lifetime concerns how long a value or object remains usable. Garbage collection reclaims unreachable object memory, but external resources require explicit management.

Effective object-oriented design assigns responsibilities to appropriate classes, uses controlled collaboration, distinguishes identity from logical equality, and treats mutability as a deliberate choice. These foundations prepare developers for inheritance, polymorphism, interfaces, collections, exceptions, testing, and software architecture.

Key Terms

Class: A programmer-defined type that specifies data and operations.

Object: A runtime instance of a class.

Instance: A particular object created from a class.

Field: A variable declared in a class.

Method: An operation declared in a class.

Constructor: Special initialization logic executed when an object is created.

Reference: A value that identifies an object or is null.

Identity: The property that distinguishes one object from another.

State: The values held by an object at a particular time.

Behavior: The operations an object can perform.

Encapsulation: Restricting direct access to representation and controlling state changes.

Abstraction: Focusing on essential characteristics while hiding unnecessary detail.

Invariant: A condition that must remain true for a valid object.

Aliasing: The condition in which multiple references identify the same object.

Scope: The region of source code where a name is usable.

Lifetime: The period during which a value or object exists or remains usable.

Garbage collection: Automatic reclamation of memory for unreachable objects.

Immutability: The property of not changing observable state after construction.

Association: A relationship in which objects know or use one another.

Equality: A rule determining whether two values or objects should be considered the same.

Review Questions

5 Short Answer Questions

  1. What is the difference between a class and an object?
  2. Explain the distinction among state, behavior, and identity.
  3. Why should fields commonly be declared private?
  4. What happens when two reference variables are assigned to the same object?
  5. Why does garbage collection not replace explicit closing of a file or database connection?

5 Long Answer Questions

  1. Explain how constructors, validation, and invariants work together in a well-designed Java class.
  2. Compare identity equality and logical equality, including the relationship between equals and hashCode.
  3. Discuss scope and lifetime using local variables, instance fields, and static fields as examples.
  4. Explain how encapsulation and immutability reduce defects in collaborative software.
  5. Compare composition and inheritance as mechanisms for organizing object collaboration.

3 Analytical / Application Questions

  1. Design classes for a hospital appointment system. Identify at least four entities, their important fields, their methods, and the collaborations among them. Justify where validation should occur.
  2. A method receives a List<Order> and sorts it in place. Analyze the possible effects on the caller and propose two designs if the method should not mutate the caller’s list.
  3. A banking application permits clients to modify an account’s balance through a public field. Identify the risks and redesign the class so that deposits, withdrawals, identity, equality, and invalid transactions are handled safely.

Multiple Choice Questions with Answers

Question 1

Which statement best describes an object in Java?

A. A template used only by the compiler

B. A concrete runtime instance of a class

C. A method parameter with a primitive value

D. A package containing related classes

Correct Answer: B. A concrete runtime instance of a class

Question 2

What does this refer to inside an instance method?

A. The superclass object

B. The class loader

C. The current object

D. The most recently created object

Correct Answer: C. The current object

Question 3

What happens when Student second = first; is executed and both variables have a reference type?

A. A deep copy is always created

B. Both variables refer to the same object

C. The object is immediately garbage-collected

D. The fields are converted to primitives

Correct Answer: B. Both variables refer to the same object

Question 4

Which statement about Java constructors is correct?

A. A constructor must declare a return type

B. A constructor can have any name

C. A constructor helps establish the initial object state

D. Constructors can be called only after an object is used

Correct Answer: C. A constructor helps establish the initial object state

Question 5

Which access modifier restricts a member to its declaring class?

A. public

B. protected

C. private

D. static

Correct Answer: C. private

Question 6

What does null indicate for a reference variable?

A. The object contains only zero values

B. The reference identifies no object

C. The object is immutable

D. The object has been copied

Correct Answer: B. The reference identifies no object

Question 7

Which statement about Java argument passing is accurate?

A. Java passes object variables by reference in the C++ sense

B. Java always passes deep copies of objects

C. Java passes values; an object argument value may be a reference

D. Java passes only primitive values

Correct Answer: C. Java passes values; an object argument value may be a reference

Question 8

Why should a class normally override hashCode when it overrides equals?

A. To make constructors run faster

B. To ensure equal objects have equal hash codes

C. To prevent all object mutation

D. To make fields automatically private

Correct Answer: B. To ensure equal objects have equal hash codes

Question 9

When may garbage collection reclaim an object’s memory?

A. Immediately after every method call

B. When the object is unreachable, at implementation-dependent time

C. Whenever any field becomes zero

D. Only when the programmer calls delete

Correct Answer: B. When the object is unreachable, at implementation-dependent time

Question 10

Which design best protects a shopping cart’s internal collection?

A. Return the mutable internal list directly

B. Make the collection public

C. Expose controlled operations or an unmodifiable view

D. Store all items in public static fields

Correct Answer: C. Expose controlled operations or an unmodifiable view

Object-Oriented Programming (Java)
Chapter 2 of 15