L
LLLOS.ai
Learn
L

Chapter 10 — Inheritance and Interfaces

Class 12 · Computer Science

Overview

This unit explains inheritance and interfaces in object-oriented programming for Class 12 Computer Science. It covers how classes can derive properties and behaviour from other classes (inheritance) and how interfaces define contracts that classes implement. The unit presents syntax and rules for extending classes, types of inheritance commonly used, method overriding, the role of super and constructor chaining, and access control that affects inheritance. It shows how abstract classes and interfaces differ and how modern interfaces can include default and static methods. The unit also explains how multiple inheritance of type works through interfaces, how polymorphism results from inheritance and interfaces, and how final and instanceof affect class hierarchies. Students are shown design principles such as Liskov Substitution and when to prefer composition over inheritance. Practical examples — banking accounts, shapes, listeners — and UML class diagrams illustrate design and implementation. Learning this unit prepares students to write clear, reusable, and maintainable object-oriented programs, to design class hierarchies responsibly, and to answer board-level programming and design questions with correct code and diagrams.

Learning Objectives

  • Explain the concept of inheritance and why it supports code reuse and extensibility.
  • Differentiate between types of inheritance: single, multilevel and hierarchical, and state which are supported by Java-like languages.
  • Implement and demonstrate method overriding, use of super, and constructor chaining in derived classes.
  • Describe access control (public, protected, private, default) and how it affects inherited members.
  • Define and use abstract classes and abstract methods to design partially implemented types.
  • Define interfaces, implement them in classes, and show how interfaces enable multiple inheritance of type.
  • Demonstrate polymorphism using reference variables of superclass or interface types.
  • Use final, instanceof and default/static methods in interfaces appropriately.
  • Design simple UML class diagrams illustrating inheritance and interface implementation.

Topics in this chapter

14 topics · tap a topic title to jump straight to it.

💻1

Object-Oriented Concepts and Motivation for Inheritance

What object-oriented programming models
Object-oriented programming (OOP) models software using classes and objects. A class groups data (attributes) and operations (methods) that act on that data. Objects are instances of classes. OOP supports concepts like encapsulation, inheritance and polymorphism. These ideas help manage complexity when programs grow.

Why inheritance exists
Inheritance exists to capture common features shared by several classes in a single place. When different types share attributes and behaviour, we move the shared code into a common superclass. Subclasses then inherit that code and add or specialise behaviour. This reduces duplication and helps keep the program consistent: if a shared method must change, update it in the superclass and all subclasses benefit.

Is-a versus has-a
Inheritance expresses an is-a relationship: a Car is a Vehicle. Composition expresses has-a: a Car has an Engine. Use inheritance only when the subclass truly is a specialised form of the superclass. Prefer composition when combining behaviours or when the relationship is part-whole rather than type-specialisation.

Benefits in practice
Major benefits include code reuse, improved organisation and easier extension. Code written to a superclass type can work with any subclass instance, enabling polymorphism. This makes it straightforward to add new classes: write a new subclass, and existing code that uses the superclass can accept objects of the new type without change.

Costs and cautions
Inheritance also has costs. Deep hierarchies increase coupling and can be fragile when superclass changes ripple through many subclasses. Overuse of inheritance for code reuse leads to unnatural hierarchies that violate design principles. Always evaluate whether subclassing models the domain correctly and whether composition or interfaces are better alternatives.

Real-world analogy and summary
Think of inheritance like a family tree: common family traits come from ancestors. In programming, a superclass provides common traits; subclasses inherit them and specialise. Use inheritance for clear is-a relationships, maintain open/closed design (open for extension, closed for modification) and prefer composition or interfaces when flexibility or multiple behaviours are required.

📌 Examples
  • Vehicle -> Car: move(), stop() are in Vehicle; Car adds trunkCapacity and airConditioning controls.
  • Animal -> Dog: eat() and sleep() in Animal; Dog adds bark().
  • UI Widget -> Button and TextBox: shared painting methods in Widget; each subclass customises drawing.
🧮 Formulas
  1. IS-A relationship: Subclass extends Superclass
  2. Prefer IS-A for inheritance; prefer HAS-A (composition) when relationship is part-whole
📊 Visual ideas
Simple class hierarchy: Vehicle at top, arrows to Car and Motorcycle below
Diagram contrasting is-a (inheritance) arrow vs has-a (composition) arrow
🧾2

Syntax of Inheritance, extends and Member Inheritance Rules

Declaring inheritance
To create a subclass from a superclass, use the extends keyword: class Sub extends Super { ... }. This tells the compiler that Sub should inherit accessible fields and methods from Super. Many Java-style languages allow only one immediate superclass (single class inheritance) though multiple interfaces may be implemented.

Which members are inherited
Members declared public or protected in the superclass are inherited by subclasses and are accessible according to visibility rules. Default (package-private) members are inherited only when subclass is in the same package. Private members are not directly accessible by subclasses; they still exist in the object but you cannot refer to them directly—use public or protected getters/setters to work safely with private state.

Static vs instance members
Static members belong to the class rather than to instances. Subclasses can access static members using the class name or through inheritance, but static methods are not dynamically dispatched — they are hidden, not overridden. Instance methods are subject to overriding and polymorphism.

Overloading versus overriding
Overloading means defining methods with the same name but different parameter lists in the same class (or in subclasses) — resolved at compile time. Overriding means a subclass provides a new implementation with the same signature as a superclass method — resolved at runtime for instance methods. Correct overriding requires identical parameter lists and compatible return type.

Constructors and inheritance
Constructors are not inherited. When creating an object, subclass constructors must ensure the superclass part is initialised by calling super(...) explicitly or implicitly. If the superclass lacks a no-argument constructor, the subclass must call a matching super(...) constructor; otherwise compilation fails.

Practical rules
Use public for truly public API, protected to allow subclass access where safe, and private for internal state. Prefer to expose behaviour through well-defined methods and keep fields private to maintain encapsulation. Use @Override (or equivalent) to ensure correct overriding and avoid accidental overloading.

📌 Examples
  • class A { protected int x; public void show() { ... } } class B extends A { public void show() { ... } } // B overrides show()
  • class A { private int secret; public int getSecret() { return secret; } } class B extends A { void use() { System.out.println(getSecret()); } }
🧮 Formulas
  1. class Subclass extends Superclass { }
  2. Overriding condition: same method signature (name + parameters) and compatible return type
📊 Visual ideas
Class Super with method m() and class Sub extends Super with overridden m()
Diagram showing static member belongs to class, instance members belong to objects
💻3

Types of Inheritance: Single, Multilevel and Hierarchical

Single inheritance
Single inheritance means a class extends one immediate superclass. This produces a simple tree of types and avoids ambiguity of multiple parent classes. Many mainstream languages like Java adopt single class inheritance for concrete classes to keep the model clear and predictable.

Multilevel inheritance
Multilevel inheritance chains classes across multiple levels: Grandparent -> Parent -> Child. Each subclass inherits from its immediate parent and indirectly from ancestors. When objects are constructed, constructors run from the topmost ancestor down to the most derived class; understanding this order is important to ensure correct initialisation.

Hierarchical inheritance
Hierarchical inheritance occurs when several subclasses extend the same superclass. This is common when a broad concept has several specialisations. For example, a Shape superclass may have Circle, Rectangle and Triangle as subclasses. Shared behaviour remains in the superclass and each subclass provides its specific details.

Why multiple class inheritance is often restricted
Languages often disallow multiple class inheritance because of problems like the diamond problem: if a class inherits from two classes that both inherit from a common ancestor and override the same method or hold state, ambiguity arises about which implementation or copy of state to use. Restricting class inheritance simplifies resolution and prevents subtle bugs.

Interfaces provide multiple inheritance of type
To gain flexibility without the diamond problem of state, languages allow classes to implement multiple interfaces. Interfaces provide method signatures (and possibly default methods) but do not carry instance state, so combining multiple interfaces avoids field ambiguity. When default methods conflict, the class must resolve the conflict explicitly by overriding the method.

Design guidance
Use single and multilevel inheritance to model true is-a hierarchies where subclasses are natural specialisations. Keep hierarchies shallow to reduce coupling. Use interfaces for orthogonal capabilities so a class can adopt many roles without inheriting state from multiple parents.

📌 Examples
  • Single: class Car extends Vehicle { }
  • Multilevel: class Animal -> Mammal -> Dog with constructors chained via super()
  • Hierarchical: class Shape with subclasses Circle, Rectangle, Triangle
🧮 Formulas
  1. Inheritance depth: Child -> Parent -> Grandparent ...
  2. Single class inheritance: one immediate superclass per class
📊 Visual ideas
Vertical chain for multilevel inheritance Grandparent -> Parent -> Child
Top node with multiple arrows to children for hierarchical inheritance
💻4

Method Overriding, Rules and Dynamic Dispatch

Overriding defined
Method overriding is when a subclass supplies a new implementation for a method declared in its superclass. The signature — method name, parameter types and order — must match exactly. The return type must be the same or covariant (a subtype) depending on the language rules. Overriding allows subclasses to change or extend the behavior inherited from their parent.

Visibility and exceptions
The overriding method must not reduce visibility — a public method in the superclass cannot be made protected or private in the subclass. For checked exceptions, many languages restrict the overriding method so it cannot declare new checked exceptions not present in the superclass method's throws clause; it may declare fewer or narrower exceptions.

Static and final considerations
Static methods belong to the class rather than an instance and are not dynamically dispatched; if a subclass declares a static method with the same signature it hides the superclass static method rather than overriding it. A final method cannot be overridden—this prevents subclasses from changing critical behaviour.

Dynamic method dispatch
Dynamic dispatch (runtime polymorphism) occurs when a reference of the superclass type refers to an instance of a subclass and an overridden method is called. The actual method executed is determined at runtime based on the object's real class. This is the basis for polymorphism: the same code (calling a method on a superclass reference) produces different results depending on the actual object type.

Practical patterns
Frameworks use overriding to let users customise behaviour: base classes provide a template method that calls overridden hooks in subclasses. Always use annotations like @Override (where available) to catch signature mistakes at compile time. When designing, ensure subclasses honour superclass contracts to avoid breaking client code (Liskov Substitution Principle).

Debugging tips
If the wrong method runs, check the reference type and the object's runtime class, ensure signatures match exactly, and look for accidental overloading. Print getClass().getName() in debugging to confirm the actual object type being used.

📌 Examples
  • Superclass: class Bird { void fly() { System.out.println("Bird flies"); } } Subclass: class Sparrow extends Bird { @Override void fly() { System.out.println("Sparrow flies low"); } }
  • Polymorphism: Bird b = new Sparrow(); b.fly(); // Sparrow.fly() runs at runtime
🧮 Formulas
  1. Overriding conditions: same method signature and compatible return type; access not reduced
  2. Dynamic dispatch: referenceType.method() => implementation determined by objectType at runtime
📊 Visual ideas
Reference of type Super pointing to objects of different Sub types to illustrate dynamic binding
💻5

The super Keyword, Field Shadowing and Constructor Chaining

super meaning and member access
The keyword super refers to the immediate superclass of the current class. It is used to access superclass members that are hidden or overridden by the subclass. For example, if a subclass defines a field with the same name as one in the superclass (field shadowing), using super.fieldName accesses the superclass field. Similarly, super.methodName(...) calls the superclass version of a method when you need to reuse or extend its behaviour.

Constructor chaining details
Constructors are not inherited, but object construction requires initialising the superclass part of the object. Subclass constructors invoke superclass constructors explicitly with super(arguments) as the first statement. If super(...) is omitted, most compilers insert a default no-argument super() call; if the superclass has no matching no-arg constructor, a compile-time error occurs. Chaining proceeds upward: when creating an instance, constructors are executed from the topmost superclass down to the subclass, after static initialisers and instance initialisers run in their own order.

Practical examples and passing parameters
When a superclass needs parameters at construction, the subclass must supply them. Example: class Person { Person(String name) { ... } } class Student extends Person { Student(String name, int id) { super(name); this.id = id; } } This ensures the Person part is properly initialised before Student handles its own fields.

Combining super and overriding
Often a subclass method overrides a superclass method but still wants to use the original behaviour as part of its new implementation. In that case call super.methodName(...) inside the overriding method. This is common when extending rather than replacing behaviour, for instance adding logging around the superclass logic.

Potential pitfalls
A common error is forgetting to call the right super(...) when required, causing compilation errors. Another problem is assuming private fields are inherited; they are not accessible directly, so use protected getters or public methods. Avoid deep constructor chains that make reasoning about initialisation hard—prefer simpler constructors or builder/factory patterns for complex cases.

📌 Examples
  • class A { A(int x) { ... } } class B extends A { B(int y) { super(y*2); ... } } // subclass passes parameter to superclass
  • Using super.method(): class Parent { void show() { System.out.println("P"); } } class Child extends Parent { @Override void show() { super.show(); System.out.println("C"); } }
🧮 Formulas
  1. Subclass constructor must call superclass constructor: super(...) as first statement if required
  2. Initialisation order: topmost superclass constructors run before subclass constructors
📊 Visual ideas
Stack-like constructor call chain: Child() -> Parent() -> Grandparent() showing order of execution
💻6

Access Control: public, protected, private and Default

Why access control matters
Access control is essential to maintain encapsulation and separate a class's public interface from its internal implementation. Correct access modifiers prevent misuse, reduce the chance of bugs, and make future refactoring safer because internal details can change without affecting clients. Inheritance interacts closely with access control: which members a subclass can use depends on their visibility.

Public visibility (+)
Public members are accessible from any other class, whether in the same package or a different one. Public methods form the API that clients use. Declaring a field public exposes internal representation and is usually discouraged; prefer public methods that provide controlled access instead. Public methods and constants are inherited and can be used by subclasses freely.

Protected visibility (#)
Protected members are accessible within the same package and also by subclasses even if those subclasses are in different packages. Protected is commonly used to give subclasses the ability to reuse or override helper methods or access state while keeping those members hidden from general external use. However, protected increases coupling because subclasses depend on parent internals; use it judiciously and prefer protected accessor methods over exposing fields directly.

Default (package-private) visibility
When no modifier is specified, members have default or package-private visibility: they are visible to all classes in the same package but not to classes in other packages. This supports package-level modularity: related classes in the same package can collaborate closely while hiding details from external packages. A subclass in a different package cannot access default members; this is a common source of confusion when classes are moved between packages.

Private visibility (-)
Private members are visible only inside the class where they are declared. Subclasses cannot directly access private fields or methods. Private is the strongest encapsulation and allows changing implementation without affecting subclasses. If subclasses need access, provide protected or public getters/setters that enforce invariants instead of exposing fields.

How inheritance is affected
Public and protected members are inherited by subclasses and available according to their visibility rules. Default members are inherited only when subclass resides in the same package. Private members are not accessible though they remain part of the object’s state and can be manipulated through public/protected methods. When designing class hierarchies, prefer private fields and protected/public methods for controlled subclass access to reduce coupling and preserve invariants.

Practical examples and recommendations
Declare fields private, and provide public or protected methods for necessary subclass access. Use protected for methods intended as extension points. Use package-private for classes that collaborate closely within a module. Avoid making everything public or protected—expose the minimum required. When you find subclasses accessing many protected fields, consider refactoring to use composition or clearer protected APIs to reduce brittle dependencies.

📌 Examples
  • class Super { private int secret; protected int p; public int pub; } class Sub extends Super { void show() { System.out.println(p); System.out.println(pub); /* secret is not accessible directly */ } }
  • Package example: classes A and B in same package can access default members; subclasses in other packages cannot.
🧮 Formulas
  1. Visibility order: private < default (package) < protected < public
  2. Inherited if public or protected (and default if same package)
📊 Visual ideas
Venn-like layout of visibility scopes: inner private, larger package, protected including subclasses, outer public
💻7

Abstract Classes, Abstract Methods and Template Design

Abstract classes explained
An abstract class is a type that cannot be instantiated directly because it represents a concept that is not complete on its own. It may include both concrete (fully implemented) methods and abstract methods that declare a signature but have no body. Abstract classes allow you to define common fields and behaviour while forcing subclasses to provide specific implementations for essential operations.

Abstract methods and obligations
An abstract method sets a requirement for subclasses: they must implement that method to become concrete. For example, an abstract class Shape may declare abstract double area(); this ensures every concrete shape class provides its own area computation. If a subclass does not implement all abstract methods, it too must be declared abstract. This mechanism ensures compile-time enforcement of expected capabilities across subclasses.

Why use abstract classes
Abstract classes are suitable when subclasses share significant code or state. They let you provide shared helper methods, default behaviours, and protected fields that subclasses can use. For example, an abstract Account class can manage a protected balance field and provide deposit(double) as a concrete method while leaving withdraw(double) abstract for account-specific rules.

Template Method pattern
The Template Method pattern is a common use of abstract classes. The abstract class defines a template method that outlines a sequence of steps, some implemented in the abstract class and others declared abstract for subclasses to fill in. This provides a stable algorithm structure while allowing customisation at defined points. It avoids duplication and enforces a consistent procedure across implementations.

Design and maintenance considerations
Prefer abstract classes when there is shared implementation or state; prefer interfaces when only method signatures are needed. Keep abstract classes focused: avoid mixing unrelated responsibilities. Document the intended contracts and invariants that subclasses must respect (for example, whether methods should be idempotent or thread-safe). Provide protected helper methods rather than exposing fields to reduce brittleness.

Practical examples and pitfalls
Common pitfalls include using abstract classes when no shared code exists (then interfaces are better), and exposing mutable protected fields that subclasses misuse. Use abstract classes to centralise common logic but keep extension points clear. When adding new behaviour to broad abstract classes, consider impact on existing subclasses; sometimes introducing a new interface with a default implementation is safer.

📌 Examples
  • abstract class Shape { String color; abstract double area(); void setColor(String c) { color = c; } } class Circle extends Shape { double area() { return Math.PI * r * r; } }
  • abstract class Account { protected double balance; abstract void withdraw(double amt); void deposit(double amt) { balance += amt; } }
🧮 Formulas
  1. abstract class ClassName { abstract returnType methodName(params); }
  2. Concrete subclass must implement all abstract methods unless it is abstract
📊 Visual ideas
Class block for abstract Shape with abstract area() and concrete setColor(), arrow to Circle implementing area()
🧾8

Interfaces: Contracts, Syntax and When to Use

Interfaces as contracts
An interface defines a set of method signatures that any implementing class must provide, forming a contract. Interfaces express capabilities or roles that multiple unrelated classes can share. When a class implements an interface, it promises to support the interface's methods, enabling code to work with any implementing class through the common interface type.

Syntax and implementation rules
Declare an interface with the interface keyword: interface I { returnType method(params); }. A class implements it using implements: class C implements I { public returnType method(params) { ... } }. All declared methods must be implemented by the concrete class unless the class is abstract. Historically, interfaces could only declare abstract methods and constants; modern language versions allow default and static methods to ease API evolution.

Why interfaces are useful
Interfaces allow multiple inheritance of type: a class can implement several interfaces and thus adopt multiple roles without inheriting state. This supports flexible design where behaviours are orthogonal. For example, a class can implement both Drivable and ElectricPowered interfaces to indicate it can be driven and recharged. Programming to interfaces decouples code from concrete implementations, improving testability and substitutability.

Design recommendations
Keep interfaces small and focused on a single responsibility (Interface Segregation Principle). Large interfaces force implementors to provide unnecessary methods. Use interfaces for plugin-style designs and for services where different implementations may be swapped, such as DataSource or Logger. When an interface needs to evolve, prefer default methods or introduce a new interface to avoid breaking existing implementors.

Concrete vs abstract classes
Choose an abstract class when you need to provide shared implementation or maintain common state across subclasses. Choose an interface when you only need to define capabilities that many unrelated classes might implement. Often combine both: provide an abstract base class with shared code and an interface for the public contract so different hierarchies can interoperate.

Practical pitfalls
A common mistake is defining very broad interfaces that change frequently; this forces many classes to be updated. Another is exposing implementation details through interface methods. Document intended behaviour and pre/post conditions for interface methods so implementors understand expected semantics.

📌 Examples
  • interface Drivable { void accelerate(int v); void brake(); } class Car implements Drivable { public void accelerate(int v) { ... } public void brake() { ... } }
  • Multiple: class SmartPhone implements Camera, MusicPlayer { public void click() { } public void play() { } }
🧮 Formulas
  1. interface I { returnType method(params); } class C implements I { public returnType method(params) { ... } }
  2. A class may implement multiple interfaces: class C implements I1, I2, ... { }
📊 Visual ideas
Interface box with methods and arrows (implements) from classes A and B showing they implement the interface
🧴9

Multiple Inheritance via Interfaces, Default Methods and Conflict Resolution

Multiple inheritance of type
While many languages prevent a class from extending multiple concrete classes, they allow a class to implement multiple interfaces. This gives the benefits of multiple inheritance—being able to act as several types—without inheriting multiple copies of state. Interfaces specify behaviour contracts; a class can combine several contracts to offer composite functionality.

Default methods and why they exist
Default methods in interfaces let interface designers provide a standard implementation so that adding a new method to an interface does not break existing implementors. A default method has a body and is marked default. Implementing classes inherit that behaviour automatically but can override it. This eases API evolution for widely used interfaces.

Possible conflicts and their handling
If a class implements two interfaces that both provide a default method with the same signature, there is a conflict that the compiler will force you to resolve. The implementing class must override the method and provide a concrete implementation. Many languages allow calling a specific interface's default implementation from the overriding method using a qualified call like InterfaceName.super.method(). Also, if a superclass provides a concrete method and an interface provides a default method with the same signature, the superclass method usually takes precedence.

Design guidelines
Design interfaces small and focused to reduce the chance of conflicts. Use default methods sparingly and only for backward compatibility or when a common behaviour is genuinely appropriate for most implementors. When combining many interfaces, clearly document semantic expectations to avoid accidental behaviour mismatch across implementors.

Practical example and pattern
Suppose interfaces A and B both have default void log() implementations. A class C implements A, B — the compiler requires C to override log() and choose behaviour. Inside C, you might combine behaviours: A.super.log(); B.super.log(); or implement entirely new logic. This explicit resolution keeps behaviour clear and avoids the diamond ambiguity of state.

When to prefer other approaches
If shared behaviour requires state, prefer an abstract class or composition because interfaces should not own instance fields. If many default methods are needed, consider providing an abstract helper class that implements the interface and holds common state, or use composition to delegate to helper objects. This keeps interfaces focused on contracts and classes on stateful behaviour.

📌 Examples
  • class Device implements Chargeable, Connectable { public void charge() { ... } public void connect() { ... } }
  • Conflict resolution: interface A { default void m() { ... } } interface B { default void m() { ... } } class C implements A,B { public void m() { A.super.m(); /* combine or override */ } }
🧮 Formulas
  1. class C implements I1, I2 { ... }
  2. If default methods conflict, implementing class must override to resolve
📊 Visual ideas
Diagram showing class C with dashed implementation arrows from interfaces I1 and I2, indicating multiple implementation
💻10

Polymorphism: Static vs Dynamic, Interface Polymorphism and Casting

Polymorphism overview
Polymorphism allows code to treat different types uniformly through a common interface or superclass. The same operation can behave differently depending on the object's actual class. Polymorphism promotes extensibility: new types can be introduced and integrated with existing code that relies on the common abstract type.

Static (compile-time) polymorphism
Static polymorphism is achieved by method overloading. When there are multiple methods with the same name but different parameter lists, the compiler resolves which method to call based on the compile-time types of arguments. Because resolution happens at compile time, runtime object types do not affect which overloaded method is chosen.

Dynamic (runtime) polymorphism
Dynamic polymorphism is achieved through method overriding. A superclass reference can point to an instance of a subclass. When calling an overridden instance method through the superclass reference, the runtime determines which implementation to execute based on the actual object's class. This enables behaviour that depends on the concrete type while keeping code general and reusable.

Interface polymorphism
Interfaces enhance polymorphism by allowing variables and parameters to be typed by interface rather than concrete class. Any class implementing the interface can be passed where the interface type is expected. This decouples code from implementation details and enables mechanisms like dependency injection and mocking for testing.

Casting and instanceof
At times you need subclass-specific features not present on the declared reference type. Downcasting converts a general reference to a more specific type, but it must be done carefully. Use instanceof to test before casting to prevent runtime exceptions. Excessive casting suggests the design should be refactored to expose the needed behaviour via polymorphic methods rather than type checks.

Practical advice
Design functions to accept the most general useful type (interface or superclass) to maximise reuse. Use polymorphism to handle new types without modifying existing code. Prefer overriding and interfaces to explicit type checks, and use instanceof and casting sparingly for exceptional cases like object deserialisation or mixed-type collections.

📌 Examples
  • Overloading: void print(int x) and void print(String s) — compile-time selection.
  • Overriding: Animal a = new Cat(); a.sound(); // Cat.sound() runs at runtime
  • Interface: List l = new ArrayList(); l.add(...); works with any List implementation
🧮 Formulas
  1. Static polymorphism = method overloading (compile-time)
  2. Dynamic polymorphism = method overriding + superclass reference to subclass object (runtime)
📊 Visual ideas
Diagram: Super reference pointing to different Sub objects showing runtime binding
💻11

final, instanceof and Safe Type Handling

The final modifier
final is a modifier that restricts further change. A final class cannot be subclassed; this is useful when providing immutable types or when preventing extension for security or correctness reasons. A final method cannot be overridden, ensuring stable behaviour across subclasses. A final variable cannot be reassigned after initialization; when applied to a field that refers to an object, the reference is fixed though the object's internal state may still change if it is mutable.

Using final effectively
Use final for constants, and consider final for classes or methods that must not change for design reasons. For example, utility classes that are not intended to be extended can be declared final. Marking fields final helps make classes immutable, which simplifies reasoning about code and makes objects thread-safe by design when combined with private fields and no mutators.

instanceof operator for safe checks
instanceof tests whether an object is an instance of a specified type or its subtype, returning a boolean. It is commonly used to guard downcasts: if (obj instanceof Circle) { Circle c = (Circle) obj; ... }. Using instanceof before casting prevents ClassCastException at runtime. Note that instanceof returns false for null references.

Limitations and design considerations
Relying heavily on instanceof and casting often signals that the design is not using polymorphism effectively. Prefer adding abstract or interface methods so behaviour is invoked polymorphically rather than by checking types. Use instanceof in limited scenarios such as deserialisation, interoperability code or when working with truly heterogeneous collections where polymorphic methods cannot express the required behaviour.

Combining final, instanceof and design
When a class is final, you know it has no subclasses; instanceof checks are therefore simpler. For API design, document why a type is final and how clients should interact with it. When designing frameworks, avoid forcing clients to perform many instanceof checks: design extension points and visitor-like patterns that let objects participate without revealing their concrete types.

Practical tips
Prefer to program to interfaces, avoid leaking implementation details, declare fields private, use final for constants and immutable classes, and reserve instanceof for rare cases. These practices make code safer, clearer and easier to maintain.

📌 Examples
  • final class Utility { /* cannot be extended */ }
  • Object obj = new String("hi"); if (obj instanceof String) { String s = (String) obj; System.out.println(s.length()); }
🧮 Formulas
  1. final class C { } // cannot be extended
  2. obj instanceof Type // checks runtime type before casting
📊 Visual ideas
Diagram showing reference type Super pointing to object of type Sub; instanceof(Sub) is true
💻12

Adapter Classes, Marker Interfaces and Modern Alternatives

Adapter classes explained
Adapter classes implement an interface by providing empty or default method bodies so that subclasses can override only the methods they need. This reduces boilerplate when an interface declares many methods but a concrete implementation needs only a few. Adapter classes are often abstract and serve as convenience base classes for event listeners or callback handlers.

Why adapters are useful
In event-driven APIs, listener interfaces may have multiple callback methods for different events. Implementing all methods every time is tedious and error-prone. By extending an adapter with empty implementations, a class overrides only the callbacks it cares about, improving clarity and reducing code volume. This pattern is widely used in GUI frameworks where many listener methods are optional.

Marker interfaces and their intent
Marker interfaces are empty interfaces used to tag classes with a property that can be tested at runtime using instanceof. A classic example is an interface indicating serialisability. Marker interfaces provide a simple typed signal to frameworks or libraries. They are lightweight but limited compared to annotations because they cannot carry additional metadata beyond type identity.

Modern alternatives: default methods and annotations
With default methods, interfaces can supply common implementations and reduce the need for separate adapter classes. Annotations provide richer metadata than marker interfaces and are often preferred for declaring properties or behaviours that frameworks inspect at runtime. When designing new APIs, consider whether default methods or annotations better serve goals than adapters or markers.

Design guidance and pitfalls
Use adapter classes when maintaining compatibility with legacy interfaces that have many methods and adding defaults is not possible. Use marker interfaces sparingly and document why a class is marked. Prefer annotations when you need metadata beyond a simple tag. Keep the API consistent so implementors can choose clear, minimal extension points without being forced into large inheritance chains.

📌 Examples
  • ListenerAdapter that implements MouseListener with empty methods; specific class extends ListenerAdapter and overrides mouseClicked() only.
  • Marker interface Persistable used to tag classes for persistence checks: if (obj instanceof Persistable) { persist(obj); }
🧮 Formulas
  1. Adapter: abstract class Adapter implements Interface { public void method1() {} ... }
  2. Marker: interface Marker { } // empty
📊 Visual ideas
Diagram: Interface -> Adapter (implements) -> ConcreteClass (extends Adapter) to show reduction of boilerplate
💻13

Design Principles: Liskov, Interface Segregation and Composition

Design goals with inheritance and interfaces
Design should balance reuse, clarity and flexibility. Key principles guide how and when to use inheritance and interfaces so systems remain maintainable and extensible. Well-chosen abstractions reduce code changes when new features are added.

Liskov Substitution Principle (LSP)
LSP requires that objects of a subclass should be usable wherever the superclass is expected without breaking correctness. Subclasses must honour the contracts (behavioural expectations) of their superclasses. Violations happen when subclasses change observable behaviour in ways clients do not expect. For example, if a subclass throws extra exceptions or changes method semantics, code expecting the superclass behaviour may fail.

Interface Segregation Principle
Interface segregation advises splitting large interfaces into smaller, focused ones so clients implement only what they need. This prevents forcing classes to implement irrelevant methods and makes mocking and testing easier. Small interfaces are easier to understand, implement and evolve. For instance, instead of one large Printer interface, provide separate Printable and Faxable interfaces for different client needs.

Prefer composition over inheritance
Composition means a class holds references to other classes and delegates behaviour to them (has-a). It is often preferable because it avoids tight coupling inherent in inheritance and allows behaviour to be changed at runtime by swapping components. Composition supports the Open/Closed Principle: classes are open for extension by providing new components but closed for modification.

Refactoring and evolution strategies
If many classes share code, consider extracting a superclass or a helper component used via composition. When many instanceof checks appear, introduce polymorphic methods or visitor patterns. Use adapter or facade classes to simplify complex APIs. When adding methods to widely implemented interfaces, prefer default methods or create new interfaces to avoid breaking existing code.

Practical checklist
Ask whether a relationship is truly is-a before using inheritance; keep hierarchies shallow; keep fields private and provide controlled accessors; use interfaces to express roles; prefer composition for flexible behaviour; and document invariants subclasses must maintain. Following these practices reduces bugs and supports long-term maintenance.

📌 Examples
  • LSP violation: making Square extend Rectangle may break client code if Square changes setWidth behaviour.
  • Composition: class Car { Engine engine; } uses an Engine instance instead of inheriting from Engine.
🧮 Formulas
  1. Design rule: Prefer composition (has-a) over inheritance (is-a) when appropriate
  2. Interface segregation: several small interfaces are better than one large interface
📊 Visual ideas
Diagram contrasting inheritance (is-a) arrow to composition (has-a) with a delegation arrow
🔷14

UML Diagrams, Case Studies (Banking, Shapes) and Common Errors

UML for classes and interfaces
UML class diagrams are a concise way to show class names, attributes and operations, and relationships such as inheritance and implementation. A class is a rectangle with compartments. Inheritance uses a solid line with a hollow closed arrow pointing to the superclass. Interface implementation is shown with a dashed line and an arrow or with «interface» notation. Visibility is marked with +, -, and # for public, private and protected respectively.

Banking case study
Model a bank with an abstract Account class that holds protected double balance and methods deposit() and withdraw(). SavingsAccount extends Account and implements addInterest(); CurrentAccount extends Account and adds overdraftLimit. Use polymorphism so banking code can hold references of type Account and operate on any account subtype. A Payable interface with pay(amount) can be implemented by Employee and Vendor classes to handle payments uniformly.

Shapes case study
Define an abstract Shape with abstract double area() and concrete setColor(). Circle and Rectangle extend Shape and implement area(). Use a collection of Shape references to compute total area polymorphically. This shows reuse and extension: adding a new shape only needs a new subclass without changing existing code that operates on Shape.

Common student errors and debugging
Typical mistakes include trying to access private superclass fields directly, forgetting to call super(...) when required, accidentally overloading instead of overriding due to signature mismatch, and unsafe downcasts without instanceof checks. Use @Override to catch overriding errors, print getClass().getName() to verify runtime types, and check constructor availability when compilation fails.

Best practices summary
Keep fields private, expose behaviour via methods, use interfaces for capabilities, prefer composition when no true is-a exists, avoid very deep hierarchies, and design small focused interfaces. When adding API methods, consider default methods or adapter classes to avoid breaking existing implementors. Draw clear UML diagrams for design and show key members and relationships when answering board questions.

📌 Examples
  • UML: abstract Account with deposit(), withdraw(); subclasses SavingsAccount and CurrentAccount with their specific methods.
  • Common error: forgetting to implement an abstract method in a concrete subclass leads to compile error.
🧮 Formulas
  1. Total area: total = sum of s.area() for each s in collection
  2. Constructor rule: if superclass lacks no-arg constructor then subclass must call super(args)
📊 Visual ideas
UML for Account with subclasses SavingsAccount and CurrentAccount; UML for Shape with Circle and Rectangle

Key Concepts

Inheritance
A mechanism where a class (subclass) acquires fields and methods from another class (superclass) forming an is-a relationship.
Superclass / Base class
The class whose members are inherited by another class.
Subclass / Derived class
A class that extends another class, inheriting and possibly overriding its members.
Method overriding
A subclass provides a new implementation for a method declared in its superclass with the same signature.
super
A keyword used to refer to the immediate superclass's members and to invoke its constructor.
Constructor chaining
The process where subclass constructors call superclass constructors in sequence to initialise the object.
Access modifiers
Keywords like public, protected, private, and default that control visibility of class members.
Abstract class
A class that cannot be instantiated and may contain abstract methods to be implemented by subclasses.
Interface
A type that declares method signatures that implementing classes must define, representing a contract.
Polymorphism
The ability of code to use objects of different classes through a common interface or superclass.
final
A modifier that prevents further modification: final class cannot be subclassed, final method cannot be overridden, final variable cannot be reassigned.
instanceof
An operator that tests whether an object is an instance of a specified type or its subtype.
Default method
A method in an interface with a default implementation available to implementing classes.
Static method in interface
A utility method defined in an interface that is called using the interface name.
Adapter class
A class that implements an interface with default or empty methods so subclasses can override selectively.
Marker interface
An empty interface used to mark classes for special treatment at runtime.
Liskov Substitution Principle
A design principle stating subclasses must be usable in place of their superclasses without changing program correctness.

Practice Questions

  1. Explain inheritance and give one advantage. / उत्तराधिकार (Inheritance) क्या है और एक लाभ बताइए।
    Show answer

    Inheritance is a mechanism where a class (subclass) acquires properties and methods from another class (superclass), forming an is-a relationship. An advantage is code reuse: shared fields and methods are defined once in the superclass and reused by subclasses, reducing duplication. / उत्तराधिकार एक ऐसी प्रणाली है जिसमें एक कक्षा (सबक्लास) दूसरी कक्षा (सुपरक्लास) की गुणधर्म और विधियाँ प्राप्त करती है, जो कि 'is-a' संबंध दर्शाती है। एक लाभ कोड पुन: उपयोग है: सामान्य क्षेत्र और विधियाँ सुपरक्लास में एक बार परिभाषित कर सबक्लास द्वारा फिर से उपयोग की जा सकती हैं, जिससे पुनरावृत्ति कम होती है।

  2. What is method overriding and how does dynamic method dispatch work? / मेथड ओवरराइडिंग क्या है और डायनामिक मेथड डिस्पैच कैसे काम करता है?
    Show answer

    Method overriding occurs when a subclass provides its own implementation of a superclass method with the same signature. Dynamic method dispatch means that when a superclass reference points to a subclass object, a call to an overridden instance method is resolved at runtime to the actual object's method implementation. / मेथड ओवरराइडिंग तब होती है जब एक सबक्लास उसी सिग्नेचर वाली सुपरक्लास मेथड का अपना कार्यान्वयन देती है। डायनामिक मेथड डिस्पैच का अर्थ है कि जब सुपरक्लास रेफरेंस किसी सबक्लास वस्तु की ओर संकेत करता है, तो ओवरराइड की गई इंस्टेंस मेथड का कॉल रन-टाइम पर उस वस्तु के वास्तविक मेथड पर निर्देशित होता है।

  3. Describe the difference between an abstract class and an interface. / एक abstract क्लास और एक interface में क्या अंतर है बताइए।
    Show answer

    An abstract class can have fields, constructors, concrete methods and abstract methods; it cannot be instantiated and is intended for classes that share code. An interface is a contract declaring method signatures (and possibly constants) that implementing classes must define; modern interfaces may also include default and static methods. Use abstract class for shared implementation with some common state; use interfaces to define capabilities across unrelated classes. / एक abstract क्लास में फील्ड, कन्स्ट्रक्टर, कॉन्क्रीट मेथड और abstract मेथड हो सकते हैं; इसे निष्पादित नहीं किया जा सकता और यह उन कक्षाओं के लिए है जो कुछ कोड साझा करती हैं। एक interface एक अनुबंध है जो मेथड सिग्नेचर (और संभवत: कन्स्टेंट) घोषित करता है जिन्हें लागू करने वाली कक्षाओं को परिभाषित करना होता है; आधुनिक इंटरफेस में default और static मेथड भी हो सकते हैं। साझा क्रियान्वयन और सामान्य स्थिति होने पर abstract क्लास उपयोग करें; असंबंधित कक्षाओं में समान क्षमता व्यक्त करने के लिए इंटरफेस उपयोग करें।

  4. Can a class implement multiple interfaces? Give a short example. / क्या एक कक्षा एक से अधिक इंटरफेस लागू कर सकती है? एक छोटा उदाहरण दें।
    Show answer

    Yes. A class can implement multiple interfaces, allowing it to promise several capabilities. Example: class SmartPhone implements Camera, MusicPlayer { public void click() { } public void play() { } } where Camera and MusicPlayer are interfaces. / हाँ। एक कक्षा कई इंटरफेस लागू कर सकती है, जिससे वह कई क्षमताओं का वादा कर सकती है। उदाहरण: class SmartPhone implements Camera, MusicPlayer { public void click() { } public void play() { } } जहाँ Camera और MusicPlayer इंटरफेस हैं।

  5. What does the super keyword do? Give two uses. / super कीवर्ड क्या करता है? इसके दो उपयोग बताइए।
    Show answer

    super refers to the immediate superclass. Two uses: (1) super.methodName(...) calls the superclass version of a method from a subclass. (2) super(arguments) invokes the superclass constructor from a subclass constructor to initialise inherited state. / super नजदीकी सुपरक्लास का संदर्भ देता है। दो उपयोग: (1) super.methodName(...) सबक्लास से सुपरक्लास की मेथड को कॉल करने के लिए। (2) super(arguments) सबक्लास कन्स्ट्रक्टर से सुपरक्लास कन्स्ट्रक्टर को कॉल करके विरासत में मिली स्थिति को आरम्भ करने के लिए।

  6. Why prefer composition over inheritance in some cases? / कुछ मामलों में inheritance की बजाय composition क्यों पसंद करनी चाहिए?
    Show answer

    Composition (has-a) is preferred when reuse does not reflect an is-a relationship. Composition makes designs more flexible because behaviour can be changed at runtime by swapping components, and it avoids tight coupling and fragile class hierarchies. Use composition to delegate responsibilities rather than force classes into unnatural inheritance. / जब पुन: उपयोग किसी is-a संबंध को नहीं दर्शाता तब composition (has-a) पसंद की जाती है। कंपोजिशन डिज़ाइन को अधिक लचीला बनाती है क्योंकि व्यवहार को रन-टाइम पर घटकों को बदलकर बदला जा सकता है, और यह कड़ी जुड़ाव और नाज़ुक क्लास हायरेरकी से बचाती है। अस्वाभाविक इनहेरिटेंस के बजाय दायित्वों को डेलिगेट करने हेतु कंपोजिशन का उपयोग करें।

  7. How do default methods in interfaces help with evolving APIs? / इंटरफेस में default मेथड्स एपीआई को विकसित करने में कैसे मदद करते हैं?
    Show answer

    Default methods provide a concrete implementation in the interface so new methods can be added without breaking existing implementors; classes that implement the interface inherit the default behaviour and can override it if needed. This supports backward compatibility when evolving APIs. / Default मेथड्स इंटरफेस में एक कॉन्क्रीट कार्यान्वयन देते हैं ताकि नई मेथड्स जोड़ी जा सकें बिना पहले से मौजूद implementors को तोड़े; इंटरफेस को लागू करने वाली कक्षाएँ default व्यवहार विरासत में पाएंगी और आवश्यकता होने पर इसे ओवरराइड कर सकती हैं। यह एपीआई के विकास में बैकवर्ड कंपैटिबिलिटी प्रदान करता है।

  8. Write a short code fragment showing safe downcasting using instanceof. / instanceof का उपयोग करके सुरक्षित डाउनकास्टिंग दिखाते हुए संक्षिप्त कोड लिखिए।
    Show answer

    Example: Object obj = new Circle(); if (obj instanceof Circle) { Circle c = (Circle) obj; System.out.println(c.area()); } This checks type before casting to avoid runtime exceptions. / उदाहरण: Object obj = new Circle(); if (obj instanceof Circle) { Circle c = (Circle) obj; System.out.println(c.area()); } यह कास्ट करने से पहले प्रकार की जाँच करता है ताकि रनटाइम त्रुटियों से बचा जा सके।

  9. State Liskov Substitution Principle with a brief example of violation. / Liskov Substitution Principle बताइए और इसका एक उल्लंघन उदाहरण संक्षेप में दीजिए।
    Show answer

    Liskov Substitution Principle: objects of a subclass should be replaceable for objects of the superclass without altering program correctness. Violation example: if Square extends Rectangle but Square changes setWidth or setHeight so that rectangle assumptions break, code using Rectangle may malfunction; this shows Square should not extend Rectangle. / लिस्कोव सब्स्टीट्यूशन प्रिंसिपल: सबक्लास की वस्तुएँ सुपरक्लास की वस्तुओं की जगह बिना प्रोग्राम की सहमति बदले उपयोग की जा सकती हैं। उल्लंघन उदाहरण: यदि Square, Rectangle को बढ़ाता है पर setWidth/setHeight व्यवहार को बदल देता है जिससे Rectangle पर आधारित कोड गलत काम करे, तो यह उल्लंघन है; इसका अर्थ है कि Square को Rectangle से बढ़ाना उपयुक्त नहीं था।

  10. Explain adapter classes and give one scenario where it is useful. / adapter क्लास क्या होती है और एक परिदृश्य बताइए जहाँ यह उपयोगी हो।
    Show answer

    An adapter class provides empty or default implementations of an interface so subclasses can override only methods they need. It is useful for listener interfaces with many methods, for example a MouseAdapter that implements MouseListener with empty methods so a class can override only mouseClicked(). / एक adapter क्लास इंटरफेस के लिए खाली या डिफ़ॉल्ट कार्यान्वयन प्रदान करती है ताकि सबक्लास केवल उन विधियों को ओवरराइड करे जिनकी आवश्यकता हो। यह उन listener इंटरफेस के लिए उपयोगी है जिनमें कई मेथड होते हैं; उदाहरण के लिए MouseAdapter जो MouseListener को खाली मेथड्स के साथ लागू करता है ताकि कोई क्लास केवल mouseClicked() ओवरराइड कर सके।

Related Laws & Principles

Explore all

Foundational laws & principles connected to this chapter — tap to open in the Laws Explorer.

Loading related laws…
Sourced from 0 content files · LLOS Learn · browse all chapters