Nesting with Java interfaces

Three ways to use nested interfaces in your Java code

Deep, nested topographical layers / binary code [RED]
FILO / Aleksei Derin / Getty Images

If you've read my Java 101 tutorial introducing static classes and inner classes, you should be familiar with the basics of working with nested classes in Java code. In this Java tip, you'll learn three common ways to use nesting techniques with Java interfaces. I'll also briefly demonstrate nesting interfaces with static methods, also known as static method interfaces.

Nesting interfaces in classes

Java lets you declare interfaces inside of classes. Once declared, an interface is automatically a static member of the class. There is no need to declare the interface with the static keyword. Consider the following example:


class EnclosingClass
{
   interface EnclosedInterface1
   {
   }

   static interface EnclosedInterface2
   {
   }
}

EnclosedInterface1 and EnclosedInterface2 are static member interfaces. There's no equivalent to local classes because interfaces cannot be declared in blocks. However, interfaces can be used in anonymous class contexts.

Nested interfaces are typically not accessed from beyond their enclosing classes. Otherwise, you could declare them as top-level interfaces. Instead, they're accessed from inside their enclosing classes, as demonstrated here:


class EnclosingClass
{
   interface EnclosedInterface1
   {
   }

   static interface EnclosedInterface2
   {
   }

   static class EnclosedClass1 implements EnclosedInterface1, EnclosedInterface2
   {
   }

   class EnclosedClass2 implements EnclosedInterface1, EnclosedInterface2
   {
   }

   void m()
   {
      class EnclosedClass3 implements EnclosedInterface1, EnclosedInterface2
      {
      }

      new EnclosedInterface1()
      {
      };
   }
}

Note that static member class EnclosedClass1, non-static member class EnclosedClass2, and local class EnclosedClass3 implement both nested interfaces. However, only one interface can be implemented in an anonymous class context. (See Static classes and inner classes in Java for more about anonymous classes.)

Nesting interfaces in interfaces

Java also lets you declare interfaces inside of interfaces. In this case, a nested interface is a static member of its enclosing interface, and once again the static keyword is redundant. Also, the nested interface is implicitly public. Here's an example:


interface EnclosingInterface
{
   interface EnclosedInterface1 // implicitly static and public
   {
   }

   static interface EnclosedInterface2 // explicitly static and implicitly public
   {
   }
}

You access an enclosed interface by prefixing its name with the enclosing interface's name and member access operator. For example, specify EnclosingInterface.EnclosedInterface1 to access EnclosedInterface1.

Nesting classes in interfaces

One of Java's stranger language features is the ability to nest a class inside an interface. As with a nested interface, the nested class is implicitly public and static. Here's an example:


interface EnclosingInterface
{
   class EnclosedClass
   {
   }
}

Despite its strangeness, being able to nest a class inside an interface can be useful, especially when there is a tight relationship between the enclosing interface and the enclosed class. Capturing this relationship improves the source code's readability. In cases where the class and interface carry the same name, nesting can also help you avoid a name conflict between the two.

Example: Addressable interface encloses Address class

Suppose you want to model addressable entities such as letters, postcards, and parcels. Each entity will be described by its own class, but they all share the commonality of being addressable to some destination. Each class could be designed with duplicate address fields, or you might abstract these details into a common superclass. Alternatively, you could leverage Listing 1's Addressable interface.

Listing 1. Addressable.java


public interface Addressable
{
   public class Address
   {
      private String boxNumber;
      private String street;
      private String city;

      public Address(String boxNumber, String street, String city)
      {
         this.boxNumber = boxNumber;
         this.street = street;
         this.city = city;
      }

      public String getBoxNumber()
      {
         return boxNumber;
      }

      public String getStreet()
      {
         return street;
      }

      public String getCity()
      {
         return city;
      }

      public String toString()
      {
         return boxNumber + " - " + street + " - " + city;
      }
   }

   public Address getAddress();
}

The Addressable interface describes an addressable entity as containing an address. This address is described by the nested Address class. The getAddress() method is implemented by whatever class implements Addressable.

Listing 2 presents the source code to a Letter class that implements Addressable.

Listing 2. Letter.java


public class Letter implements Addressable
{
   private Addressable.Address address;

   public Letter(String boxNumber, String street, String city)
   {
      address = new Addressable.Address(boxNumber, street, city);
   }

   public Address getAddress()
   {
      return address;
   }
}

Letter stores a single address field of type Addressable.Address. This nested class is instantiated by Letter's constructor. The implemented getAddress() method returns this object.

Now consider what happens when we add Postcard and Parcel classes to the Addressables application. Listing 3 presents the source code to an Addressables application, which demonstrates the Postcard, Parcel, and Letter types.

Listing 3. Addressables.java


public class Addressables
{
   public static void main(String[] args)
   {
      Addressable[] addressables =
      {
         new Letter("100", "Main Street", "Town A"),
         new Postcard("200", "Waterfront Drive", "Town B"),
         new Parcel("300", "10th Ave", "Town C")
      };
      for (int i = 0; i < addressables.length; i++)
         System.out.println(addressables[i].getAddress());
   }
}

The main() method first creates an array of Addressable objects. It then iterates over these objects, invoking getAddress() on each object. The returned Addressable.Address object's toString() method is invoked by System.out.println() to return a string representation of the object, and this representation is subsequently output.

Compile Listings 2 and 3 along with Postcard.java and Parcel.java as follows:


javac *.java

Run the application as follows:


java Addressables

You should observe the following output:


100 - Main Street - Town A
200 - Waterfront Drive - Town B
300 - 10th Ave - Town C

Nested interfaces with static methods (static method interfaces)

Many developers believe that nesting a class in an interface violates object-oriented programming and the purpose for an interface. However, it's good to know about this capability because you might encounter it when maintaining someone else's code. Furthermore, now that interfaces have evolved to include default and static methods, you might encounter additional reasons to nest a class in an interface. As an example, here is a local class nested within a static method interface:


interface I
{
   static void m()
   {
      class C
      {
      }
   }
}

Conclusion

I've presented three common ways to use nesting techniques with Java classes and interfaces, and also demonstrated a controversial technique of nesting interfaces with static methods. See the complete Java 101 tutorial to learn more about nesting with static classes and inner classes in Java.

This story, "Nesting with Java interfaces" was originally published by JavaWorld.

Copyright © 2020 IDG Communications, Inc.