Princeton University
Computer Science Dept.

Computer Science 441
Programming Languages
Fall 1998

Lecture 16


Even more Java

Oops! A type insecurity with arrays

Suppose we have the following method in a class:
   public m(A[] aArray){...}
If B extends A, then the Java type-checker allows an array of B to be used as an actual parameter to m. As we saw earlier, this is not type-safe. (As an exercise, construct an example where this would destroy type safety.) As a result, dynamic checks are inserted into the code to make sure that nothing breaks (e.g., your array of HourlyEmployees doesn't come back holding an Employee after using it as a parameter).

In order to perform these checks, Java arrays must keep track of the type they were declared with. Thus if emp were declared to be an array of Employee, but were passed as a parameter into a method expecting an array of Object, then it would be fine to assign a value from class HourlyEmployee into it, but it would not be OK to assign a value from class Integer, even though Integer extends Object.

While this can be seen as an unfortunate design decision (requiring lots of dynamic checks in order to retain type safety), it does make it possible to fake generic routines. As a very simple example, suppose we want to write a method to swap the first two elements in an array. We can do this so that it works for all reference (object) types by declaring the parameter to be an array of Object:

   public swap(Object[] swapper){
      if (swapper.length > 1){
         Object temp = swapper[0];
	 swapper[0] = swapper[1];
	 swapper[1] = swapper[0];
      }
   }
I can now pass in an array of employees and have this work fine. If Java did not allow this hole in the static type system it would be impossible to write such a routine in Java.

I had always presumed that this is why Java violated static type safety in this way, but a few weeks ago I got e-mail from Bill Joy of Sun stating

actually, java array covariance was done for less noble reasons than mimicing genericity: it made some generic "bcopy" (memory copy) and like operations much easier to write... i proposed to take this out in 95, but it was too late (we voted in our group that was "finishing" and "cleaning up" and the proposal to take it out was defeated). i think it is unfortunate that it wasn't taken out... it would have made adding genericity later much cleaner, and doesn't pay for its complexity today.

wnj

We'll discuss this in more detail when we talk about extending Java with generics (parametric polymorphism)

Casts and instanceof

Because of subtyping, a variable c with static type C can hold a value from a class extending C. This can be handy, for example, if you have a heterogeneous data structure which is intended to hold elements of many different types. In this instance, however, when you remove items from the data structure, you may want to do different things depending on their actual (run-time) type.

In this case the instanceof method is exactly what you need:

   if (emp instanceof HourlyEmployee){...}
The method instanceof (note the unusual lack of capitalization) is written as infix and returns a boolean value. It returns true iff the value of the argument on the left side is from the class (or interface) on the right side or is from any of its extensions.

If instanceof returns true, then it is OK to "cast" the expression to the type you checked. Type casts are indicated by placing the (static) type you want the value to be treated as in parentheses in front of the expression.

   if (emp instanceof HourlyEmployee){
      HourlyEmployee hemp = (HourlyEmployee)emp;
      hemp.setHourlyPay...}
   else {...}
The purpose of a cast is to inform the static type-checker that it is OK to treat an expression as though it had a different type. In the example above, the type-checker would not allow emp to be assigned to hemp without the cast. Similarly the static type-checker would not allow the message setHourlyPay to be sent to emp. A cast which succeeds has no run-time effect! (Note that this is different from many casts in languages like C and C++.) However the cast does initiate a run-time check to make sure that the value of the expression is of the type it is being cast to. If it is not, then it will raise an exception.

As you might guess, Java will allow you to do a cast without first checking the run-time type using instanceof, however you must then be ready to handle the exception if it is possible you will ever be wrong.

Casts for base types work differently from references. There the cast will actually have a semantic effect. For instance you can convert a double to an int by writing (int)dbleValue.

There is one more complexity with casts and arrays. Suppose we have:

   Object[] obs = new Object[20]
   for (int index = 1; index <20; index++)
      obs[index] = new Integer(index);
   Integer[] ints = (Integer[])obs;  //?????????
While the entire array obs is filled with Integer's, the cast on the last line will fail! The problem is that array casts do not involve dynamic checks of each element of the array (probably because it would be too expensive). Also, recall that successful casts are not supposed to have any run-time effect. They merely signal to the static type-checker that it is OK to perform certain operations. Because arrays carry around their types, an Object array filled with Integers is not the same as an Integer array filled with exactly the same values. Their "run-time" types (or classes) are different! Thus you must be very careful with type issues related to arrays.

Finally, some advice: usually you can avoid casts of references if you redesign your methods a bit so that the overridden methods in subclasses do different things than in the superclass. However, sometimes casts just can't be avoided.

The top class: Object

All classes in Java are implicitly subclasses of the class Object which includes the following methods:
  public boolean equals(Object obj){...}
  public String toString(){...}
  protected Object clone() throws CloneNotSupportedException{...}
The equals method
By default, equals determines whether the parameter is the same reference as the receiver of the message. Much of the time this is not what you would like to do, but you can always override the class to provide the desired behavior. For example you might want to override equals in Employee so that two employees are equal if they have the same name.

Unfortunately you run into a big problem if you do that, as the parameter has type Object, which means it definitely will not understand a getName message. There are two possible solutions to this:

  1. Define a different method (i.e., don't override equals), or
  2. Use type casts and instanceof

Let's take these in order. Of course it is easy to define a brand new method, say,

   public bool equalEmployee(Employee other)
and provide a body for it. One could also overload the method equals with
   boolean equals(Employee other)
though this is quite dangerous. See the discussion later on about problems with overloading methods.

The first of these works fine, but sometimes you actually need to override the equals method from Object (or some other superclass) because you are using subtype polymorphism. In this case you override and use casting:

   boolean equals(Object other){
      if (other instanceof Employee){
         Employee otherEmp = (Employee)other;
         return (name.equals(otherEmp.getName())); // can't use == (why?)
      } else {...} 
That code is fine except that it's often hard to know what to do in the else clause. Reasonable defaults might be to always return false or to raise an exception or ...?

The toString method
We've already discussed toString briefly earlier. It is called automatically if you put an object expression in a context where a string is expected. Normally, you should override the method in each of your classes so that it will return a string providing the appropriate information about an object.

The clone method
The clone method can be very useful in protecting objects from unexpected changes. Suppose class Restaurant has a method getMenu() which returns the current menu. If the menu is held in an instance variable of the class, and Menu has a method addItem then someone who asks for the menu can send it a message adding a new item to the menu. Now when anyone else asks for a menu they will get the updated menu with the new item added.

Clearly the problem here is that we have handed the customer the master copy of the menu, rather than printing off a separate customer copy. The solution is to give the customer a copy of the menu rather than the instance being held as an instance variable. This copying is done using the clone method.

Unfortunately, clone has some of the same problems as equals, but is even a bit more complicated because it is declared in Object as being protected and throws an exception. This means that the restaurant can never clone a menu (unless the restaurant class extends menu, which is pretty unlikely) and users must worry about it throwing an exception.

We haven't yet looked at exceptions in Java. The only important point here is that if a method is declared to throw an exception then any program which sends that message to an object must have a handle clause to catch that exception. This is a royal pain in the neck if we know that our code for the method will never throw that exception!

One more complication: the default clone method for object simply allocates enough space to hold a new object of the same type, attaches the same method suite, and makes copies of the values stored in each of the instance variables of the original. This is fine if the original's instance variables are all base types. But if any of them are objects, then the clone will share a reference to the same objects that are held in the instance variables of the original. This is usually not what is desired!

The user has three choices:

  1. Decide the default clone is good enough,
  2. Decide the default clone can be fixed by calling clone on the instance variables which are objects, or
  3. Decide that cloning is inappropriate for the objects of this class.

The third option is the default. You can accomplish either of the other two by ensuring that the class

  1. implements the interface Cloneable spelled that way!
  2. redefines the method clone so that it is public.
The only difference between the first and second options above is whether the redefined clone simply calls super.clone() or follows that with a series of messages to clone the instance variables.

For some reason, Java is paranoid about letting anyone clone an object. Normally, just overriding the method and making it public would be enough. Forcing the class to implement Cloneable is exceptional!

Abstract classes and methods

Sometimes it makes sense to create a class only to be used as a superclass of other classes. This usually is done when the subclasses will all share a collection of instance variables and methods, where the implementations of many of the methods are the same in all subclasses.

An example is a class for graphic objects which can be dragged around the screen. Each of these will have x and y coordinates as well as move and draw methods. The move method will be the same for all graphic objects as it will just involve changing the x and y coordinates, while the draw methods will depend on the actual kind of object. We saw another example in the Employee class discussed earlier. There the getName method was the same for all subclasses, but getWeeklyPay depended on the kind of employee.

In Employee we put in a default or dummy body for getWeeklyPay by just having it return 0. However, because there really should never be created any objects of class Employee, it would have been better to define it as an abstract class:

public abstract class Employee implements EmployeeSpec{
/** Class to represent an Employee.  */
// fields

   protected String name;      /** name of employee */
   protected Date birthDate;   /** employee's date of birth */

// constructors  -- note that constructors do not have return type!

   public Employee (String emp_name, Date emp_bday){
   /* post: creates Employee object from name and birthdate   */
      name = emp_name;         // initialize fields
      birthDate = emp_bday;
   }

// methods

   public String getName()
   // post: Returns name of Employee
   {
      return name;               // return name field
   }
   
   public int getAge()
   /** post: returns the age of the Employee   */
   {
            // declares today and initializes to today's date
      Date today = new Date();            
            // declare yearDiff and initialize to diff of years
      int yearDiff = (today.getYear() - birthDate.getYear());
      
      if ((today.getMonth() > birthDate.getMonth()) || 
         (today.getMonth() == birthDate.getMonth() && 
                  today.getDay() > birthDate.getDay())){
         return yearDiff;       // already had birthday this year
      } else{                        
         return yearDiff - 1;   // adjust age if not had birthday yet this year
      }
   }
   
   public abstract double getWkPay();   
   /** post: Return weekly pay. */

   // No main method as it would never be run!
}
A class must be declared abstract if it has one or more abstract methods. The programmer may not create instances of abstract classes. However, notice that I have included a constructor for the class. This is included so that it can be called in subclasses of the class as they must all initialize these instance variables. Notice that this would be even more critical if the instance variables were private as the subclasses could not get access to them.

Java does not support multiple inheritance

Some object-oriented languages, including both C++ and Eiffel, support multiple inheritance. Multiple inheritance allows a class to extend two or more classes. Java does not support multiple inheritance.

Most (but certainly not all) experts in object-oriented programming believe that multiple inheritance is more trouble than it is worth. Horrible problems can arise, for example, with name conflicts and with the same code being inherited in different paths through superclasses. We will look at some of these issues when we consider Eiffel later.

Interfaces

So what does a Java programmer do if s/he wishes to inherit from two or more classes? Studies of real code using multiple inheritance has shown that most (but certainly not all) uses of multiple inheritance involve one class with actual methods, but the other classes are primarily or completely composed of abstract methods.

In order to provide support for this situation, Java includes interfaces. As we have seen, an interface is a specification of public entities. A class implementing an interface must have a public entity corresponding to each element of the interface.

An abstract class with no instance variables and all methods public and abstract is really the same thing as an interface, and should always be declared instead as an interface. I often see books which include such purely abstract classes without thinking (usually because they have just translated code from C++). This is generally considered bad Java style for two reasons, the first principled and the second pragmatic:

  1. Inheritance is designed primarily for reuse of implementation. If there is no implementation, then there is nothing to reuse. Hence an interface, which is designed to specify the public methods of an object (and hence is closely related to subtyping) is a better match.

  2. While a class may only extend one other class, it may implement as many interfaces as desired. Thus an interface can be much more useful than a purely abstract class.
As indicated above, most uses of multiple inheritance involve abstract classes which are really there just to allow one to treat the class as a subclass of another in order to use subtyping. Declaring the class to implement the interface gives the same benefits, so nothing is lost.

On the other hand, if you really need to inherit code from two or more classes you don't any nice options. One way around this is to just copy and paste code from one of the classes you wish to inherit from. The bad part of this is that changes in the code of the superclass won't be reflected in the new class. The other option is to have the new class contain an instance variable initialized to be an object of the desired superclass. You can then write adapter methods to "forward" all calls of the superclass methods and send them to the instance variable. Sometimes this works nicely and sometimes it doesn't. In either case, if you want subtyping to hold, you must make sure that the new and old class implement the same interface and that you use the interface consistently in place of the class for variable and parameter declarations. The bottom line is that there are no great solutions to emulating multiple inheritance. Luckily, the real need for it shows up pretty rarely, and most observers agree that including multiple inheritance adds more complications to a language than it is worth. We'll see some of the complications that can arise when we discuss Eiffel, which does support multiple inheritance.

Why you should really use interfaces!
Above I gave you Sun's justification for the use of interfaces. In fact, I believe that interfaces should be considered more important than just a way of getting around the lack of multiple inheritance.

Interfaces support the same kind of information hiding as ADT languages. Generally, in order to write an algorithm, one needs only to know the methods that the objects respond to. As a result, using an interface rather than a class as the type of variables and parameters should cause no difficulty. It also makes it easier to make changes to your program later on if you wish to change the implementation of objects from one class to another or even if you wish to mix and match different implementations as long as they support the same interface. For example you might have a method which takes a Point (class) as a parameter. While it will handle all extensions of Point, we would be unable to get it to accept a completely different implementation (e.g. polar point) which did not inherit from Point.

The general rule should be to use classes where you need to be certain of a particular implementation (e.g., in creating objects or extending classes) and to use interfaces elsewhere. This does have the disadvantage of causing you to create interfaces in situations where a class name would work fine, but has the advantages of flexibility in the long term.

Static overloading

Java supports static overloading of methods. That is, it is legal in Java to have several methods in a class (or even across subclasses) that have the same name, but different signatures. For example, the following is legal:
   class C{
      E m(A a){...}
      F m(B b, D d){...}
}
Overloaded methods are treated by the compiler as though they had entirely different names (e.g., as if the compiler renamed them as m1 and m2).

One restriction is that different "overloadings" of a method m must have different parameter types. For example, it is illegal for a class to contain both of the following methods

   int m(A a){...}
   Point m(A a){...}
This is the same restriction as would be found in C++ (though Ada would presumably allow it). It is possible to overload across class boundaries in Java (I believe this is not true in C++, though students in class are checking that out).
   class A{
      void m(A a){...}  -- implementation 1
   }

   class B extends A{
      void m(B b){...}  -- implementation 2
   }
Inside class B, both versions of m are visible. However that does not mean that it is easy to see which would be called under a various circumstances. One thing you must keep in mind is that overloading is resolved statically (at compile time), while dynamic method invocation (choosing which of the overridden method bodies to execute) is done dynamically (at run time). Adding a method in a subclass with exactly the same signature as one with the same name in the superclass results in overriding (dynamic method dispatch), while adding one with the same name but different signature results in overloading (resolved statically). The example above is complicated by the fact that the parameter of the version of m added in B is an extension (and hence treated like a subtype) of the version in A. Suppose we have the following declarations of variables using the classes A and B defined above:
   A a1 = new A();
   A a2 = new B();  -- legal because B extends A
   B b = new B();
Exercise worked in class: Each of the following are legal. Please determine which method body is executed for each message send.
   a1.m(a1);        a2.m(a1);          b.m(a1);
   a1.m(a2);        a2.m(a2);          b.m(a2);
   a1.m(b);         a2.m(b);           b.m(b);
The first and last are pretty easy. The others are not as obvious as they may first seem. Only rarely have I seen anyone get them all right the first time (though most people don't have too much trouble understanding the explanations as to why the correct answers are indeed correct). See me for answers or ask someone who was in class.

As a result of these confusions between static resolution of overloading and dynamic method dispatch I strongly recommend that you avoid overloading as much as possible.


CS441 | CS Department | Princeton University