Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Object-oriented language basics, Part 6

Use interfaces for safe multiple inheritance and a great deal more

  • Print
  • Feedback

Page 4 of 7

Now that you have the DiagnoseStartStop and Counter interfaces, you can develop a program that tracks how many times you fail at starting a washing machine. Listing 5 does just that:

Listing 5. FaultyAppliance.java

// FaultyAppliance.java
class Appliance implements Counter, DiagnoseStartStop
{
   private int count;
   private String brand;
   protected boolean started;
   Appliance (String brand)
   {
      this.brand = brand;
   }
   String getBrand ()
   {
      return brand;
   }
   public void increment ()
   {
      count++;
   }
   public int getCount ()
   {
      return count;
   }
   public void start ()
   {
      increment ();
   }
   public void stop ()
   {
   }
   public boolean isStarted ()
   {
      return started;
   }
}
class WashingMachine extends Appliance
{
   WashingMachine (String brand)
   {
      super (brand);
   }
   public void start ()
   {
      if (isStarted ())
      {
          System.out.println ("Washing machine already running.");
          return;
      }
      if (rnd (4) > 1)
      {
          System.out.println ("Trying to start washing machine.");
          super.start ();
      }
      else
      {
          started = true;
          System.out.println ("Washing machine is running.");
      }
   }
   public void stop ()
   {
      if (started)
      {
          started = false;
          System.out.println ("Washing machine is stopped.");
      }
      else
          System.out.println ("Washing machine already stopped."); 
   }
   // Return a random number between 0 and limit - 1 (inclusive).
   int rnd (int limit)
   {
      return (int) (Math.random () * limit);
   }
}
class FaultyAppliance
{
   public static void main (String [] args)
   {
      WashingMachine wm = new WashingMachine ("Maytag");
      for (int i = 0; i < 10; i++)
           wm.start ();
      if (wm.isStarted ())
          wm.stop ();
      System.out.println ("Number of faulty start attempts: " +
                          wm.getCount ());
   }
}


FaultyAppliance's main() method creates a WashingMachine object and tries to start the washing machine 10 times. If the washing machine starts, it must stop. If the first attempt to start the washing machine succeeds, getCount() returns zero. Otherwise, that method returns the number of faulty start attempts. To accomplish its simulation, FaultyAppliance uses Math's random() method, which returns a random number that ranges from 0.0 to (almost) 1.0. By multiplying random()'s return value by some limit, and by converting the result to an integer, it is possible to generate an integral random number greater than or equal to zero and less than some limit. (I will discuss Math and its methods in a future column.)

The Appliance class inherits from both the Counter and DiagnoseStartStop interfaces. In contrast to the general use of multiple implementation inheritance, Appliance's multiple interface inheritance is safe. To see why, let's compare both inheritance approaches. We begin with a look at read/write variables and conclude with a look at methods.

What is wrong with multiple implementation inheritance in the context of read/write variables? To answer that question, keep in mind that the compiler must generate instructions that the JVM executes to allocate memory for a variable. The amount of memory the JVM allocates depends on the variable's type. Suppose two superclasses declare a read/write variable with the same name but different types and/or initial values. If Java permitted a subclass to inherit from both superclasses, the compiler would face a dilemma: should it assume the first type (and, possibly, initial value) or the second type (and, possibly, initial value)? A subclass's type implementation is fixed and cannot accommodate both types, thus, the dilemma. For example, suppose the subclass contains x = 2;, and one superclass declares x to be of integer type, whereas the other superclass declares x to be of double-precision floating-point type. Does the compiler generate instructions that assign a 32-bit twos-complement bit sequence -- representing an integer -- to a 32-bit memory location? Or does it generate instructions that assign a 64-bit IEEE-754 format floating-point bit sequence -- representing a double-precision floating-point value -- to a 64-bit memory location? That problem exacerbates if each superclass contains a method that modifies its same-named variable according to the variable's type. Assuming a subclass inherits both methods, the compiler must deal with two different type implementations for the same variable.

  • Print
  • Feedback

Resources