A basic understanding of Java Classes and Interfaces

Java classes and interfaces are essential building blocks in Java programming. Here’s a simple explanation of both:


Java Classes

A class in Java is a blueprint or template used to create objects (instances). It contains:

  • Fields (Instance Variables): To store the state of objects.
  • Methods: To define behaviors or functionalities of the objects.
  • Constructors: To initialize objects.

Key Characteristics of a Class:

  1. It can extend (inherit from) another class (single inheritance).
  2. It can implement multiple interfaces.
  3. Commonly used for defining real-world entities with their properties and behaviors.

Example of a Class:

public class Animal {
    // Fields
    private String name;
    private int age;

    // Constructor
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Method
    public void speak() {
        System.out.println(name + " says hello!");
    }

    // Getter
    public String getName() {
        return name;
    }
}

How to use a class:

public class Main {
    public static void main(String[] args) {
        Animal dog = new Animal("Buddy", 3);
        dog.speak();  // Output: Buddy says hello!
    }
}

Java Interfaces

An interface in Java is a contract that defines a set of methods that a class must implement. Interfaces do not provide implementation but only the method declarations (method signatures).

Key Characteristics of Interfaces:

  1. A class that implements an interface must provide concrete implementations for all of its methods.
  2. A class can implement multiple interfaces (unlike inheritance where a class can extend only one class).
  3. Interfaces in Java 8+ can have:
    • Default Methods: Methods with a default implementation.
    • Static Methods: Methods that belong to the interface and can be invoked without an instance.

Example of an Interface:

public interface AnimalBehavior {
    void eat();  // Abstract method
    void sleep();
}

Implementing an Interface:

public class Dog implements AnimalBehavior {
    @Override
    public void eat() {
        System.out.println("The dog is eating.");
    }

    @Override
    public void sleep() {
        System.out.println("The dog is sleeping.");
    }
}

How to use the class with interface:

public class Main {
    public static void main(String[] args) {
        AnimalBehavior myDog = new Dog();
        myDog.eat();     // Output: The dog is eating.
        myDog.sleep();   // Output: The dog is sleeping.
    }
}

Differences Between Classes and Interfaces

Feature Class Interface
Purpose Blueprint for creating objects. Contract defining behavior (method signatures).
Inheritance Supports single inheritance. Can be implemented by multiple classes.
Access Modifiers Methods can have different access modifiers. Methods are public by default (abstract).
Implementation Contains method implementations. No method implementations (except default/static in Java 8+).
Usage Used for defining states and behaviors. Provides abstraction and enforces contract.

Summary:

  • Use classes to define what something is and its behavior.
  • Use interfaces to define what something can do (define a contract for behavior).

What are Static Methods on interface in Java?

In Java SE 8 and later, you can define static methods on interfaces. A static method is a method associated with the class, not the instance. This means you can call a static method without creating an instance of the class.

This feature can be particularly useful when providing utility methods that act on instances of the interface. You would normally keep these in a separate utility class, but by having these on the interface itself can lead to more readable and maintainable code.

Here is a simple example:

interface MyInterface {
    static void myStaticMethod() {
        System.out.println("Static Method on Interface");
    }
}

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

In this example, myStaticMethod() is a static method defined on MyInterface. You call it using the interface name (MyInterface.myStaticMethod()), without needing to create an instance of MyInterface.

Keep in mind that static methods in interfaces are not inherited by classes that implement the interface or sub-interfaces, so you always have to use the interface name when calling them.

The Stream interface in Java has several static methods that provide useful functionality for working with sequences of elements, such as collections. Here is an example that uses the Stream.of() static method, which allows you to create a Stream from a set of objects:

import java.util.stream.*;

public class Main {
    public static void main(String[] args) {
        Stream.of("Hello", "World", "Interface", "Static", "Methods")
              .map(String::toUpperCase)
              .forEach(System.out::println);
    }
}

In this example, we use Stream.of() to create a Stream from a set of String objects. We then use map() to convert each string in the stream to uppercase, and forEach() to print out each string.

Here is another example, this time using the IntStream.range() static method:

import java.util.stream.*;

public class Main {
    public static void main(String[] args) {
        IntStream.range(1, 6)
                 .forEach(System.out::println);
    }
}

In this example, IntStream.range(1, 6) creates a stream of integers from 1 (inclusive) to 6 (exclusive). The forEach() method is then used to print out each integer in the stream.

What is Default Methods in Java?

Default methods are a feature introduced in Java 8, allowing the declaration of methods in interfaces, apart from abstract methods. They are also known as defender methods or virtual extension methods.

With the use of default keyword, these methods are defined within the interface and provide a default implementation. This means they can be directly used by any class implementing this interface without needing to provide an implementation for these methods.

The main advantage of default methods is that they allow the interfaces to be evolved over time without breaking the existing code.

Here’s an example of a default method in an interface:

interface MyInterface {
    void abstractMethod();

    default void defaultMethod() {
        System.out.println("This is a default method in the interface");
    }
}

In the above example, any class implementing MyInterface needs to provide an implementation for abstractMethod(), but not for defaultMethod() unless it needs to override the default implementation.

Before Java 8, we could declare only abstract methods in interfaces. It means that classes which implement the interface were obliged to provide an implementation of all methods declared in an interface. However, this was not flexible for developers, especially when they wanted to add new methods to the interfaces.

For instance, here is an interface used by multiple classes:

interface Animal {
    void eat();
}

Now, if we wanted to add a new method called run(), all classes that implement Animal would need to define this method, which could potentially introduce bugs and is quite cumbersome if we have many classes that implement the interface.

To mitigate such issues, Java 8 introduced default methods in interfaces. With default methods, we can now add new methods in the interface with a default implementation, thereby having the least impact on the classes that implement the interface.

interface Animal {
    void eat();

    default void run() {
        System.out.println("Running");
    }
}

So in the updated Animal interface, the run() method is a default method. Classes implementing Animal can choose to override this method, but they are not obliged to do so. If a class does not provide an implementation for this method, the default implementation from the interface will be used.

Here’s an example implementation of the Animal interface:

class Dog implements Animal {
    @Override
    public void eat() {
        System.out.println("Dog is eating");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.eat();  // Output: Dog is eating
        dog.run();  // Output: Running
    }
}

As you can see, the Dog class didn’t implement the run method, but we’re still able to call dog.run() because of the default implementation in the Animal interface.

Note: In case a class implements multiple interfaces and these interfaces have default methods with identical signatures, the compiler will throw an error. The class must override the method to resolve the conflict.

How do I get direct superclass and interfaces of a class?

Java reflection also dealing with inheritance concepts. You can get the direct interfaces and direct super class of a class by using method getInterfaces() and getSuperclass() of java.lang.Class object.

  • getInterfaces() will returns an array of Class objects that represent the direct super interfaces of the target Class object.
  • getSuperclass() will returns the Class object representing the direct super class of the target Class object or null if the target represents Object class, an interface, a primitive type, or void.
package org.kodejava.lang.reflect;

import javax.swing.*;
import java.util.Date;

public class GetSuperClassDemo {
    public static void main(String[] args) {
        GetSuperClassDemo.get(String.class);
        GetSuperClassDemo.get(Date.class);
        GetSuperClassDemo.get(JButton.class);
        GetSuperClassDemo.get(Timer.class);
    }

    public static void get(Class<?> clazz) {
        // Gets array of direct interface of clazz object
        Class<?>[] interfaces = clazz.getInterfaces();

        System.out.format("Direct Interfaces of %s:%n",
                clazz.getName());
        for (Class<?> clz : interfaces) {
            System.out.println(clz.getName());
        }

        // Gets direct superclass of clazz object
        Class<?> superclz = clazz.getSuperclass();
        System.out.format("Direct Superclass of %s: is %s %n",
                clazz.getName(), superclz.getName());
        System.out.println("====================================");
    }
}

Here is the result of the code snippet:

Direct Interfaces of java.lang.String:
java.io.Serializable
java.lang.Comparable
java.lang.CharSequence
Direct Superclass of java.lang.String: is java.lang.Object 
====================================
Direct Interfaces of java.util.Date:
java.io.Serializable
java.lang.Cloneable
java.lang.Comparable
Direct Superclass of java.util.Date: is java.lang.Object 
====================================
Direct Interfaces of javax.swing.JButton:
javax.accessibility.Accessible
Direct Superclass of javax.swing.JButton: is javax.swing.AbstractButton 
====================================
Direct Interfaces of javax.swing.Timer:
java.io.Serializable
Direct Superclass of javax.swing.Timer: is java.lang.Object 
====================================

How do I check if a class represent an interface type?

You can use the isInterface() method call of the java.lang.Class to identify if a class objects represent an interface type.

package org.kodejava.lang.reflect;

import java.io.Serializable;

public class IsInterfaceDemo {
    public static void main(String[] args) {
        IsInterfaceDemo.get(Serializable.class);
        IsInterfaceDemo.get(Long.class);
    }

    private static void get(Class<?> clazz) {
        if (clazz.isInterface()) {
            System.out.println(clazz.getName() +
                    " is an interface type.");
        } else {
            System.out.println(clazz.getName() +
                    " is not an interface type.");
        }
    }
}

Here is the result of the program:

java.io.Serializable is an interface type.
java.lang.Long is not an interface type.