Interfaces with inheritance

CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

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

Once
you’ve implemented an
interface,
that implementation becomes an ordinary class that can be extended in the
regular way.

You
can choose to explicitly declare the method declarations in an
interface
as
public.
But they are
public
even if you don’t say it. So when you
implement
an
interface,
the methods from the
interface
must be defined as
public.
Otherwise they would default to “friendly” and you’d be
restricting the accessibility of a method during inheritance, which is not
allowed by the Java compiler.

You
can see this in the modified version of the
Instrument
example. Note that every method in the
interface
is strictly a declaration, which is the only thing the compiler allows. In
addition, none of the methods in
Instrument5
are declared as
public,
but they’re automatically
public
anyway:

//: 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

In
a derived class, you aren’t forced to have a base class that is either an
abstract
or “concrete” (one with no
abstract
methods). If you
do
inherit from a non-
interface,
you
can inherit from only one. All the rest of the base elements must be
interfaces.
You place all the interface names after the
implements
keyword
and separate them with commas. You can have as many
interfaces
as you want and each one becomes an independent type that you can upcast to.
The following example shows a concrete class combined with several
interfaces
to produce a new class:

//: 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

Fields
defined in interfaces are automatically
static
and
final.
These cannot be “blank finals,” but they can be initialized with
non-constant expressions. For example:

//: 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.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read