Java Interfaces
An interface in Java is a reference type that serves as a blueprint for classes. It can contain constants, method signatures, default methods, static methods, and private methods. Interfaces cannot be instantiated directly but are implemented by classes or extended by other interfaces.
Defining and Implementing an Interface
Interfaces are declared using the interface keyword. By default, all members of an interface are public. Abstract methods within an interface are implicitly public abstract, and variables are implicitly public static final.
public interface Connectable {
// Implicitly public static final
String PROTOCOL = "HTTPS";
// Abstract method
void connect();
// Default method (Java 8+)
default void logStatus() {
System.out.println("Current protocol: " + PROTOCOL);
}
// Static method (Java 8+)
static boolean isAvailable(String service) {
return service != null && !service.isEmpty();
}
}
A class implements an interface using the implements keyword. If a class implements an interface, it must provide concrete implementations for all its abstract methods unless the class itself is abstract.
public class WebServer implements Connectable {
private String serverName;
public WebServer(String name) {
this.serverName = name;
}
@Override
public void connect() {
System.out.println(serverName + " is connecting via " + PROTOCOL);
}
}
Class Inheritance
Inheritance allows a subclass to inherit fields and methods from a superclass. In Java, a class can only extend one superclass (single inheritance), but it can implement multiple interfaces. All Java classes implicitly inherit from the java.lang.Object class.
The super Keyword
The super keyword is used to refer to the immediate superclass. It is commonly used to call the superclass constructor or to access overridden methods.
public class Device {
protected String brand;
public Device(String brand) {
this.brand = brand;
}
public void powerOn() {
System.out.println("Device powering on...");
}
}
public class Smartphone extends Device {
private double batteryLevel;
public Smartphone(String brand, double battery) {
// Calls the superclass constructor
super(brand);
this.batteryLevel = battery;
}
@Override
public void powerOn() {
super.powerOn(); // Calls the parent method
System.out.println(brand + " smartphone at " + batteryLevel + "% battery.");
}
}
Polymorphism and Type Casting
Polymorphism allows an object to take multiple forms. A subclass object can be referenced by a variable of its superclass type or any interface it implements. This is known as upcasting.
Device myPhone = new Smartphone("Apple", 85.0);
myPhone.powerOn(); // Executes the overridden method in Smartphone
Downcasting is the process of casting a superclass reference back to a subclass type. This requires an explicit cast and should be checked using the instanceof operator to avoid a ClassCastException.
if (myPhone instanceof Smartphone) {
Smartphone sp = (Smartphone) myPhone;
System.out.println("Accessed subclass specific behavior.");
}
Abstract Classes vs. Interfaces
While both provide abstraction, they serve different purposes:
- Abstract Classes: Used when classes share a common identity and state. They can have non-static fields and non-public constructors.
- Interfaces: Used to define a common contract or capability across unrelated classes. They focus on "what an object can do" rather than "what it is."
public abstract class AbstractSensor {
private int id;
public AbstractSensor(int id) {
this.id = id;
}
public abstract double readData();
public void displayId() {
System.out.println("Sensor ID: " + id);
}
}
Anonymous Inner Classes
Anonymous classes allow you to declare and instantiate a class at the same time. They are useful for one-time implementations of interfaces or abstract classes without creating a formal named class.
Connectable tempConnection = new Connectable() {
@Override
public void connect() {
System.out.println("Temporary connection established.");
}
};
tempConnection.connect();
Java Initialization Sequence
Understanding the order in which blocks of code are executed is critical for debugging complex inheritance hierarchies. The order is generally as follows:
- Static Members: Static variables and static blocks are initialized when the class is first loaded, starting from the base class down to the most derived class.
- Instance Members: When an object is created, instance variables and instance initialization blocks of the superclass are processed.
- Constructors: The superclass constructor is executed before the subclass constructor.
public class Base {
static { System.out.println("Base: Static Block"); }
{ System.out.println("Base: Instance Block"); }
public Base() { System.out.println("Base: Constructor"); }
}
public class Derived extends Base {
static { System.out.println("Derived: Static Block"); }
{ System.out.println("Derived: Instance Block"); }
public Derived() { System.out.println("Derived: Constructor"); }
public static void main(String[] args) {
new Derived();
}
}
// Output:
// Base: Static Block
// Derived: Static Block
// Base: Instance Block
// Base: Constructor
// Derived: Instance Block
// Derived: Constructor
Final Methods and Classes
The final keyword provides restrictions on inheritance:
- A
finalclass cannot be extended. - A
finalmethod cannot be overridden by subclasses.