Publishing Tips and Discussing Questions about Inheritance and Polymorphism in Java
In this document, I will cover the questions asked in inheritance_and_polymorphism.pdf under section 3.10 and write down neat stuff I’ve learned about inheritance by myself. For certain questions and topics, I will also involve the project “School of Java” which is further described in the document above.
Private vs Public Classes
Aside from the common classes which are marked public and have the same name as the file they’re in, a file can also contain separate package-private classes and nested inner-classes. While package-private classes are defined in the same scope as the public classes, inner-classes are defined in another class. Inner-classes are a security mechanism in java — while normal classes can not be private, defining a class in another class allows to use proper information hiding and marking the class as private. These private classes then can only be accessed by the parent/container class where they are defined in.
To address the actual question behind this section: Yes, private classes can also be extended. Just like with class attributes, using information hiding prevents certain other classes from using the variable, but classes that retain access to it still can use it to its full potential. The same goes for classes: When extending from private classes, you only need to ensure that the scope where the new class is defined also has access to the parent class.
In Java, marking a class as final prevents it from being inherited from. Quite literally, the class is made “final”. A possible use case for this would be preventing third-party developers from modifying your library’s code. If you are working on a library which will end up being used by other people, they could modify some of your library’s functionality by creating child classes of your classes and then using those classes instead of your library’s built-in classes. Marking your classes as final will prevent them from doing that.
Aside from classes and attributes, there is a third important thing in Java that can be made final: Methods. Similar to classes, this will prevent inheritance — or in this case — overriding. For both features, the one for marking a class final and the one for marking a method final, the use cases I can think of are very similar. Unless you will have third-party-code running on your system, I do not see a big advantage in using final, but in the end, adding final to places that are not extended anyway doesn’t break the system, so some developers might prefer marking classes and methods they don’t extend or override as private to keep some kind of integrity.
If you work with inheritance in Java, sooner or later you will not be able to avoid the keyword “abstract”. It can be used with classes and prevents the creation of instances of this specific class. In the end, that means that you need to create subclasses that extend from this abstract class in order to create instances of it. Using abstract ensures that you do not get an instance of the parent class, when you are expecting an instance of a subclass. With one or two exceptions, they’re almost similar to interfaces: They allow you to define methods and attributes in the abstract class and use them in your subclasses. If you want to define methods and attributes that your subclasses need to manage, you can use the abstract keyword also for these which results in a similar effect as if these methods and attributes would have been defined in an interface. Another difference to interfaces is that classes still can only extend from only one abstract class, while they can implement multiple interfaces. This, however, is a limitation to extending classes, and does not explicitly have to do with these classes being abstract.
The main idea of interfaces is to separate methods and attributes from the implementation. You can imagine interfaces as abstraction without inheritance: If a class implements an interface, the class has all methods and attributes defined in the interface. However, the classes and methods themselves are implemented in the class and are not implemented by the interface.
A well-known design pattern is the “Factory Pattern”. In Factory pattern, objects are created using a common interface and without exposing the creation logic to the client. In the end, you will have “Factory”-Classes that creates and returns new instances of classes. In most cases, interfaces are the specified return type of the factory methods, allowing the factory to have more control over the created object.
Polymorphism means “many forms” and is strongly tied to inheritance: It allows to inherit attributes and methods from a different class. This allows performing a single action in different ways. In “School of Java”, I do not explicitly use polymorphism. In the end however, any kind of overriding is polymorphism — so even though it might not be that present, I use polymorphism in my Student class where I override the “toString” method from the Object parent. One feature using polymorphism I can think of would be to add a “getCallName” function to the Person class which would return the first name of the person. However, in the Person object, I would override this method to return the salutation and the last name instead of the first name, e.g. Ms. Miller.
Encapsulation & Information Hiding
Encapsulation and information hiding definitely have their qualities in inheritance too. While normal classes usually use the private and public access modifiers, in inheritance the protected access modifier plays a big role: It allows child classes to access the method or attribute, but does not allow other classes to use the field. This allows leaving open a wide variety of possible implementations, while still maintaining encapsulation and information hiding.
Can inheritance also be a nuisance?
While inheritance makes it possible to bring a certain structure into a program, it can also be exactly this structure that leads to unexpected problems. For example, if you work on a larger project with other developers and use inheritance, another developer who is not completely familiar with the system can cause errors.
Inheritance brings additional dependencies into the code: If the inherited class is changed, this change also occurs in all inheriting classes — unless these classes override the modified method or attribute. This means that after a change of a class, the inheriting classes must be checked and tested to ensure that no unexpected behavior occurs.
Thank you for reading this document. Feel free to contact me if you have any questions or concerns.