CS 334
|
A variable declared to be final must clearly be a constant (be sure to initialize it in the declaration or you will never be able to set it). A final method is one that may not be overridden. A final class is one that may not be extended.
In each case, the declaration of an entity to being final provides a guarantee to both the programmer and the compiler that the entity will never be changed again. This may result in a gain of efficiency for the compiler and better understanding for the programmer, but if mis-used can result in a loss of flexibility (the usual binding time trade-offs!).
A constant is almost always declared as public static final ... as it is generally visible, there only needs to be one copy for the entire class (there need not be a separate copy in each object) and it may not be changed.
If class C implements interface IC then objects of type C can be used in any context expecting an object of type IC.
While we haven't shown examples yet, one interface may also extend another. This also results in subtyping.
Subtyping is by declaration (name) in Java. Thus one type may be used as a subtype of another only if there is a chain of extends and implements going from the first to the second. For example if we have
SC extends C C implements IC IC extends IBCthen an object from class SC can be used in any context expecting an object of interface IBC.
Thus of the basic data structures or building blocks for data structures, only arrays must be dealt with. Arrays in Java are objects, and hence must be created. The declaration for an array of employees would be written:
protected Employee[] emp;Technically, you can also write the declaration as in C++:
protected Employee emp[];but I prefer the former as it makes it clear what the type is and makes a bit more sense when the declaration also initializes the array:
protected Employee[] emp = new Employee[75];Notice that the declaration of the array does not include the size of the array, only the type of elements that it holds. Thus at various points in the program emp could hold arrays of different sizes. It is only the call to the constructor that determines the size of the array to be created. In particular, then, Java avoids Pascal's problems with passing arrays as parameters since the size of the array is not part of the type.
The programmer does not need to declare a constructor for arrays (in fact it's hard to imagine where one would put such a constructor). Thus, once the Employee class exists it is easy to construct an array of employees. However, one must be sure not to confuse creating an array with creating its elements.
Before the call of the constructor above, the value of emp was
null. After the execution of the constructor,
Multi-dimensional arrays are obtained by creating arrays of arrays. Java
does not provide the same nice abbreviations as Pascal to make you think of
them as multi-dimensional arrays. However, it does provide a bit more
flexibility:
If you read the previous comments about parameter passing in Java, you may
be wondering how you can change the values in an array if the array is
passed as a parameter. Luckily there is no problem. You still can't
assign to an array parameter as a whole (well, actually you can but it
won't have any impact on the original array ...), but you can assign to
individual elements of the array. Thus if emp is a formal
parameter of array type, then emp[3] = newEmployee changes the
entry in slot 3 of the actual parameter, at least if it has a third slot.
By the way, Java, unlike C++, automatically checks array boundaries when
accessing array entries. Arrays do keep track of their length, and you can
get access to the length of an array by writing emp.length. Be
sure not to include parentheses after length as it is an instance
variable rather than a method (bad Java designers, bad!).
In this case the instanceof method is exactly what you need:
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.
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:
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.
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:
Let's take these in order. Of course it is easy to define a brand new
method, say,
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:
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:
The third option is the default. You can accomplish either of the other
two by ensuring that the class
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!
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:
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.
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:
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.
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.
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
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.
Omitted in most OOL's: Smalltalk, Eiffel, C++.
What do modules provide?
Employee[][] squareEmps = new Employee[75][75];
// this builds a 75 by 75 array.
Employee[][] triangleEmps = new Employee[75][];
// this builds an array with 75 rows, each of which is currently empty.
for (index = 0; index < 75; index++)
triangleEmps[index] = new Employee[index+1];
The for loop assigns row index the size of index+1. Thus the 0th row has
size 1 up to the 74th row having size 75. Of course you still need to
initialize the individual elements of the array. You would get access to
an individual element by writing triangleEmps[3][2], for example.
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.
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 (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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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).
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.
Modules in OO Languages
Classes typically used instead.
Aren't classes enough?
No!
What's Wrong with Java Packages? Supposed to provide facilities of modules.
Details of Java Packages and visibility: A class (or interface) belongs to a package if it includes a declaration at the top of the form:
package SomePackage;
Visibility restrictions in Java: public, default, protected, or private
Notes:
Problems with packages:
package A; public class X { void m(int x){...} void n(...){... self.m ...} } package A; public class Z extends Y { void m(int x){...} } X x = new X(); Y y = new Y(); X z = new Z(); |
package B; public class Y extends X { void m(int x){...} void p(){... m(5)...} } x.n(...); y.n(...); z.n(...); z.p(); |
What happened?
Doesn't provide the correct kind of visibility control.
Java programs require lots of type casts (as do C++, Object Pascal, etc.). Why?
The Object class in Java illustrates most of the problems.
public class Object{ protected Object clone(){...} public boolean equal(Object other){...} }
Recall not allowed to change signature of methods in extensions. Also all classes automatically inherit from Object.
public class A implements Cloneable{ protected B b = ...; ... public Object clone(){ A other = super.clone(); other.b = b.clone(); // type error return other; } } A a1 = new A(); A a2 = a1.clone(); // type error
Both errors would disappear if could change return types of methods. (OK in C++)
Suppose also want to override equals in A:
public boolean equal(Object other){ if (other instanceof A){ A aother = (A)other; return (b = aother.b); else ???
Problems:
Get similar problems if try to define doubly-linked node as subclass of linked node.
public void setNext(Node newNext){...}
Want parameter type to be doubly-linked in subclass.
Methods where parameters should be of same type (class) as receiver called "binary methods".
Source of many typing problems in OOL's.
Still other problems:
public class Circle{ protected Point center; ... }
If define ColorCircle as extension, might want center to be ColorPoint.
Can't make any of these changes in signature (types) in Java, Object
Pascal, and C++ (aside from return type in C++)