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.
Learning Objectives
- Understand what Inheritance and Polymorphism are and how to implement them
- Learn the benefits of working with Interfaces and Abstract Classes
Prerequisites
- A basic understanding of software development
- A basic understanding of the software development life cycle
Intended Audience
- 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 interfaces and abstract classes. Again you need to master these concepts to ensure that you are able to develop code using object-oriented principles. We'll review the following topics, define supertype contracts using abstract classes, implement concrete classes based on abstract classes, define supertype contracts using interfaces, implement concrete classes based on interfaces, explaining the advantage of interfaces over abstract classes and explaining the advantage of abstract classes over interfaces. At the conclusion of this lecture, you should be able to perform each of the items listed above. Take a moment to rate yourself on each of these items on a scale of one through to five. At the conclusion of this lecture, these objectives will be reviewed. You should rate yourself again to see how much benefit you have received from this lecture. The most fundamental rule in object-oriented programming is that the internal state of an object is not directly accessible from external objects, instead aspects of the object's state are accessed through methods. We call this internal hiding of state encapsulation. With a well-defined set of methods on a type, we say that the type provides an interface to external objects. As long as the interface doesn't change, we can change the internal implementation of the object without affecting the rest of the application. This is a core principle regarding software reliability and maintainability. In Java there are two language constructs that allow us to represent an interface, the abstract class and the interface. Keep in mind that this is in addition to the standard Java class which we've seen so far, which is also considered a type. Abstract classes. An abstract class is a class with zero or more abstract methods. An abstract method is a method signature with no body. They allow for a class to provide a partial implementation requiring that a complete implementation is created in a subclass or concrete class. A concrete class is a conventional term used to distinguish a class from an abstract class. And an abstract class cannot be instantiated, only extended. An abstract class can extend another abstract class. And any concrete subclasses must ensure that all abstract methods are implemented. Abstract classes can themselves have concrete implementations of methods. These methods are inherited just like a method in a non-abstract class. In fact an abstract class can have no abstract methods, although it would not be that useful. The rule in Java is that if a class has at least one abstract method, then it is self-abstract and must have the abstract keyword in its definition. Let's consider shape as an abstract class. Since shape is really too generic to exist on its own, we'll create or implement it as an abstract class. This way we can provide some functionality in the class, yet not allow it to be instantiated. It can be subclassed however, and subclass must then provide the implementation of the draw method, which presumably can only be done by a real shape. Polymorphism with abstract classes. We cannot instantiate an abstract class. We can only instantiate its concrete subclasses. But we can create an object reference to an abstract class. This object reference can point or refer to any of its subclasses. This allows us to call methods defined in the abstract class without knowing how the subclass implements them. Interfaces. An interface is similar to an abstract class with the exception that they do not contain any instance variables. They can be used to define a public interface of a particular type. And in doing so, within them you define a set of public abstract method signatures. Using the public and abstract key words is optional. Using them or not using them would have the same result. Interfaces are typically adjectives, e.g. runnable, steerable, sortable. Any class that implements an interface must implement all of the abstract interface methods, otherwise the class must be declared abstract. Implementing interfaces. To implement an interface, use the implements keyword as shown above. Note that a class can implement more than one interface. If this is done, separate the interfaces with a comma. If a class cannot implement one or more of the methods defined in the interfaces, then as previously mentioned, it must be declared abstract. Note, abstract classes can themselves implement interfaces. You can build very large hierarchies by combining multiple interfaces. Here we have isolated various capabilities as separate interfaces. A class that has a capability indicates this by implementing that particular interface. The example above also shows the concept of a sub-interface. Oval is a class that is both drawable and printable, and as a non-abstract class, it must implement the methods in those interfaces. When the circle class extends the oval class, it adds its own capability unique to circles which is the existence of a radius. In this next example we have implemented shape as an interface. By doing so, we're losing a little bit of functionality, that shape head and its implementation as an abstract class. However it does now allow our circle to use its single inheritance to extend from a much more functional superclass. Polymorphism with interfaces. Since classes, abstract classes, and interfaces are all considered equal Java types, we can apply the rules and benefits of polymorphism to interfaces in the same way as we did with classes and abstract classes. Type checking. Using the appropriate interface type on the formal parameters of the methods guarantees that the object being passed will have the correct methods. Some interfaces are used simply to warn the programmer that they have to construct a class in an appropriate manner. A serializable object, for instance, is one which can be safely sent over a network to a remote object. It contains no methods, but the type is required for the method writeObject. The notion is that the programmer will have been reminded to make sure that no JVM-specific information is non-transient. Evolving interfaces. Over the past few years a lot of development teams have adopted the agile methodology for software development. As soon as some of the requirements become clear, these deliverables will be prioritized depending on the value each deliverable provides to the customer. Designers would start designing the application once a prioritized list was created, keeping in mind that the list would not contain all the deliverables just yet. Once the design of a single deliverable was completed, developers could start implementing, testers could start testing and so on. As a result interfaces tend to change over time. Methods are continually needed to be added to existing interfaces far into the project. As the development iterations go by, new functionality is designed and needs to be added to existing interfaces. The problem with interfaces is of course that all classes that implement this interface need to provide an implementation of all of the abstract methods defined within the interfaces. So every time a new method is added to the interface, all implementing classes need to be updated, recompiled, and retested. Defining default interface methods. When an interface defines a default method, implementations of the interface inherit the implementation of the method. In the example above the Java 8 training class implements the training interface, but does not implement the isMultipleDays method. Calling this method now returns the result of the default implementation in the interface. Java 8 interfaces can still not contain instance variables, however just like with abstract classes, it is possible to call upon any of the abstract methods in the interface to implement the default behavior of the method. When two interfaces define a default method with the same method signature, and a class attempts to implement both interfaces, the compiler will throw an error. When two interfaces define a default with the same method signature, and your class needs to implement both interfaces, you must provide an implementation of the method. Within the implementation, it is now possible to reference the default implementation by using InterfaceName.super. In addition to default methods, interfaces can now also contain static methods. As a result, utility methods that work on implementations of this class no longer need to be implemented in a separate companion class, but can now be defined within the interface. As a result, static methods can now be invoked on a interface reference. The implementation of the class does not inherit the method. As soon as multiple default or static methods were being added to the interface, it became clear that in some cases the implementations of this methods were very similar. Until the introduction of Java 9, functionality had to be duplicated among these various methods. Starting with Java 9, an interface may also contain private methods. Functionality that can be reused by multiple default or static methods can now be defined in this private method. The private interface can now be invoked by all other methods defined on the interface while its implementation is still hidden from the implementation classes. Let's now consider abstract classes versus interfaces. A class, abstract or concrete, extends zero or one other class, and implements zero or more interfaces. An interface, on the other hand, extends zero or more interfaces. Interfaces cannot contain instance variables, and by extending an abstract class, a specialized type is defined. To compare the functionality of abstract classes versus interfaces, remember that you can only inherit from one class but you can implement as many interfaces as you want. Abstract classes, unlike interfaces, can also supply implementations for some methods, which access instance variables. And remember to apply the is a versus can do a test to see if you should be using an abstract class or an interface. Okay, before we complete this lecture, pause this video and consider the following questions to test yourself on the content that we have just reviewed. Write down your answers for each question and then resume the video to compare answers. Okay, the answers to the above questions are. One, creating a supertype as an interface allows the implementer to subclass from another class. Two, yes, an interface can have zero or more methods. Three, none, classes, interfaces, and abstract classes all support the identical type model. Four, a class can extend another abstract class and is not required to implement its methods. The subclass would also have to be defined as abstract. A class can be completely implemented yet defined as abstract so it cannot be instantiated, only subclassed. Five, an abstract class can contain instance variables, and interfaces cannot. Classes can only extend from one other class, but can implement multiple interfaces. And six, it allows the reuse of functionality between multiple default or static methods in the interface.
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).