(This is the start of an experiment: putting short lectures into Web pages. Way down the road, this might turn into a book, traditionally published or otherwise. Use in any way you please. My only request: if you find an error, tell me. Or a better example. Or an unclear explanation. Send mail to Dan@ccnyddm.com. And thank you.)
Introduction and Review: Class and method basics
Second Abstract Class: Add Polymorphism to the Mix
But You Didn't Really Believe It, Did You? It Was a Trick, Right?
Polymorphism with an ArrayList and Individually Named Animals
But Now I Want a Fish in my Zoo, and Fish Don't Breathe.
I assume that you have had two or more programming courses. Most readers will have had CSc 221, Software Development Lab, which at City College is a transitional course between data structures and software engineering, and it is the course where Java is introduced. (Prior courses use C and C++.)
Java is a big, powerful language, because it is used to do big powerful and varied jobs. There are some 3500 classes in the Java Foundation Class library. No one course could conceivably "cover" the entire language. You wouldn't do it that way anyway. The material on servlets and EJBs, for example, belongs in a course on middleware, or Web-based applications development.
I'm assuming that you were introduced to Object-Oriented Programming (OOP) in Data Structures, and therefore know the concepts of encapsulation and inheritance. But there may be gaps in your knowledge (that's OK) and that you don't really quite get polymorphism. This tutorial is intended to address that.
By all means copy this code and run it. Modify it and experiment. Copy and paste. Later I suppose I'll simplify the process.
Ah, well, you do, if you want to pass this course, but there are better reasons.
Let's start with basics. A class is a recipe/blueprint/cookie-cutter. A class by itself doesn't do anything. Most classes exist to be instantiated into objects, like spaghetti sauces/houses/cookies. Objects do things. Usually. In rare cases it may make sense to have a class with only data and no operations, and in our present discussion an abstract class can't be instantiated. So what good is it?
Before we go on, let's get the terminology right. A class (normally) has three things:
Let's look at an example before the discussion gets too, well, abstract. (An abstract class is not "abstract" in the sense just used. We'll get a precise definition soon.)
Here's a class that holds the name of a student and has methods for getting and setting the student's name. The name itself is private, meaning it is not visible outside this class. All this is standard practice and standard terminology. In the class after this one we'll instantiate this class twice, for two students.
public class Student {
private String studentName;
// Constuctor. This one sets the student name
// when the class is instantiated.
public Student ( String name ) {
studentName = name;
} // end constructor
// Setter
public void setStudentName( String name) {
studentName = name;
}
// Getter
String getStudentName() {
return studentName;
}
} // end class
To do anything with this class we must instantiate it. Let's instantiate it twice, in the following class.
public class SetUpStudents {
public static void main(String[] args) {
// Instantiate two student objects
Student s1 = new Student("Fred Flintstone");
Student s2 = new Student("Sanjay Krishnan");
// Display what we've done
System.out.println("Student1 is: " + s1.getStudentName());
System.out.println("Student2 is: " + s2.getStudentName());
} // end main
} // end class
This is the output:
Student1 is: Fred Flintstone
Student2 is: Sanjay Krishnan
If you are in the target readership for this material, this should be review--although if you are not entirely clear about constructors don't worry about it. We'll be back to that.
Let's look at our two classes again. The first one, Student, had to be instantiated to be of any use. We did not instantiate SetUpStudents, although we could have. The thing is, a static method as in "public static void main," can be executed without instantiating its class. Say what? Trust me. And we will definitely be back to this static business.
OK, so what is an abstract class? For a first answer, it's a class that cannot be instantiated. Huh? Then what's its point? Fair question, and the one we set out to answer. First let's get straight on the mechanics and the syntax.
An
abstract method is one that has no body. No body means no
curly braces, and the declaration ends with a semicolon, like so:
public abstract void Method2(int z);
An abstract method may or may not have arguments. It must have the modifier abstract, or the compiler will whine: "This method requires a body instead of a semicolon."
So what good is an abstract method? None, unless a class somewhere down this class'es inheritance chain provides an implementation. And why would anybody want to do that? Patience. Answer coming soon.
Any class that contains an abstract method, whether or not it has
other methods that are concrete (have bodies) must be abstract. The
compiler could figure that out, but you have to say it:
public abstract class Abstract1 {
etc.
Here is a complete illustrative abstract class:
public abstract class Abstract1 {
// This is a concrete method (it has a body).
public void Method1(int x) {
System.out.println("Hi from Method1 in Abstact1. x = " + x);
}
// This method is abstract because:
// 1. The modifier "abstract" says so;
// 2. It ends with a semicolon;
// 3. It has no curly braces enclosing a body.
public abstract void Method2(int z);
} // end class Abstract1
The next step in the plot is to show a class that inherits from this class:
public class Concrete1 extends Abstract1 {
// This implements Method2, which was abstract in Abstract1
public void Method2(int x) {
System.out.println(
"Hi from Method2 in Concrete1. x squared = " + x*x);
}
} // end class Concrete1
The word extends in:
public class Concrete1 extends Abstract1 {
means that Concrete1 is a subclass of Abstract1. That, in turn, means that Concrete1 inherits everything in Abstract1, specifically including Method1 and Method2. If we were to instantiate Concrete1 right now, we could proceed exactly as though Method1 were part of it. As it is, in a practical sense: Method1 is inherited from Abstract1.
Method2, on the other hand, is abstract in Abstract1; if we want to do anything with it, we will have to provide an implementation. Which we do. Now, Concrete1 can be treated as having two methods, Method1 and Method2.
Finally, let's see a class that uses all this--and something of its own.
public class TestAbstract {
// The "static" is required because the
// variable is used in the (static) main method.
private static int z = -4;
public static void main(String[] args) {
// This next would be illegal, of course;
// Can't instantiate an abstract class.
// Abstract1 abs1 = new Abstract1();
// Instantiate Concrete1.
Concrete1 concrete = new Concrete1();
// Method1 is _declared_ in Abstract1,
// the superclass of Concrete1.
// Method1 is _implemented_ in Concrete1.
// Now we send it as a message to the object concrete.
concrete.Method1(z);
// Method2 is declared and implemented in Concrete1.
concrete.Method2(z);
// Method3 is declared and implemented here. (Below)
Method3(z);
} // end main method
// Since Method3 is called from (static) main, must be static.
public static void Method3(int y) {
System.out.println("Hi from Method3 in TestAbstract. x cubed = " + y*y*y);
}
} // end class TestAbstract
We first try to instantiate Abstract1, just to prove it can't be done. Uncomment that, and you get an error message that is completely clear (for once): "Cannot instantiate Abstract1." Well, that's right. An abstract class can never be instantiated. It exists only to serve as a superclass.
Running this produced:
Hi from Method1 in Abstact1. x = -4 Hi from Method2 in Concrete1. x squared = 16 Hi from Method3 in TestAbstract. x cubed = -64
A reasonable person might well look at that example and say, "How cute. But what good is it? You could have done all that with one class." Quite right. We needed to get some concepts and syntax straight before going on to the Good Stuff.
Here's an overview of what we're going to do now.
We have a zoo. It has dogs, cows, and ducks, so I guess it's a "petting zoo," for little kids. We're going to build a collection of animals, and sometimes we'll want to do some processing involving all of them. Here's the catch: we don't know in advance how many of each kind there will be, or their names. (We get new ones every day.) That wouldn't be so bad, but we want to say what each animal eats and what sound it makes--still without knowing in advance what our collection is.
Sounds like we need to be able to create an array of animals, where an element of the array can be any of the three kinds. Big News: we cannot make an array that holds some dogs, some cows, and some ducks, as such. Any more than you can have an array that contains three Strings, a double or two, and three Integers. Every element in an array must contain the same kind of "thing." OK, we'll make an array of Animals, where Animal is a class we'll build. But what kind of animal is it, in the first element of the array, and the rest? And it matters, because every kind of animal eats something different and makes a different sound.
Enough preliminaries. Animal will be an abstract class, with abstract methods named Eat and MakeNoise. Then there will be a Dog class, a Cow class, and a Duck class. Each of these will provide its own implementation for Eat and MakeNoise. Just to make things interesting, we'll put a concrete method in Animal, called Breathe. All animals breathe. Maybe not exactly in the same way in all species, but little kids don't care about that. (Did you notice? You just saw polymorphism. Didn't hurt, did it?)
To keep things simple while we get the ideas down, the first two versions of this illustration will not involve user input.
Here's our abstract class Animal:
public abstract class Animal {
// All animals breathe, but we don't care about details.
// This method will be inherited unchanged.
public void breathe() {
System.out.println("Yes, relax, I'm alive and breathing.");
}
// All animals eat too, but we do care about details.
// This method will be implemented in subclasses.
public abstract void eat();
// Ditto comments on eat().
public abstract void makeNoise();
// See text on why this has to be abstract.
public abstract String getName();
} // end class Animal
Conceptually, there are no new ideas here. Just as with
Abstract1, we have a
concrete method; here we have three abstract methods. The reason they all
need to be abstract will become clear as we go along.Let's see
Dog:
public class Dog extends Animal{
private String Name = new String("Rover");
public String getName() {
return Name;
}
public void eat() {
System.out.println("I'm a dog, so I eat kibble. Yuck.");
}
public void makeNoise() {
System.out.println("Arf! Arf!");
}
} // end class Dog
Note the extends Animal in the class declaration. Animal is thus the superclass of Dog.
In this first version, every dog we create will be named Rover. (And we'll see
later that it's the same object, in Java terms, too.) We assign the name with:
private String Name = new String("Rover");
The first method is a getter for the string
Name:
public String getName() {
return Name;
}
This implements the abstract method of the same name in Animal. So why did it need to be abstract in Animal? Patience. Answer soon.
The Eat and MakeNoise methods in Dog are specific to a dog. (Or anyway to this dog.) It seems natural, I hope, that we would want to override the abstract methods in Animal to provide the behavior specific to our dog.
Of course another way of looking at it is that any abstract method that is not overridden somewhere down the inheritance chain is useless. Which may again raise the question of why we need the abstract methods at all. You're about to get your answer to that, as soon as we get to making our zoo.
In this first version, there is one dog named Rover, one cow named Clarabelle, and one duck named Donald. We'll get to the version where the user enters the choice of pet and its name just a little later.
Duck and Cow are quite similar, but let's see them anyway, and then talk about what they all do, with relation to their superclass, which in each case is Animal.
public class Duck extends Animal {
private String Name = new String("Donald");
public String getName() {
return Name;
}
public void eat() {
System.out.println("I'm a duck, but I don't know what I eat.");
}
public void makeNoise() {
System.out.println("Quack! Quack!");
}
} // end class Duck
public class Cow extends Animal {
private String Name = "Clarabelle";
public String getName() {
return Name;
}
public void eat() {
System.out.println("I'm a cow, so I eat hay.");
}
public void makeNoise() {
System.out.println("Mooooo!");
}
}
What these classes do is easily stated: they let us treat each of them as an Animal. Not just an animal, but an Animal. That is, we can make an array of Animals, put in instances of Dog, Duck, and Cow--and have these latter treated as Animals. Polymorphism!
Let's say it again: all the elements of an array must be of the same type. You can't have an array that contains mostly doubles, but also a few ints and a String or two. Likewise, you can't have an array that holds dogs, ducks, and cows. But you can have an array that holds Animals, where some elements hold Animals that are actually dogs, some hold ducks, and some hold cows. For this to work, of course we have to make our dogs, ducks, and cows "look like" Animals, in some sense. In what sense? Behold our zoo.
public class Zoo {
Dog d = new Dog();
Cow c = new Cow();
Duck dk = new Duck();
Animal[] animals;
// constructor
Zoo() {
animals = new Animal [3];
animals[0] = d;
animals[1] = dk;
animals[2] = c;
// Simplest version: no user input
for ( int i = 0; i < animals.length; i++ ) {
System.out.println("My name is " + animals[i].getName());
animals[i].makeNoise();
animals[i].eat();
animals[i].breathe();
System.out.println("");
} // end for
} // end Zoo constructor
} // end Zoo class
In this simplest first version, we simply instantiate one each
Dog,
Duck, and
Cow, giving the objects the
names d,
dk,
and c, respectively. Then we
create an array of Animals and
place these objects in it.But wait! you say. How can an array of Animals hold dogs, ducks, and cows??? Ah, I thought you would never ask: because each of them is a subclass of Animal. The dog says, "Well, of course I'm a dog. What do I look like, a cat? But I'm also an animal, right? Do you mind if I call myself an animal for the purposes of putting myself in a zoo along with other animals? You OK with that? Thank you very much."
Now (finally!) we can see why all those abstract classes in Animal had to be abstract. When we want to tell an animal to eat or make noise, we have to use a method that is known in the Animal class. That's exactly what we do in the for loop that runs through the zoo. (Never seen a for loop running through a zoo before? Neither have I, actually. But I digress.)
When the for loop comes to
animals[i].makeNoise();
for example, the run-time program looks to see what is in
animals[i]. The first time
through our loop here, that will turn out to be a
Dog, so the version of
makeNoise in the
Dog class will be invoked. The
makeNoise in
Animal was there only to be
implemented in a subclass, remember?
If you didn't understand abstract classes and inheritance as well as you now
do, you might look at that loop and say, "When I tell an
Animal to
makeNoise, it's going to
see an abstract class and say, 'Jeez, I don't know how to make noise in the
abstract,' and the program will die." But now you know that when the
for loop sees
animals[i].makeNoise();
it's just going to check what kind of animal
animals[i] is, and invoke
the animals[i] method
implemented
for that animal.
That's it! If you understand everything to here, you are ahead of a whole lot of students studying Java. If you don't, read it three more times, carefully. If you still don't understand it, send me mail at Dan@ccnyddm.com, explaining where you got lost. I'll try to help, and then rewrite as needed.
I left one small (?) detail for a postscript: to make this run, there has to be a main method somewhere. As we will frequently do, this is in what is sometimes called a driver class, which exists to do one thing: instantiate whatever gets the application going. Here's ours:
public class BuildSimpleZoo {
public static void main(String[] args) {
new Zoo();
}
} // end BuildSimpleZoo
And here's the output of the program:
My name is Rover Arf! Arf! I'm a dog, so I eat kibble. Yuck. Yes, relax, I'm alive and breathing. My name is Donald Quack! Quack! I'm a duck, but I don't know what I eat. Yes, relax, I'm alive and breathing. My name is Clarabelle Mooooo! I'm a cow, so I eat hay. Yes, relax, I'm alive and breathing.
(Small PS: You may be used to seeing the instantiation in
public static void main written as:
Zoo app = new Zoo();
This works, but the variable app
is never used, so you can skip the
Zoo app = in this case. It's nugatory. [Look it up.])
I can read minds, even over the Internet, and I know what you are thinking: "Smoke and mirrors! Hogwash! The compiler had all the information needed, at compile time, to set everything up to work right!" As a writer, it is now my obligation to prove that this works in conditions where the crucial decision are made at run time, and then you will have achieved Enlightenment, and say, "Ah, yes. Now I get polymorphism."
(Actually, it might be possible that a compiler could know enough to do everything at compile time, in this particular case. But I'll bet a whole lot it didn't do it that way, and you are now going to see a case where the necessary information simply isn't known until run time.)
Where do we start? Well, first, nothing at all changes about the Animal class. We are not changing the functionality of the program. The one concrete and two abstract methods in Animal are still needed, unchanged.
The classes Dog, Duck, and Cow are also unchanged. Everything new is in a modified zoo class, now called Zoo2. Here it is.
import java.util.Scanner;
public class Zoo2 {
Dog d = new Dog();
Cow c = new Cow();
Duck dk = new Duck();
Animal[] animals;
Scanner sc = new Scanner(System.in);
// constructor
Zoo2() {
animals = new Animal[5];
System.out.println("Please enter animal numbers for five animals.");
System.out.println("0 for dog, 1 for cow, 2 for duck.");
System.out.println("One line or separate lines; doesn't matter.");
// In this version we ask for exactly five animal numbers
// Not distinct, obviously, with only three kinds of animals.
int a = 0; // animal number
for (int i = 0; i < animals.length; i++) {
a = sc.nextInt();
switch (a) {
case 0:
animals[i] = d;
break;
case 1:
animals[i] = dk;
break;
case 2:
animals[i] = c;
break;
default:
System.out.println("Hey! 0, 1 or 2!");
} // end switch
} // end for
for (int i = 0; i < animals.length; i++) {
System.out.println("My name is " + animals[i].getName());
animals[i].makeNoise();
animals[i].eat();
animals[i].breathe();
// To show that there are only three objects here,
// and to demo toString on an object.
System.out.println(animals[i].toString());
System.out.println("");
} // end for
} // end constructor
} // end Zoo2 class
If you don't know what a Scanner is, don't worry about the details. Look it up
if you care, or wait until I write something about it. Which will probably be
never; I'm not writing tutorials on simple things you can look up in any text or
in the API at Sun. Or Google for it; there are tutorials on everything, if you
just dig a bit. Overview: a scanner gives us a relatively simple way to enter
data at a console. Would never ever be used in a modern GUI-based,
Web-based, application. But cool for checking out programs that focus on other
concepts. Just read the code. It does what it looks like it does. (A try/catch
block would definitely be a good idea; if the user types a letter, say, the
result will be a long stack trace and a program crash. We really try to avoid
that. But one thing at a time.) We instantiate one each Dog, Duck, and Cow, just as before. Peeking ahead, this does mean that however many dogs, say, the user specifies, they are are all going to be named Donald. And in fact they will all be the same object. Don't believe it? Just you wait. Don't think that makes a very interesting zoo? You're right; version three fixes that.
In the constructor we first declare an array named animals that holds Animal objects. I'm assuming you know basic Java well enough to read the first for loop and the switch statement. (If you don't know why the break statements are needed, then you need to go back and review Java basics.) The result of this loop is to populate the animals array with some combination of dogs, ducks, and cows.
The second for loop does the same job as in the first version, except that now the various methods are invoked on array elements.
There is one completely new feature here, off-topic actually, but something you should know about. The Java Virtual Machine (JVM) uses two types of storage, the heap and a stack. (Lots of stacks, in real life, one for each thread--but that's way ahead of the story.) We have no control over where the combination of the compiler and the JVM puts things in the heap, but (much) later on we may want to know the identification that was assigned to an object. This is done with hashing (remember data structures?), in a way that we'll look into later.
(NOTE 1: the word "heap" is used in computer science in two unrelated ways. Here, it's a term for the non-stack storage. In data structures it's a particular kind of binary tree. Sorry about that. Not my doing.)
If you want to know an object's identifying number you can ask to have it printed as a string. The class at the top of the hierarchy tree, Object (with a big Oh), has a handful of methods that are inherited by absolutely everything. In a great many cases these methods are overridden, but the top-level methods are there. It turns out that if we apply the toString method to a POJO (Plain Old Java Object), we get a hexadecimal number that is the hash code for the object.
(NOTE 2: As you surely remember from data structures, hashing can and usually does produce collisions, where two things hash to the same bucket; a collision resolution scheme finds what you want. This means that the hashcode for an object is not a unique identifier. We're reasonably safe here only because we have so few entries in the hashtable. See the tutorial "Why == Doesn't Equal equals()", and a future tutorial on Java collections.)
OK, let's try it. (Oh, the driver was changed to instantiate Zoo2 instead of Zoo, which is all it takes to call our new zoo class into action.)
Please enter animal numbers for five animals. 0 for dog, 1 for cow, 2 for duck. One line or separate lines; doesn't matter. 0 1 2 0 0 My name is Rover Arf! Arf! I'm a dog, so I eat kibble. Yuck. Yes, relax, I'm alive and breathing. abstractClasses.Dog@3e25a5 My name is Donald Quack! Quack! I'm a duck, but I don't know what I eat. Yes, relax, I'm alive and breathing. abstractClasses.Duck@19821f My name is Clarabelle Mooooo! I'm a cow, so I eat hay. Yes, relax, I'm alive and breathing. abstractClasses.Cow@addbf1 My name is Rover Arf! Arf! I'm a dog, so I eat kibble. Yuck. Yes, relax, I'm alive and breathing. abstractClasses.Dog@3e25a5 My name is Rover Arf! Arf! I'm a dog, so I eat kibble. Yuck. Yes, relax, I'm alive and breathing. abstractClasses.Dog@3e25a5
The last line for each animal gives the JVM heap identification. The first
one is:
abstractClasses.Dog@3e25a5
This is enough for the JVM to retrieve the object. (There will NOT be
a tutorial on how the JVM works.) The
3e25a5 is the hexadecimal form
of the hashed identification of the object.
abstractClasses
is the package (not shown in code here) and of course
Dog is the class.
For our immediate purposes the interesting thing is that all three Dog objects that were created at the user's request are the same object: note the 3e25a5 value for each. This should make complete sense: we instantiated one dog, one only, and put it in the array three times.
Boring. Let's make a more interesting zoo.
I'm running low on time for today, and maybe you're getting tired of the discursive style here anyway. Here's the final version, with condensed explanations.
The Animal class remains unchanged. We now want to let the user specify any combination of any number of dogs, cows, and/or ducks, each with a name. The first change needed is in the Dog, Duck, and Cow classes: they need to have constructors that set the value of the private instance variable Name. Here's Dog:
package abstractClasses2;
public class Dog extends Animal{
private String Name;
// Constructor
Dog(String n) {
Name = n;
}
public String getName() {
return Name;
}
public void eat() {
System.out.println("I'm a dog, so I eat kibble. Yuck.");
}
public void makeNoise() {
System.out.println("Arf! Arf!");
}
} // end class Dog
Nothing new here except the constructor. Later, in Zoo3, when we instantiate a Dog object, we'll provide a name. (To be precise, we'll plug in the name the user provided.) Cow and Duck have the same change. I left the package name showing because otherwise bright students would flog me over something that wouldn't work, as shown without the package info.
Now for Zoo3:
package abstractClasses2;
import java.util.Scanner;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
public class Zoo3 {
// No longer needed. We construct each dog, cow, or duck
// "on demand," as the user enters data.
// _Now_ will you believe the compiler couldn't know???
// Dog d = new Dog();
// Cow c = new Cow();
// Duck dk = new Duck();
List animals = new ArrayList();
Scanner sc = new Scanner(System.in);
// constructor
Zoo3() {
// In this version we ask for any number of animals
// These animals will all be distinct objects,
// although user could enter same name more than once.
System.out.println("Please enter animal numbers and names for any number of animals.");
System.out.println("0 for dog, 1 for cow, 2 for duck.");
System.out.println("One line or separate lines; doesn't matter.");
System.out.println("Sample input on one line:");
System.out.println("0 Fido 0 Mutt 2 Bossy 1 Daffy");
System.out.println("Ctrl-Z to end input.");
System.out.println("");
int a = 0; // animal number: 0 = dog, etc.
String animalName; // Entered by user
while (sc.hasNext()) {
a = sc.nextInt();
animalName = sc.next();
switch (a) {
case 0:
Dog d = new Dog(animalName);
animals.add( d );
break;
case 1:
Cow c = new Cow(animalName);
animals.add( c );
break;
case 2:
Duck dk = new Duck(animalName);
animals.add( dk );
break;
default:
System.out.println("Hey! 0, 1 or 2!");
} // end switch
} // end for
Iterator i = animals.iterator();
while (i.hasNext()) {
Animal an = (Animal) i.next();
System.out.println("My name is " + an.getName());
an.makeNoise();
an.eat();
an.breathe();
// To prove that these are now all distinct objects
System.out.println(an.toString());
System.out.println("");
} // end while
System.out.println("There are " + animals.size() + " of us.");
} // end constructor
} // end class Zoo3
New features, in quick summary.Here is the output when a user entered:
0 Harold 1 Bossy 2 Donald 2
Daffy 2 Daffy
One dog (my best-dog-ever Harold, gone 14 years now, sob), one cow, and three ducks, the last two of which are both named Daffy.
Please enter animal numbers and names for any number of animals.
0 for dog, 1 for cow, 2 for duck.
One line or separate lines; doesn't matter.
Sample input on one line:
0 Fido 0 Mutt 2 Bossy 1 Daffy
Ctrl-Z to end input.
0 Harold 1 Bossy 2 Donald 2 Daffy 2 Daffy
My name is Harold
Arf! Arf!
I'm a dog, so I eat kibble. Yuck.
Yes, relax, I'm alive and breathing.
My name is Bossy
Mooooo!
I'm a cow, so I eat hay.
Yes, relax, I'm alive and breathing.
My name is Donald
Quack! Quack!
I'm a duck, but I don't know what I eat.
Yes, relax, I'm alive and breathing.
My name is Daffy
Quack! Quack!
I'm a duck, but I don't know what I eat.
Yes, relax, I'm alive and breathing.
My name is Daffy
Quack! Quack!
I'm a duck, but I don't know what I eat.
Yes, relax, I'm alive and breathing.
There are 5 of us.
The last three objects are all instances of the class
Duck, but the
toString
method shows that they are three separate things on the Heap.
A real app would do some validation, obviously, and not allow two ducks named Daffy, unless for some reason that made sense and the user certified the intention.
Added 8.29.2005, after a delightful morning yesterday with my buddy Willy Farrell of IBM. He clarified my thinking with this summary: you implement an abstract class; you override a concrete class. In an example of simultaneous inspiration, we both thought of adding a fish to the zoo. Fish need oxygen, but they get it from the water with gills, and that isn't called breathing. So it would be reasonable to want to override the breathe().
For today, I'm just going to show the Fish class and the output. Other than adding a fourth case to the switch statement in Zoo, nothing else has to be changed. Ah, which is part of the point, right?
package abstractClasses3;
public class Fish extends Animal{
private String Name;
// Constructor
Fish(String n) {
Name = n;
}
public String getName() {
return Name;
}
// Implement abstract class in superclass
public void eat() {
System.out.println("I'm a fish, so I eat smaller fish.");
}
// Implement abstract class in superclass
public void makeNoise() {
System.out.println("I don't exactly have voice. I'm not a whale.");
}
// Override concrete class in superclass
public void breathe() {
System.out.println("I get oxygen, but I don't call it breathing.");
}
} // end class Fish
After making the minor changes in Zoo, the program produced this output for the input shown:
2 Donald 3 Goldie 0 Adam 0 Hadem 1 Redtop My name is Donald Quack! Quack! I'm a duck, but I don't know what I eat. Yes, relax, I'm alive and breathing. abstractClasses3.Duck@19821f My name is Goldie I don't exactly have a voice. I'm not a whale. I'm a fish, so I eat smaller fish. I get oxygen, but I don't call it breathing. abstractClasses3.Fish@addbf1 My name is Adam Arf! Arf! I'm a dog, so I eat kibble. Yuck. Yes, relax, I'm alive and breathing. abstractClasses3.Dog@42e816 My name is Hadem Arf! Arf! I'm a dog, so I eat kibble. Yuck. Yes, relax, I'm alive and breathing. abstractClasses3.Dog@9304b1 My name is Redtop Mooooo! I'm a cow, so I eat hay. Yes, relax, I'm alive and breathing. abstractClasses3.Cow@190d11 There are 5 of us.
"That's it? Cool explanation, but what's the point? I don't do any programs involving dogs, ducks, or cows. Or fish."
Point well taken. We shall return to this. There is a lot more to the story, interfaces especially. Stay tuned.