Inheritance and Polymorphism in Java: Detailed Notes, Examples & Practice Questions (Object Oriented Programming)

Hello dear students! Today we are going to learn two very important topics in Object Oriented Programming: Inheritance and Polymorphism. These two topics carry a lot of marks in your Ethiopian university exam. So please read each part carefully and try all the questions I give you. Ready? Let us start!

What is Inheritance?

Inheritance is one of the four pillars of Object Oriented Programming. The word itself tells us the meaning. Just like a child inherits some properties from their parents, in programming, a new class can inherit the properties and behaviors of an existing class.

Think about it this way. Your father has a house, a car, and some money. When you grow up, you get access to all those things without buying them yourself. In the same way, a child class gets all the attributes and methods of its parent class without writing them again.

The class that gives its properties is called the Parent Class (or Superclass or Base Class). The class that receives the properties is called the Child Class (or Subclass or Derived Class).

Parent Class (Superclass) |— name |— age |— displayInfo() | | (inherits) | v Child Class (Subclass) |— name (inherited) |— age (inherited) |— displayInfo() (inherited) |— studentId (own property) |— calculateGrade() (own method)

The main advantage of inheritance is code reusability. You write code once in the parent class, and many child classes can use it. This saves time and reduces errors.

Important Notes to Remember for Exam:
1. Inheritance uses the “is-a” relationship. For example, a Student IS A Person, a Car IS A Vehicle.
2. The keyword used for inheritance in Java is extends. In C++, it is a colon :
3. A child class inherits all NON-private members of the parent class.
4. Private members of the parent class are NOT directly accessible in the child class.
5. Java does NOT support multiple inheritance with classes (a class cannot extend more than one class).
6. Constructors are NOT inherited by the child class.

Syntax of Inheritance in Java

class ParentClass { // attributes and methods } class ChildClass extends ParentClass { // additional attributes and methods }

Simple Example of Inheritance

Let me give you a very clear example. Suppose we have a class called Person and we want to create a Student class that inherits from Person.

class Person { String name; int age; void displayInfo() { System.out.println(“Name: ” + name); System.out.println(“Age: ” + age); } } class Student extends Person { int studentId; String department; void displayStudentInfo() { displayInfo(); // calling parent class method System.out.println(“Student ID: ” + studentId); System.out.println(“Department: ” + department); } } public class Main { public static void main(String[] args) { Student s = new Student(); s.name = “Abebe”; s.age = 20; s.studentId = “ERT1234”; s.department = “Computer Science”; s.displayStudentInfo(); } }

Output:

Name: Abebe Age: 20 Student ID: ERT1234 Department: Computer Science

Look at this carefully. Inside the Student class, we never declared name and age. We also never wrote the displayInfo() method. But we are using all of them because Student inherited them from Person. This is the power of inheritance!

MCQ

Question 1: Which keyword is used to inherit a class in Java?

A) implements    B) extends    C) inherits    D) super

Answer: B) extends

The extends keyword is used in Java to create a child class from a parent class. For example, class Student extends Person. The implements keyword is used for interfaces, not classes. The super keyword is used to refer to the parent class, but it is not used for inheritance itself. The word inherits is not a keyword in Java.

MCQ

Question 2: A child class in Java can directly access which members of the parent class?

A) Only public members    B) Only protected members    C) Public and protected members    D) All members including private

Answer: C) Public and protected members

In Java, a child class can directly access public and protected members of the parent class. Private members are only accessible within the same class. Default (package-private) members are accessible only if both classes are in the same package. So the correct answer is C because both public and protected are always accessible to the child class regardless of the package.

Short Answer

Question 3: What is code reusability in the context of inheritance? Explain with a simple example.

Answer: Code reusability means writing code once in a parent class and using it in multiple child classes without rewriting it. For example, if a Vehicle class has methods like start() and stop(), then Car, Bike, and Bus classes that extend Vehicle can all use these methods. We do not need to write start() and stop() separately in each class. This saves time, reduces code duplication, and makes maintenance easier because if we need to change the start() method, we change it only in one place.

Types of Inheritance

In Object Oriented Programming, there are different types of inheritance. Understanding each type is very important for your exam because questions often come from identifying the type from a diagram or code.

1. Single Inheritance

This is the simplest type. One child class inherits from one parent class. That is it. One parent, one child.

Person | | extends v Student
class Animal { void eat() { System.out.println(“This animal eats food.”); } } class Dog extends Animal { void bark() { System.out.println(“The dog barks.”); } }

2. Multilevel Inheritance

In multilevel inheritance, there is a chain of inheritance. Class B inherits from Class A, and then Class C inherits from Class B. It is like a grandfather, father, and son relationship.

Animal | | extends v Mammal | | extends v Dog
class Animal { void eat() { System.out.println(“Animal eats food.”); } } class Mammal extends Animal { void breathe() { System.out.println(“Mammal breathes air.”); } } class Dog extends Mammal { void bark() { System.out.println(“Dog barks.”); } } // Dog can use eat(), breathe(), and bark()

See how the Dog class has access to methods from both Animal and Mammal? This chain can go as long as you need, but in practice, keeping it short is better for readability.

3. Hierarchical Inheritance

In hierarchical inheritance, one parent class has multiple child classes. Think of one father having many children. Each child inherits from the same parent.

Animal / | \ / | \ v v v Dog Cat Bird
class Animal { void eat() { System.out.println(“Animal eats food.”); } } class Dog extends Animal { void bark() { System.out.println(“Dog barks.”); } } class Cat extends Animal { void meow() { System.out.println(“Cat meows.”); } } class Bird extends Animal { void fly() { System.out.println(“Bird flies.”); } }

All three child classes (Dog, Cat, Bird) can use the eat() method from Animal, but each child also has its own special method.

4. Multiple Inheritance (Through Interfaces in Java)

Multiple inheritance means one child class inherits from more than one parent class at the same time. Now, here is a very important exam point: Java does NOT support multiple inheritance with classes. This is because of the “Diamond Problem” which we will discuss shortly.

However, Java does support multiple inheritance through interfaces. A class can implement multiple interfaces at the same time.

Interface A Interface B \ / \ / v v Class C (implements A, B)
interface Walkable { void walk(); } interface Swimmable { void swim(); } class Duck implements Walkable, Swimmable { public void walk() { System.out.println(“Duck walks.”); } public void swim() { System.out.println(“Duck swims.”); } }

5. Hybrid Inheritance

Hybrid inheritance is a combination of two or more types of inheritance. For example, combining hierarchical and multilevel inheritance together. In Java, this is only possible through interfaces because of the restriction on multiple inheritance with classes.

Important Notes to Remember for Exam:
1. Java supports Single, Multilevel, and Hierarchical inheritance with classes.
2. Java does NOT support Multiple inheritance with classes (Diamond Problem).
3. Java supports multiple inheritance only through interfaces.
4. C++ supports all types including multiple inheritance with classes.
5. In exam questions, if they show a diamond-shaped diagram, the answer is usually about the Diamond Problem.
MCQ

Question 4: Which type of inheritance is NOT supported by Java with classes?

A) Single    B) Multilevel    C) Multiple    D) Hierarchical

Answer: C) Multiple

Java does not allow a class to extend more than one class at the same time. This restriction exists to avoid the Diamond Problem. If class A has a method display(), and both class B and class C override it differently, and class D extends both B and C, then calling d.display() becomes ambiguous. Which version should run? Java avoids this problem by not allowing multiple inheritance with classes.

Identify Type

Question 5: Look at the following class structure and identify the type of inheritance:

class Vehicle { } class Car extends Vehicle { } class Truck extends Vehicle { } class Bus extends Vehicle { }

Answer: Hierarchical Inheritance

In this structure, one parent class (Vehicle) has three child classes (Car, Truck, Bus). When multiple child classes inherit from the same single parent class, it is called hierarchical inheritance. The shape of the diagram looks like a tree with one root and multiple branches.

The Diamond Problem

This is a very common exam question. Let me explain it clearly so you never get it wrong.

The Diamond Problem occurs in multiple inheritance when two parent classes inherit from the same grandparent class, and then a child class inherits from both parent classes. This creates a diamond shape in the diagram.

Class A / \ / \ v v Class B Class C \ / \ / v v Class D

Now imagine Class A has a method called show(). Both Class B and Class C override this method with their own version. When Class D inherits from both B and C, and we call d.show(), the compiler gets confused. Should it call B’s version or C’s version? This confusion is called the Diamond Problem.

Java solves this by simply not allowing multiple inheritance with classes. C++ solves it using something called “virtual inheritance.” But for your Java exam, just remember: Java avoids the Diamond Problem by not allowing a class to extend more than one class.

Exam Tip: Whenever you see a diamond-shaped diagram in the exam question, immediately think of the Diamond Problem. The question is probably asking why Java does not support multiple inheritance or how the Diamond Problem occurs.

The “super” Keyword

The super keyword is very important in inheritance. It is used to refer to the immediate parent class of a child class. You can use it for three main purposes:

1. To access parent class attributes:

class Parent { String name = “Parent Name”; } class Child extends Parent { String name = “Child Name”; void showNames() { System.out.println(name); // prints: Child Name System.out.println(super.name); // prints: Parent Name } }

2. To call parent class methods:

class Animal { void eat() { System.out.println(“Animal eats food.”); } } class Dog extends Animal { void eat() { System.out.println(“Dog eats dog food.”); } void showEating() { eat(); // calls Dog’s eat() super.eat(); // calls Animal’s eat() } }

3. To call parent class constructor:

class Person { Person() { System.out.println(“Person constructor called.”); } } class Student extends Person { Student() { super(); // must be the first statement System.out.println(“Student constructor called.”); } } // Output: // Person constructor called. // Student constructor called.
MCQ

Question 6: What happens if you write super() as the second statement in a child class constructor?

A) It works fine    B) Compile-time error    C) Runtime error    D) Calls grandparent constructor

Answer: B) Compile-time error

The super() call must ALWAYS be the very first statement in the constructor. If you put any statement before it, even a simple System.out.println(), the Java compiler will give an error. This is a rule enforced by Java to ensure that the parent class is fully initialized before the child class starts its own initialization.

Fill in the Blank

Question 7: The _________ keyword is used to access the parent class members when both parent and child classes have members with the same name.

Answer: super

When parent and child classes have attributes or methods with the same name, the child class version hides the parent class version. To access the parent class version from inside the child class, we use the super keyword. For example, super.name accesses the parent’s name variable, and super.method() calls the parent’s method.

Constructor Chaining in Inheritance

Now let me explain something that students often get confused about. What happens to constructors when we use inheritance?

Remember this rule: Constructors are NOT inherited. But when you create an object of a child class, the parent class constructor is still called. Why? Because the child class needs the parent class part to be initialized first.

Let me show you what happens step by step:

class A { A() { System.out.println(“A’s constructor”); } } class B extends A { B() { System.out.println(“B’s constructor”); } } class C extends B { C() { System.out.println(“C’s constructor”); } } public class Main { public static void main(String[] args) { C obj = new C(); } }

Output:

A’s constructor B’s constructor C’s constructor

Notice the order! Even though we only created object C, the constructors ran from top to bottom: A first, then B, then C. This is called constructor chaining. Each constructor implicitly calls super() which goes to its parent constructor.

Now what if the parent class does NOT have a no-argument constructor? Then the child class MUST explicitly call the parent constructor using super with the right parameters.

class Person { String name; Person(String n) { // no default constructor name = n; } } class Student extends Person { int id; Student(String n, int i) { super(n); // MUST call this explicitly id = i; } }
Important Notes to Remember for Exam:
1. If the parent class has only a parameterized constructor (no default), the child class MUST call super(parameters) explicitly.
2. Constructor chaining always starts from the topmost parent and goes down to the child.
3. Even with multilevel inheritance, the order is always from grandparent to parent to child.
4. A common exam trick is to show code where the parent has only a parameterized constructor but the child does not call super() with parameters. That code will NOT compile.
MCQ

Question 8: What will be the output of the following code?

class X { X() { System.out.print(“X “); } } class Y extends X { Y() { System.out.print(“Y “); } } class Z extends Y { Z() { System.out.print(“Z “); } } public class Test { public static void main(String[] args) { new Z(); } }

A) Z Y X    B) X Y Z    C) Y Z X    D) Compile error

Answer: B) X Y Z

When we create new Z(), Z’s constructor runs first. But before Z’s body executes, it calls super() which goes to Y’s constructor. Before Y’s body executes, it calls super() which goes to X’s constructor. X has no parent, so its body runs first and prints “X “. Then control returns to Y’s body, which prints “Y “. Then control returns to Z’s body, which prints “Z “. So the output is X Y Z.

What is Polymorphism?

The word “Polymorphism” comes from Greek. Poly means “many” and morph means “forms.” So polymorphism means “many forms.” In programming, it means the same thing can behave differently in different situations.

Let me give you a real-life example first. Think about the word “play.” A child says “I want to play” and means playing with toys. A musician says “I want to play” and means playing an instrument. A football player says “I want to play” and means playing football. The same word “play” has many forms depending on who is using it. That is polymorphism!

In programming, polymorphism allows us to:

  • Use the same method name to perform different tasks
  • Write flexible code that can work with different types of objects
  • Make our programs easier to extend and maintain

There are two main types of polymorphism in Java:

Polymorphism / \ / \ v v Compile-time Runtime (Static) (Dynamic) | | v v Method Method Overloading Overriding
Important Notes to Remember for Exam:
1. Polymorphism means “one interface, multiple implementations.”
2. Compile-time polymorphism is achieved through method overloading.
3. Runtime polymorphism is achieved through method overriding (with inheritance).
4. A very common exam question is to distinguish between overloading and overriding. Learn the differences well!

Method Overloading (Compile-Time Polymorphism)

Method overloading means having multiple methods in the same class with the same name but different parameters. The parameters must differ in number, type, or order.

The compiler decides at compile time which method to call based on the arguments you pass. That is why it is called compile-time polymorphism.

Rules for Method Overloading

  • Methods must have the same name
  • Methods must have different parameters (number, type, or order)
  • Return type alone is NOT enough to overload a method
  • Access modifier can be different
  • It happens in the same class
class Calculator { // Method 1: two int parameters int add(int a, int b) { return a + b; } // Method 2: three int parameters (different number) int add(int a, int b, int c) { return a + b + c; } // Method 3: two double parameters (different type) double add(double a, double b) { return a + b; } // Method 4: different order of parameters int add(int a, double b) { return (int)(a + b); } int add(double a, int b) { return (int)(a + b); } } public class Main { public static void main(String[] args) { Calculator calc = new Calculator(); System.out.println(calc.add(5, 3)); // calls Method 1: 8 System.out.println(calc.add(5, 3, 2)); // calls Method 2: 10 System.out.println(calc.add(5.5, 3.5)); // calls Method 3: 9.0 } }

Look at how the compiler knows which method to call. When we pass two integers, it calls the first method. When we pass three integers, it calls the second one. When we pass two doubles, it calls the third one. The decision is made at compile time.

Important Point: Return Type Alone Cannot Overload

// THIS IS WRONG – will NOT compile int calculate(int a, int b) { return a + b; } double calculate(int a, int b) { return a + b; }

The above code will give a compile error because both methods have the same name AND the same parameters. Even though the return type is different, Java cannot distinguish between them. The compiler looks at the method signature (name + parameters), not the return type.

Important Notes to Remember for Exam:
1. Overloading is determined at compile time (early binding).
2. Only parameters must be different, return type does not matter for overloading.
3. Changing only the return type is NOT valid overloading.
4. Overloading can also happen with constructors (constructor overloading).
5. Method signature = method name + parameter list (return type is NOT part of the signature).
True or False

Question 9: Two methods with the same name and same parameters but different return types are considered overloaded methods.

Answer: False

This is a very common trick question in exams. Two methods with the same name and same parameter list but different return types will cause a compile-time error. The Java compiler uses the method signature (method name + parameter list) to identify methods. Since both methods have the same signature, the compiler sees them as duplicate methods, regardless of the return type difference.

MCQ

Question 10: Method overloading is an example of which type of polymorphism?

A) Runtime polymorphism    B) Compile-time polymorphism    C) Both    D) Neither

Answer: B) Compile-time polymorphism

Method overloading is resolved at compile time. The compiler looks at the method call and matches it with the correct method definition based on the number and types of arguments. Since the decision is made before the program runs (at compile time), it is called compile-time polymorphism or static polymorphism or early binding.

Method Overriding (Runtime Polymorphism)

Method overriding is completely different from method overloading, even though their names sound similar. In method overriding, a child class provides a new implementation of a method that is already defined in its parent class.

The key idea is: the parent class has a method, and the child class says, “I do not like your version, I will write my own version.” The child class method must have the exact same signature as the parent class method.

Rules for Method Overriding

  • The method in the child class must have the same name as in the parent class
  • The method must have the same parameters (same number, same type, same order)
  • The return type must be the same or a covariant type (subclass of the original return type)
  • The access level cannot be more restrictive (can be same or less restrictive)
  • Private, static, and final methods cannot be overridden
  • Overriding happens between parent and child classes (needs inheritance)
class Animal { void makeSound() { System.out.println(“Animal makes a sound.”); } } class Dog extends Animal { @Override void makeSound() { System.out.println(“Dog barks: Woof Woof!”); } } class Cat extends Animal { @Override void makeSound() { System.out.println(“Cat meows: Meow Meow!”); } } class Cow extends Animal { @Override void makeSound() { System.out.println(“Cow moos: Moooo!”); } } public class Main { public static void main(String[] args) { Animal a1 = new Dog(); Animal a2 = new Cat(); Animal a3 = new Cow(); a1.makeSound(); // Dog barks: Woof Woof! a2.makeSound(); // Cat meows: Meow Meow! a3.makeSound(); // Cow moos: Moooo! } }

Now look at this very carefully. I declared the variables as Animal type, but the objects are Dog, Cat, and Cow. When I call makeSound(), Java does NOT look at the variable type. It looks at the actual object type and calls the method of that object. This decision happens at runtime, not at compile time. That is why it is called runtime polymorphism!

The @Override Annotation

You might have noticed the @Override annotation above the method. This is not required, but it is a very good practice. It tells the compiler, “I am trying to override a method from the parent class.” If you make a mistake (like a spelling error in the method name), the compiler will warn you. Always use @Override when overriding methods.

Access Modifier Rules for Overriding

The child class method cannot have a more restrictive access modifier than the parent class method. Think of it as: the child cannot have fewer rights than the parent.

Parent Method AccessChild Method Can BeChild Method Cannot Be
publicpublicprotected, default, private
protectedprotected, publicdefault, private
defaultdefault, protected, publicprivate
privateCannot be overridden (not visible)
Important Notes to Remember for Exam:
1. Overriding happens at runtime (late binding / dynamic dispatch).
2. The method signature must be exactly the same in parent and child.
3. static methods cannot be overridden. If a child class has the same static method, it is called method hiding, not overriding.
4. final methods cannot be overridden.
5. private methods cannot be overridden because they are not visible to the child class.
6. Constructors cannot be overridden.
7. For runtime polymorphism to work, you must use a parent type reference pointing to a child type object.
MCQ
See also  Packages in Java: Detailed Notes, Examples & Practice Questions (Object Oriented Programming)

Question 11: What will be the output of the following code?

class Animal { void sound() { System.out.println(“Animal sound”); } } class Lion extends Animal { void sound() { System.out.println(“Lion roars”); } } public class Test { public static void main(String[] args) { Animal a = new Lion(); a.sound(); } }

A) Animal sound    B) Lion roars    C) Compile error    D) Runtime error

Answer: B) Lion roars

This is the classic runtime polymorphism question. The reference variable a is of type Animal, but the actual object created is of type Lion. At runtime, Java looks at the actual object type (Lion), not the reference type (Animal). Since Lion has overridden the sound() method, the Lion’s version is called. The output is “Lion roars”. This is called dynamic method dispatch.

True or False

Question 12: A static method in a parent class can be overridden by a static method in the child class with the same signature.

Answer: False

Static methods belong to the class, not to objects. They cannot be overridden. When a child class defines a static method with the same signature as a parent class static method, it is called method hiding, not method overriding. The difference is important: in method hiding, the method called depends on the reference type, not the actual object type. This is the opposite of overriding where the actual object type decides.

MCQ

Question 13: A parent class method is declared as protected. Which of the following is a valid override in the child class?

A) private void method()    B) void method() (default)    C) public void method()    D) Both B and C

Answer: D) Both B and C

The parent method is protected. The child class can use the same or a less restrictive access modifier. The order from most to least restrictive is: private > default > protected > public. So the child can use protected (same) or public (less restrictive). Default is actually MORE restrictive than protected, so technically option B would cause a compile error. Wait, let me correct this. The correct order is: private (most restrictive) > default > protected > public (least restrictive). So default is MORE restrictive than protected. Therefore, only C) public is correct. I apologize for the confusion. The answer is C.

Difference Between Method Overloading and Method Overriding

This comparison is asked in almost every OOP exam. Please memorize these differences carefully.

FeatureMethod OverloadingMethod Overriding
DefinitionSame method name, different parameters in the SAME classSame method name and same parameters in parent and child class
Where it happensSame classParent and child classes (needs inheritance)
ParametersMUST be differentMUST be exactly the same
Return typeCan be differentMust be same or covariant
Access modifierCan be differentCannot be more restrictive
When resolvedCompile time (early binding)Runtime (late binding)
Polymorphism typeCompile-time polymorphismRuntime polymorphism
static methodsCan be overloadedCannot be overridden (method hiding)
final methodsCan be overloadedCannot be overridden
private methodsCan be overloadedCannot be overridden (not visible)
List and Explain

Question 14: List any four differences between method overloading and method overriding.

Answer:

1. Class relationship: Overloading happens within the same class. Overriding happens between a parent class and a child class (requires inheritance).

2. Parameters: In overloading, the parameter list MUST be different (different number, type, or order). In overriding, the parameter list MUST be exactly the same.

3. Resolution time: Overloading is resolved at compile time (early binding). Overriding is resolved at runtime (late binding / dynamic dispatch).

4. Return type: In overloading, the return type can be anything (it does not matter). In overriding, the return type must be the same as the parent or a covariant type (a subclass of the original return type).

Dynamic Method Dispatch

Dynamic method dispatch is the mechanism by which a call to an overridden method is resolved at runtime rather than compile time. This is the core of runtime polymorphism.

When you have a parent class reference pointing to a child class object, and you call an overridden method, Java does not decide which method to call at compile time. Instead, it waits until the program is running, checks the actual type of the object, and then calls the appropriate method.

class Shape { void draw() { System.out.println(“Drawing a shape”); } } class Circle extends Shape { void draw() { System.out.println(“Drawing a circle”); } } class Rectangle extends Shape { void draw() { System.out.println(“Drawing a rectangle”); } } class Triangle extends Shape { void draw() { System.out.println(“Drawing a triangle”); } } public class Main { public static void main(String[] args) { Shape s; // parent class reference s = new Circle(); s.draw(); // Drawing a circle s = new Rectangle(); s.draw(); // Drawing a rectangle s = new Triangle(); s.draw(); // Drawing a triangle } }

Notice how the same variable s calls different versions of draw() depending on which object it points to. The reference type is always Shape, but the behavior changes based on the actual object. This flexibility is the real power of polymorphism!

Important Notes to Remember for Exam:
1. Dynamic method dispatch only works with overridden methods, not overloaded methods.
2. The reference variable type determines which methods are visible (you can only call methods that exist in the parent class).
3. The actual object type determines which version of the overridden method is executed.
4. If the child class has a method that does NOT exist in the parent class, you CANNOT call it through a parent reference.
MCQ

Question 15: In dynamic method dispatch, the method to be executed is decided by:

A) The reference variable type    B) The actual object type    C) The return type    D) The access modifier

Answer: B) The actual object type

In dynamic method dispatch, the JVM looks at the actual object in memory (not the reference variable type) to decide which overridden method to call. For example, if Animal a = new Dog(), the reference type is Animal but the actual object is Dog. So a.makeSound() will call Dog’s version of makeSound(), not Animal’s version.

MCQ

Question 16: Given the code below, which line will cause a compile error?

class Animal { void eat() {} } class Dog extends Animal { void eat() {} void fetch() {} } public class Test { public static void main(String[] args) { Animal a = new Dog(); a.eat(); // Line 1 a.fetch(); // Line 2 } }

A) Line 1    B) Line 2    C) Both lines    D) Neither line

Answer: B) Line 2

Line 1 works fine because eat() exists in the Animal class and Dog overrides it. But Line 2 causes a compile error because the reference variable a is of type Animal, and the Animal class does NOT have a fetch() method. Even though the actual object is a Dog (which has fetch), the compiler only looks at the reference type to check if a method exists. This is a very common exam question, so remember it well!

Use of “instanceof” Operator

When we use parent class references with child class objects (runtime polymorphism), sometimes we need to know what the actual object type is. The instanceof operator helps with this.

class Animal {} class Dog extends Animal {} class Cat extends Animal {} public class Main { public static void main(String[] args) { Animal a1 = new Dog(); Animal a2 = new Cat(); System.out.println(a1 instanceof Dog); // true System.out.println(a1 instanceof Animal); // true System.out.println(a1 instanceof Cat); // false System.out.println(a2 instanceof Cat); // true } }

The instanceof operator returns true if the object is an instance of the specified class or any of its parent classes. It returns false otherwise. This is very useful when you need to perform type-specific operations.

Abstract Classes and Polymorphism

Abstract classes and polymorphism work together beautifully. An abstract class is a class that cannot be instantiated (you cannot create objects from it). It is meant to be a blueprint for other classes.

An abstract method is a method that has no body. The child class MUST provide the implementation of abstract methods.

abstract class Shape { abstract void draw(); // no body, abstract method void display() { // concrete method with body System.out.println(“This is a shape.”); } } class Circle extends Shape { void draw() { System.out.println(“Drawing a circle”); } } class Square extends Shape { void draw() { System.out.println(“Drawing a square”); } } public class Main { public static void main(String[] args) { // Shape s = new Shape(); // ERROR! Cannot instantiate abstract class Shape s1 = new Circle(); Shape s2 = new Square(); s1.draw(); // Drawing a circle s2.draw(); // Drawing a square s1.display(); // This is a shape. } }

Notice how we used polymorphism here. The reference type is Shape (abstract), but the objects are Circle and Square. When we call draw(), the appropriate child class method runs. The abstract class forces every child class to implement draw(), which guarantees that every shape can be drawn.

Important Notes to Remember for Exam:
1. An abstract class can have both abstract methods and concrete (normal) methods.
2. An abstract method has NO body; it ends with a semicolon.
3. If a class has even one abstract method, the class MUST be declared abstract.
4. You CANNOT create an object of an abstract class.
5. The child class MUST override all abstract methods, otherwise the child class must also be declared abstract.
6. Abstract classes ARE commonly used with polymorphism (parent reference = child object).
MCQ

Question 17: Which of the following is TRUE about abstract classes in Java?

A) You can create an object of an abstract class    B) An abstract class must have at least one abstract method    C) An abstract method must be overridden by the child class    D) Abstract methods can have a body

Answer: C) An abstract method must be overridden by the child class

Let me explain why the others are wrong. Option A is wrong because you cannot instantiate an abstract class. Option B is wrong because an abstract class can have zero abstract methods (though that would be unusual). Option D is wrong because abstract methods have no body. Option C is correct because the child class MUST provide an implementation for all abstract methods, or else the child class itself must be declared abstract.

Final Keyword and Inheritance

The final keyword has an important relationship with inheritance and polymorphism. Let me explain its three uses:

1. Final variable: Once assigned, its value cannot be changed (constant).

final double PI = 3.14159; // PI = 3.14; // ERROR! Cannot change final variable

2. Final method: A final method CANNOT be overridden by any child class.

class Parent { final void show() { System.out.println(“This cannot be overridden”); } } class Child extends Parent { // void show() { } // ERROR! Cannot override final method }

3. Final class: A final class CANNOT be inherited by any class. No child class can extend it.

final class MathUtils { // utility methods } // class AdvancedMath extends MathUtils { } // ERROR! Cannot extend final class

A good example of a final class in Java is the String class. You cannot create a class that extends String. This is done for security and performance reasons.

MCQ
See also  Introduction to Object-Oriented Programming (OOP): Detailed Notes, Examples & Practice Questions (Object Oriented Programming)

Question 18: What happens when you try to override a final method of a parent class?

A) The method runs twice    B) Compile-time error    C) Runtime error    D) The override works but with a warning

Answer: B) Compile-time error

The final keyword on a method explicitly prevents overriding. If a child class attempts to override a final method, the Java compiler will immediately give an error. The error message typically says something like “Cannot override the final method from ParentClass.” This is useful when a parent class designer wants to ensure that a particular method’s behavior is never changed by child classes.

Complete Example: Putting It All Together

Now let me give you one big example that combines inheritance, method overriding, and polymorphism. This type of question often appears in exams as a “find the output” question.

class Employee { String name; double basicSalary; Employee(String name, double basicSalary) { this.name = name; this.basicSalary = basicSalary; } double calculateSalary() { return basicSalary; } void displayInfo() { System.out.println(“Name: ” + name); System.out.println(“Salary: ” + calculateSalary()); } } class Manager extends Employee { double bonus; Manager(String name, double basicSalary, double bonus) { super(name, basicSalary); this.bonus = bonus; } @Override double calculateSalary() { return basicSalary + bonus; } } class Developer extends Employee { int overtimeHours; double hourlyRate; Developer(String name, double basicSalary, int overtimeHours, double hourlyRate) { super(name, basicSalary); this.overtimeHours = overtimeHours; this.hourlyRate = hourlyRate; } @Override double calculateSalary() { return basicSalary + (overtimeHours * hourlyRate); } } public class Main { public static void main(String[] args) { Employee e1 = new Employee(“Abebe”, 5000); Employee e2 = new Manager(“Kebede”, 8000, 3000); Employee e3 = new Developer(“Almaz”, 7000, 20, 150); e1.displayInfo(); System.out.println(“—“); e2.displayInfo(); System.out.println(“—“); e3.displayInfo(); } }

Output:

Name: Abebe Salary: 5000.0 — Name: Kebede Salary: 11000.0 — Name: Almaz Salary: 10000.0

Let me walk you through what happened:

  • e1 is a regular Employee. calculateSalary() returns just the basic salary: 5000.
  • e2 is a Manager but referenced as Employee. calculateSalary() is overridden in Manager, so it returns 8000 + 3000 = 11000.
  • e3 is a Developer but referenced as Employee. calculateSalary() is overridden in Developer, so it returns 7000 + (20 * 150) = 7000 + 3000 = 10000.

The displayInfo() method is in the Employee class and it calls calculateSalary(). Because of runtime polymorphism, the correct overridden version of calculateSalary() is called for each object. This is a beautiful example of how polymorphism makes our code flexible!

Find the Output

Question 19: What will be the output of the following code?

class Base { void show() { System.out.println(“Base show”); } void display() { System.out.println(“Base display”); show(); } } class Derived extends Base { void show() { System.out.println(“Derived show”); } } public class Test { public static void main(String[] args) { Base b = new Derived(); b.display(); } }

Output:

Base display Derived show

Explanation: When b.display() is called, the display() method is found in the Base class (it is not overridden in Derived). So Base’s display() runs and prints “Base display”. Then inside display(), there is a call to show(). Now, show() IS overridden in Derived. So at this point, runtime polymorphism kicks in. The actual object is Derived, so Derived’s show() is called, printing “Derived show”. This is a tricky question that tests your deep understanding of how overridden methods work even when called from inside a parent class method!

Exercises with Answers

Exercise 1: Create a class hierarchy for vehicles. Create a base class Vehicle with attributes brand and year, and a method start(). Create two child classes Car and Bike that override the start() method. Write a main method that demonstrates runtime polymorphism.
class Vehicle { String brand; int year; Vehicle(String brand, int year) { this.brand = brand; this.year = year; } void start() { System.out.println(“Vehicle is starting…”); } void display() { System.out.println(“Brand: ” + brand + “, Year: ” + year); } } class Car extends Vehicle { Car(String brand, int year) { super(brand, year); } @Override void start() { System.out.println(“Car ” + brand + ” starts with key ignition.”); } } class Bike extends Vehicle { Bike(String brand, int year) { super(brand, year); } @Override void start() { System.out.println(“Bike ” + brand + ” starts with kick or self-start.”); } } public class Main { public static void main(String[] args) { Vehicle v1 = new Car(“Toyota”, 2020); Vehicle v2 = new Bike(“Honda”, 2022); v1.display(); v1.start(); System.out.println(“—“); v2.display(); v2.start(); } } // Output: // Brand: Toyota, Year: 2020 // Car Toyota starts with key ignition. // — // Brand: Honda, Year: 2022 // Bike Honda starts with kick or self-start.
Exercise 2: Identify the errors in the following code and explain each error:
class A { private void show() { System.out.println(“A”); } } class B extends A { @Override public void show() { System.out.println(“B”); } }

Errors:

Error 1: The @Override annotation will cause a compile error. The show() method in class A is private. Private methods are NOT visible to child classes, so they cannot be overridden. The @Override annotation tells the compiler to check if this method truly overrides a parent method, and since the parent method is private, the compiler will say there is nothing to override.

Fix: Change the access modifier of show() in class A from private to public or protected. If you remove the @Override annotation, the code will compile, but it will be method hiding (a new method in B), not overriding.

Exercise 3: What is the output of the following code? Explain your answer.
class X { static void greet() { System.out.println(“Hello from X”); } } class Y extends X { static void greet() { System.out.println(“Hello from Y”); } } public class Test { public static void main(String[] args) { X x = new Y(); x.greet(); } }

Output:

Hello from X

Explanation: This is NOT method overriding. This is method hiding. Static methods belong to the class, not to objects. When you call x.greet(), the compiler looks at the reference type (which is X), not the actual object type. Since X has a static greet() method, X’s version is called. Even though the actual object is Y, the static method of Y is not used. This is the key difference between overriding (where object type decides) and hiding (where reference type decides).

Exercise 4: Create an abstract class BankAccount with an abstract method calculateInterest() and a concrete method displayBalance(). Create two subclasses SavingsAccount and CurrentAccount that implement calculateInterest(). Demonstrate polymorphism in the main method.
abstract class BankAccount { String accountHolder; double balance; BankAccount(String accountHolder, double balance) { this.accountHolder = accountHolder; this.balance = balance; } abstract double calculateInterest(); void displayBalance() { double interest = calculateInterest(); System.out.println(“Account Holder: ” + accountHolder); System.out.println(“Balance: ” + balance); System.out.println(“Interest: ” + interest); System.out.println(“Total: ” + (balance + interest)); } } class SavingsAccount extends BankAccount { SavingsAccount(String accountHolder, double balance) { super(accountHolder, balance); } @Override double calculateInterest() { return balance * 0.05; // 5% interest } } class CurrentAccount extends BankAccount { CurrentAccount(String accountHolder, double balance) { super(accountHolder, balance); } @Override double calculateInterest() { return balance * 0.02; // 2% interest } } public class Main { public static void main(String[] args) { BankAccount b1 = new SavingsAccount(“Abebe”, 10000); BankAccount b2 = new CurrentAccount(“Tigist”, 25000); b1.displayBalance(); System.out.println(“—“); b2.displayBalance(); } } // Output: // Account Holder: Abebe // Balance: 10000.0 // Interest: 500.0 // Total: 10500.0 // — // Account Holder: Tigist // Balance: 25000.0 // Interest: 500.0 // Total: 25500.0

Practice Questions for Exam Preparation

MCQ

Question 20: Which of the following statements about inheritance is FALSE?

A) Inheritance promotes code reusability    B) Java supports multiple inheritance through classes    C) The protected members of a parent class are accessible in the child class    D) Constructors are not inherited in Java

Answer: B) Java supports multiple inheritance through classes

This statement is FALSE because Java does NOT support multiple inheritance through classes. A Java class can extend only one class. Java supports multiple inheritance only through interfaces (a class can implement multiple interfaces). All other statements are true: inheritance does promote code reusability, protected members are accessible in child classes, and constructors are not inherited.

MCQ

Question 21: What is the output of the following code?

class P { P() { System.out.print(“P “); } } class Q extends P { Q() { super(); System.out.print(“Q “); } } public class Test { public static void main(String[] args) { Q q = new Q(); } }

A) P    B) Q    C) P Q    D) Q P

Answer: C) P Q

When new Q() is called, Q’s constructor runs. The first statement is super() which calls P’s constructor. P’s constructor prints “P “. Then control returns to Q’s constructor which prints “Q “. So the output is “P Q”. Even if we remove the explicit super() call, the result would be the same because Java automatically inserts it.

MCQ

Question 22: Can an abstract class have a constructor?

A) No, because you cannot create objects of abstract classes    B) Yes, and it is called when a child class object is created    C) Yes, but only a default constructor    D) No, constructors are not allowed in abstract classes

Answer: B) Yes, and it is called when a child class object is created

Abstract classes CAN have constructors, including parameterized constructors. Even though you cannot directly create an object of an abstract class using new, the constructor is called when a child class object is created (because the child constructor calls super()). The abstract class constructor is used to initialize the fields that the abstract class defines. This is a commonly misunderstood concept in exams.

Find the Output

Question 23: What will be the output?

class A { int x = 10; void show() { System.out.println(“A: ” + x); } } class B extends A { int x = 20; void show() { System.out.println(“B: ” + x); } } public class Test { public static void main(String[] args) { A obj = new B(); System.out.println(obj.x); obj.show(); } }

Output:

10 B: 20

Explanation: This is a very important question! For variables, Java uses the reference type to decide which variable to access. obj.x uses the reference type A, so it prints A’s x which is 10. But for methods, Java uses the actual object type (runtime polymorphism). obj.show() calls B’s overridden show() method, which uses B’s x, so it prints 20. Remember: variables are resolved by reference type, methods are resolved by object type.

Short Answer

Question 24: Explain the difference between “is-a” relationship and “has-a” relationship in OOP. Give one example for each.

Answer:

“Is-a” relationship represents inheritance. It means one class is a specialized version of another class. For example, a Car “is a” Vehicle. In code, this is implemented using extends. Example: class Car extends Vehicle.

“Has-a” relationship represents composition or association. It means one class contains an object of another class as a member. For example, a Car “has a” Engine. In code, this is implemented by declaring a reference variable of another class inside the class. Example: inside Car class, we have Engine engine;. The “has-a” relationship is also called composition because the containing class is made up of other classes.

Final Exam Tips:
1. Always remember: variables follow reference type, methods follow object type in polymorphism.
2. If a question shows Animal a = new Dog(), remember that a can only call methods that exist in Animal, but the Dog’s overridden version will run.
3. The super() call must always be the FIRST statement in a constructor. This rule has NO exceptions.
4. A class with an abstract method MUST be declared abstract. But an abstract class does NOT need to have an abstract method.
5. Practice writing code by hand. Many exam questions ask you to find the output of code without running it.
6. When in doubt about overloading vs overriding, check: same class or different classes? Same parameters or different parameters?
7. The Diamond Problem is the reason Java does not support multiple inheritance with classes. Be ready to explain and draw the diagram.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top