Abstract Classes and Interfaces

Learn about abstract classes, interfaces, and their role in achieving abstraction and defining contracts.


Defining Contracts with Interfaces in Java

Interfaces are a fundamental concept in Java that play a crucial role in defining contracts and ensuring compatibility between different classes. They allow you to specify a set of methods that a class must implement, without dictating *how* those methods are implemented. This promotes loose coupling and allows for more flexible and maintainable code.

What is an Interface?

In Java, an interface is a reference type, similar to a class, but it's essentially a 100% abstract class. It can contain only abstract methods (methods without a body), default methods, static methods, and constants (final variables). Interfaces define a *what* without specifying *how*. It's a blueprint for a class.

Here's the key difference between a class and an interface:

  • A class can have both concrete (implemented) and abstract methods.
  • An interface can only contain abstract methods (before Java 8), default methods, and static methods.
  • A class uses the extends keyword to inherit from another class.
  • A class uses the implements keyword to implement an interface. A class can implement multiple interfaces.

Why Use Interfaces?

Interfaces provide several benefits:

  • Abstraction: They allow you to focus on what an object *does* rather than how it does it.
  • Multiple Inheritance: Java doesn't support multiple class inheritance, but a class can implement multiple interfaces, effectively achieving a form of multiple inheritance.
  • Loose Coupling: Interfaces reduce the dependencies between classes, making the code more modular and easier to maintain.
  • Polymorphism: You can treat objects of different classes uniformly if they implement the same interface. This enables you to write generic code that works with any object that adheres to the interface contract.
  • Contracts: They define a clear contract that any implementing class must adhere to. This ensures consistency and compatibility.

Defining an Interface

To define an interface, use the interface keyword followed by the interface name.

 public interface  MyInterface {
      // Abstract methods (implicitly public and abstract)
      void method1();
      int method2(String str);

      // Constants (implicitly public, static, and final)
      int MY_CONSTANT = 10;
  } 

Implementing an Interface

To implement an interface, a class uses the implements keyword. The class must provide concrete implementations for all abstract methods defined in the interface.

 public class MyClass implements MyInterface {
      @Override
      public void method1() {
          System.out.println("Implementation of method1");
      }

      @Override
      public int method2(String str) {
          System.out.println("Implementation of method2 with string: " + str);
          return str.length();
      }
  } 

Example: Shape Interface

Let's say we want to define a contract for shapes that can calculate their area and perimeter.

 // Shape interface
    interface Shape {
        double getArea();
        double getPerimeter();
    }

    // Circle class implementing Shape
    class Circle implements Shape {
        private double radius;

        public Circle(double radius) {
            this.radius = radius;
        }

        @Override
        public double getArea() {
            return Math.PI * radius * radius;
        }

        @Override
        public double getPerimeter() {
            return 2 * Math.PI * radius;
        }
    }

    // Rectangle class implementing Shape
    class Rectangle implements Shape {
        private double length;
        private double width;

        public Rectangle(double length, double width) {
            this.length = length;
            this.width = width;
        }

        @Override
        public double getArea() {
            return length * width;
        }

        @Override
        public double getPerimeter() {
            return 2 * (length + width);
        }
    }

    public class Main {
        public static void main(String[] args) {
            Shape circle = new Circle(5);
            Shape rectangle = new Rectangle(4, 6);

            System.out.println("Circle Area: " + circle.getArea());
            System.out.println("Circle Perimeter: " + circle.getPerimeter());
            System.out.println("Rectangle Area: " + rectangle.getArea());
            System.out.println("Rectangle Perimeter: " + rectangle.getPerimeter());
        }
    } 

In this example, the Shape interface defines the getArea() and getPerimeter() methods. The Circle and Rectangle classes both implement the Shape interface, providing their own specific implementations for these methods. This allows you to treat both Circle and Rectangle objects as Shape objects, demonstrating polymorphism. The Main class showcases how you can use these classes through the interface.

Default Methods (Java 8 and later)

Java 8 introduced default methods in interfaces. These are methods that have a body within the interface. This allows you to add new methods to an interface without breaking existing implementations.

 interface MyInterface {
      void method1();

      default void method2() {
          System.out.println("Default implementation of method2");
      }
  }

  class MyClass implements MyInterface {
      @Override
      public void method1() {
          System.out.println("Implementation of method1");
      }

      // MyClass can choose to override method2 or use the default implementation.
  } 

Static Methods (Java 8 and later)

Java 8 also introduced static methods in interfaces. Like static methods in classes, static methods in interfaces can be called directly using the interface name. They are not inherited by implementing classes.

 interface MyInterface {
      void method1();

      static void myStaticMethod() {
          System.out.println("Static method in interface");
      }
  }

  public class Main {
      public static void main(String[] args) {
          MyInterface.myStaticMethod(); // Call the static method
      }
  } 

Conclusion

Interfaces are a powerful tool in Java for defining contracts, promoting loose coupling, and achieving polymorphism. They are essential for writing well-designed, maintainable, and scalable code. By using interfaces effectively, you can create more flexible and robust applications.