Interfaces with inheritance

Bruce Eckel's Thinking in Java Contents | Prev | Next

//: Music5.java
// Interfaces
import java.util.*;
 
interface Instrument5 {
  // Compile-time constant:
  int i = 5; // static & final
  // Cannot have method definitions:
  void play(); // Automatically public
  String what();
  void adjust();
}
 
class Wind5 implements Instrument5 {
  public void play() {
    System.out.println("Wind5.play()");
  }
  public String what() { return "Wind5"; }
  public void adjust() {}
}
 
class Percussion5 implements Instrument5 {
  public void play() {
    System.out.println("Percussion5.play()");
  }
  public String what() { return "Percussion5"; }
  public void adjust() {}
}
 
class Stringed5 implements Instrument5 {
  public void play() {
    System.out.println("Stringed5.play()");
  }
  public String what() { return "Stringed5"; }
  public void adjust() {}
}
 
class Brass5 extends Wind5 {
  public void play() {
    System.out.println("Brass5.play()");
  }
  public void adjust() { 
    System.out.println("Brass5.adjust()");
  }
}
 
class Woodwind5 extends Wind5 {
  public void play() {
    System.out.println("Woodwind5.play()");
  }
  public String what() { return "Woodwind5"; }
}
 
public class Music5 {
  // Doesn't care about type, so new types
  // added to the system still work right:
  static void tune(Instrument5 i) {
    // ...
    i.play();
  }
  static void tuneAll(Instrument5[] e) {
    for(int i = 0; i < e.length; i++)
      tune(e[i]);
  }
  public static void main(String[] args) {
    Instrument5[] orchestra = new Instrument5[5];
    int i = 0;
    // Upcasting during addition to the array:
    orchestra[i++] = new Wind5();
    orchestra[i++] = new Percussion5();
    orchestra[i++] = new Stringed5();
    orchestra[i++] = new Brass5();
    orchestra[i++] = new Woodwind5();
    tuneAll(orchestra);
  }
} ///:~ 

“Multiple inheritance” in Java

//: Adventure.java
// Multiple interfaces
import java.util.*;
 
interface CanFight {
  void fight();
}
 
interface CanSwim {
  void swim();
}
 
interface CanFly {
  void fly();
}
 
class ActionCharacter {
  public void fight() {}
}
 
class Hero extends ActionCharacter 
    implements CanFight, CanSwim, CanFly {
  public void swim() {}
  public void fly() {}
}
 
public class Adventure {
  static void t(CanFight x) { x.fight(); }
  static void u(CanSwim x) { x.swim(); }
  static void v(CanFly x) { x.fly(); }
  static void w(ActionCharacter x) { x.fight(); }
  public static void main(String[] args) {
    Hero i = new Hero();
    t(i); // Treat it as a CanFight
    u(i); // Treat it as a CanSwim
    v(i); // Treat it as a CanFly
    w(i); // Treat it as an ActionCharacter
  }
} ///:~ 

You can see that Hero combines the concrete class ActionCharacter with the interfaces CanFight, CanSwim, and CanFly. When you combine a concrete class with interfaces this way, the concrete class must come first, then the interfaces. (The compiler gives an error otherwise.)

Note that the signature for fight( ) is the same in the interface CanFight and the class ActionCharacter, and that fight( ) is not provided with a definition in Hero. The rule for an interface is that you can inherit from it (as you will see shortly), but then you’ve got another interface. If you want to create an object of the new type, it must be a class with all definitions provided. Even though Hero does not explicitly provide a definition for fight( ), the definition comes along with ActionCharacter so it is automatically provided and it’s possible to create objects of Hero.

In class Adventure, you can see that there are four methods that take as arguments the various interfaces and the concrete class. When a Hero object is created, it can be passed to any of these methods, which means it is being upcast to each interface in turn. Because of the way interfaces are designed in Java, this works without a hitch and without any particular effort on the part of the programmer.

Keep in mind that the core reason for interfaces is shown in the above example: to be able to upcast to more than one base type. However, a second reason for using interfaces is the same as using an abstract base class: to prevent the client programmer from making an object of this class and to establish that it is only an interface. This brings up a question: Should you use an interface or an abstract class? An interface gives you the benefits of an abstract class and the benefits of an interface, so if it’s possible to create your base class without any method definitions or member variables you should always prefer interfaces to abstract classes. In fact, if you know something is going to be a base class, your first choice should be to make it an interface, and only if you’re forced to have method definitions or member variables should you change to an abstract class.

Extending an interface

with inheritance

//: HorrorShow.java
// Extending an interface with inheritance
 
interface Monster {
  void menace();
}
 
interface DangerousMonster extends Monster {
  void destroy();
}
 
interface Lethal {
  void kill();
}
 
class DragonZilla implements DangerousMonster {
  public void menace() {}
  public void destroy() {}
}
 
interface Vampire 
    extends DangerousMonster, Lethal {
  void drinkBlood();
}
 
class HorrorShow {
  static void u(Monster b) { b.menace(); }
  static void v(DangerousMonster d) {
    d.menace();
    d.destroy();
  }
  public static void main(String[] args) {
    DragonZilla if2 = new DragonZilla();
    u(if2);
    v(if2);
  }
} ///:~ 

DangerousMonster is a simple extension to Monster that produces a new interface. This is implemented in DragonZilla.

Grouping constants

//: Months.java
// Using interfaces to create groups of constants
package c07;
 
public interface Months {
  int
    JANUARY = 1, FEBRUARY = 2, MARCH = 3, 
    APRIL = 4, MAY = 5, JUNE = 6, JULY = 7, 
    AUGUST = 8, SEPTEMBER = 9, OCTOBER = 10,
    NOVEMBER = 11, DECEMBER = 12;
} ///:~ 

Notice the Java style of using all uppercase letters (with underscores to separate multiple words in a single identifier) for static final primitives that have constant initializers – that is, for compile-time constants.

The fields in an interface are automatically public, so it’s unnecessary to specify that.

Now you can use the constants from outside the package by importing c07.* or c07.Months just as you would with any other package, and referencing the values with expressions like Months.JANUARY. Of course, what you get is just an int so there isn’t the extra type safety that C++’s enum has, but this (commonly-used) technique is certainly an improvement over hard-coding numbers into your programs. (This is often referred to as using “magic numbers” and it produces very difficult-to-maintain code.)

//: Month2.java
// A more robust enumeration system
package c07;
 
public final class Month2 {
  private String name;
  private Month2(String nm) { name = nm; }
  public String toString() { return name; }
  public final static Month2
    JAN = new Month2("January"), 
    FEB = new Month2("February"),
    MAR = new Month2("March"),
    APR = new Month2("April"),
    MAY = new Month2("May"),
    JUN = new Month2("June"),
    JUL = new Month2("July"),
    AUG = new Month2("August"),
    SEP = new Month2("September"),
    OCT = new Month2("October"),
    NOV = new Month2("November"),
    DEC = new Month2("December");
  public final static Month2[] month =  {
    JAN, JAN, FEB, MAR, APR, MAY, JUN,
    JUL, AUG, SEP, OCT, NOV, DEC
  };
  public static void main(String[] args) {
    Month2 m = Month2.JAN;
    System.out.println(m);
    m = Month2.month[12];
    System.out.println(m);
    System.out.println(m == Month2.DEC);
    System.out.println(m.equals(Month2.DEC));
  }
} ///:~ 

The class is called Month2 since there’s already a Month in the standard Java library. It’s a final class with a private constructor so no one can inherit from it or make any instances of it. The only instances are the final static ones created in the class itself: JAN, FEB, MAR, etc. These objects are also used in the array month, which lets you choose months by number instead of by name. (Notice the extra JAN in the array to provide an offset by one, so that December is month 12.) In main( ) you can see the type safety: m is a Month2 object so it can be assigned only to a Month2. The previous example Months.java provided only int values, so an int variable intended to represent a month could actually be given any integer value, which wasn’t too safe.

Initializing fields in interfaces

//: RandVals.java
// Initializing interface fields with 
// non-constant initializers
import java.util.*;
 
public interface RandVals {
  int rint = (int)(Math.random() * 10);
  long rlong = (long)(Math.random() * 10);
  float rfloat = (float)(Math.random() * 10);
  double rdouble = Math.random() * 10;
} ///:~ 

Since the fields are static, they are initialized when the class is first loaded, upon first access of any of the fields. Here’s a simple test:

//: TestRandVals.java
 
public class TestRandVals {
  public static void main(String[] args) {
    System.out.println(RandVals.rint);
    System.out.println(RandVals.rlong);
    System.out.println(RandVals.rfloat);
    System.out.println(RandVals.rdouble);
  }
} ///:~ 

The fields, of course, are not part of the interface but instead are stored in the static storage area for that interface.


[28] This approach was inspired by an e-mail from Rich Hoffarth.



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: September 17, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Another day, another end-of-support deadline. You've heard enough about the hazards of not migrating to Windows Server 2008 or 2012. What you may not know is that there's plenty in it for you and your business, like increased automation and performance, time-saving technical features, and a lower total cost of ownership. Check out this upcoming eSeminar and join Rich Holmes, Pomeroy's practice director of virtualization, as he discusses the …

  • Live Event Date: September 10, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild". This loop of continuous delivery and continuous feedback is …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds