Inheritance in Java: extended drinks machines

Extending Java classes

An important aspect of object-oriented programming languages like Java is inheritance. This has two important effects. Firstly, it enables you to define a class of objects in terms of how it differs from some other class of objects, either by adding new capabilities or changing existing capabilities. Secondly, it enables you to write general code which works for a range of classes so long as those classes share the capabilities required by the code.

In a previous set of notes, we looked at code which implemented objects of a class DrinksMachine. This was intended to simulate a type of machine with buttons for taking money, dispensing two different varieties of cans of drink ("coke" and "fanta"), and returning change. Suppose we want to simulate another type of drinks machine which is like the previous one, but which has a button for dispensing a third variety of drink ("sprite") as well as the two varieties of the previous machine. We can do this through inheritance. Here is the code for it:

class ExtDrinksMachine1 extends DrinksMachine
{
 private ArrayList<Can> sprites;

 public ExtDrinksMachine1(int p)
 {
  super(p);
  sprites = new ArrayList<Can>();
 }

 public ExtDrinksMachine1(int p,int c, int f,int s)
 {
  super(p,c,f);
  sprites = new ArrayList<Can>();
  for(int i=0; i<s; i++)
     loadSprite(new Can("sprite"));
 }

 public Can pressSprite()
 {
  if(sprites.size()>0&&balance>=price)
     {
      Can can = sprites.get(0);
      sprites.remove(0);
      balance=balance-price;
      cash=cash+price;
      return can;
     }
  else
     return null;
 }

 public void loadSprite(Can can)
 {
  sprites.add(can);
 }

 public boolean spritesEmpty()
 {
  return sprites.size()==0;
 }

}
This defines objects of a class called ExtDrinksMachine1. The words extends DrinksMachine in the header to this class indicate that this class is an extension by inheritance of the class DrinksMachine. On an object of class ExtDrinksMachine1 you can call all the methods for the class DrinksMachine plus the additional methods listed in the code above. So the methods you can call coming from DrinksMachine are:
insert(int)
getBalance()
collectCash()
getPrice()
pressChange()
pressCoke()
pressFanta()
loadCoke(Can)
loadFanta(Can)
cokesEmpty()
fantasEmpty()
setPrice(int)
Where methods take arguments, the type of the argument is given in this listing. You do not have to give code for these methods in the class ExtDrinksMachine1 because a call on them on an object of this class will automatically use the code from the class DrinksMachine due to the extends DrinksMachine in the header. The methods listed in the code for ExtDrinksMachine1 are only those which are extra to the ones given in DrinksMachine and they are:
pressSprite()
loadSprite(Can)
spritesEmpty()
An object of type ExtDrinksMachine1 has within it all the variables which objects of type DrinksMachine have within them and which are listed in the code for that class, integers price, balance and cash and two arrayLists of Can objects, cokes and fantas. It also has within it an extra variable which is an arrayLists of Can objects, called sprites. This is indicated by the declaration in the code for ExtDrinksMachine1:
private ArrayList<Can> sprites;
Note that in order for the methods given in class ExtDrinksMachine1 to refer to the variables price, balance and cash those variables have to be declared as protected rather than private in the class DrinksMachine. A variable which is declared in a class as private exists in objects of a class which extends it, but cannot be accessed in the code which extends it.

You can see that the extra methods in the class ExtDrinksMachine1 manipulate the arrayList called sprites in the same way that the related methods in the class DrinksMachine manipulate the variables cokes and fantas. The constructors for ExtDrinksMachine1 are

 public ExtDrinksMachine1(int p)
 {
  super(p);
  sprites = new ArrayList<Can>();
 }
and
 public ExtDrinksMachine1(int p,int c, int f,int s)
 {
  super(p,c,f);
  sprites = new ArrayList<Can>();
  for(int i=0; i<s; i++)
     loadSprite(new Can("sprite"));
 }
The first sets up an extended drinks machine which contains no cans. Its only argument is the initial price of drinks for the machine. The second constructor sets up an extended drinks machine which has cans within it, its four arguments are the initial price, and the initial number of cans of coke, fanta and sprite. Note that if the first statement in a constructor consists of the word super followed by some arguments enclosed within rounded brackets the result is to use the code of the constructor of the class which the class extends to fill values of the variables in the new object created by the constructor. So in the first constructor for ExtDrinksMachine1, the statement super(p) calls the code in the constructor for DrinksMachine which has one argument of type int. This is:
 public DrinksMachine(int p)
 {
  price = p;
  balance = 0;
  cash = 0;
  cokes = new ArrayList<Can>();
  fantas = new ArrayList<Can>();
 }
so the result is to set the new object's price variable to the value p, its balance and cash variables to 0 and its cokes and fantas variables to new arrayLists of Cans of initial size 0. The additional code in the first constructor for ExtDrinksMachine1 initialises the variable sprites in the new object being created to a new arrayList of Cans of initial size 0.

In the second constructor for ExtDrinksMachine1, the call super(p,c,f) uses the code from the constructor for DrinksMachine which takes three arguments of type int to set the value of the price, balance and cash variables, and to create c new Can objects to put in the cokes arrayList and f new Can objects to put in the fantas arrayList. After executing this code from the constructor of DrinksMachine, this constructor of ExtDrinksMachine1 sets the variable sprites to a new empty arrayList of Cans, and then adds s new Can objects to it.

Classes and subclasses

Up to this point you might think that every Java object has its own distinct type. But actually the situation is more complex than this. A Java object may have more than one type. The reason for this is that Java has the concept of "subclass". Every object of a type which extends some other type can be considered an object of both the original type and the extended type. In the example we have been discussing, every object of type ExtDrinksMachine1 is also of type DrinksMachine. The consequence of this is that a variable of type DrinksMachine can be made to refer to an object of type ExtDrinksMachine1. Also a method which has a parameter of type DrinksMachine can take an object of type ExtDrinksMachine1 as its matching argument. We say that ExtDrinksMachine1 is a subclass of DrinksMachine, or that DrinksMachine is a superclass of ExtDrinksMachine1.

As an example, we saw previously a static method called cheaper which took two DrinksMachine objects as its arguments and returned a reference to whichever was the cheapest. Here is its code:

 public static DrinksMachine cheaper(DrinksMachine m1,DrinksMachine m2)
 {
  if(m1.getPrice()<m2.getPrice())
     return m1;
  else
     return m2;
 }
This will still work even if one or both of its arguments are of type ExtDrinksMachine1. For example, if we have the code fragment:
 DrinksMachine mach1 = new DrinksMachine(m);
 ExtDrinksMachine1 mach2 = new ExtDrinksMachine1(n);
 DrinksMachine mach3 = cheaper(mach1,mach2);
then mach3 will be an alias of either mach1 or mach2 depending on which of m or n is the lower.

When an object is referred to through a variable, we have the concept of its actual type and its apparent type. This is because a variable which is declared of one type may refer to an object which was created through a constructor of a subclass type. So, after the above code fragment is executed, if n is less than m the variable mach3 refers to the object created by the call new ExtDrinksMachine1(n). The "apparent type" is the type the variable is declared as, but the actual type of the object is the name that was used to construct it. Here the apparent type would be DrinksMachine and the actual type would be ExtDrinksMachine1.

Note that if a method call is attached to a variable, it must be a method which appropriate for the apparent type of that variable. So, following the execution of the code fragment above, we could have

 mach3.pressCoke();
but not
 mach3.pressSprite();
since the variable mach3 is of type DrinksMachine and the method pressCoke is declared in DrinksMachine but the method pressSprite is only declared in its subclass ExtDrinksMachine1. When the code is compiled, the Java compiler cannot tell whether mach3 would refer to an object of actual type ExtDrinksMachine1. Remember that although every object of type ExtDrinksMachine1 is also of type DrinksMachine, it is not the case that every object of type DrinksMachine is also of type ExtDrinksMachine1.

If you have a reference to an object through a variable of one type, and you know the actual type of that object is some particular subtype, you can treat it as an object of that subtype by using casting. This is done by preceding the reference to the object by the name of the subtype enclosed within rounded brackets. For example, if we have variable mach4 declared of type ExtDrinksMachine1:

ExtDrinksMachine1 mach4;
then we can have:
mach4 = (ExtDrinksMachine1) mach3;
If this were executed when mach3 referred to an object whose actual type was not ExtDrinksMachine1 an exception of type ClassCastException would be thrown. Note that assignment the other way round:
mach3 = mach4;
does not require casting as it is always possible to assign an object of a subclass to a variable of its superclass.

If you want to test whether a variable of one type refers to an object whose actual type is a subclass, Java has the keyword instanceof to do that. The test r instanceof t, where r is a reference to an object and t is a type name, evaluates to true if r has the type t (or a type which is a subclass of t) and to false otherwise. Here is an example of a code fragment which uses that:

  Can c;
  if(mach3 instanceof ExtDrinksMachine1)
     c = ((ExtDrinksMachine1) mach3).pressSprite();
  else
     c = mach3.pressFanta();
which has the effect of setting c to the result of pressing the "sprite" button of the machine referred to by mach3 if it has a "sprite" button, otherwise setting it to the result of pressing the "fanta" button. Note that ((ExtDrinksMachine1) mach3).pressSprite() combines casting the variable mach3 to the type ExtDrinksMachine1 and then calling a method from that subclass without the need for an extra variable of that subclass. It has the effect of viewing the object referred to by mach3 as of type ExtDrinksMachine1 and then calling the method pressSprite() on the result. It is necessary to have both sets of brackets to do this since the method attachment operator has higher precedence than the casting operator. This means that just (ExtDrinksMachine1) mach3.pressSprite() would be interpreted as (ExtDrinksMachine1) (mach3.pressSprite()), that is an attempt to call pressSprite() on mach3 and then view the result as of type ExtDrinksMachine1 (which does not make sense).

Here is some code you can use to test ExtDrinksMachine1 objects:

import java.util.Scanner;

class UseDrinksMachines5
{
 public static void main(String[] args)
 {
  int p,c,f,s;
  Scanner input = new Scanner(System.in);
  System.out.println("Machine 1 is a standard machine");
  System.out.print("Enter the price for drinks on machine 1: ");
  p = input.nextInt();
  System.out.print("Enter the number of cokes in machine 1: ");
  c = input.nextInt();
  System.out.print("Enter the number of fantas in machine 1: ");
  f = input.nextInt();
  DrinksMachine mach1 = new DrinksMachine(p,c,f);
  System.out.println("Machine 2 is an extended machine");
  System.out.print("Enter the price for drinks on machine 2: ");
  p = input.nextInt();
  System.out.print("Enter the number of cokes in machine 2: ");
  c = input.nextInt();
  System.out.print("Enter the number of fantas in machine 2: ");
  f = input.nextInt();
  System.out.print("Enter the number of sprites in machine 2: ");
  s = input.nextInt();
  ExtDrinksMachine1 mach2 = new ExtDrinksMachine1(p,c,f,s);
  DrinksMachine cheaper = DrinksMachineOps.cheaper(mach1,mach2);
  System.out.print("Enter the amount of money you wish to spend on cokes "+
                   "on the cheaper machine: ");
  int amount = input.nextInt();
  int cokes = DrinksMachineOps.spendOnCokes(amount,cheaper);
  amount = cheaper.pressChange();
  System.out.println("You have "+cokes+" cokes and "+amount+"p change");
  mach2.insert(amount);
  System.out.println("Put the change in Machine 2 and press the Sprite button");
  Can can = mach2.pressSprite();
  amount = mach2.pressChange();
  if(can==null)
     System.out.println("No sprites available");
  else
     System.out.println("You have a "+can+" and "+amount+"p change");
 }

}
This code assumes the static method cheaper and a static method spendOnCokes are in a class called DrinksMachinesOps. We used the method spendOnCokes previously for illustration. Here is some code for it:
 public static int spendOnCokes(int sum,DrinksMachine mach)
 {
  int count=0;
  mach.insert(sum);
  while(!mach.cokesEmpty()&&mach.getBalance()>=mach.getPrice())
     {
      mach.pressCoke();
      count++;
     }
  return count;
 }
The idea of this method is to simulate putting a certain amount of money in a machine then keep pressing the "coke" button until either the money runs out, or the machine runs out of cokes. The code in the file UseDrinksMachine5.java performs this operation on the cheaper machine, so it is only when the code is run that it is determined whether the method spendOnCokes is executed on an object of type ExtDrinksMachine1 or an object which is just of type DrinksMachine. Then any remaining money is put into the machine with the "sprite" button, that button is pressed and the "change" button on that machine is pressed.

You can find the files this example needs in the directory

~mmh/DCS128/code/drinksmachines
The files required are DrinksMachine.java, ExtDrinksMachine1.java, DrinksMachineOps.java and UseDrinksMachines5.java.

Changing operation effects

As well as adding new operations to an existing class, inheritance in Java can also be used to make a subclass where the effect of operations is different from that in the superclass. The way this is done is to write a method which has the same header as a method in the superclass, but different code in its body. The effect then is that if a method is called on an object of the subclass, the code in that subclass will be used to execute that method rather than the code in the superclass.

To demonstrate this, we will consider another extension of the original DrinksMachine class. We will name this ExtDrinksMachine2. This represents a drinks machine which looks just like the original drinks machine. However, this drinks machine has a link to a supplier company. When it runs out of cokes or fantas, it sends a message to the supplier company informing it of this. Here is the code:

class ExtDrinksMachine2 extends DrinksMachine
{
 private DrinksCompany supplier;
 private String identity;

 public ExtDrinksMachine2(DrinksCompany link,String id,int p)
 {
  super(p);
  identity = id;
  supplier = link;
 }

 public ExtDrinksMachine2(DrinksCompany link,String id,int p,int c, int f)
 {
  super(p,c,f);
  identity = id;
  supplier = link;
 }

 public Can pressCoke()
 {
  if(cokes.size()>0&&balance>=price)
     {
      Can can = cokes.get(0);
      cokes.remove(0);
      if(cokes.size()==0)
         supplier.cokesEmpty(this);
      balance=balance-price;
      cash=cash+price;
      return can;
     }
  else
     return null;
 }

 public Can pressFanta()
 {
  if(fantas.size()>0&&balance>=price)
     {
      Can can = fantas.get(0);
      fantas.remove(0);
      if(fantas.size()==0)
         supplier.fantasEmpty(this);
      balance=balance-price;
      cash=cash+price;
      return can;
     }
  else
     return null;
 }

 public String getIdentity()
 {
  return identity;
 }

}
Objects of type ExtDrinksMachine2 have the variables and methods of objects of type DrinksMachine. This is given by the extends DrinksMachine bit, there is no need for any more. However, alternative code is given for the methods pressCoke and pressFanta. Also there are two extra variables in an object of type ExtDrinksMachine2 on top of the ones inherited from DrinksMachine, these extra variables are identity of type String and supplier of type DrinksCompany. There is one extra method, getIdentity which returns the value of the variable identity. The constructors for ExtDrinksMachine2 have arguments which are used to set the identity and supplier variables.

The code for the method pressCoke in the class ExtDrinksMachine2 is like the code for pressCoke in DrinksMachine with the addition that if after removing an item from the arrayList cokes the size of this arrayList falls to 0, the method cokesEmpty with argument this is called on the DrinksCompany object referenced by the supplier variable. Similar applies to the code for the method pressFantas. Note this use of the word this to mean "the object this method is called on". For example, if the call mach2.pressCoke() is made, and the object referred to by mach2 is an object of type ExtDrinksMachine2 with the cokes variable referring to an arrayList of length 1, then the call to supplier.cokesEmpty(this) which will result will in effect be a call supplier.cokesEmpty(mach2). The method sends a reference to the whole object to the object referred to by the ExtDrinksMachine2 object's supplier variable.

As we saw previously when discussing the ExtDrinksMachine1 type, a variable of a particular class may reference an object of a subclass of that class. So a variable of type DrinksMachine may reference an object of type ExtDrinksMachine1 or ExtDrinksMachine2. If the parameter to a method is given as type DrinksMachine that method is general for objects of type DrinksMachine or any of its subclasses, such as the two given here ExtDrinksMachine1 and ExtDrinksMachine2. This raises an issue: suppose we have a variable of type DrinksMachine which happens to be storing a reference to an object of actual type ExtDrinksMachine2. Then suppose the method pressCoke is called on this variable. Which code will be used to execute this method, the code from the DrinksMachine class or the code from the ExtDrinksMachine2 class?

The answer is ExtDrinksMachine2, this is because method calling in Java works using a feature known as dynamic binding. So if a method call is attached to a variable, the method must be one defined in the class of that variable or a superclass as this is checked when the code is compiled. However, when the code is run, the actual class of the object the variable refers to (which may be a subclass of the class of the variable) is looked at to see if there is a method in it which matches the call and that method is used.

To illustrate this, let us consider a code fragment similar to that we considered above:

 DrinksMachine mach1 = new DrinksMachine(m);
 ExtDrinksMachine mach2 = new ExtDrinksMachine(n);
 DrinksMachine mach3 = cheaper(mach1,mach2);
 mach3.pressCoke();
When the last statement here is executed, which version of pressCode is used will depend on whether mach3 is an alias for mach1 or for mach2. If it is an alias for mach2, it will have the effect of calling the cokesEmpty method on the supplier variable of the object referred to by mach2 if that object's cokes arrayList drops to length 0. This is the behaviour we would want to simulate real life machines. Two machines may look identical with "coke" and "fanta" buttons. One is of the sort which contacts the supplier when it runs out, the other is of the simpler sort which does not. We would expect, when we press the "coke" button, for a machine to react appropriately according to its own type, even if we do not know which type the machine is.

Here is some code for testing the ExtDrinksMachine2 example:

import java.util.Scanner;

class UseDrinksMachines6
{
 public static void main(String[] args)
 {
  int p,c,f;
  Scanner input = new Scanner(System.in);
  System.out.println("Machine 1 is a standard machine");
  System.out.print("Enter the price for drinks on machine 1: ");
  p = input.nextInt();
  System.out.print("Enter the number of cokes in machine 1: ");
  c = input.nextInt();
  System.out.print("Enter the number of fantas in machine 1: ");
  f = input.nextInt();
  DrinksMachine mach1 = new DrinksMachine(p,c,f);
  System.out.println("Machine 2 is an extended machine");
  System.out.print("Enter the price for drinks on machine 2: ");
  p = input.nextInt();
  System.out.print("Enter the number of cokes in machine 2: ");
  c = input.nextInt();
  System.out.print("Enter the number of fantas in machine 2: ");
  f = input.nextInt();
  DrinksCompany comp = new DrinksCompany();
  ExtDrinksMachine2 mach2 = new ExtDrinksMachine2(comp,"no. 2",p,c,f);
  DrinksMachine cheaper = DrinksMachineOps.cheaper(mach1,mach2);
  System.out.print("Enter the amount of money you wish to spend on cokes "+
                   "on the cheaper machine: ");
  int amount = input.nextInt();
  int cokes = DrinksMachineOps.spendOnCokes(amount,cheaper);
  amount = cheaper.pressChange();
  System.out.println("You have "+cokes+" cokes and "+amount+"p change");
  mach2.insert(amount);
  System.out.println("Put the change in Machine 2 and press the Fanta button");
  Can can = mach2.pressFanta();
  amount = mach2.pressChange();
  if(can==null)
     System.out.println("No fantas given");
  else
     System.out.println("You have a "+can+" and "+amount+"p change");
 }
}
As in the previous example, it creates two machines, one of the standard type and one of the extended type, and runs the method spendOnCokes on the cheaper machine. Which is the cheaper machine is only determined when the code is actually run. For this code to work, you will also need a class which provides DrinksCompany objects. Here is an example:
class DrinksCompany
{
 public DrinksCompany()
 {
 }

 public void cokesEmpty(ExtDrinksMachine2 mach)
 {
  System.out.println("Machine "+mach.getIdentity()+" out of cokes");
  for(int i=0; i<10; i++)
      mach.loadCoke(new Can("coke"));
 }

 public void fantasEmpty(ExtDrinksMachine2 mach)
 {
  System.out.println("Machine "+mach.getIdentity()+" out of fantas"); 
  for(int i=0; i<10; i++)
     mach.loadFanta(new Can("fanta"));
 }
}
All that is required of a DrinksCompany object from the code in the front-end UseDrinksMachines6 is that it can have the methods cokesEmpty and fantasEmpty called on it, with arguments of type ExtDrinksMachine2. In this example, the identity of the machine is used in a message that is printed on the screen, and the machine is loaded with ten new cans. If the cheaper machine is of type ExtDrinksMachines2 it will never run out of cokes, because as soon as it does it is instantly replenished. This is because when cokesEmpty is called on the DrinksCompany object the code for it is executed, and execution returns to the code for pressCoke and from that to the code for spendOnCokes afterwards. A more sophisticated simulation might have the DrinksCompany object on a separate "thread", so that its code is executed in its own time and a delay is observed before the machine is replenished. However, that is beyond the scope of this course.

The files this example requires are: DrinksMachine.java, Can.java, EmptyCanException.java ExtDrinksMachine2.java, DrinksCompany.java, DrinksMachineOps.java and UseDrinksMachines6.java.


Matthew Huntbach

Last modified: 3 March 2006