Core Java 2 - Volume I - Fundamentals, 5th Ed.
Inheritance
Abstract Classes | Object: the Cosmic Superclass | Array Lists | The Class class | Reflection | Design Hints
Extending Classes
Inheritance is one of the fundamental concepts of Object Oriented Programming. It's also very simple to understand and implement. All it entails is taking the functionality of an existing class, adding some things to it, and having a unique class with all the benefits of the first class and more. Sounds easy and is.
For example, let's say I have a class called Mustang. It beeps, you can drive it, it guzzles gas. Now lets say I want a class called custom_Mustang that has all the functionality of the already existing class and a few little added extras like more horsepower and better tires. All I do is inherit the Mustang class into my new custom_Mustang class and add the new features!
The book uses the example of a manager and an employee. They speak to the "is a" relationship. A manager "is a" employee so it's a good candidate for inheritance.
Inheritance in Java has some subtle differences from inheritance in C++. Instead of the : token you use the
extends // followed by class you want to inherit
keyword. All inheritance in Java is public (as opposed to private and protected in C++) and a subclass (the class doing the inheriting) does not automatically have access to the private fields of a superclass (the class being inherited from).
You only define differences in a subclass. You can override a method to make it work differently than the method in the superclass and you can add new methods or data.
You need to explicitly call data and methods from the superclass using the keyword super. You see this in the following snippet of code...
package chap5;
import java.util.*;
public class ManagerTest
{
public static void main(String[] args)
{
// construct a Manager object
Manager boss = new Manager("Carl Craker",80000,1987,12,15);
boss.setBonus(5000);
Employee[] staff = new Employee[3];
// fill the staff array with your boss and two lackys
staff[0] = boss;
staff[1] = new Employee("HArry Hacker", 50000, 1989,10,1);
staff[2] = new Employee("Tommy Tester", 40000, 1990,3,15);
// Now Print out their info
for ( int i = 0; i < staff.length; i++ )
{
Employee e = staff[i];
System.out.println("name = " + e.getName() + " salary = " + e.getSalary());
}
}// eom main
}// eoc ManagerTest
class Employee
{
public Employee(String n, double s, int year, int month, int day)
{
name = n;
salary = s;
GregorianCalendar calendar
= new GregorianCalendar(year, month-1, day);
// remember GC uses 0 for January...
hireDay = calendar.getTime();
}
public String getName()
{
return name;
}
public double getSalary()
{
return salary;
}
public Date getHireDay()
{
return hireDay;
}
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent * 100;
salary += raise;
}
private String name;
private double salary;
private Date hireDay;
} // EOC Employee
class Manager extends Employee
{
/**
@param n the employees name
@param s the salary
@param year the hire day
@param month the hire month
@param day the hire day
*/
public Manager(String n, double s, int year, int month, int day)
{
super(n, s, year, month, day);
bonus = 0;
}
public double getSalary()
{
double sal = super.getSalary() + bonus;
return sal;
}
public void setBonus(double b)
{
bonus = b;
}
private double bonus;
} // EOC Manager
As you can see, Manager inherits from Employee and adds new functionality ( the addition of a bonus). Note how the constructor of the superclass is called with super and how the superclasses methods are called explicitly to avoid a nasty recursive loop (keeps calling itself). In the above piece of code pretty much lies the basics of inheritance.
Multiple inheritance is not supported (unlike C++), but Interfaces will return much of their functionality later.
Note in the above code how the variable e in the print loop can refer to employee objects and manager objects. This is big. It's polymorphism and is a big deal in OOP. To be able to refer to various types with the same variable is VERY useful (wait until you fill a vector with 8 different types).
Polymorphism is possible due to dynamic binding. I'm not going to go into this here, but the book has a great section describing what the Java system does to implement dynamic binding and all the C++ folk already know about V Tables and such. We spend years waiting for dynamic binding and then when we get it we're told to use it sparingly because V tables chew up RAM. Go figure. Anyway, use static binding when you can, but right now you're learning so live it up.
You can prevent inheritance by making a class or a method final.
Casting - just remember that you can only cast a sub to it's super and not vice versa. You also need the correct object type to use a subs full potential. In the above example you could not have used managers full capacity when using the e variable (it's type employee) but you could when it was "boss" because the object variable was type manager. This is mostly common sense, but be wary...
People get freaked by these. Don't. They're just place holders for functions and data you're going to use later. If you have an abstract class A, and then have classes B and C that inherit from it, you can then create an array of objects of type A and keep both B's and C's in there. That's all it's about.
You cannot instantiate an abstract class! They're just what they say they are, Abstract. They're Meta classes to help you define the classes that inherit from them, that's all...
You can keep real data and methods in an abstract class, but because they cannot be turned into a real object (Pinocchio classes) you can only use these in subclasses.
C++ folk know abstract methods as pure virtual functions. ( foo() = 0;)
Enough talk, let's bang out some code...
package chap5;
import java.text.*;
public class PersonTest
{
public static void main(String[] args)
{
Person[] people = new Person[2];
// give each object a different type...
people[0] = new Worker("Jimmy Jamoni", 85000);
people[1] = new Student("Bobby Hey", "Comp Sci");
// now print out the names and descriptions
for (int i = 0; i < people.length; i++ )
{
Person p = people[i];
System.out.println("name= " + p.getName() + " ** info = " + p.getDescription());
}
}// eom main
} // eoc PersonTest
abstract class Person
{
public Person(String n)
{
name = n;
}
public abstract String getDescription();
public String getName()
{
return name;
}
private String name;
} // eoc Person
class Worker extends Person
{
public Worker(String n, double s)
{
super(n);
salary = s;
}
public double getSalary()
{
return salary;
}
public String getDescription()
{
NumberFormat formatter = NumberFormat.getCurrencyInstance();
return "a worker with a salary of " + formatter.format(salary);
}
private double salary;
} // eoc Worker
class Student extends Person
{
/**
@param n the student's name
@param m the student's major
*/
public Student(String n, String m)
{
super(n); // passes the name to Person
major = m;
}
public String getDescription()
{
return "Student with a Major of " + major;
}
private String major;
} // eoc Student
Some of the class names have been changed to avoid conflicts in my chap 5 package. Don't go nuts if you have the book.
See what I mean? Easy. Both Worker and Student use Person as a blueprint, that's all. Don't get freaked by the kooky comments, the books authors are trying to get you ready to use the javadoc utility in the future so I figured I'd play along while recoding and testing.
Protected Access - You can declare data and methods as protected if you want sub classes to access the data and methods directly, but only other subclasses can access each others data, not objects of the super class. That's why it's not public ;->. This is the default when you learn C++ and it's why the explicit calls to the super class seem strange at first. C++ acts the same way, but classic C++ training avoids private super class data hiding at first to get the point of inheritance across...
Java's protected security is a little lax though, as protected data can be accessed by any other method in the package, not just sub classes. Some grist for the Java 3 mill...
Object : The Cosmic Superclass
Now that you understand inheritance it's time to let you in on a dirty java secret. Every class in Java inherits from a cosmic superclass class called Object. You don't declare your class...
public Myclass extends Object // <-- case counts, capitalize Object
though, it just happens automatically. The book calls it the ultimate ancestor. Anyway, this is important for two reasons. First, you can grab a variable of type Object and put ANYTHING in it (as long as it's a class object, read on). Pretty cool. Secondly, it gives all classes a set of common methods and data they inherit from the Object class. We're gonna look at two of them and then bang out some code...
The equals method tells you if your object is equal to another. The system will tell you if it's in the same memory location with the "==" relational operator, but we want to know if two separate objects are of the same class and hold the same data, or scientifically, are their states the same? This method butters that biscuit, but you have to override the inherited method to work with your class.. Not hard, as you'll see.
Now the book goes nuts with a full page note on what it means to be equal. reflexive, symmetric, transitive, blah blah ow my brain hurts. The Java Language Spec tells you all of this and more so buy the book or hit the web (it's actually a very interesting review of what equality is and should be met, beaten, and absorbed)
This method is a debugger's dream. It just spits back the current state of the objects variables and what package and class it is. This is great for debugging those nasty runtime errors. You use the format of class then variables and just send them to whatever called the function as a String.
This is the kicker. If you just concatenate your variable name with a string the compiler calls the toString() function automatically.... It's always a good idea to write a toString() function for ALL of your classes. The guys who have to debug your stuff later will love you.
Here's the goods, check it out
package chap5;
import java.util.*;
public class EqualsTest
{
public static void main(String[] args)
{
TheEmployee alice1 = new TheEmployee("Alice Adams",75000,1987,12,15);
TheEmployee alice2 = alice1;
TheEmployee alice3 = new TheEmployee("Alice Adams",75000,1987,12,15);
TheEmployee bob = new TheEmployee("Bob Brandson",50000,1989,10,1);
System.out.println("alice1 == alice2: " + (alice1 == alice2));
System.out.println("alice1 == alice3: " + (alice1 == alice3));
System.out.println("alice1.equals(alice3): " + (alice1.equals(alice3)));
System.out.println("alice1.equals(bob): " + (alice1.equals(bob)));
System.out.println("bob.toString(): " + bob);
// test the subclass now
TheManager carl = new TheManager("Carl Cracker", 80000, 1987, 12, 15);
TheManager boss = new TheManager("Carl Cracker", 80000, 1987, 12, 15);
boss.setBonus(5000);
System.out.println("boss.toString(): " + boss);
System.out.println("carl.equals(boss): " + carl.equals(boss));
}// eom main
}// eoc EqualsTest
class TheEmployee
{
public TheEmployee(String n, double s, int year, int month, int day)
{
name = n;
salary = s;
GregorianCalendar calendar
= new GregorianCalendar(year, month-1, day);
// remember GC uses 0 for January...
hireDay = calendar.getTime();
}
public String getName()
{
return name;
}
public double getSalary()
{
return salary;
}
public Date getHireDay()
{
return hireDay;
}
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent * 100;
salary += raise;
}
public boolean equals(Object otherObject)
{
// a quick test to see if they're identical
if (this==otherObject) return true;
// must return false if explicit parameter is null
if (otherObject == null) return false;
// if the classes don't match how can they be equal?
if (getClass() != otherObject.getClass()) return false;
// now we know other obj is a non null Employee, cast it so we can use it
TheEmployee other = (TheEmployee)otherObject;
// test whether the fields have identical values
return name.equals(other.name) && salary == other.salary && hireDay.equals(other.hireDay);
}
public String toString()
{
return getClass().getName()
+ "[name= " + name
+ " ,salary= " + salary
+ " ,hireDay= " + hireDay
+ " ]";
}
private String name;
private double salary;
private Date hireDay;
} // EOC Employee
class TheManager extends TheEmployee
{
public TheManager(String n, double s, int year, int month, int day)
{
super(n, s, year, month, day);
bonus = 0;
}
public double getSalary()
{
double sal = super.getSalary() + bonus;
return sal;
}
public void setBonus(double b)
{
bonus = b;
}
public boolean equals(Object otherObject)
{
// check if same class and super data good
if (!super.equals(otherObject)) return false;
// now cast it to check the added data
TheManager other = (TheManager) otherObject;
// check the bonus var for equality
return bonus == other.bonus;
}
public String toString()
{
return super.toString()
+ "[bonus= " + bonus + "]";
}
private double bonus;
} // EOC TheManager
Again, lotsa renamed classes to avoid conflicts...
The Output
alice1 == alice2: true alice1 == alice3: false alice1.equals(alice3): true alice1.equals(bob): false bob.toString(): chap5.TheEmployee[name= Bob Brandson ,salary= 50000.0 ,hireDay= Sun Oct 01 00:00:00 EDT 1989 ] boss.toString(): chap5.TheManager[name= Carl Cracker ,salary= 80000.0 ,hireDay= Tue Dec 15 00:00:00 EST 1987][bonus=5000.0] carl.equals(boss): false
You see how the second test comes back false even though they're the same!
Generic Programming - Like I said above, you can take any class object and stick it in an Object variable. But remember that this only hold for Class types! Primitives cannot be stored in an Object variable (we overcome this later with wrappers) so be careful. With Generic programming you can pass an array of Employee types into a method looking for Object types and the code will work. Java cleverly remembers the original type at runtime and prevents any incompatibilities (like storing a Date in an Employee array, can't give a Date a raise can you?)
This section reviews a very helpful Java class called ArrayList. It's in the java.util package and lets you create an array of any size at runtime (dynamic arrays used to be a big hassle, not anymore), add to it, change it, and even trim it to size when you're sure you are done with it. This is a result of generic programming as you can add whatever class type you desire. I'm not going to go into crazy detail here, check out the API docs and look at this piece of code.
package chap5;
import java.util.*;
public class ArrayListTest
{
public static void main(String[] args)
{
//fill a staff array with three MyEmployee objects
ArrayList staff = new ArrayList();
staff.add(new MyEmployee("Carl Cracker", 75000,1987,12,15));
staff.add(new MyEmployee("Harry Hacker", 50000,1989,10,1));
staff.add(new MyEmployee("Tony Tester", 40000,1990,3,15));
// raise everyone's salary by 5%
for (int i = 0; i< staff.size(); i++ )
{
MyEmployee e = (MyEmployee)staff.get(i);
e.raiseSalary(5);
}
// Now print out the goods
for (int i = 0; i< staff.size(); i++ )
{
MyEmployee e = (MyEmployee)staff.get(i);
System.out.println("Name= " + e.getName() + " salary= " + e.getSalary()
+ " hireday= " + e.getHireDay() );
}
}// eom main
}// eoc ArrayListTest
class MyEmployee
{
public MyEmployee(String n, double s, int year, int month, int day)
{
name = n;
salary = s;
GregorianCalendar calendar = new GregorianCalendar(year, month-1,day);
// gregorian uses 0 for Jan
hireDay = calendar.getTime();
}
public String getName()
{
return name;
}
public double getSalary()
{
return salary;
}
public Date getHireDay()
{
return hireDay;
}
public void raiseSalary(double byPercent)
{
double raise = salary * (byPercent / 100);
salary += raise;
}
private String name;
private double salary;
private Date hireDay;
}// eoc MyEmployee
The Output?
Name= Carl Cracker salary= 78750.0 hireday= Tue Dec 15 00:00:00 EST 1987 Name= Harry Hacker salary= 52500.0 hireday= Sun Oct 01 00:00:00 EDT 1989 Name= Tony Tester salary= 42000.0 hireday= Thu Mar 15 00:00:00 EST 1990
Works pretty good, eh?
Object Wrappers - A wrapper is nothing but a piece of code that makes something look like it isn't what it is. Huh? Put simply, they are classes that "wrap" around other types and make them usable for your purposes. The most common use is for primitives. The classes Integer, Long, Float, Double <-- (note the caps, difference between a keyword and a class name) are there so you can wrap a primitive and do things (like store them in an Object array) that you couldn't do otherwise. There's a wrapper for pretty much everything. Hit the online API and start reading!
list.add(new Double(3.14)); // works like gangbusters
You just put a primitive in an Object array.
You commonly come into contact with these wrapper classes because the people who developed Java thought they were the logical place to put static methods for parsing and casting. Remember this?
int x = Integer.parseInt(MyString);
Wrappers don't solve that nasty "pass by value" problem if your thinking about passing a wrapper into a method to increment a value or do a swap. The values inside a wrapper class are immutable.
Things are about to get hairy. If you're new to programming or you want to learn the rudiments of Java quickly than you can safely skip the rest of this chapter review and go on to Interfaces and Inner Classes. But, if you want to be a systems programmer and, as most coders do, have the attitude that if it's under the hood you want to know about it and how it works, then stick around.
Now, say your writing a tool for a programmer or a piece of system code for a UNIX system and you need to know information about a class but won't know what class until runtime? The Object class has a method called getClass() that has a return type of (confusingly) type Class. The resulting object contains mountains of information and allows you to find out anything about the class who's object was passed to getClass(). First you make the call....
Employee e; ... Class cl = e.getClass();
Voila! Now you have a Class object. You can also use the Class method forName() or a neat little trick that the Java boys threw in for shorthand. Any type in Java has a cooresponding Class object. You access it by calling .class
Class c1 = Employee.class;
(The forName() method is examined in detail in the book but I'm glossing over it here because I don't want to go nuts, as the authors did at this point, on Exceptions and catching them)
Now, Class object in hand, we are ready for reflection. What is it? It's very simple as usual. Any program or code that has the ability to analyze the capabilities of classes is called reflective. Easy enough. By using the package java.lang.reflect we can use a whole slew of methods to discover things about classes and objects.
Three classes in the package, Field, Method, and Constructor, give you all the info you need from your Class object...
Check out the following code that uses these methods to analyze the class of your choice....(cut, paste and try it!)
import java.lang.reflect.*;
import javax.swing.*;
public class ReflectionTest
{
public static void main(String[] args)
{
// read class name from command line args or user input
String name;
if (args.length > 0)
name = args[0];
else
name = JOptionPane.showInputDialog
("Class name (e.g. java.util.Date): ");
try
{
// print class name and superclass name (if != Object)
Class cl = Class.forName(name);
Class supercl = cl.getSuperclass();
System.out.print("class " + name);
if (supercl != null && supercl != Object.class)
System.out.print(" extends " + supercl.getName());
System.out.print("\n{\n");
printConstructors(cl);
System.out.println();
printMethods(cl);
System.out.println();
printFields(cl);
System.out.println("}");
}
catch(ClassNotFoundException e) { e.printStackTrace(); }
System.exit(0);
}
/**
Prints all constructors of a class
@param cl a class
*/
public static void printConstructors(Class cl)
{
Constructor[] constructors = cl.getDeclaredConstructors();
for (int i = 0; i < constructors.length; i++)
{
Constructor c = constructors[i];
String name = c.getName();
System.out.print(Modifier.toString(c.getModifiers()));
System.out.print(" " + name + "(");
// print parameter types
Class[] paramTypes = c.getParameterTypes();
for (int j = 0; j < paramTypes.length; j++)
{
if (j > 0) System.out.print(", ");
System.out.print(paramTypes[j].getName());
}
System.out.println(");");
}
}
/**
Prints all methods of a class
@param cl a class
*/
public static void printMethods(Class cl)
{
Method[] methods = cl.getDeclaredMethods();
for (int i = 0; i < methods.length; i++)
{
Method m = methods[i];
Class retType = m.getReturnType();
String name = m.getName();
// print modifiers, return type and method name
System.out.print(Modifier.toString(m.getModifiers()));
System.out.print(" " + retType.getName() + " " + name
+ "(");
// print parameter types
Class[] paramTypes = m.getParameterTypes();
for (int j = 0; j < paramTypes.length; j++)
{
if (j > 0) System.out.print(", ");
System.out.print(paramTypes[j].getName());
}
System.out.println(");");
}
}
/**
Prints all fields of a class
@param cl a class
*/
public static void printFields(Class cl)
{
Field[] fields = cl.getDeclaredFields();
for (int i = 0; i < fields.length; i++)
{
Field f = fields[i];
Class type = f.getType();
String name = f.getName();
System.out.print(Modifier.toString(f.getModifiers()));
System.out.println(" " + type.getName() + " " + name
+ ";");
}
}
}
Sticklers to detail will see that I just pasted this example from the book code and didn't recode it. Gettin lazy I guess.
See how the Class object is used to list everything about the class? That's just a class though. How do you examine an object of this class? Why would I want to when I can just access it normally? Well, what if you didn't know the class would exist at compile time? Reflection handles it.
There is a get() method in the Field class that, when given an object, returns the data. Amazing huh?
Employee harry = new Employee("Harry Hacker",35000,1989,2,5);
Class c1 = harry.getClass();
// the class object that represents Employee
Field f = cl.getField("name");
// the name of the field in the Employee class
Object v = f.get(harry);
// grabs the field named "name" from any object you give it!
// in this instance the String "Harry Hacker"
...
It gets better. What if the field was private? Java's reflection package adheres to security as best as it can but, get ready, you can reset it and get any info you want! The next time some wise guy tells you that you can't get to his private data in a class you can give him a hardy laugh (actually, there is a way to prevent it, but I ain't tellin... well, maybe later. Keep reading). You can use the setAccessable() method to turn private data into something you can get to. (this method is actually in the class AccessibleObject which Field, Method and Constructor all inherit from). It's for debuggers and people in the know, like you.
Using this there is nothing you can't find out about an object you didn't even know about at compile time.
The book proceeds to go nuts and shows you how to write generic equals() and toString() methods for any class using reflective methods. They also show you how to dynamically grow an array using a special array class in the reflect package. I'll leave these things as a good reason to buy the book and move on. Neat pieces of code though.
Function Pointers? - For those of you out there who know C++, Java does not support function pointers and says they are dangerous and error-prone. But what if you want them anyway? There's no pointer to a function, but you can pass a function into another function to be run by passing in the type Method and using one of it's methods called invoke(). This way if you want a method to take some numbers and a method to run on them you can pull it off. Again, I'm not going to list the code here, I just wanted you to know that you can do it and the reflection package makes a back door way of pulling it off possible.
The book lists some handy tips on using Inheritance in your programs. Get the book for the gory details.
Place common operations and fields in Super classes.
Don't use protected fields, make them private
Use inheritance to model an "is-a" relationship.
Don't use inheritance unless ALL the methods make sense.
Use polymorphism, not type info, to implement common concepts.
Don't overuse reflection. (It's hairy and better ways to skin a cat are usually available.)
That chapter was a beast, eh? If you're comfy with all the previous topics then go on to Interfaces and Inner Classes!
Send mail to lars@sorcon.com with
questions or comments about this web site.
Copyright © 2004 Sorensen Consulting