CS61B, Lecture #6, 6/27/00. P. N. Hilfinger, modifications by M. Brudno READING: For today Friesen pp. 135-155 For tomorrow Friesen pp. 157-183 For Thursday Friesen pp. 185-214 Instanceof ---------- One can inquire about the dynamic type of a value using the `instanceof' operator. E.g., if TA and Instructor extend Worker, Professor extends Faculty, then after Worker worker1 = new Worker(), worker2 = new TA(), worker3 = new Professor(), worker4 = null; the following table Type |Worker TA Instructor Professor -------------------------------------------- V worker1 | true false false false a worker2 | true true false false l worker3 | true false true true worker4 | false false false false shows the value of Val instanceof Type. That is, it is true iff the dynamic type of Val is a subtype of Type (that is, "Val is a Type"). The dynamic type of the null value is "the null type", which isn't any nameable type. Stylistic note: You should not need `instanceof' often. That is, you should not have to say ``if X is a T, do A, and otherwise B,'' because defining a method that does either A or B depending on type is usually cleaner. When you use instanceof a lot, therefore, scrutinize your design. Abstract Classes: The Animal Kingdom -------- -------- --- ------ ------- When we had the worker example in last lecture, the work() method did those parts of working that all workers had in common. In some cases, however, you will see situations that while all sub-classes should do something in some way, the ways each one actually goes about it have nothing in common. Take, for instance, animals. Most of them know how to make sounds. On the other hand it makes completely no sense to ask just a generic animal to make a noise, if you don't specify what type of animal it is. For these situations java has abstract classes: public abstract class Animal { String name; boolean domestic; public Animal(String n) { name = n; } public void eat() { System.out.println("I am eating"); } abstract public void sound(); } The "abstract" keyword in the class header means that this class exists only to be extended; one is never supposed to say `new Animal(...)'. As you can see, there is no method body for the method sound(). The abstract keyword next to it tells the compiler that the method will be defined in subclasses. This will begin to make sense when we define a few subclasses: public class Duck extends Animal { public Duck(String n) { super(n); domestic = false; } public void sound() { System.out.println("quack-quack"); } } public class Cat extends Animal { public Cat(String n) { super(n); domestic = true; } public void sound() { System.out.println("Meow"); } } public class Fish extends Animal { public Fish(String n) { super(n); domestic = false; } public void sound() { System.out.println(""); } } Each of these subclasses define the sound() method all of them inherited from Animal, while ingeriting the eat() method. So now we can define Animal[] zoo = {new Duck("Daffy"), new Cat("Tom"), new Fish("Wanda")} After which we can write: for (int i = 0; i < zoo.length; i++) { System.out.print(zoo[i].name + " says "); zoo[i].sound(); } The abstract method in the animal class need not have been abstract: we could have just defined a method with no body. This way, however, when you are making the 499th animal and forget to include the sound() method, the compiler will warn you. Otherwise you will spend hours figuring out why your dogs just don't want to bark. Example: OutputStream -------- ------------ In the package java.io, there is an abstract class called OutputStream. Here's part of it package java.io; public abstract class OutputStream { public OutputStream() { } public abstract void write(int b); public void write(byte b[]) { write(b, 0, b.length); } public void write(byte b[], int off, int len) { for (int i = off; i < len; i += 1) write(b[i]); } } An OutputStream is supposed to be something that receives a stream of bytes from the program (think of them as characters for now, although they aren't) and does something with them (stores them in file, perhaps, or writes them to a screen). The class is abstract because it does not define any particular thing to do with the stream of bytes; it merely shows what methods the program will call to communicate this stream of bytes. The first `write' method is labeled "abstract", meaning again that it is merely an interface---as you can see, there is no body. The other two `write' methods show a default implementation for writing an array of bytes that uses the (undefined) abstract `write' method. One possible "something to do" with bytes from a program is to write them to a file. For this purpose, there is: package java.io; public class FileOutputStream extends OutputStream { public FileOutputStream(String name) { ... } public void write(int b) { ... } public void write(byte b[]) { ... } public void write(byte b[], int off, int len) { ... } } [These actually have "throws" clauses also, but we haven't gotten to that yet, so I'll ignore it.] When one writes OutputStream aFile = new FileOutputStream("foo.bar"); aFile will point to a FileOutputStream that puts everything written into it into a file named `foo.bar'. FileOutputStream must define write(int) in order to be usable. It didn't have to define the other two write methods, because they can inherit their definitions; the authors of this class decided to redefine those methods anyway, however, in order to give them a faster implementation. Another possible "something to do" is to write bytes into one big array for later use. We can define public class ArrayOutputStream extends java.io.OutputStream { private byte[] outputArea; private int n; public ArrayOutputStream(byte[] destination) { outputArea = destination; n = 0; } public void write(int b) { // Causes an error when outputArea overflows. outputArea[n] = (byte) b; n += 1; } } and write byte[] someBytes = new byte[1024]; OutputStream anArray = new ArrayOutputStream(someBytes); Both aFile and anArray are valid OutputStreams, and may be used wherever an OutputStream is used. If I write a method that works on OutputStreams, such as /* Write every other byte of FROM into OUT. */ static void copyEvenBytes(OutputStream out, byte[] from) { for (int i = 0; i < from.length; i += 2) out.write(from[i]); } it will work for both FileOutputStream and ArrayOutputStreams. Interfaces ---------- A class may only extend one class (by default, Object). Sometimes, however, this does not create quite the effect one wants. Java contains a standard type in package java.util with the following definition: public interface Enumeration { public abstract boolean hasMoreElements(); public abstract Object nextElement(); } For the moment, think of an interface as a class. The idea here, says the manual, is that if something is an Enumeration, "it generates a series of elements, one at a time. Successive calls to the nextElement method return successive elements of the series." Now I can write methods such as static void printAll(Enumeration E) { while (E.hasMoreElements()) System.out.println(E.nextElement()); } and it will print all the elements of any Enumeration. All well and good. Furthermore, I can imagine another kind of Enumeration, a resetable Enumeration, that allows me to go back to the beginning: public interface ResettableEnumeration extends Enumeration { public abstract void reset(); } I create a new kind of Enumeration: class MyEnumerator implements Enumeration { ... definitions for hasMoreElements and nextElement ... } For the moment, think of `implements' as `extends'. Now I want to create a new class in which I add stuff to MyEnumerator to make it resettable. I'd like it to be a ResettableEnumeration, BUT that would seem to require extending BOTH MyEnumerator AND ResettableEnumeration. You can't do that with `extends'. However, Java allows you to use ANY number of interfaces (declared like the two above) to a class. Interfaces are just like abstract classes, but (1) ALL their methods are abstract and public, (2) the only fields they may contain must be public, static, and constant (final), (3) they are listed in the `implements' clause of any class they are supposed to apply to. As a matter of style, one doesn't actually declare methods of interfaces as abstract and public or fields as public, static, and final: these attributes are automatically supplied for interfaces. Bodies for the methods declared in an interface must be supplied in (or inherited by) any non-abstract class that implements them. Abstract classes don't have to have such definitions, since they are not required to implement all their methods. Like abstract classes, interfaces may not be created by `new', so no pointer value actually points to a `bare' interface. An interface may `extend' any number of interfaces. Under our programming model, if T is an abstract class or an interface type, then there are no values whose dynamic type is T. There are containers whose static type is T. If I is a class that extends T (for T an abstract class) or implements it (for T an interface type), then we say that 'an I is a T', so that a container of type T may contain a pointer to an I. Likewise (most importantly, perhaps), an I is an acceptable parameter to any method that is expecting a T. Interface Fields --------- ------ Fields in interfaces are a kind of odd corner of the language. They are all constant ("final" in Java terminology) and static. Their principle use is for defining symbolic constants [for those of you who already know some C and C++, those languages use enum types for part of this.] One can write interface Movement { /** Turning Directions */ int UP = 0, RIGHT = 1, DOWN = 2, LEFT = 3; /** Movement Directions */ int FORWARD = 0, BACKWARD = 1; } and then define one's class like this: class Robot implements Movement { /** Turn 90 degrees in the indicated turning direction. */ void turn (int turningDir) { switch (turningDir) { case UP: ...; case DOWN: ...; } } /** Move in the indicated movement direction */ void move (int dir) { ... } ... } Since a class can implement any number of interfaces, there is no limit on the number of collections of such definitions that one can use conveniently in a class. Strictly speaking, inheritance isn't needed here, if one is willing to be verbose: class Robot { /** Turn 90 degrees in the indicated turning direction. */ void turn (int turningDir) { switch (turningDir) { case Movement.UP: ...; case Movement.DOWN: ...; } } /** Move in the indicated movement direction */ void move (int dir) { ... } ... }