IS-A vs HAS-A Relationship in Java – Inheritance vs Composition Explained with Code

Illustration for IS-A vs HAS-A Relationship in Java – Inheritance vs Composition Explained with Code
By Last updated:

Introduction

In Object-Oriented Programming (OOP), modeling real-world relationships between entities is key to writing maintainable and intuitive software. Two foundational relationships in Java OOP are IS-A and HAS-A, which represent inheritance and composition respectively.

Understanding the difference between these two is critical when designing Java applications. Should your class extend another or contain it as a member? The wrong choice leads to rigid, tightly coupled systems. This tutorial will walk you through the definitions, examples, UML representations, pros and cons, and best practices for using IS-A vs HAS-A relationships in Java.


What Is an IS-A Relationship in Java?

Definition

The IS-A relationship represents inheritance. It means one class is a subtype of another class or implements an interface.

Java Syntax

class Animal {
    void eat() {
        System.out.println("Eating...");
    }
}

class Dog extends Animal {
    void bark() {
        System.out.println("Barking...");
    }
}

In this case: Dog IS-A Animal

Use Cases

  • When there's a clear hierarchical relationship
  • You want to reuse and extend functionality
  • You plan to use polymorphism

UML Representation

Animal (Superclass)
↑
Dog (Subclass)

What Is a HAS-A Relationship in Java?

Definition

The HAS-A relationship represents composition. It means one class contains another as a member.

Java Syntax

class Engine {
    void start() {
        System.out.println("Engine starting...");
    }
}

class Car {
    private Engine engine = new Engine();

    void startCar() {
        engine.start();
        System.out.println("Car started.");
    }
}

In this case: Car HAS-A Engine

Use Cases

  • You want to reuse functionality without inheriting
  • You want better encapsulation and loose coupling
  • The “contained” object can vary (via dependency injection, strategies, etc.)

UML Representation

Car -----> Engine
(Composition)

Core Differences Between IS-A and HAS-A

Feature IS-A (Inheritance) HAS-A (Composition)
Meaning Subclass relationship Member object relationship
Code Reuse Yes, via inheritance Yes, via delegation
Flexibility Less flexible More flexible
Coupling Tight coupling Loose coupling
Polymorphism Supported Not directly supported
Change Impact Parent class changes affect all Independent behavior
Java Keywords extends, implements Regular class/object references
Inheritance Depth Can lead to deep hierarchies Usually flat

Real-World Analogy

  • IS-A: A Dog IS-A Animal, a Circle IS-A Shape
  • HAS-A: A Car HAS-A Engine, a Person HAS-A Address

When to Use IS-A (Inheritance)

  • When there's a clear generalization/specialization relationship
  • When shared behavior across classes is essential
  • When polymorphism is required

Example

class Bird {
    void fly() { System.out.println("Flying..."); }
}

class Sparrow extends Bird {
    void chirp() { System.out.println("Chirping..."); }
}

When to Use HAS-A (Composition)

  • When behavior changes dynamically or varies
  • To achieve better modularity and testability
  • To reduce coupling and improve separation of concerns

Example

class Engine {
    void ignite() { System.out.println("Igniting..."); }
}

class Truck {
    private Engine engine;

    Truck(Engine engine) {
        this.engine = engine;
    }

    void drive() {
        engine.ignite();
        System.out.println("Truck moving...");
    }
}

Refactoring Example

Before (IS-A Misuse)

class Printer extends Document {
    // Printer IS-A Document? ❌ Bad modeling
}

After (HAS-A Refactor)

class Document {}

class Printer {
    private Document doc;

    void print() { /* access doc and print */ }
}

Java 17/21 Considerations

Sealed Classes (Java 17)

Enforce strict IS-A hierarchies:

sealed class Shape permits Circle, Rectangle {}

final class Circle extends Shape {}
final class Rectangle extends Shape {}

Records (Java 16+)

Great for HAS-A relationships as they define simple data carriers:

record Address(String city, String country) {}

class Person {
    private Address address;
}

Pros and Cons

IS-A (Inheritance)

✅ Easy reuse of behavior
✅ Polymorphism support
❌ Tight coupling
❌ Fragile base class problem

HAS-A (Composition)

✅ Flexible and decoupled design
✅ Easier to test and mock
❌ May involve more delegation code
❌ Cannot leverage polymorphism directly


Best Practices

  • Favor composition over inheritance (per Effective Java)
  • Use inheritance only for "true IS-A" relationships
  • Avoid deep inheritance trees (max 2–3 levels)
  • Compose behaviors dynamically using interfaces or strategies
  • Refactor monolithic classes by extracting responsibilities into composed parts

Conclusion

Understanding the IS-A vs HAS-A relationship is fundamental to effective Java OOP design. Inheritance and composition both offer ways to reuse code and structure systems, but they differ in flexibility, coupling, and extensibility. Choosing the right model can dramatically affect the maintainability of your software.


Key Takeaways

  • IS-A = Inheritance (extends, implements); HAS-A = Composition (object references)
  • Use IS-A for specialization, HAS-A for modular behavior sharing
  • Prefer composition in modern design for flexibility and maintainability
  • Java 17 features like sealed classes can reinforce inheritance discipline

FAQ – IS-A vs HAS-A in Java

1. Can a class use both IS-A and HAS-A?
Yes. A subclass can contain member objects too.

2. What’s the problem with deep inheritance trees?
They become fragile and hard to maintain—prefer flatter structures.

3. Can interfaces help with composition?
Absolutely. Interfaces can be injected into classes for polymorphic composition.

4. When should I avoid inheritance?
If there's no true IS-A relationship or when the parent class might change frequently.

5. Is composition always better than inheritance?
Not always. But composition is more flexible and preferred in modern OOP.

6. Can I switch from inheritance to composition later?
Yes, but it’s better to decide early. Refactoring can be expensive.

7. Do Java records support inheritance?
No. Records are final and don't support inheritance.

8. Can I compose abstract classes?
Yes. You can include them as fields and delegate behavior.

9. Is delegation the same as composition?
Delegation is an implementation strategy within composition.

10. Can composition enable polymorphism?
Indirectly, yes—via interfaces or function pointers (lambdas).