Everyone agrees that, for example, a Dog is an Animal, biologically speaking. In Java, just as in biology, this is called “inheritance.”
Without giving too many specifics, let’s say we want for the Dog class to
receive some behaviors from Animal, or we want to slightly tweak Animal’s
behavior as it comes across in a Dog.
Before we go any further, it’s important to realize the obvious fact that all
Dogs are Animals, but not necessarily all Animals are Dogs. This matters when
we are talking about inheritance.
Let’s say an Animal has:
- a name—this is a String
- an age—this is an integer
- a type—this is a String
- a color—this is a String
- a munch(int) method—this represents that the Animal can eat, and takes in a number; “much” is then printed that many times, each time on a new line
- and a makeSound(int) method—this represents that the animal can make a sound, and takes in the number of times that sound should happen; the default sound is “moo”
We like all that in Animal, but obviously, Dogs don’t “moo”,
they “woof.” Through inheritance—linking Animal and Dog in a way that gives Dog
Animal’s properties for free—we can then say “in the special case that an
Animal is a Dog, change the makeSound() behavior to ‘woof’ from ‘moo.’”
We imply the “is a” relationship (as in, “a Dog is an Animal”)
with the extends keyword in Java.
- Implement Animal
- Then start working on Dog, declared as public class Dog extends Animal
- Then, when you create a Dog object—let’s say, by Dog lucky = new Dog();, you can call lucky.makeSound(3); and you will see
Moo
Moo
Moo
But Lucky (my actual dog) is a Golden Retriever, not a cow, so he does not moo.
Inside Dog, we can then change this behavior by declaring the following method:
one that has the same signature as makeSound() did in Animal, but appears exclusively
in Dog.
Inside that, we can print “Woof” instead, and when lucky.makeSound(n) is
called, we will see many “Woof”s instead of many “Moo”s.
There are two things we can do with those method signatures:
overload and override.
- Overloading is when you use the same name with different parameters, like two versions of an add() method, one that takes in ints and another that takes in doubles
- Overriding, on the other hand, is when Dog extends Animal and Dog’s makeSound() says “Woof” when the generic Animal makeSound() says “Moo”—you change the behavior in the more specific class to fit your needs, from what it was in the more generic.
Since you get access to all the public fields and methods of an object upon inheritance from that—this is why Dog objects can, for free, get the default behavior of makeSound() and the other Animal fields and methods—every object ever has several common methods before anything is ever explicitly defined.
Two of the most common methods from Object are:
- hashCode(): which feeds the object in question (that is, one particular FluffyUnicorn) through a function that is only easily computable in one direction (and nearly impossible in the other, with the caveat that checking the original direction’s result is just as easy), to get a unique identifier for that object
- toString(): which finds some way—by default, with where the object is in memory—of converting the object to a String for visual representation
If I have a CollegeStudent who extends Student (explicitly,
and, of course, Object, implicitly), where CollegeStudent samJones is in memory
is of next to no value to anyone. I would much rather know what samJones.major,
samJones.year, samJones.college, samJones.GPA, etc., are. So, many times,
people override the toString method to something much more useful, like a “{“
plus something like “GPA:“ + samJones.GPA, and then comma-separating the name
of the property and its value, before closing out with a matching “}”
like, for instance
@Override
public String
toString() {
return
"CollegeStudent{" +
"name='" + name + '\'' +
", idNumber=" + idNumber +
", age=" + age +
", creditHours=" + creditHours +
", points=" + points +
", GPA=" + String.format("%.2f", getGPA()) +
", courses=" + courses +
'}';
}
which might produce an output something like CollegeStudent{name='Alice Smith',
idNumber=12345, age=20, creditHours=30, points=90, GPA=3.00, courses=[Math 101,
English 201]}
The @Override line is called an “annotation.” They’re good practice but omitting
them is not an error that will trip the compiler. They simply tell the compiler
that some default behavior is being replaced by more desirable behavior.
Overloading and overriding are dealt with by the compiler at different times:
overloading at compile-time and overriding only at runtime.
Extending a parent class into a child (in “Dog extends
Animal,” Animal is the parent class of Dog, since all Dogs are Animals but not
vice versa) gives you access to anything public in the parent class,
for free. It also means that, for instance
Animal myAnimal = new Dog();
Is valid, since every Dog is an Animal, but that
Dog myDog = new Animal() is not allowed, since not every
Animal is guaranteed to be a Dog.
This (correct usage, like Animal myAnimal = new Dog()) is an example of polymorphism in
Java, which is a really important concept. I cannot stress enough how critical
it is to understand this relationship, and why the first of these two last code
snippets works, and why the second one does not.
Finally, let’s say I have a class Percheron. There are some properties in Horse
I would like to get, and some other particular properties in DraftHorse I would
like to get. In some languages, you could certainly say
public class Percheron extends DraftHorse, Horse
and be totally fine.
However, this is called “multiple inheritance” and, except for the fact that
everything implicitly inherits from Object, this (inheriting from both Horse
and DraftHorse) is not allowed at all in Java. You must
pick whether Percheron should inherit from Horse or DraftHorse, and then if it extends
any of them, it must extend at most one of them.
No comments:
Post a Comment