Inheritance
Introduction
You might have asked yourself when creating the LinkedList using nodes storing int values if you must write a new class for every type a list could store. We notice that the list is a much more general concept than storing int values. It could also store any other primitive type values or references to objects and we would still refer to it as a list, as the underlying mechanics have not changed. We would hence like to define a list based on its behaviour and then allow for any type to be stored within the list. The methods for insertion, deletion, search, etc. will still work the same the type which is stored in the list will have no effect on that.
Inheritance
Motivation

To motivate the use of inheritance further let us consider the LinkedIntList once more. We will now add a tail attribute which stores a reference to the last node in the list to allow for more efficient insertion / deletion at the end of the list. We would now copy & paste the existing code of the LinkedIntList into a class named FastLinkedIntList and then add a tail attribute while modifying the methods that insert / remove elements from the end of the list. This means that we reuse a lot of the code.
What if we wanted to reuse the given code for the LinkedIntList to make a LinkedDoubleList that contains double values in its nodes? We would have to change the underlying ListNode implementation. This would mostly require us to change the attribute from type int to type double. One can see that this copy and paste approach is redundant. This is the reason why inheritance exists.
Inheritance
Real Life Example

In the lecture the example used to explain is a hospital. A hospital has employees. Each employee has a different function. There are nurses, doctors, surgeons, administrators and many more types of employees. One can see that all of these are also employees. A surgeon is a doctor which in turn also is an employee.
This yields the hierarchy to the right with the addition of the "specialist". One can see that anything an employee can do can also be done by anything lower in the hierarchy. However all employees on the same level cannot fulfil each others function, i.e. a nurse cannot do what an administrator does. In real life this really is not the case, but in Java it is hence let us assume this is the case.
Inheritance
Real Life Example

Every employee gets a 40 page handbook that describes their job, what they can do and how they work. This is analogous to the class description in Java. A nurse gets this handbook as well. They however additionally get a 20 page handbook which describes additional functionality which only a nurse can do. In Java this corresponds to an object of the class Employee having methods and attributes which the class Nurse also has because a Nurse is an employee. This relation however does not work the other way around. Not every employee is a nurse. Similarly not every Animal is a Dog, but every Dog is an Animal.
Understanding that this relation is not symmetric is important and key to understanding how inheritance works in Java.
Inheritance
Advantages

The advantages of doing this are many, however let us discuss two main advantages of using inheritance.
-
Less Redundancy: One can efficiently define classes with the same methods and attributes as another class without having to rewrite code.
-
Modifyability: One does not have to change all classes if they want to modify the behaviour of some class lower in the inheritance hierarchy. We can simply change the class itself without having to worry about the other classes.
-
Using code that works: Sometimes one wants to reuse code from classes which are part of Java and hence have been shown to work (more or less) reliably. This is possible by using inheritance.
Inheritance
Inheritance Hierarchy

Whenever we consider inheritance we like to do this by visualising the inheritance in a so-called inheritance hierarchy. We will illustrate this using the surgeon is-a doctor is-a employee relationship. All (non-private) attributes and methods of the employee class are also present in the doctor class. In addition to that the doctor class may itself have attributes and methods which are not present in the employee class. The same goes for the surgeon class which contains all methods and attributes of the doctor and employee class.
Inheritance
Avoiding Confusion

When a class inherits from an other class they do share the same attributes and methods. This simply means that the blueprint of a given class is contained within the class that inherits from this class, it may then build on top of this blueprint and even override certain things inside this blueprint.
Every class exists on its own and does not have to rely on an instance of a other class to exist for instances of it to exist. Instances of a class have a state which is a combination of all inherited attributes and its own attributes and a behaviour which is given by inherited methods and methods defined inside the class.
Inheritance
Java Syntax
public class Doctor extends Employee {
// class declaration
}
public class Surgeon extends Doctor {
// class declaration
}
Whenever we use the extend keyword to inherit from another class all (non-private) attributes and methods exist automatically within the class which inherits from the extended class. We call the inheriting class the subclass and the class which is being inherited from the superclass. Any class can only inherit from a single class. This is very important and one must remember this. It is possible to combine multiple blueprints which can be done using interfaces which will be discussed later.
Inheritance
Overriding
public class Doctor extends Employee {
public doJob() {...}
}
public class Surgeon extends Doctor {
@Override
public doJob() {...}
}
We sometimes do not want to inherit all methods from the superclass and want to select what behaviour the subclass should inherit and which not. We can do this by simply redeclaring the method again which will give the declaration in the subclass priority over the one in the superclass. In Java we can add the @override javadoc to make sure that what we are doing is an actual override. This will be checked by the Java system and Eclipse will give you a warning if that is not the case. This is useful especially because method overloading is a rather complex process that takes many factors into consideration to determine whether two methods are the same or not. The following rules apply when overriding methods from a superclass in a subclass.
-
Method name and parameters (method signature) must be exactly the same as in the parent class.
-
The return type can be the same or a subclass of the return type in the parent class method.
-
The access level can't be more restrictive than the parent class method.
-
The overriding method can throw narrower or fewer exceptions, i.e. it cannot throw exceptions which are thrown "more often".
Inheritance
Overloading

One can define functions with the same name which are recognised by Java as different functions. This process is called overloading. There are three ways to make overloading work.
-
Number of parameters: Methods can have the same name but a different number of parameters.
-
Types of parameters: Methods can have the same name and the same number of parameters, but with different types.
-
Order of parameters: Methods can have the same name and the same number of parameters with the same or different types, but in a different order.
It is important that the return type, access modifiers or throws declaration do not contribute to method overloading and hence cannot be used to distinguish between methods with the same name.
Inheritance
Shadowing


Shadowing can occur in two contexts. Shadowing is different from two variables existing with the same name but being out of each others scope. There we simply have no mutual visibility and hence these two variables with same name may exist independently of each other. Shadowing is when two or more variables can see each other but there are rules in place to make sure that this is well-defined.
-
Inter class shadowing: Inter class shadowing occurs when a class contains an attribute and its subclass also declares the same attribute. Then the attribute in the subclass shadows the attribute in the superclass, i.e. any object of the subclass will see the value of the attribute inside the subclass.
-
Intra class shadowing: Intra class shadowing occurs when a variable in a smaller scope (e.g., a method or a block) has the same name as a variable in a larger scope (e.g., a class member). This "shadows" the larger scope's variable within the smaller scope, meaning the name in the smaller scope will refer to the variable in that scope, not the variable in the larger scope.
Inheritance
Accessing the Superclass

In Java, the super keyword is used in several contexts, primarily to refer to members (variables, methods) of a superclass from a subclass. It serves the following two purposes.
-
Access superclass members: When a member in a subclass has the same name as a member in its superclass, the subclass's member shadows the superclass's member. The super keyword allows you to access the shadowed members (variables, methods) of the superclass.
-
Invoke superclass constructors: We can call constructors of the superclass using the super keyword. This is often done to ensure that the superclass is properly initialised before the subclass constructor adds its own initialisation.
In Java, if a constructor does not explicitly invoke a superclass constructor, the Java compiler automatically inserts a call to the default constructor of the superclass. Whenever one uses a call to a super constructor in a constructor of a sublass then it must be the first statement inside of the constructor of the subclass.
Inheritance
Constructors

Constructors act differently from methods and attributes in inheritance. The following things one must keep in mind when working with constructors in inheritance.
-
Constructors are not inherited: Unlike normal methods, constructors are not inherited by subclasses. Therefore, a subclass cannot directly access the constructor of its superclass.
-
Default constructor calls: When a subclass constructor doesn't explicitly call a superclass constructor, Java automatically inserts a call to the default (no-argument) constructor of the superclass. If the superclass doesn't have a default constructor (a constructor without parameters), you must explicitly call one of the available superclass constructors using super.
-
Constructor chaining: In a class hierarchy, when a new instance of a subclass is created, constructors are called in a top-down fashion, starting from the topmost superclass down to the subclass. This ensures that the initialization of an object starts from the hierarchy's root, respecting inheritance and object composition rules.
Inheritance
Method Selection

In an inheritance hierarchy, a subclass can override a method of its superclass. This means providing a new implementation for the method in the subclass. The actual method to be executed is determined at runtime, based on the object's runtime type (the actual class of the object), not the type of the reference that points to that object. This behaviour is known as dynamic binding.
Dynamic binding allows Java to support polymorphism. It enables Java programs to be written more generically and to handle objects of several different types in a uniform way. It allows subclasses to define specific behaviours that are different from their superclass or other subclasses, leading to more flexible and reusable code.
The key takeaway here is that methods are selected not based on the type of the reference but instead the underlying object's runtime type. There are three exceptions to this rule when it comes to methods which will be discussed in the next section.
Inheritance
Attribute Selection

If a subclass has a field with the same name as a field in its superclass, the subclass's field shadows the one in the superclass. Unlike methods, which field is accessed is determined at compile time based on the reference type, not the actual object's type. This behaviour is known as static binding.
Three method types are also confined to static binding, it is useful to remember this.
-
Static methods: Static methods are bound at compile time based on the class type and not on the runtime object. This is because static methods are class methods, associated with the class itself rather than any specific instance. Static methods cannot be overridden. If a subclass defines a static method with the same signature as a static method in a superclass, it's known as method hiding, not overriding.
-
Private methods: Private methods are not visible to subclasses and, as such, cannot be overridden. Since they are confined to the class they are declared in, the binding is done at compile time, and invoking a private method is always a case of static binding.
-
Final methods: Final methods cannot be overridden by subclasses. Since their behavior cannot be changed in subclasses, they are bound at compile time.
Inheritance
Hidding

In Java, static methods cannot be overridden in the way instance methods can be. When you define a static method with the same signature in a subclass that already exists in the superclass, this is not method overriding but rather method hiding.
Which hidden method is called depends on the type of the reference, not the type of the object it points to.
Inheritance
Protected Modifier
When a class is derived from a superclass, it inherits all accessible members of the superclass. protected members included in this inheritance are thus accessible to the subclass Unlike package-private (with default modifier, i.e. no modifier specified) members, protected members can be accessed by subclasses that reside in different packages. This is a common scenario in large applications and libraries where subclasses might extend superclasses across package boundaries.
Protected constructors, methods and attributes are accessible to subclasses and to any class within the package, making it possible to limit instantiation of a class to its subclasses and other classes in the same package.
Inheritance
Modifier Summary
Let us summarise the information we have on the modifiers quickly.
-
Private modifier:
-
Accessibility: The private access modifier restricts the visibility to the class only. Private members cannot be accessed from outside their own class, not even by subclasses.
-
Scope: Best used when you want the class member to be hidden from any other class.
-
-
Default (no) modifier:
-
Accessibility: When no access modifier is specified, it defaults to package-private access. This means that the class or member is accessible only within its own package and is invisible to classes from other packages.
-
Scope: Use this when you want to allow access to classes within the same package but not expose them to the outside world.
-
-
Protected modifier:
-
Accessibility: The protected access modifier allows the class member to be accessed within its own package and by subclasses even if they are in different packages.
-
Scope: It is typically used when you expect the class to be subclassed and want to allow subclasses to have more privileges than other classes.
-
-
Public modifier:
-
Accessibility: The public access modifier makes the class or member accessible from any other class everywhere, whether within its own package or in others.
-
Scope: Use this when you want the members to be accessed globally.
-
Polymorphism
Introduction

In Java, polymorphism allows to use a reference of a superclass to refer to a subclass. Binding plays a key role in this. There are two types of polymorphism.
-
Compile-time Polymorphism: This includes any selection of methods and attributes selected during compilation, i.e. static binding or method overloading.
-
Run-time Polymorphism: This includes any selection of methods and attributes during runtime, i.e. dynamic binding or method overriding.
Polymorphism
Casting

Casting in Java is the process of converting a variable from one type to another. There are two main categories of casting: primitive type casting and reference type casting. Here, we will focus on reference type casting, which involves upcasting and downcasting.
-
Upcasting: Upcasting is casting a subtype to a supertype, upward to the inheritance tree. It is always safe and does not require an explicit cast. It is implicit, so you do not need to do anything special syntax-wise.
-
Downcasting: Downcasting is casting a supertype to a subtype, downward to the inheritance tree. It is not inherently safe because it assumes that the object is actually an instance of the subtype. You can downcast only after you have upcast an object. Hence you use a downcast if an upcast already occured and you want to prevent the compiler from producing an error. This does not change the runtime method selection and only affects attributes and static, final or private method selection.
Polymorphism
instanceof

In Java, instanceof is a binary operator used at runtime to test whether an object is an instance of a specified type (class or subclass or interface). The operator is also used to test whether an object is an instance of a class that implements a particular interface.
The instance of operator is used to check that the runtime type of an object a reference points to is the same as the type specified as the second operand. To the left one can see the main use of the instanceof operator in overriding the equals method from the Object class.