Nested objects are objects that contain other objects as fields. This creates a hierarchical structure where one object can "own" or "contain" other objects. This is a fundamental concept in object-oriented programming called composition.
Think of nested objects like:
- A House contains Rooms
- A Car contains an Engine and Wheels
- A Person has an Address
- A Library contains many Books
Objects contain other objects as fields:
class Car {
Engine engine; // Car HAS an Engine
Wheel[] wheels; // Car HAS Wheels
Person driver; // Car HAS a Driver
}Objects extend other objects:
class SportsCar extends Car { // SportsCar IS A Car
// Inherits all Car properties and methods
}Composition is often preferred because it's more flexible and creates clearer relationships.
One object contains another as a field:
class Person {
String name;
Address address; // Person contains an Address
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
public String getCity() {
return address.getCity(); // Delegate to nested object
}
}One object contains multiple objects in collections:
class Classroom {
String roomNumber;
ArrayList<Student> students; // Classroom contains many Students
Teacher teacher; // Classroom contains one Teacher
public void addStudent(Student student) {
students.add(student);
}
public int getStudentCount() {
return students.size();
}
}Objects containing objects that contain other objects:
class University {
ArrayList<Department> departments;
}
class Department {
ArrayList<Course> courses;
ArrayList<Professor> professors;
}
class Course {
ArrayList<Student> enrolledStudents;
Professor instructor;
}Objects that reference each other:
class Course {
ArrayList<Student> students;
public void enrollStudent(Student student) {
students.add(student);
student.enrollInCourse(this); // Update both objects
}
}
class Student {
ArrayList<Course> courses;
public void enrollInCourse(Course course) {
courses.add(course);
}
}Pass nested objects to constructors:
class Order {
String orderId;
Customer customer;
ArrayList<OrderItem> items;
public Order(String orderId, Customer customer) {
this.orderId = orderId;
this.customer = customer;
this.items = new ArrayList<>();
}
}Create complex nested structures:
class PersonFactory {
public static Person createPersonWithAddress(String name, String street,
String city, String state) {
Address address = new Address(street, city, state);
return new Person(name, address);
}
}Build complex objects step by step:
class OrderBuilder {
private Order order;
public OrderBuilder(String orderId, Customer customer) {
order = new Order(orderId, customer);
}
public OrderBuilder addItem(Product product, int quantity) {
OrderItem item = new OrderItem(product, quantity);
order.addItem(item);
return this;
}
public Order build() {
return order;
}
}class Person {
Address address;
public String getCity() {
if (address != null) {
return address.getCity();
}
return "Unknown";
}
}
// Usage
Person person = new Person("Alice", address);
String city = person.getCity(); // Accesses nested object propertyclass Account {
Customer owner;
public String getOwnerCity() {
return owner.getAddress().getCity(); // Chain through nested objects
}
}class Employee {
Department department;
public String getDepartmentName() {
if (department != null) {
return department.getName();
}
return "No Department";
}
// Or more concise:
public String getDepartmentNameSafe() {
return department != null ? department.getName() : "No Department";
}
}Delegate method calls to nested objects:
class Car {
Engine engine;
public void start() {
engine.start(); // Delegate to engine
}
public int getHorsepower() {
return engine.getHorsepower(); // Delegate to engine
}
}Perform operations across collections of nested objects:
class ShoppingCart {
ArrayList<CartItem> items;
public double calculateTotal() {
double total = 0.0;
for (CartItem item : items) {
total += item.getSubtotal(); // Each item calculates its own total
}
return total;
}
public int getTotalQuantity() {
return items.stream().mapToInt(CartItem::getQuantity).sum();
}
}Navigate through complex object structures:
class Company {
ArrayList<Department> departments;
public ArrayList<Employee> getAllEmployees() {
ArrayList<Employee> allEmployees = new ArrayList<>();
for (Department dept : departments) {
allEmployees.addAll(dept.getEmployees());
}
return allEmployees;
}
public Employee findEmployee(String employeeId) {
for (Department dept : departments) {
Employee emp = dept.findEmployee(employeeId);
if (emp != null) {
return emp;
}
}
return null;
}
}Treat individual objects and collections uniformly:
interface FileSystemComponent {
int getSize();
void display();
}
class File implements FileSystemComponent {
String name;
int size;
public int getSize() { return size; }
public void display() { System.out.println("File: " + name); }
}
class Directory implements FileSystemComponent {
String name;
ArrayList<FileSystemComponent> components = new ArrayList<>();
public int getSize() {
return components.stream().mapToInt(FileSystemComponent::getSize).sum();
}
public void display() {
System.out.println("Directory: " + name);
for (FileSystemComponent component : components) {
component.display();
}
}
}class Person {
Passport passport; // One person has one passport
public void setPassport(Passport passport) {
this.passport = passport;
passport.setOwner(this); // Bidirectional relationship
}
}class Author {
ArrayList<Book> books; // One author has many books
public void addBook(Book book) {
books.add(book);
book.setAuthor(this); // Set reverse relationship
}
}class Student {
ArrayList<Course> courses;
public void enrollInCourse(Course course) {
if (!courses.contains(course)) {
courses.add(course);
course.addStudent(this); // Update both sides
}
}
}
class Course {
ArrayList<Student> students;
public void addStudent(Student student) {
if (!students.contains(student)) {
students.add(student);
student.enrollInCourse(this); // Update both sides
}
}
}Don't expose nested objects directly:
class BankAccount {
private ArrayList<Transaction> transactions;
// Don't do this - exposes internal collection
public ArrayList<Transaction> getTransactions() {
return transactions; // Dangerous!
}
// Better - return copy or read-only view
public ArrayList<Transaction> getTransactionHistory() {
return new ArrayList<>(transactions); // Return copy
}
// Even better - provide specific operations
public int getTransactionCount() {
return transactions.size();
}
public Transaction getLatestTransaction() {
return transactions.isEmpty() ? null : transactions.get(transactions.size() - 1);
}
}Always check for null nested objects:
class Employee {
Department department;
public String getDepartmentBudget() {
if (department != null && department.getBudget() != null) {
return department.getBudget().toString();
}
return "No budget information";
}
}Keep related objects in consistent states:
class Order {
Customer customer;
ArrayList<OrderLine> orderLines;
double total;
public void addOrderLine(OrderLine line) {
orderLines.add(line);
updateTotal(); // Maintain consistency
}
private void updateTotal() {
total = orderLines.stream().mapToDouble(OrderLine::getTotal).sum();
}
}Create nested objects only when needed:
class Report {
private ArrayList<ReportSection> sections;
public ArrayList<ReportSection> getSections() {
if (sections == null) {
sections = generateSections(); // Create only when needed
}
return sections;
}
}// Dangerous - can cause infinite loops
class Parent {
ArrayList<Child> children;
public String toString() {
return "Parent with " + children.toString(); // Calls Child.toString()
}
}
class Child {
Parent parent;
public String toString() {
return "Child of " + parent.toString(); // Calls Parent.toString() - INFINITE LOOP!
}
}// Dangerous
public String getEmployeeDepartmentName(Employee emp) {
return emp.getDepartment().getName(); // NullPointerException if department is null
}
// Safe
public String getEmployeeDepartmentName(Employee emp) {
Department dept = emp.getDepartment();
return dept != null ? dept.getName() : "No Department";
}// Poor - exposes internal structure
class Team {
public ArrayList<Player> players; // Public field - anyone can modify
}
// Better - controlled access
class Team {
private ArrayList<Player> players;
public void addPlayer(Player player) {
players.add(player);
}
public ArrayList<Player> getPlayers() {
return new ArrayList<>(players); // Return copy
}
}class Course {
ArrayList<Student> students;
public void addStudent(Student student) {
students.add(student);
// FORGOT: student.addCourse(this); // Relationship is now inconsistent
}
}When testing nested objects, consider:
@Test
public void testPersonWithAddress() {
Address address = new Address("123 Main St", "Springfield", "IL");
Person person = new Person("Alice", address);
assertEquals("Alice", person.getName());
assertEquals("Springfield", person.getAddress().getCity());
}@Test
public void testStudentCourseEnrollment() {
Student student = new Student("Bob", "S001");
Course course = new Course("CS101", "Programming");
student.enrollInCourse(course);
assertTrue(student.isEnrolledIn(course));
assertTrue(course.hasStudent(student));
}@Test
public void testNullNestedObject() {
Person person = new Person("Charlie", null); // No address
assertEquals("Unknown", person.getCity()); // Should handle null gracefully
}Nested objects are everywhere in real applications:
- Order contains Customer, OrderLines, ShippingAddress
- OrderLine contains Product, quantity, price
- Bank contains Customers, Accounts
- Customer contains Accounts, Transactions
- Account contains Transactions, Customer
- User contains Posts, Friends, Profile
- Post contains Comments, Likes, Author
- Game contains Players, Levels
- Player contains Inventory, Stats, Character
- Inventory contains Items
- Document contains Sections, Author, Metadata
- Section contains Paragraphs, Images
Remember: Nested objects help you model complex real-world relationships in your code. They make your programs more organized, maintainable, and closer to how we naturally think about systems!