Java and Inheritance

Lectures: 1/25/2000, 1/27/2000, 2/1/2000

Object-Oriented Programming Languages

    Java is an object-oriented programming language. An "object" is a programming construct which is used to represent both data and actions to work on that data in a computer program. These actions are known as "methods". Methods sometimes need more knowledge than what an object has in order to do their job effectively. We can give this knowledge by passing information to our methods in the form of "parameters". Our methods can also give feed back about the action it performed as return values.

    We define the characteristics of an object by creating a "class". A class contains all of the things available to an object. These "things" are commonly referred to as "members" of the class, and are either data members ("variables") or method members. A class serves as a blue-print for an object: the class only shows what an object can do. However, nothing has been created yet: to make the class effective we often have to create an instance of a class, which is what an object is. A primary goal of OOP is code reusability: once we have a class defined, we create instances of that class in use them in many different programs.

A Java class

    Let us look at an example Java class: a class used to represent a point in Cartesian coordinates:

        class Point {
        
        	private double x, y;
        
        	public Point(double x, double y) {
        	    this.x = x;
        	    this.y = y;
        	}
        
        	public void move(double dx, double dy) {
        	    x += dx;
        	    y += dy;
        	}
        
        	public String toString() {
        	    return "(" + x + ", " + y + ")";
        	}
        
        	public void equals(Point p) {
        	    return (p.x == x && p.y == y);
        	}
        }
    

    The class here is called "Point". It has two data members, x and y. It also has three methods: move, toString, and equals. There is also something which is used to create a new Point, a "constructor".

    The data members of this class are of a primitive data type, known as double. A primitive data type is different from an object in how it is represented in your computer. There are a total of 8 primitive data types in Java (byte, short, int, long, float, double, char, and boolean). The Java reserved word "private" in front of the declaration of x and y is known as an "access modifier": it is used to control what is allowed to use the variables x and y. The word private means that x and y can only be accessed inside of this class. This concept is part of "encapsulation" which deals with keeping members of a class which a user-programmer does not need to know about in order to make the class useful. A member which is "public", however, is accessible outside of the class.

    The next line (public Point(double x, double y) {) is known as the constructor for the class. This will be used to create new Point objects. For instance, to create a Point at (1.0, -1.0), the following statement would be used:

        Point p = new Point(1.0, -1.0);
    
    This statement creates a reference p and assigns it to refer to a location in memory which is a point containing the specified values, like the picture below.

    The constructor is public because we want other classes to be able to create an instance of a Point. The constructor also uses the "self-referencing pointer" this to avoid name-scope confusion.

    The move is a method. Because it is public, a programmer will be able to call this method directly on a Point object by using the dot operator. This method takes two parameters, and returns nothing. Because the method does not return anything, the reserved-word void is used as the return type.

    The toString method, on the other hand, does return a value. In this case, it returns a String object. Because the method returns a value, the last statement executed must be of the form

        return <expression>;
    
    where <expression> is the value we want this method to return. Note that the type of this expression must match the type specified in the method header.

    Our final method has both a parameter and a return type. This method is used to determine if a given Point is equal to another Point. It is necessary because the built-in Java operator == may not go what we want it to do. For example, consider the following situation:

        Point p = new Point(1.0, -1.0); 
        Point q = p; 
        Point r = new Point(1.0, -1.0);
    
    The memory diagram for this situation is given below.

    The == bases equality on whether two references refer to the same location in memory. If they do, they are equal. If not, then the references are not ==, no matter the contents of either memory location. Hence, p == q is true, while p == r is false. If we care about the values in memory, we need an equals method.

    Note that this method seemingly does something illegal: it directly accesses the x and y members of a Point, even those are declared as private. This is actually acceptable: private members are used to hide implementation details of a class that a user does not need to know. However, since the the parameter is a Point object, we are allowed to directly use the members of the object.

Inheritance

    One of the advantage of the Point class above is that it could be used in a variety of applications. For instance, we could use a Point object for information in a chart, or for paddle and ball positions in Pong, ...

    However, the Point class is not good enough to keep track of a player's position in Quake III: we need a third dimension. See we could re-define an entirely new class called Point3d which has many similar features to a Point object, but adds a third dimension. Instead, though, we would prefer to build upon the existing Point class, using the parts of the class we know work (the x and y) and add a z-coordinate.

    There is a mechanism in objected-oriented programming languages known as "inheritance" which allows this to happen. Inheritance allows us to build upon an existing class, use the things that work in that class, change the things that do not ("overriding"), and also add completely different functionality.

    We often draw pictures like the following:

    We say that Point is a "super class" of Point3d, and that Point3d is a "sub class" of Point. Another common phrase is that "Point3d is-a Point." One useful consequence of this is that we can assign a Point3d object into a Point. This will come in handy later.

    As sub class inherits an initial set (with the caveats discussed below) of methods and data members from the super class, which can be used as if they were defined in the sub-class.

Inheritance in Java

    In Java, we have a sub class inherit from a super class by using the Java reserved word extends:
        class Subclass extends Superclass {
            // class contents
        }
    

    For example, we could create a Point3d as below:

        class Point3d extends Point {
        	
        	private double z;
        
        	public Point3d(double x, double y, double z) {
        	    super(x, y);
        	    this.z = z;
        	}
        
        
        	public double getZ() {
        	    return z;
        	}
        
        
        	public void move(double dx, double dy, double dz) {
        	    super.move(dx, dy);
        	    z += dz;
        	}
        
        
        	public boolean equals(Point3d p) {
        	    return super.equals(p) && p.z == z;
        	}
        
        
        	public String toString() {
        	    // This will only work if x and y are protected in the super class
        	    return "(" + x +", " + y + ", " + z + ")";
        	}
        }
    
    The subclass then has direct access to any member (data or method) of the super class, provided that the member is declared with either a public or protected access modifier. The protected modifier is likely new to you. A protected member is similar to private in that a protected member is not accessible by anybody. However, a protected member is directly accessible in any sub class, while a private member is not.

    It is important to point out that private members are still inherited in a sub class, they are simply not directly accessible in the sub class. So why are they inherited? Because the super class may be depending upon those values to do its job. To use the super class in a sub class, you need to use the super reserved word. These issues come into play in the move and equals methods above. x and y remain as private in the Point class, it becomes necessary to use the super class' move method. Similarly, the only way we can effectively write the equals method is to pass our parameter to the super's equals method. Note we are allowed to pass a Point3d object into a method which takes a Point object because a Point3d object is-a Point object.

    The super reference may be used in other ways, as well. In the above example, it is used in the constructor. When we make a call to

        super(...);
    
    in a sub classes constructor, we are calling the super's corresponding constructor. Generally, the super constructor is called with a subset of the sub class' constructor's parameters. When making a call to the super constructor, it must be the first line in the sub class' constructor. Indeed, if you do not make a call to a super constructor, Java automatically calls the default constructor super() for you. Note that this "first line" rule is only enforced when calling a super constructor.

    One final important note is that in Java all classes, either directly or indirectly, automatically extend the Object class. The Object has many methods defined in it, including a toString() method and an equals(...) method. These will be discussed further below. That is, the inheritance hierarchy looks like:

Polymorphism

The equals method and instanceof

    Another method defined in the Object class is the equals(...) method. Notice that there are also equals methods in both the Point and Point3d class. It may seem like these are overriding the equals method in the Object class. However, this is not quite true: the equals(...) method in the Object class takes an Object as its parameter.

    We could try to change the equals methods in the Point class like this:

        public boolean equals(Object o) {
            return o.x == x && o.y == y;
        }
    
    A problem with this is that we are treating o as if it is a Point: there are no x and y members in the Object class. We fix this problem by casting:
        public boolean equals(Object o) {
            Point p = (Point) o;
    	return p.x == x && p.y == y;
        }
    
    This works so long as we actually passed a Point to the method: if we did not, we would get a ClassCastException. We need some way to make sure that the parameter is actually a Point. Clearly, if it is not there is no way equality can hold. There is an operator in Java which can tell us whether or not our parameter is a Point: instanceof

    The instanceof operator is a boolean operator which takes two operands:

        <obj> instanceof <Class name>
    
    which returns true if <obj> can be assigned into a reference of type <Class name>. That is, instanceof checks if the <obj> is-an instance of the specified class. Therefore, any reference o shows o instanceof Object to be true, provided ois not null.

    Therefore, we should write our equals method as:

        public boolean equals(Object o) {
            if (o instanceof Point) {
    	    Point p = (Point) o;
                return p.x == x && p.y == y;
            }
    	else {
    	    return super.equals(o);
            }
        }
    

Abstract classes

    We turn our discussion now to Java abstract classes. An "abstract class" is an incomplete class: not all of the methods have been completely written, and will be left to the derived class to implement. An abstract class can have data, methods, and constructors just like normal, however abstract classes can not be explicitly created using the new operator. However, we can still perform up-casting on them: that is, we can create an object of a class which is derived from an abstract class and then assign that object into a reference of the type of the abstract class. Basically, you can safely make all constructors in an abstract class protected so that they may only be called from the implementing class. If the sub class does not fully define all of the abstract methods of the super class, the sub class must be declared as abstract as well.

    To create an abstract class, you use the Java reserved word abstract:

        abstract class <Class name> {
    
    You create abstract methods in a similar manner, by using the word abstract:
        abstract <access> <return type> <identifier>(<parameters>);
    
    Notice the use of a semi-colon (;) at the end of the method header.

    Below is an example of the use of an abstract class:

        abstract class Food {
        	abstract public void isTasty();
        }
        
        
        class Lutefisk extends Food {
        
        	public Lutefisk() {
        	}
        
            // completing the abstract class Food
        	public void isTasty() {
        	    System.out.println("Lutefisk looks disgusting, but tastes great!");
        	}
        }
        
        
        class Pizza extends Food {
          
        	public Pizza() {
        	}
        
        
            // completing the abstract class Food
        	public void isTasty() {
        	    System.out.println("I never met a pizza I didn't like!");
        	}
        }
        
        
        class Dinner {
        	public static void main(String [] args) {
        	    Food dinner;
        	    Pizza pizza = new Pizza();
        	    Lutefisk lute = new Lutefisk();
        
        	    if (Math.random() < .05) {
        		dinner = pizza;
        	    }
        	    else {
        		dinner = lute;
        	    }
        
        	    dinner.isTasty();
        	}
        }
    

Interfaces

    We have discussed the word "interface" before: an interface describes how to use a class. In Java, it is possible to create something known as an interface which is simply a collection of constants and abstract methods. In fact, you might consider an interface to be a "really abstract abstract class." Just like with abstract classes, it is not possible to create an instance of an interface with new, but you can perform up-casting.

    To define an interface, we use the following syntax:

        interface <interface name> {
            // variables (constants)
    	// these are declared as they "normally" would be: no need for 
    	// final or static
    
    	// Methods: no need for abtract (it is redundant)
    	<access> %lt;return type> <identifier>(<parameters>);
        }
    
    We then have classes implement this interface by completing all of the methods described in the interface definition:
        class <class name> implements <interface name> {
    
    Interfaces are useful because they allow us to "extend" more than one class: according to the rules of inheritance, it was only possible to directly extend one class. This limits the number of up-casts we can make with this class. However, a single class is allowed to both extend a super class and implement an interface. Indeed, a class is allowed to implement multiple interfaces: just list all of the interfaces to be implemented by a comma-separated list.

    Below we see an example of how interfaces can be used.

        import java.util.*;
        
        public class Dinner2 {
        
        	public static void main(String [] args) {
        	    Pizza pizza = new Pizza();
        	    Lutefisk lute = new Lutefisk();
        	    Enumeration e = pizza;
        	    Food f = lute;
        
        	    f.isTasty();
        	    pizza.isTasty();
        
        	    System.out.println("Grabbing slices from that enumeration:");
        
        	    while (e.hasMoreElements()) {
        		System.out.println(e.nextElement());
        	    }
        
        	    System.out.println("Is there any pizza left?");
        
        	    System.out.println(pizza.nextElement());
        	}
        }
        
        abstract class Food {
        
        	abstract public void isTasty();
        }
        
        class Lutefisk extends Food {
        
        	public void isTasty() {
        	    System.out.println("Lutefisk looks disgusting, but tastes great!");
        	}
        }
        
        class Pizza extends Food implements Enumeration {
          
        	private int slices_remaining = 8;
        
        	// Completing the isTasty abstract method from the Food class
        	public void isTasty() {
        	    System.out.println("I've never met a pizza I didn't like!");
        	}
        
        	// Completing the Enumeration interface
        	public boolean hasMoreElements() {
        	    return slices_remaining != 0;
        	}
        
        	// Completing the Enumeration interface
        	public Object nextElement() {
        	    if (slices_remaining > 0) {
        		slices_remaining--;
        		return "Another slice gone!";
        	    }
        	    else {
        		return "No more slices!";
        	    }
        	}
        }
    

Exceptions

    Notice that in the above example if there are no slices left, we simply return an error message. In a more sophisticated example, we would prefer to warn the user of this in a different way: by issuing an exception.

    Recall exceptions are used to a notify a programmer or program when something bad happens. We can issue an exception by using the java reserved word throw:

        throw <exception object>;
    
    For instance, to issue a NoSuchElementException when there are no more slices, we might write the following:
        public Object nextElement() {
            if (slices_remaining > 0) {
                slices_remaining--;
    	    return "Another slice gone!";
    	}
    	else {
    	    throw new NoSuchElementException("No more slices!");
            }
        }
    
    A NoSuchElementException is a runtime (or unchecked) exception. This means that there is no need for the exception to be handled when we compile our program, like a checked exception requires you to (with throws or a try-catch block).

    It is also possible to define your own exceptions: you simply need to make your exception the sub class of the correct super class. For checked exceptions, your class should extend Exception. For un-checked exceptions, your class should extend RuntimeException.


© 2000 Michael Wade
All rights reserved