← Back to Course Hub
• OOP in Java — Chapter 3

Relationships
in Java

// Association · Aggregation · Composition · Dependency · Generalization

📊

Overview: How Classes Relate

In OOP, classes don't exist in isolation. They form relationships that model real-world connections between entities. This chapter covers Association and its special cases (Aggregation, Composition), Dependency, and previews Generalization (next chapter).

Association
USES-A / HAS-A
Two classes are aware of each other and interact independently. Includes Aggregation and Composition.
Aggregation
HAS-A (weak)
A whole contains parts, but parts can exist independently.
Composition
HAS-A (strong)
Parts are owned by the whole and die with it.
Dependency
USES temporarily
A class temporarily uses another (via method parameter/return).
Generalization
IS-A (Next Chapter)
A child class extends a parent class using inheritance.
UML Arrow Reference
Association (solid line)
Aggregation (solid, open diamond)
Composition (solid, filled diamond)
Dependency (dashed arrow)
Generalization (solid, filled arrowhead)

💡 Memory Trick: The Big Picture

Ask yourself two questions about any two classes A and B:

① Can A be described as a B? → Generalization (next chapter)
② Does A have or use a B? → Association / Aggregation / Composition / Dependency

Association is the parent category — Aggregation and Composition are special cases showing different lifecycle strengths.

Then ask: who controls the lifecycle of B? That narrows it down to Aggregation vs Composition.

Tutorial Tip: Decision Tree for Choosing Relationships

Use this decision tree when designing your classes:

Can A be described as a B? YES → Generalization (IS-A) → use extends Does A have or use a B? YES → Is the reference permanent (stored as a field)? YES → Does A create B internally? YES → Composition ◆ (strong HAS-A) NO → Aggregation ◇ (weak HAS-A) NO → Is it just a method parameter? YES → Dependency - - > (temporary USES) NO → They are unrelated classes
📋

Java Copy Constructor | Constructor Overloading

Creating duplicate objects with the same characteristics

Introduction
?
What is a Copy Constructor?
A copy constructor is a special constructor used to create a new object as a copy of an existing object. It takes an object of the same class as a parameter and creates a duplicate.
!
Why Use Copy Constructors?
To create cloned objects that have the same characteristics (field values) as the original object but exist at different memory locations, allowing independent manipulation.
Java vs C++: Unlike C++, Java does not provide a default copy constructor. You must explicitly define one if you need this functionality.
Department Class with Copy Constructor
Java public class Department { private String name; private int id; // Regular constructor public Department(int id, String name) { this.id = id; this.name = name; } // Copy constructor public Department(Department oldDepartment) { this.id = oldDepartment.id; this.name = oldDepartment.name; } // Getters and Setters public String getName() { return name; } public void setName(String name) { this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } @Override public String toString() { return "Department Id: " + id + "\tDepartment Name: " + name; } }
Explanation

The Department class demonstrates a basic copy constructor:

1
Regular Constructor: Creates a new Department with provided id and name.
2
Copy Constructor: Takes another Department object and copies its field values to the new object.
3
Field Access: Since we're inside the same class, we can directly access private fields of the parameter object.
Employee Class - Shallow Cloning Version
Java public class Employee { private String name; private int id; private Department department; // Regular constructor public Employee(int id, String name, Department department) { this.id = id; this.name = name; this.department = department; } // Copy constructor with SHALLOW cloning public Employee(Employee oldEmployee) { this.id = oldEmployee.id; this.name = oldEmployee.name; // shallow cloning this.department = oldEmployee.department; } // Getters and Setters public Department getDepartment() { return department; } public void setDepartment(Department department) { this.department = department; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } }
Shallow vs Deep Cloning

Notice the critical line in the copy constructor:

Java this.department = oldEmployee.department;

This creates a shallow copy:

Shallow Cloning: Copies the reference, not the object. Both the original and cloned Employee share the same Department object in memory.
Deep Cloning: Creates a new Department object. Each Employee has its own independent Department instance.

Problem: Changes to the cloned employee's department will affect the original employee's department!

Test Class - Demonstrating the Problem
Java public class CopyConstructorTest { public static void main(String[] args) { // Create a department Department department = new Department(1, "Finance"); // Create original employee Employee originalEmployee = new Employee(1, "Ram", department); // Clone the employee using copy constructor Employee clonedEmployee = new Employee(originalEmployee); // Print both employees System.out.println("Original:- " + originalEmployee); System.out.println("Duplicate:- " + clonedEmployee); System.out.println(); // Modify the cloned employee clonedEmployee.setId(2); clonedEmployee.setName("Laxman"); clonedEmployee.getDepartment().setName("HR"); // Print again to see the problem System.out.println("Original:- " + originalEmployee); System.out.println("Duplicate:- " + clonedEmployee); } }
Output with Shallow Cloning
Output Original:- Employee Id: 1 Employee Name: Ram Department Id: 1 Department Name: Finance Duplicate:- Employee Id: 1 Employee Name: Ram Department Id: 1 Department Name: Finance Original:- Employee Id: 1 Employee Name: Ram Department Id: 1 Department Name: HR Duplicate:- Employee Id: 2 Employee Name: Laxman Department Id: 1 Department Name: HR
⚠ The Problem: Notice that when we changed the cloned employee's department name to "HR", the original employee's department also changed to "HR"! This is because both employees share the same Department object in memory (shallow cloning).

Solution: Let's make a small change in the copy constructor of the Employee class to use deep cloning instead.
Fixed Employee Class - Deep Cloning
Java public class Employee { private String name; private int id; private Department department; // Regular constructor public Employee(int id, String name, Department department) { this.id = id; this.name = name; this.department = department; } // Copy constructor with DEEP cloning public Employee(Employee oldEmployee) { this.id = oldEmployee.id; this.name = oldEmployee.name; // deep cloning - create new Department object this.department = new Department(oldEmployee.department); } // Getters and Setters omitted for brevity }
Output with Deep Cloning
Output Original:- Employee Id: 1 Employee Name: Ram Department Id: 1 Department Name: Finance Duplicate:- Employee Id: 1 Employee Name: Ram Department Id: 1 Department Name: Finance Original:- Employee Id: 1 Employee Name: Ram Department Id: 1 Department Name: Finance Duplicate:- Employee Id: 2 Employee Name: Laxman Department Id: 1 Department Name: HR

✓ Success! Now the original employee's department remains "Finance" while the cloned employee's department is "HR". Each employee has its own independent Department object.
Summary
Key Takeaway: Now everything goes fine and this is what we call deep cloning using copy constructors. When an object contains references to other objects, always use deep cloning to create truly independent copies.

💡 Shallow vs Deep Cloning Memory Trick

S
Shallow Cloning: Copies the reference → Same object in memory → Changes affect both
D
Deep Cloning: Creates new object → Different memory locations → Independent copies
SHALLOW shares, DEEP duplicates
Try It Yourself: Convert Shallow Clone to Deep Clone

Given this shallow copy constructor:

public Employee(Employee other) { this.name = other.name; this.department = other.department; // shallow! }

Change the last line to create a deep clone. What should it be?

this.department = new Department(other.department);

Now each Employee has its own independent Department. Changing one doesn't affect the other!

Generalization

The IS-A relationship. A subclass inherits fields and methods from its superclass using the extends keyword.

📖 Next Chapter Content: Generalization (including Inheritance and Interface concepts) will be covered in detail in the next chapter. This section provides a preview of these important concepts.
Core Idea: "A Dog IS-A Animal." Everything a generic Animal can do, a Dog can do too — plus it might add its own behaviors. Java only supports single inheritance for classes.
Parent Class
Java public class Animal { private String name; private int age; public Animal(String name, int age) { this.name = name; this.age = age; } public void eat() { System.out.println(name + " is eating"); } public void sleep() { System.out.println(name + " is sleeping"); } }
Child Class
Java public class Dog extends Animal { private String breed; public Dog(String name, int age, String breed) { // call parent ctor super(name, age); this.breed = breed; } // Overriding parent method @Override public void eat() { System.out.println("Dog eats kibble"); } public void bark() { // new method System.out.println("Woof!"); } }
Key Concepts
1
super keyword — Refers to the parent class. Used to call parent constructors (super()) or methods (super.eat()).
2
2
@Override — Annotation indicating a method overrides a parent method. Helps catch typos at compile time.
3
Polymorphism — A Dog reference can be stored in an Animal variable: Animal a = new Dog(...);
4
final class — Prevents inheritance. String is a famous example of a final class in Java.
5
abstract class — Cannot be instantiated; intended to be subclassed. May contain abstract methods with no body.
⚠ Pitfall: Avoid deep inheritance chains (more than 3 levels). They become hard to maintain and reason about. Prefer composition over inheritance when in doubt.

Association

The USES-A or HAS-A relationship where two classes know about each other and interact, but each has its own independent lifecycle.

Core Idea: "A Teacher has Students." But if the Teacher leaves the school, the Students still exist. Neither object owns the other.
Bidirectional Association
Java public class Teacher { private String name; private List<Student> students; public void teach() { for (Student s : students) { s.learn(); } } } public class Student { private String name; private Teacher teacher; public void learn() { System.out.println( name + " is learning"); } }
Types of Association
TypeDescription
UnidirectionalA knows about B, but B doesn't know about A
BidirectionalBoth classes hold a reference to each other
Self-AssociationA class references itself (e.g., linked list Node → Node)
Multiplicity1-to-1, 1-to-many, many-to-many (shown with UML multiplicity)
💜 Key Distinction: Association is the broadest category representing any HAS-A or USES-A relationship. Aggregation and Composition (covered in the following sections) are special cases of Association that differ in lifecycle ownership. Association itself implies no lifecycle dependency.
Try It Yourself: Add a removeStudent() method

The Teacher class has addStudent(). Can you write a removeStudent(Student s) method that removes a student from the list and also sets the student's teacher to null?

public void removeStudent(Student s) { students.remove(s); s.setTeacher(null); }

Think about it: What should happen to the bidirectional reference when a student is removed? Both sides need updating!

Aggregation

Weak HAS-A relationship. The container holds references to parts, but the parts can exist independently. The part's lifecycle is NOT controlled by the whole.

Core Idea: "A Department HAS-A list of Professors." If the Department is shut down, the Professors still exist — they can move to another department.
Code Example
Java public class Professor { private String name; public Professor(String name) { this.name = name; } public void teach() { // method implementation } } public class Department { private String name; // Professors passed in — not created here private List<Professor> professors; public Department(String name, List<Professor> profs) { this.name = name; // reference this.professors = profs; } } // Professor exists BEFORE department Professor p = new Professor("Dr. Ali"); Department d = new Department("CS", ...);
Aggregation Checklist
The part can exist without the whole
The part is passed into the container, not created inside it
If the container is destroyed, the parts live on
UML notation: open diamond ◇ on the container side
Represented by storing a reference to an externally created object
Try It Yourself: Convert this Aggregation to Composition

The Department-Professor example uses Aggregation (professors passed in from outside). How would you change it to Composition?

Key change: Instead of accepting professors as a parameter, create them inside the Department constructor:

public Department(String name) { this.name = name; this.professors = new ArrayList<>(); professors.add(new Professor("Dr. Smith")); professors.add(new Professor("Dr. Jones")); }

Now Department owns its professors. If Department is deleted, the professors go with it.

Composition

Strong HAS-A relationship. The whole owns its parts completely. Parts are created by the whole and die with it — they cannot exist independently.

Core Idea: "A House HAS-A Room." If the House is demolished, the Rooms cease to exist. A Room cannot exist without its House.
Code Example
Java public class Room { private String type; private int size; public Room(String type, int size) { this.type = type; this.size = size; } } public class House { private String address; private List<Room> rooms; public House(String address) { this.address = address; // House CREATES its own rooms rooms = new ArrayList<>(); rooms.add(new Room("Living", 30)); rooms.add(new Room("Bedroom", 20)); } // When House is GC'd, Rooms go too }
Composition Checklist
The part is created inside the whole's constructor
The part cannot exist without the whole
If the whole is destroyed, the parts are destroyed too
UML notation: filled diamond ◆ on the container side
No outside reference to the part should be handed out

🔑 Aggregation vs Composition — The Lifecycle Test

Ask: "If I delete the container, do the parts die?"

Yes → Composition ◆
No → Aggregation ◇

Another way: Was the part created inside the constructor? If yes — Composition. If it was passed in from outside — Aggregation.

Try It Yourself: Add a third Room type

The House has a LivingRoom and a Bedroom. Add a Kitchen with area 15 m² inside the constructor.

rooms.add(new Room("Kitchen", 15));

Think about GC: If the House is set to null, all three Room objects (Living, Bedroom, Kitchen) become eligible for garbage collection — because no external references exist.

Dependency

The weakest relationship. Class A uses Class B temporarily — through a method parameter, local variable, or return type — without holding a persistent reference.

Core Idea: "A Person uses a Taxi." The person doesn't own or hold onto the Taxi permanently. The relationship only exists during the taxi ride (the method call).
Code Example
Java public class Printer { public void print(String text) { System.out.println(text); } } public class Report { private String content; public Report(String content) { this.content = content; } // Dependency: Printer is a parameter // No field storing Printer public void printOut(Printer p) { p.print(this.content); } // After method ends, Printer ref gone }
3 Ways Dependency Appears
1
Method parameter: void printOut(Printer p) — the most common form
2
Local variable inside method: Formatter f = new Formatter(); — created and discarded locally
3
Return type: Printer getPrinter() — returns an object of another class

Comparison Table

A side-by-side reference of all Java OOP relationships.

Relationship Type Keyword Lifecycle UML Strength Real Example
Association HAS-A / USES-A Reference field Independent Solid line → Loose Teacher ↔ Student
Aggregation HAS-A (weak) Reference field (passed in) Part survives Solid + open diamond ◇ Weak Dept ◇── Professor
Composition HAS-A (strong) Field created in constructor Part dies with whole Solid + filled diamond ◆ Strong House ◆── Room
Dependency USES temporarily Method parameter / local var Momentary Dashed arrow - - → Weakest Report uses Printer
Generalization IS-A extends Child outlives parent Solid + filled arrow ▷ Tight Dog extends Animal

RELATIONSHIP STRENGTH SPECTRUM

💡 Decision Flowchart

1
"Is A a type of B?" → YES → Generalization (next chapter)
2
"Does A store a reference to B?" → YES → Go to step 3
3
"Does A create B inside its constructor?" → YES → Composition | NO → Aggregation or Association
4
"Does A only use B temporarily in a method?" → YES → Dependency

Knowledge Check

Test your understanding of OOP relationships in Java. Click an answer to check it.

YOUR SCORE
0 / 10