11. Inheritance

Your children are not your children.
They are the sons and daughters of Life’s longing for itself.
They come through you but not from you,
And though they are with you yet they belong not to you.

— Khalil Gibran

11.1. Problem: Boolean circuits

In Chapter 4, we talked extensively about Boolean algebra and how it can be applied to if statements in order to control the flow of execution in your program. The commands that we give in software must be executed by hardware in order to have an effect. It shouldn’t be surprising that computer hardware is built out of digital circuits that behave according to the same rules as Boolean logic. Each component of these circuits is called a logic gate. There are logic gates corresponding to all the Boolean operations you’re used to: AND, OR, XOR, NOT, and others.

The output of an AND gate is the result of performing a logical AND on its inputs. That is, its output is true if and only if both of its inputs are true. The same correlation exists between each gate and the Boolean operator with the same name. At the level of circuitry, a 1 (or “on”) is often used to represent true, and a 0 (or “off”) is often used to represent false. Modern computer circuitry is built almost entirely out of such gates, performing addition, subtraction, and all other basic operations as complicated combinations of logic gates, where each digit of every number is a 1 or 0 determined by the circuit.

Because these circuits can become large and unwieldy, your problem is to write a Java program that will allow a user to specify the design of such a circuit and then see what its output is. The input to this program will be a text file redirected to standard input that gives the number of gates of the circuit, lists what each gate is, and then lists the connections between them.

The following is an example of input to make a circuit with six components.

6
true
false
AND
XOR
NOT
OUTPUT 1
2 0 1
3 2 1
4 3
5 4

The first line specifies the total number of components. The next two components give either true or false inputs, depending on their names. The AND and the XOR correspond to gates of the same name which each take two inputs. The NOT corresponds to a NOT gate with a single input. Finally, OUTPUT 1 is the single output of this circuit that we’re interested in. After the list of gates is a list of how they’re connected. The line 2 0 1 specifies that the gate at index 2, which happens to be an AND gate, has an input from the gate at index 0 and an input from the gate at index 1. In other words, the AND gate has one true input and one false input. The final circuit produced would look like the following.

circuit
Figure 11.1 Final circuit diagram showing the six components.

We’re only interested in the value of any OUTPUT gates. Thus, the program that’s simulating this circuit would print the following.

OUTPUT 1: true

There are many different ways to implement a solution to this problem. The total number of gates is given as the first input. Thus, you can make an array of gates. When you go to connect them, the indexes given in the input will map naturally onto the gates in the array. But what type should the array be? You could create a Gate class that could do the work of any conceivable gate, but the implementation would be awkward. All gates would have to have two inputs even if they don’t need any. Adding different kinds of gates later (like a NAND, for example) would mean rewriting the Gate class.

Instead, a cleaner approach to the solution is to use inheritance. In object-oriented languages like Java, inheritance is a process that allows you to create a new, specialized class from a more general, preexisting class.

We recommend the following inheritance hierarchy, in which the arrows point from each child class to its parent class.

gates
Figure 11.2 Recommended inheritance hierarchy.

As you can see, every class inherits from the Gate class. If your array can be of type Gate, it will be able to hold objects of any child of Gate. UnaryOperator, BinaryOperator, True, and False are children of the basic Gate class. Then, Output and Not are children of UnaryOperator since each only has a single input. Naturally, And, Or, and Xor are children of BinaryOperator, since they all have two inputs.

If this jumble of classes seems bewildering, don’t be discouraged. Each is very short and easy to write. We’ll explain what the inheritance relationship means and how to use it in the next few sections.

11.2. Concepts: Refining classes

Here we give a brief overview of inheritance that will give us enough information to continue onward. We’ll cover some of the deeper areas of the subject in Chapter 17.

11.2.1. Basic inheritance

The process of creating an inherited class out of an existing class is called inheriting a class, deriving a class, or simply subclassing. The class that already exists is called a parent class, base class, or superclass and the new class is called a child class, derived class, or subclass.

When you create a child class from a parent class, the child class inherits all of its fields and methods. Thus, you can use a child class object anywhere you’d use the parent class object. This relationship explains the terms superclass and subclass: Since you can treat any subclass object as if it were a superclass object, the superclass type can be thought of as a superset including all subclass objects.

The names superclass and subclass can sound misleading because the subclass can usually do more than the superclass. From that perspective, the child class has a superset of the fields and methods of its parent class. To avoid confusion, we favor the terminology of parent and child classes.

11.2.2. Adding functionality

When creating a child class, a programmer will normally add functionality above and beyond the original parent class. Otherwise, there’s little point in creating a child class. For example, a simple Fish class might be able to do things like swim and feed. A child Flounder class has the additional ability to camouflage itself. Another child class, the Shark class, adds the ability to eat other Fish objects.

By adding a few methods, we can create a new class with special abilities without interfering with the basic functionality of the underlying class.

11.2.3. Code reuse

Of course, a programmer who wished to program a Shark class could simply copy and paste in all the code from the Fish class and then make the necessary additions. In many ways the evolution of modern programming languages has been to reduce the need for copying and pasting.

Old mistakes are propagated with copying and pasting. When discovered, they must be fixed in several different locations. New mistakes can also be introduced by cutting and pasting. Instead, we wish to guarantee that working code from a parent class continues to work in a child class. Ideally, code from parent classes will not need to be debugged a second time when a child class is created.

Even without the issue of errors introduced by copying and pasting, the total amount of code increases. By minimizing the amount of code, issues of performance and storage can be improved, but not always. Object-oriented languages have taken criticism for low speed and high memory use due to the additional complexities of objects and inheritance, but compiler optimization, good library design, and improved JVM performance have brought Java a long way in this area.

11.3. Syntax: Inheritance in Java

In this section we discuss the mechanism for creating a child class in Java using the extends keyword. Then, we discuss access restriction and visibility, constructor issues, the Object class, and overriding methods.

11.3.1. The extends keyword

In order to make a child class in Java, we use the extends keyword. Let’s give an example using the Fish class defined below. This class creates a basic fish that can swim, feed, and die. We can check its color, location, and whether or not it’s alive. When it runs out of energy, it dies.

import java.awt.*;

public class Fish {
    protected Color color = Color.GRAY;
    private double location = 0.0;
    private double energy = 100.0;
    private boolean alive = true;
    
    public Color getColor() { return color; }
    public double getLocation() { return location; }
    public boolean isAlive() { return alive; }
    
    public void swim() {
        if(alive) {
            location += 0.5;
            energy -= 0.25;
        }
        if(energy <= 0.0)
            die();
    }
    
    public void feed() { energy = 100.0; }
    public void die() { alive = false; }
}

From here we can create a child class called BoringFish that does exactly what Fish does. To do so, we use the extends keyword after the new class name, followed by the parent class name (in this case Fish), followed by the body of the class.

public class BoringFish extends Fish {
}

Just as we’re allowed to make an empty class, we’re allowed to make an inherited class and add nothing, but doing so is pointless. Instead, we can make a Flounder class that can change its color.

import java.awt.*;

public class Flounder extends Fish {
    public void setColor(Color newColor) { color = newColor; }
}

The Flounder class can do everything a Fish can: It can swim, feed, and die. But we also add the ability to change color since flounders are famous for their ability to mimic the ocean floor they swim over. Note that the color field in the Fish class has the protected access modifier, not private. We’ll come back to this point.

Here’s a Shark class that extends Fish in another way, by adding the capability of eating other Fish.

public class Shark extends Fish {
    public void eat(Fish fish) {
        fish.die();
        feed();     
    }
}

Here we have added an eat() method that takes another Fish object as a parameter. First, the Fish parameter is killed; then the eat() method calls feed(), restoring the energy of the Shark object. Note that the Shark object is able to call the feed() method even though it isn’t defined inside of Shark. Because it inherits from Fish, it has a version of feed().

Single inheritance only

Particularly if you’ve programmed in C++, you might be wondering if it’s possible to have one class inherit from multiple classes in Java. In multiple inheritance, a single class can have many different parents. Since C++ supports multiple inheritance, it would allow you to have a SharkAlligatorMan class that inherits from the Shark, Alligator, and Human classes. If you go back to the sorting problem from Chapter 10, multiple inheritance would allow us to solve the problem with an Age class and a Weight class from which Dog, Cat, Person, and Cheese all inherit.

However, the designers of Java decided not to allow multiple inheritance, perhaps for this reason: Imagine a River class with a run() method and a Politician class with a run() method. It seems strange to create a class which is both a river and politician, but there is no rule in C++ which makes doing so impossible. If you did have a RiverPolitician class which inherits from both, what would happen when you call the run() method? How would the RiverPolitician class know which of its parents' methods to pick? Surely, the way that a politician runs for office is very different from the way a river runs along its banks.

This problem is similar to the issue discussed in Section 10.3.1, where a class could implement more than one interface with the same default method; however, the problem is more severe in the case of multiple inheritance since it becomes unclear which fields inside of parent classes are being referred to, not just which methods.

If you find yourself in a situation where you want to use multiple inheritance in Java, try to reformulate your class hierarchy into one where your classes implement multiple interfaces. Recall that multiples interfaces can be implemented by a single class in Java, and like multiple inheritance, this practice allows a single class to be used in wildly different contexts.

Interfaces using extends

The extends keyword is not limited to classes. It’s possible for an interface to extend another interface. In fact, an interface can extend any number of other interfaces. As when a class implements multiple interfaces, each interface in an extends list is separated by commas.

When an interface extends other interfaces, it includes all the methods (and constants) they define. If a class implements an interface that extends other interfaces, it must contain versions of all the methods specified by all the interfaces. Recall the Ageable and Weighable interfaces from Chapter 10, which specified the getAge() and getWeight() methods, respectively. We could create an interface that required both of these methods by extending Ageable and Weighable.

public interface AgeableAndWeighable extends Ageable, Weighable {
}

We could add additional methods to the AgeableAndWeighable interface, but even empty it will enforce the contracts defined by both Ageable and Weighable. It’s usually not necessary to create an interface that extends other interfaces, since a class could implement each of the individual interfaces. Nevertheless, it can be used as a convenience to save typing or to create a reference type with certain guaranteed abilities.

Note that a class can never extend an interface. Likewise, an interface cannot extend a class or implement another interface.

11.3.2. Access restriction and visibility

The Shark example above gives an example of inheritance in which the child class only calls methods of the parent class and does not interfere with the fields of the parent class. Generally, leaving parent fields alone is a good thing because it protects the state of the parent class from getting corrupted. However, it’s not always possible. If we return to the earlier Flounder example, we had to change the color field directly since there was no mutator to change it.

Perhaps the Fish class was poorly designed because it didn’t have a color mutator. On the other hand, most fish cannot change their color, so it might be good design to prevent outside code from changing the color field with such a mutator. There are no absolute rules for making these kinds of decisions.

We introduced access modifiers in Section 9.3.4, but inheritance gives them new meaning. Recall that the access modifier for the color field of Fish was protected. A field or method with the protected modifier can be accessed by all child classes (as well as classes in the same package). If the modifier for color was private, the Flounder class would not be able to change it directly.

In the Shark class, it must use mutators to change the value of its own energy and the alive field of the fish object it eats since they’re both marked private. It’s generally preferable to use mutator and accessor methods whenever possible, even within the same class, so that fields are not inadvertently corrupted.

11.3.3. Constructors

When you create a child class, you can imagine that a copy of the parent class exists inside of the child. When you create an object from a child class, how do you properly initialize the fields inside the parent class?

As we discussed in Chapter 9, every class has a constructor, even if it’s a default one created for you. Whenever the constructor for a child class is invoked, the constructor for the parent class is invoked as well. If the parent class is also the child of some other class, that grandparent class will have its constructor invoked as well. This chain of constructors will continue, reaching all the way back to the ultimate ancestor, Object.

When writing the constructor for a child class, the first line of it should be the call to the parent constructor. If you don’t explicitly call the parent constructor, its default (no parameter) constructor will be called. If the parent class does not have a default constructor, then leaving off an appropriate call to a parent constructor will result in a compiler error. Consider the following two classes.

public class Parent {
    private String name;
	
    public Parent(String name) { this.name = name; }
    public String getName() { return name; }
}
public class Child extends Parent {
    public Child(String name) {
        super("Baby " + name);
    }
}

As shown above, the super keyword is used to call the constructor of a parent class. The Child constructor takes a name and prepends the String "Baby " to it before passing it on to the Parent constructor.

In a similar way, the this keyword can be used to call another constructor in the same class, provided that a constructor to the parent class is eventually reached. For example, we could add the following constructor to the Child class.

    public Child() {
        this("Unknown");
    }

This second constructor will be called whenever a new Child object is instantiated without any arguments. It will supply the String "Unknown" to the other constructor, which will add "Baby" and pass it on to the Parent class.

11.3.4. Overriding methods and hiding fields

Sometimes a parent method doesn’t provide all the power you want in the child class. It’s possible to override a parent method in the child class. Then, when that method is called on child objects, the new method will be called. The new method has exactly the same name and parameters. The return type must either be exactly the same or a child class of the original return type.

We can return to the Fish class example and make a new kind of fish that never moves.

public class LazyFish extends Fish {
    public void swim() {
        System.out.println("I think I'll just sit here.");
    }
}

Whenever someone calls the swim() method on a LazyFish object, it will announce that it’s going to sit where it is. Its location isn’t updated, and its energy doesn’t change.

On the other hand, we could create another child class that swims twice as fast as the original Fish.

public class FastFish extends Fish {
    public void swim() {
        super.swim();
        super.swim();
    }
}

Every time swim() is called on objects of type FastFish, those objects will call the swim() method from Fish twice. Thus, this fish will move twice as fast (and consume twice as much energy). Because the location and energy fields are private, we must use methods from Fish to affect them. Note the use of the keyword super, allowing us to specify that we want to call the swim() method from Fish and not just call the same method from FastFish again. Using the super keyword, we can call methods from the parent. If the parent didn’t override a method from an ancestor class, we can still use super to call a method from the most recent ancestor class that did implement the method. However, Java does not allow us to skip over a parent method to call a grandparent method if there’s an implementation in the parent class. In other words, there’s no way to call something like a super.super.swim() method.

Just as methods are overridden, fields are hidden. It’s perfectly legal to declare a field with the same name as a field from a parent class, but the new field will then be used instead of the old one.

public class A {
    protected int a;
	
    public int getA() { return a; }
    public void setA(int value) { a = value; }
}
public class B extends A {
    protected int a;
	
    public void setA(int value) { a = value; }
}

Class B is a child of class A and declares a field called a, hiding a field of the same name from A. However, which a is which can cause some confusion. Consider the following fragment of code.

A objectA = new A();
B objectB = new B();
objectA.setA(5);
objectB.setA(10);
System.out.println("A = " + objectA.getA());
System.out.println("B = " + objectB.getA());

The output of this code is:

A = 5
B = 0

Calling the setA() method on an A object sets the a field inside of A. Calling the overridden setA() method on a B object sets the a field inside of B, but since the getA() method hasn’t been overridden, the a field from the A parent class part of B is returned. Since that a field in B hasn’t been given a value, it still has the default value of 0. Both a fields exist inside of B, but the methods are poorly designed, leaving one field capable only of being set and the other capable only of being retrieved.

11.3.5. The Object class

You may not have realized it, but every class you’ve created in Java uses inheritance. To provide uniformity, the designers of Java made every class the child (or grandchild or great-grandchild…​) of a class called Object. When you omit the extends clause in a class definition, you’re making that class a direct child of Object.

As a consequence, all classes in Java are guaranteed to have the following methods.

Method Purpose

clone()

Make a separate copy of an object.

equals()

Determine if two objects are the same.

finalize()

Perform cleanup when an object is garbage collected. Similar to a destructor in C++. Rarely used.

getClass()

Find out what the class type of a given object is.

hashCode()

Get the hash code for an object, useful for making hash tables of objects.

notify()

Used for synchronization with threaded programs. More in Chapter 14.

notifyAll()

Same as previous.

toString()

Get a String representation of the given object.

wait()

Used with notify() and notifyAll().

Java provides basic implementations for most of these, but if you want them to work well for your object, you’ll have to override some of them with appropriate methods. For example, the Object version of toString() returns the virtual address of the object in JVM memory, which is not very useful information.

Nevertheless, API classes usually have good equals() and toString() methods. Aside from making a few useful methods available, having a common ancestor for all classes means that you can store any object in an Object reference. An array of type Object can hold anything, provided that you know how to retrieve it. We discuss the finer points of inheritance and polymorphism in Chapter 17 and how to build lists and other data structures using Object references in Chapter 18.

11.4. Examples: Problem solving with inheritance

Here are two extended examples showing how we can use inheritance to solve problems. First, we revisit the student roster example from Chapter 9 and then move onto an inheritance hierarchy of polygons.

Example 11.1 Graduate student roster

The Student class we created in Example 9.1 is useful but works only for undergraduate students. With only a few additions, we can make it suitable for graduate students as well. First, let’s take another look at the Student class.

Program 11.1 Basic student class, designed for undergraduates.
public class Student {
    public static final String[] YEARS = {"Freshman", "Sophomore", "Junior", "Senior"};
    private String name;
    private int year;
    private double GPA;

    public Student(String name, int year, double GPA) {
        setName(name);
        setYear(year);
        setGPA(GPA);
    }   

    public void setName(String name) { this.name = name; }  
    public void setYear(int year) { this.year = year; }

    public void setGPA(double GPA) {
        if(GPA >= 0 && GPA <= 4.0)
            this.GPA = GPA;
        else
            System.out.println("Invalid GPA: " + GPA);      
    }

    public String getName() { return name; };
    public int getYear() { return year; };
    public double getGPA() { return GPA; };

    public String toString() {
        return name + "\t" + YEARS[year] + "\t" + GPA;
    }   
}

We want to create a GraduateStudent class that inherits from Student. We need to add a thesis topic for each graduate student. Likewise, we need to update the toString() method so that outputs the appropriate data. We use 4 as the year value for graduate students.

Program 11.2 Class extending Student to add graduate student capabilities.
public class GraduateStudent extends Student {
    private String topic; (1)

    public GraduateStudent(String name, double GPA, String topic) {
        super(name, 4, GPA); (2)
        setTopic(topic);
    }   

    public void setTopic(String topic) { this.topic = topic; }  

    public String toString() { (3)
        return getName() + "\tGraduate\t" + getGPA() + "\tTopic: " + topic;
    }   
}
1 Because we’re inheriting most of the fields we need, we only need to declare the topic field.
2 Then, in the GraduateStudent constructor, we call the parent constructor with the name, year, and GPA and then set topic to the input value.
3 Finally, we override the toString() method so that "Graduate" and the thesis topic are output. Note that we must use the getName() and getGPA() accessors since those fields are private in Student.

Most code that uses Student objects should be able to incorporate GraduateStudent objects easily. Code that creates Student objects from input will need slight modifications to handle the thesis topic. Also, old code that only expects values of 0, 1, 2, or 3 for year may need to be modified so that it doesn’t break.

Example 11.2 Polygons

Let’s examine a class hierarchy used to create several different polygons. Our base class needs to be general. It can represent any kind of closed polygon, using an array of Point objects. The Point library class is a way to package up x and y values of type int. Each coordinate in the array gives the next vertex of the polygon.

import java.awt.*; (1)

public class Polygon {
    protected Point[] points; (2)

    public Polygon(Point[] points) { (3)
        this.points = points;
    }
1 The import statement allows us to use the Point class as well as the Graphics class.
2 Our array of type Point is declared protected so that the child classes we want to create can access it directly.
3 The constructor takes an array of type Point and stores it.
    public double getPerimeter() { (1)
        double perimeter = 0.0;
        for(int i = 0; i < points.length - 1; i++)
            perimeter += points[i].distance(points[i + 1]);
        perimeter += points[0].distance(points[points.length - 1]);
        return perimeter;
    }

    public void draw(Graphics g) { (2)
        for(int i = 0; i < points.length - 1; i++)
            g.drawLine(points[i].x, points[i].y, points[i+1].x, points[i+1].y);
        g.drawLine(points[0].x, points[0].y,
			points[points.length - 1].x, points[points.length - 1].y);
    }
}
1 The getPerimeter() method can determine the length of the perimeter by adding the lengths of the segments connecting the vertices. Although it’s also possible to determine the area enclosed by a list of vertices, the algorithm is complex.
2 The draw() method draws the polygon by drawing each line segment that connects adjacent vertices. We discuss the Graphics class in Chapter 15. If you compile and run this code, please note that in Java graphics, like many computer graphics environments, the upper left hand corner of the screen or window is considered (0,0), and y values increase going downward, not upward.

The number of things that can be done with this very general Polygon class are limited, but with this basic parent class defined, we can design a Triangle class as a child of it.

import java.awt.*; (1)

public class Triangle extends Polygon {
    public Triangle(int x1, int y1, int x2, int y2, int x3, int y3) { (2)
        super(toPointArray(x1, y1, x2, y2, x3, y3));
    }
    
    protected static Point[] toPointArray(int x1, int y1, int x2, int y2, int x3, int y3) {  
        Point[] array = {new Point(x1, y1), new Point(x2, y2), new Point(x3, y3)}; (3)
        return array;
    }
1 Again, the import statement is for the Point class.
2 One reasonable constructor for a triangle takes in six values, giving the x and y coordinates of the three vertices of the triangle. Of course, the Polygon class requires an array of type Point, but the super constructor must be the first line of the Triangle constructor.
3 To solve this problem, we create a static method to package the values into an array. We could have done the same thing in the argument list of the super constructor, but it would have looked messier. The toPointArray() is protected because there’s no reason to let external code have access to it.
    public String getType() {
        double a = points[0].distance(points[1]);
        double b = points[1].distance(points[2]);
        double c = points[2].distance(points[0]);
        if(a == b && b == c)
            return "Equilateral";
        if(a == b || b == c || a == c)
            return "Isosceles";
        return "Scalene";
    }
}

Finally, the getType() method allows us to do something specific with triangles. We can use the distance() method from the Point class to find the length of each of the three sides. By comparing these lengths, we can determine whether the triangle represented is equilateral, isosceles, or scalene. Of course, computing the perimeter and drawing the triangle are already taken care of by the Polygon class.

We can easily make a Rectangle class along the same lines.

import java.awt.*;

public class Rectangle extends Polygon {
    public Rectangle(int x, int y, int length, int width) { (1)
        super(toArray(x, y, length, width));
    }

    protected static Point[] toArray(int x, int y, int length, int width) { (2)
        Point[] array = {new Point(x, y), new Point(x + length, y),
            new Point(x + length, y + width), new Point(x, y + width)};
        return array;
    }

    public int getArea() { (3)
        int length = points[1].x - points[0].x;
        int width = points[2].y - points[1].y;
        return length * width;
    }
}
1 The constructor is similar to the Triangle constructor except that the upper left corner of the rectangle is specified, along with the length and the width.
2 From these values, the appropriate array of Point values is generated.
3 The rectangle-specific code that we add is the getArea() method, which determines the length and width of the rectangle by examining the points array and then calculates area.

Using inheritance as form of specialization, we can go one step further and make a Square class.

public class Square extends Rectangle {
    public Square(int x, int y, int size) {
        super(x, y, size, size);
    }
}

This very short class uses everything available in Rectangle but simplifies the constructor slightly so that the user doesn’t have to enter both length and width.

11.5. Solution: Boolean circuits

Here we present our solution to the Boolean Circuits problem. First, we define a parent class for all circuit components, called Gate.

public class Gate {
    private String name;

    public Gate(String name) { this.name = name; }
    public String getName() { return name; }
    public String toString() {
        return getName() + ": " + getValue();
    }
    public boolean getValue() { return false; }
}

The Gate class doesn’t do anything except set up ways to store a name and to get a value. It doesn’t really matter what getValue() gives back for Gate, but we can say that it’s false.

In principle, it shouldn’t be possible to create an object of type Gate, UnaryOperator, or BinaryOperator. Classes that are designed only to be parent classes and never to be instantiated are called abstract classes and are discussed in Section 17.3.1.

From Gate, we can define the most basic circuit components: gates whose value is either always true or always false.

public class True extends Gate {
    public True() { super("true"); }
    public boolean getValue() { return true; }
}
public class False extends Gate {
    public False() { super("false"); }
    public boolean getValue() { return false; }
}

To conform with the constructor for Gate, these new classes must pass a String giving their name to the super constructor. The values returned by the getValue() method are clear. Next, we want to create a class that can be used as a parent for all unary operators.

public class UnaryOperator extends Gate {
    private Gate input;

    public UnaryOperator(String name) { super(name); }
    public void setInput(Gate input) { this.input = input; }
    public Gate getInput() { return input; }
}

The important addition in the UnaryOperator class is the input field. Any unary operator must have a single input gate that it operates on. This class provides a mutator and accessor for input, as well as an appropriate constructor. From UnaryOperator, we can derive two specific operators.

public class Output extends UnaryOperator {
    public Output(int i) { super("OUTPUT " + i); }
    public boolean getValue() { return getInput().getValue(); }
}

The Output class takes in an int value and uses it to make a numbered name. Its getValue() method simply returns the value of its input. The Output class doesn’t do anything except serve as a marker for circuit output.

public class Not extends UnaryOperator {
    public Not() { super("NOT"); }
    public boolean getValue() { return !getInput().getValue(); }
}

The Not class uses "NOT" as the name supplied to the super constructor and returns the logical NOT of the value of its input.

Just as we did for unary operators, we also need a parent class for binary operators.

public class BinaryOperator extends Gate {
    private Gate operand1;
    private Gate operand2;

    public BinaryOperator(String name) { super(name); }
    public Gate getOperand1() { return operand1; }
    public Gate getOperand2() { return operand2; }
    public void setOperand1(Gate operand) { operand1 = operand; }
    public void setOperand2(Gate operand) { operand2 = operand; }
}

A BinaryOperator has two Gate fields, operand1 and operand2, representing the inputs to the operator. The BinaryOperator class has an appropriate constructor and then accessors and mutators for the operands. With BinaryOperator as a parent, only a few lines of code are necessary to define any logical binary operator.

public class And extends BinaryOperator {
    public And() { super("AND"); }

    public boolean getValue() {
        return getOperand1().getValue() && getOperand1().getValue();
    }
}
public class Or extends BinaryOperator {
    public Or() { super("OR"); }

    public boolean getValue() {
        return getOperand1().getValue() || getOperand1().getValue();
    }
}
public class Xor extends BinaryOperator {
    public Xor() { super("XOR"); }

    public boolean getValue() {
        return getOperand1().getValue() ^ getOperand1().getValue();
    }
}

In each case, a constructor passes the name of the gate to the super constructor. Then, each getValue() method gets the values from the two operands and combines them with AND, OR, or XOR, respectively. This design allows the programmer to focus only on the important element of each class. Adding new classes for NAND, NOR, or any other possible logical binary operator would be quick.

The client code that uses these classes to simulate a circuit follows.

import java.util.*;

public class BooleanCircuit {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int count = in.nextInt();
        Gate[] gates = new Gate[count];
        String name;
        int value;

First we have the import needed for Scanner. In main(), we read in the total number of gates, create an array of type Gate of that length, and declare a few useful temporary variables.

        // Create gates
        for(int i = 0; i < count; i++) {
            name = in.next().toUpperCase();
            if(name.equals("true"))
                gates[i] = new True();
            else if(name.equals("false"))
                gates[i] = new False();
            else if(name.equals("AND"))
                gates[i] = new And();
            else if(name.equals("OR"))
                gates[i] = new Or();
            else if(name.equals("XOR"))
                gates[i] = new Xor();
            else if(name.equals("NOT"))
                gates[i] = new Not();
            else if(name.equals("OUTPUT")) {
                value = in.nextInt();
                gates[i] = new Output(value);
            }
        }       

Then, we parse the input, creating an appropriate gate based on the name read in. In the case of an OUTPUT gate, we must also read in a number so that we can identify which OUTPUT gate is which later.

        //connect gates
        while(in.hasNextInt()) {
            value = in.nextInt();
            name = gates[value].getName();
            if(name.equals("AND") || name.equals("OR") || name.equals("XOR")) {
                BinaryOperator operator = (BinaryOperator)gates[value];
                operator.setOperand1(gates[in.nextInt()]);
                operator.setOperand2(gates[in.nextInt()]);
            }
            else if(name.equals("NOT") || name.startsWith("OUTPUT")) {
                UnaryOperator operator = (UnaryOperator)gates[value];
                operator.setInput(gates[in.nextInt()]);
            }
        }

As long as there’s input remaining, we read in an index. Based on the name of the gate at that index in the array, we either read in two more indexes (for binary operators) or just a single additional index (for unary operators). In either case, we set the input or inputs of the operator to the gate or gates at those indexes.

        // Compute output
        for(int i = 0; i < count; i++)
          if(gates[i].getName().startsWith("OUTPUT"))
            System.out.println(gates[i]);
      }
}

Finally, the simulation of the circuit is surprisingly simple. We look through array until we find a gate whose name starts with "OUTPUT". Then, we print out its value. In order to determine its value, it will ask its input what its value is, which in turn will ask for the values from its input. The toString() in the Gate class will assure us that the final output is nicely formatted. This system accommodates any number of output gates connected arbitrarily, as long as the circuit has no loops inside of it, such as an AND gate whose output is also one of its inputs.

11.6. Concurrency: Inheritance

Like interfaces, inheritance in Java is not closely related to concurrency. However, two ways in which inheritance interacts with concurrency deserve attention.

The first is the Thread class. Each thread of execution in Java (except the main thread) is managed with a Thread object or an object whose type inherits from Thread. Creating such types is done by extending Thread, just as you would extend any other class. Further information about extending Thread for concurrency is given in Section 13.4. Extending the Thread class to make your own customized threads of execution is an alternative to implementing the Runnable interface mentioned in Section 10.6 and is discussed in greater detail in Section 13.4.5.

The second interaction between inheritance and concurrency is again very similar to the problem with interfaces and concurrency: There’s no way to specify that a method is thread-safe. Recall that it’s not allowed to use the synchronized keyword on a method in an interface declaration. Likewise, there’s no restriction on overriding a synchronized method with a non-synchronized method or vice versa.

The rules for overriding methods in Java guarantee that an object of a child class is usable anywhere that an object of the parent class is usable. Thus, you cannot override a public method with a private one, reducing the visibility of a method. We discuss a similar restriction with exceptions in Section 17.3.4.

If it has these restrictions, why doesn’t Java prevent a synchronized method from being overridden by a non-synchronized method? In the first place, a non-synchronized method can be used anywhere a synchronized one could (unlike a private method, which is not accessible everywhere a public one is). In the second, the designers of Java put thread safety in the category of implementation details left up to the programmer. Some classes need specific methods to be synchronized and others (even child classes) do not. However, if you override a class with a synchronized method, it’s safest to mark your method synchronized as well.

11.7. Exercises

Conceptual Problems

  1. Give three advantages of using inheritance instead of copying and pasting code from a parent class. Are there any disadvantages to using inheritance?

  2. Consider classes Radish and Carrot which both extend class Vegetable and implement interface Crunchable. Which of the following sets of assignments are legal and why?

    1. Radish radish = new Radish();

    2. Radish radish = new Vegetable();

    3. Vegetable vegetable = new Radish();

    4. Crunchable crunchy = new Radish();

    5. Radish radish = new Carrot();

  3. In the context of inheritance, the keyword super can be used for two different purposes. What are they?

  4. Consider the following class definitions.

    public class A {
        private String value;
        public A(String s) { value = "A" + s + "A"; }
        public String toString() { return value; }
    }
    
    public class B extends A {
        public B(String s) { super("B" + s + "B"); }
    }
    
    public class C extends B {
        public C(String s) { super("C" + s + "C"); }
    }

    What’s output by the following code fragment?

    C c = new C("ABC");
    System.out.println(c);
  5. Beginning Java programmers often confuse package-private access (no explicit specifier) with public access. How is this confusion possible when default access is more constrained than both public and protected access? (Hint: The file system plays a role.)

  6. What are the similarities and differences between overloading a method and overriding a method?

  7. What’s field hiding? How can software bugs arise from this Java feature?

  8. Give reasons why the designers of Java decided not to allow multiple inheritance. Would you have made the same decision? Why or why not?

  9. Draw a class hierarchy establishing a sensible relationship between the Human, Soldier, Sailor, Marine, General, and Admiral classes. For this class hierarchy, refer to the U.S. military structure in which the U.S. Marine Corps is a part of the U.S. Navy.

Programming Practice

  1. Create an InternationalStudent class that extends Student. It should include String fields for country of origin and visa status. It should include mutator and accessor methods for these two new fields.

  2. Add Pentagon and Hexagon classes that extend the Polygon class. The constructor for each class should take an x, y and radius value, each of int type. Both classes should be implemented to create regular polygons, that is, polygons in which all five or six sides have the same length. The x and y values should give the center of the polygon, and each of the five or six points should be the radius distance away from that center.

    Because the internal structure of Polygon keeps all vertices as Point values, the x and y coordinates of the points must be int values. This requirement will force you to round these x and y coordinates after using trigonometry to determine their locations. As a result, the final pentagons and hexagons stored and displayed will be slightly irregular.

  3. The inheritance design of our solution to the Boolean circuits problem given in Section 11.5 makes adding new gates easy. Add classes that implement a NAND gate and a NOR gate. Then, rewrite the main() method of BooleanCircuit to accommodate these two extra classes.

  4. Re-implement the object hierarchy in the solution from Section 10.5 to the sort it out problem. This time, let the Cat, Dog, and Person classes extend the Creature class defined below.

    public class Creature implements Ageable, Weighable {
        protected int age;
        protected double weight;
        
        public Creature(int age, double weight) {
            this.age = age;
            this.weight = weight;
        }
        
        public int getAge() { return age; }    
        public double getWeight() { return weight; }
    }

    Refactor your code so that the Cat, Dog, and Person classes are as short as possible. How many lines of code do you save?

  5. Design a celestial body simulator. You’ll need to create a class containing fields for the x, y, and z locations, x, y, and z velocities, radii, and masses of each object. For each time step of length t, you must do the following.

    1. Compute the sum of forces exerted on each body by every other body. The equation for gravitational force on body b exerted by body a is given by the following equation.

      force

      As mentioned in Example 8.1, the gravitational constant G = 6.673 × 10-11 N⋅m2⋅kg-2. Also, |rab| is the distance between the centers of objects a and b. Finally, the unit vector between the centers of the two objects is given by the equation below.

      unitvector
    2. Compute the x, y, and z components of the acceleration vector a for each object using the equation F = ma once the sum of forces has been calculated.

    3. Update the x, y, and z components of the velocity vector v for each object using the equation vnew = vold + at.

Experiments

  1. Inheritance is a powerful technique, but it comes with some overhead costs. Create a class called A with the following implementation.

    public class A {
        protected int a;
    }

    Then, create 25 more classes named B through Z. Class B should extend A and add a protected int field called b. Continue in this manner, with each new class extending the previous one and adding an int field named the lowercase version of the class name. Thus, if you create an object of type Z, it will contain, through inheritance, 26 int fields named a through z. But for a single Z object to be created, it must call 27 (Z back through A plus Object) constructors. You may wish to use the file I/O material in Chapter 20 to write a program to create all these classes so that you do not have to do so by hand.

    Finally, create a new class called All which contains 26 protected fields of int type named a through z. Now, the purpose of creating all these classes is to compare the time needed to instantiate an object of type Z with one of type All, though they both only contain 26 int fields named a through z.

    Create an array of 100,000 elements of type Z and then populate it with 100,000 Z objects. Time this process. Create an array of 100,000 elements of type All and then populate that array with 100,000 All objects. You may wish to use the System.nanoTime() method described in Chapter 13 to accurately time these processes. Is there a significant difference in the times you found?