Interfaces
Introduction
interface Animal {
void makeNoise();
void eat();
}
Interfaces support the concept of multiple inheritance in Java, where a class can implement multiple interfaces, which is not possible with classes because Java does not support multiple class inheritance.
A class or another interface can implement an interface using the implements keyword. When a class implements an interface, it must provide concrete implementations for all of the interface's abstract methods.
An abstract method is a method without a body, i.e. only the method name with its signature and throws declaration. We think of an abstract method as not containing a body from now on.
Interfaces
Key Properties

The primary purpose of interfaces in Java is to define a contract for what methods a class should implement, without dictating how the methods should be implemented. This helps in achieving a form of abstraction, where the "what" is separated from the "how." Interfaces are used to encapsulate the abstract part of the concept; they tell the implementing class what methods it must have, but not how those methods should work. The following things are important when working with interfaces.
-
Method signature: All methods in an interface are implicitly public and abstract, except static and default methods. When a class implements an interface, it must provide concrete implementations for all the abstract methods declared by the interface.
-
Constants: Variables defined in interfaces are implicitly public, static, and final, meaning they are constant values that cannot change.
-
Default methods: Java 8 introduced default methods in interfaces, which are methods that have a default implementation. Classes that implement the interface can override these methods, but it's not mandatory. Default methods were introduced to provide backward compatibility for existing interfaces when new methods are added.
-
Static methods: Interfaces can have static methods which can be called independently of any object. These methods are not inherited by a class implementing the interface.
-
Inheritance: An interface can extend another interface, thereby inheriting its methods. This allows for a form of multiple inheritance, as a class can implement multiple interfaces.
-
Implementing Classes: A class can implement one or several interfaces, thereby agreeing to provide concrete implementations for all of the abstract methods from the interfaces. If a class fails to implement all the methods, it must be declared abstract.
-
Polymorphism: Interfaces are a way to achieve polymorphism in Java. They allow methods to be written in a more generic way, making the code more modular and flexible.
Interfaces
Implementation Contract
When a class in Java declares that it implements an interface, it must adhere to the following rules.
-
Implement all abstract methods: The class must provide concrete implementations for all abstract methods declared in the interface. These implementations must match the signature defined in the interface.
-
Maintain the method visibility: Since all methods in an interface are implicitly public (except static and private methods in Java 9 and higher), the implementing class must declare the implemented methods as public.
-
Adhere to the method signatures: The return type and parameter list of the implementing methods must exactly match those specified in the interface.
Interfaces
Complications

Often the following set of questions pop up in the context of interfaces.
-
Can a class that implements an interface have more methods? Yes, a class that implements an interface can have more methods than those defined in the interface. These additional methods are part of the class's own functionality and do not affect its contract with the interface.
-
Can it leave out methods? No, a class cannot leave out any abstract methods of the interface it implements. It must provide concrete implementations for all of the interface's abstract methods. If it does not, then the class must be declared as abstract, and the responsibility to implement the missing methods would fall to any concrete subclass of this abstract class.
-
What if two interfaces have the same method? When a class implements two interfaces that have a method with the same signature, the class implements a single version of the method, and that implementation is used for both interface contracts. This situation doesn't usually cause any conflict as long as the method signature is exactly the same (same name, same parameter list, and compatible return types). However, if the method from each interface has a different return type, then it is not possible for a class to implement both interfaces directly without causing a compile-time error. The Java type system does not allow this because it would not be able to determine which method signature to use in that case. If the same method is a default method in both interfaces (meaning it comes with an implementation), the class implementing these interfaces will have to override this method to resolve the ambiguity, even if the signatures and return types are the same. This ensures that there is a clear decision made by the developer about which interface's default behavior should be used, or to provide a new implementation entirely.
Abstract Classes
Introduction and Key Properties

Even though not very relevant let us still cover the abstract keyword and what it does. An abstract class in Java is a class that cannot be instantiated on its own and may contain a mix of methods that are declared but not implemented (abstract methods) and methods that are implemented (concrete methods). Abstract classes are used when you want to create a blueprint for a group of subclasses and you have some common behaviour that can be shared in the superclass. Here is the most important information.
-
Instantiation: Abstract classes cannot be instantiated directly. This means you cannot create an object of an abstract class using the new keyword.
-
Subclassing: To use an abstract class, you must subclass it and provide implementations for the abstract methods. Once all abstract methods are implemented, you can instantiate the subclass.
-
Abstract Methods: Abstract classes can have abstract methods, i.e. methods without a body. Any class that extends the abstract class must provide concrete implementations for these abstract methods, unless it is also declared as abstract.
-
Concrete Methods: Abstract classes can also have concrete methods (methods with an implementation). This allows you to define some default behavior for the methods and still allow subclasses to override these methods.
-
Constructors: Abstract classes can have constructors. These constructors are not used for creating objects directly but are used by subclasses to initialize the abstract class’s state when a subclass object is instantiated.
-
Fields and Constants: Abstract classes can have fields and constants that can be inherited by subclasses.
-
Static Methods: Abstract classes can have static methods that can be called independently of any object.
Inheritance
Main Problems
When working with inheritance three main problems can occur.
-
Incompatible types: Assignment of a type that is not a subtype. This is detected by the compiler.
-
Inconvertible types: Casting to a type that is neither a subtype nor a supertype. This is detected by the compiler.
-
ClassCastException: Casting and the actual type is not a (sub)type. This is discovered at runtime.
Inheritance
Compatibility vs. Inheritance
In Java, there are two major data types: primitive types and reference types. Understanding how compatibility and inheritance apply to these types is fundamental to grasping how Java manages data and objects.
-
Compatibility (Primitive Types): Compatibility among primitive types in Java is based on the size and value range that the types can hold. Java has eight primitive types: byte, short, int, long, float, double, char, and boolean.
-
Implicit Casting (Widening Conversion): Smaller primitives can be automatically converted to larger ones without losing information. For example, a byte can be assigned to an int variable without casting because an int is larger and can hold all possible values of a byte.
-
Explicit Casting (Narrowing Conversion): When you assign a larger primitive to a smaller one, you must use an explicit cast. This is because there's a risk of losing information if the larger type's value range exceeds that of the smaller type.
-
Incompatible Types: There's no implicit or explicit casting between boolean and any numeric type because they are not compatible in terms of their value domain. Also, without casting, floating-point types are not compatible with integral types due to the difference in how the values are stored.
-
Exceptions
Introduction
public void getSum() throws ExceptionName {
}
Exceptions are runtime anomalies or abnormal conditions that a program may encounter during its execution. Java provides a robust mechanism to handle these runtime errors so that the normal flow of the application can be maintained. In Java, both errors and exceptions represent abnormal conditions that may occur during program execution, but they differ in their nature, origin, and how developers typically deal with them.
-
Errors: Indicate conditions that a reasonable application might want to catch. They are often due to external circumstances that the application can handle.
-
Exceptions: Suggest catastrophic failures that are not typically handled by the application. They often indicate a need to fix something in the environment or the application's configuration rather than in the application's logic..
Exceptions
Types of Exceptions
The following types of exceptions exist.
-
Checked Exceptions: These are exceptions that a program must handle, otherwise it will fail to compile. They are checked by the compiler. For example, IOException, SQLException, etc.
-
Unchecked Exceptions: These are exceptions that are not checked by the compiler. They are derived from RuntimeException class. Common examples include NullPointerException, ArrayIndexOutOfBoundsException, ArithmeticException, etc.
-
Errors at runtime: These are exceptional conditions that are external to the application and that the application usually cannot anticipate or recover from. For instance, OutOfMemoryError.
Exceptions
Handling Exceptions

One can handle exceptions using a combination of the following keywords.
-
try block: The code that you suspect might raise an exception is placed inside the try block.
-
catch block: If an exception occurs in the try block, the corresponding catch block is executed. There can be multiple catch blocks following a try block, each handling a different type of exception.
-
finally block: It is optional and can be used to place important code that must execute whether an exception was thrown or not.
Exceptions
Throwing and Custom Exceptions.

You can manually throw an exception using the throw keyword. Java allows you to define your custom exception classes. To create a custom exception, you typically extend the Exception class (for checked exceptions) or the RuntimeException class (for unchecked exceptions). Let us summarise the concepts learned and add useful information for you to use in your programming.
-
try-catch-finally: Fundamental blocks for exception handling. See the examples and explanation above on how to use those.
-
throws: If a method does not handle a checked exception, it must declare it using the throws keyword.
-
throw: Used to explicitly throw an exception.
-
Exception propagation: An exception can be propagated up the call stack if it's not caught. This means it can be thrown from one method and caught in another method that calls the first.
-
getMessage(): This method returns the detail message of the exception.
-
printStackTrace(): This method prints the stack trace of the exception, which is useful for debugging.
Exceptions
Catch Ordering

The ordering of catch statements in Java is crucial due to the concept of polymorphism. If you catch a superclass exception type before a subclass exception type, the catch block for the superclass will also catch exceptions of the subclass type, making any subsequent catch blocks for the subclass type unreachable. Hence when using catch statements keep the following points in mind.
-
Subclasses First: When catching exceptions of both a superclass and a subclass type, you should always place the catch block for the subclass before the catch block for the superclass.
-
Compilation Error: If you mistakenly place the catch block for a superclass before a catch block for its subclass, the Java compiler will throw an error. This is because the catch block for the superclass will catch all exceptions of both the superclass and subclass types, making the catch block for the subclass unreachable.
-
First Match: When an exception is thrown, the JVM will execute the first matching catch block it finds. After a catch block executes, no subsequent catch blocks will be checked.