Inheritance (IS-A relationship)
Inheritance is another important feature of OOP. It is a mechanism by which one class (a.k.a. child class) is allowed to keep the variables and methods declared in another class (a.k.a parent class). The child class is declared by extending the parent class using the extend keyword. Inheritance allows us to create hierarchy among classes to help organize classes in a meaningful way.
In our examples, let us assume that our BookStore sells two types of books: E-Books and Printed books. Both types of books need the instance variables that are declared above. However E-Books need an extra instance variable to hold the URL of the book and the Printed books do not need this variable. But Printed books need an instance variable to hold the value of the type of printed book that is purchased; Hard covered, Paper back or Spiral bound. This variable is irrelevant for the E-Books. Instead of creating two classes and repeating the common variables, you can keep the above class Book and create two child classes called EBook and PrintedBook by extending the parent class Book, thereby inheriting the common variables. Lets do that as below:
class Book {
private String title;
private String author;
private double price;
public void setTitle(String title) {
this.title = title;
}
public void setAuthor(String author) {
this.author = author;
}
public void setPrice(double price) {
this.price = price;
}
public String getTitle() {
return this.title;
}
public String getAuthor() {
return this.author;
}
public double getPrice() {
return this.price;
}
}
class EBook extends Book {
private String url;
public void setUrl(String url) {
this.url = url;
}
}
class PrintedBook extends Book {
private String type;
public void setType(String type) {
this.type= type;
}
}
public class BookStore {
public static void main(String[] args) {
EBook book1 = new EBook();
book1.setTitle("Harry Potter and the Sorcerer's Stone");
book1.setAuthor("J.K. Rowling");
book1.setPrice(29);
book1.setUrl("http://ebooks.zzz/harrypotter");
System.out.println(book1.getTitle());
PrintedBook book2 = new PrintedBook();
book2.setTitle("Harry Potter and the Prisoner of Azkaban");
book2.setAuthor("J.K. Rowling");
book2.setPrice(31);
book2.setType("Paper Back");
System.out.println(book2.getTitle());
}
}
Notice that you are able to set title, author and price variables on EBook and PrintedBook even though they are not declared in EBook or PrintedBook classes.
You are able to do that because you extended the Book class which has these variables. Try removing the extend keyword and see if you can still access the variables.
Points to note
You use extends keyword to make a child of another class.
Parent class is also called Super class
Child class is also called Sub class
All the parents methods, variables and constructors are visible to the child except for the ones declared private
When ever you instantiate a child, a corresponding parent object is also created along with the child.
IS-A relationship
- An instance of a child class is not only of the type of the child class but also of the parent. In the above example, instance book1 is of type EBook and also of type Book. In OOP we say book1 IS-A EBook and book1 IS-A Book also.
What is method overriding?
If the child class has the same method signature as the parent, then it is called overriding. The child class (a.k.a subclass) will override the parent method as it needs to give a different implementation for the same method. Let us take an example:
class Animal {
public void speak() {
System.out.println("Every animal speaks in its own way");
}
}
class Cat extends Animal {
public void speak() {
System.out.println("meow meow");
}
}
public class AnimalKingdom {
public static void main(String[] args) {
Animal animal = new Animal();
animal.speak();
Cat myCat = new Cat();
myCat.speak();
}
}
In the above example, the Cat class overrides the speak method of the Animal as it is giving specific way in which a Cat speaks.
Run the above code and see the output. What do you see? Now remove the speak method in Cat and then run the same program. What do you see now? Do you understand why the output is different?
Rules In Method Overriding
- A child class cannot override a super class method that has an access modifier as private. Rest all access modifiers, public, protected and default (no modifier) can be overridden. However note that you can add a method with the same signature as super class's private method in the child class, however this is not considered overriding as private variables, methods are only accessible by that class only and none else.
- Methods that are declared as final or static cannot be overridden in the child class.
- Overriding method should have the same argument list, return type and method name.
- The overridden method cannot have more restrictive access modifier. For e.g., if the super class has public access on the method, the child overridden method should have public access only and cannot be declared with protected or no (default) access. On the other hand if the super class has default (i.e., no modifier at all) modifier, then the overridden method in the child class can have the same, protected or public modifiers
- If the super class method has a throws clause, then the overriding method cannot throw new or broader checked exceptions.
It is called overriding only if the method signatures match between the parent and the child class. If the parameter list is different between the parent and child class then it is called overloading which is covered in the next module.
Casting
In the earlier chapters, you learnt about casting rules with respect to primitives. In this lesson, you will learn the casting rules with objects.
You now know that every child IS-A child class, plus also any of its parent. So the below code is perfectly acceptable:
Animal myCat = new Cat();
myCat.speak();
So what did you notice in the above code? We declared variable myCat as type Animal but instantiated a Cat object and assigned it to this variable. Since Cat extends Animal, and hence a Cat IS-A Animal also, this is acceptable. And everything works as expected.
Now let us take another example as shown below:
Object myCat = new Cat();
myCat.speak();
In the above we replaced Animal with Object and since all the classes in Java extend the top level Object class, a Cat is also an Object and hence Object myCat = new Cat();
is perfectly legal! However, the next statement myCat.speak();
does not compile. This is because the dataype of myCat is Object and the Object class does not have a speak method But the object that is referenced by myCat is actually of Cat type and Cat does have a speak method. So to get over this issue, we need to cast the myCat datatype back to Cat and that can be done with the below statement
((Cat)myCat).speak()
What did we do here? We first cast the myCat object to Cat and then invoke the speak method on that. This works as expected and calls the speak method of the Cat class.
Object Casting Rules
- Casting an object from a sub class to a super class is not required.
- Casting is always done when invoking methods of the subclass
- Casting is done only when there is a inheritance relationship between classes and not otherwise
- ClassCastException could be thrown during run time if the object being cast is not actually an instance of that class.
More on Object class
All classes in Java extend the top level Object class that is present in java.land package. Since every object that you ever create, including Strings will also be an Object and hence when you test the object with instanceof operator against Object class, it will return true. Here is an example;
String myString = "abc";
System.out.println(myString instanceof String); // prints true
System.out.orintln(myString instanceof Object); // prints true again
Since Object class is the parent, every object will inherit all the Object classes methods. Here are some methods of Object class that are important to understand
- equals()
- hashcode()
- toString()
- getClass()
- wait()
- notify()