Java is a very popular and powerful type-safe language, used in many areas including general software development, large complex enterprise systems, mobile development, IoT devices, etc. One of Java’s core strengths is its support for object-oriented development.
This training course provides you with a deep dive into object-oriented development and how it is used and implemented within the Java language.
- Understand what Inheritance and Polymorphism are and how to implement them
- Learn the benefits of working with Interfaces and Abstract Classes
- A basic understanding of software development
- A basic understanding of the software development life cycle
- Software Engineers interested in advancing their Java skills
- Software Architects interested in using advanced features of Java to design and build both applications and frameworks
- Anyone interested in advanced Java application development and associated tooling
- Anyone interested in understanding the advanced areas and features of the Java SDK
- [Jeremy] Okay, welcome back. In this lecture we'll explore the concepts of inheritance and polymorphism in greater detail. You should master these concepts to ensure you are able to develop code using object-oriented principles. We'll review the following topics: Writing a subclass with a method that overrides a method in the superclass, grouping objects by their common supertype, utilizing polymorphism by correctly invoking subclass method implementations through superclass references, safely casting a supertype reference to a valid subtype reference, and using the final keyword on methods and classes to prevent overriding through subclassing when required. Polymorphism is one of the most powerful concepts of object-oriented programming. The key to polymorphism is deciding at runtime exactly what kind of object we are working with. Polymorphism comes from the Greek poly meaning many, morphism meaning many forms. It allows code to be written that is self-modifying so that when new classes are added to an inheritance hierarchy to augment the functionality of the program, no changes need to be made to the existing code. It depends on the principal of substitutability where a super class object can hold a reference to itself or any of its subclasses. The following subclassing example has implementation for Circle, Square, and Line. Notice that we have defined special versions of the draw methods for each of our specialized shapes. The method has the same signature as that in the superclass. This is a key requirement for making polymorphism work. Each subclass overrides the parent's draw method. We now store our specialized objects and the generalized container in calling draw methods. Notice that inside the fallout we have no idea what kind of shape we are drawing. This is polymorphism in action. The process of extraction is key to good object-oriented programming practice. Java facilitates this by allowing the specialized class to be referenced as its superclass. This allows us to treat things in a more generalized way and greatly simplifies code. One of the nice things about Java is the ability to use subclasses in the place of superclasses. What this means is that you can assign a subclass object to a superclass object reference. Additionally, the subclass does not need to be the immediate child of the superclass. The subclass could extend an object which extends the superclass. Assigning the subclass to an object reference of a grandparent is still valid. The reason that this is valid is that the grandchild object subclass, will still contain all of the characteristics of the grandparent superclass. Casting to a derived class. There are times, however, that we may need to find out if a generalized reference is really a specialized instance. Consider the following scenario, we instantiate a new Circle object but it is typed as its superclass type, in this case Shape. Then we are unable to access the methods of the Circle subclass. The getRadious method doesn't exist for the type Shape. Therefore, we must downcast to the subclass type. Circle is a subclass of Shape. The compiler allows this, but if Shape were initialized as Line, explicitly casting this would fail at runtime. The instanceof operator allows us to test what kind of instance we have. Once we have confirmed the fact that we have a specialized instance, we can then cast the general reference to the actual subtype we have and access the specialized methods of the instance. The Java language includes the keyword instanceof to help us to determine what the exact class of a subclass is. An example is shown here, however, it is better where possible to have code explicitly designed to handle a particular type of object rather than branching within a class by a stronger type. Testing the object type and then processing based on the type is considered procedural code. Whereas having a dedicated object to handle the object is more object-oriented. Let's now consider upcasting versus downcasting. Upcasting is runtime-safe, checked by the compiler, can be used to fill an array of related types, and is often done to satisfy method signature requirements. Downcasting, on the other hand, can only partially be checked by the compiler, is often used to expose additional subclass methods, and can convert collection of generic types to more specialized forms. We can always upcast because when we do so we lose access to specialized behavior. We cannot, however, downcast without checking the type because the specialized behavior may not exist. If we do a bad downcast the error will be caught at runtime not compile time. Calling superclass methods from a subclass. An overriding method in a subclass may wish to call the overridden method from the superclass. When a subclass overrides a method of its superclass the superclass's method is hidden. This causes a problem if we want to use the superclass's method instead of the subclass. To access the superclass's method we can use the super keyword. The syntax is super.methodname. The final keyword, sometimes we may wish to ensure that a class, or one of its methods, and not subclassed or overridden. If a method performs a vital function, such as authentication, it would be very trivial for someone to override this method and implement a nonsecure method in its place either intentionally or unintentionally. We can stop this by placing the final keyword before the return type of the method. Variables declared within the scope of a method can also be declared as final. Once these variables have been given a value, the value cannot be changed. As we have seen before method parameters are local variables to the method. Method parameters can also be defined as final. By doing so it becomes impossible to assign a different value or reference to the parameter. Note, while final fields cannot be changed, remember their object references are just references, not a copy of the object. So the underlying object may change even though the reference to it is declared final. Final fields and variables do not always need to be initialized when they are declared. Instance fields must be initialized when the object instance is being constructed. When the final field has not been given a value when it was declared, it must be given a value in the constructor of the class, making sure all constructors of the class initialize the final field. When a method variable is defined as final, it must be given a value before it can be used. Identical to non-final method variables, once it has been given a value its value cannot be changed. Okay, before we complete this lecture, let's provide a quick summary of the key or important takeaways when using inheritance in polymorphism within Java. You learned how to write a subclass with a method that overrides a method in the superclass, how to group objects by their common supertype, how to utilize polymorphism by correctly invoking subclass method implementations through superclass references, how to safely cast a supertype reference to a valid subtype reference, and, finally, how to use the final keyword on methods and classes to prevent overriding through subclassing.
Jeremy is a Content Lead Architect and DevOps SME here at Cloud Academy where he specializes in developing DevOps technical training documentation.
He has a strong background in software engineering, and has been coding with various languages, frameworks, and systems for the past 25+ years. In recent times, Jeremy has been focused on DevOps, Cloud (AWS, Azure, GCP), Security, Kubernetes, and Machine Learning.
Jeremy holds professional certifications for AWS, Azure, GCP, Terraform, Kubernetes (CKA, CKAD, CKS).