CS 3230 - Chap 5 Notes

Inheritance and Reflection

Inheritance (p.145)

What it is: (p.145) The ability to create new classes from existing classes.

What it buys you: Achieves code reuse. (Appeals to our sense of laziness.)

Terms and concepts (p.146 middle)

super class: Also called a base class or a parent class. If class B inherits from A, A is the super class. (Java has a super keyword that allows you to get at the parent.

subclass: Also called a derived class or a child class. If class B inherits from A, B is the subclasss. Alternatively, this word is often used as a verb meaning "to inherit".

ancestor: Distant parent class, i.e. grandparent.

descendant: Distant child class, i.e. grandchild.

hierarchy: A family of related classes. Picture on p.152. (Just in case you've never seen one before.)

The is-a relationship revisited: A reference to a parent class can hold a reference to any descendant because they share an is-a relationship. (More on this later.)

Overloading vs. Overriding

OO-languages give you the ability to make more than one implementation of a method with the same name (this is difficult if not impossible in procedural languages). There are two different ways to do this:

overloading: Making multiple definitions of a method within a same class with the same name but different signatures.

overriding: Often, a perent class will implement a method with a given signature and a child class will provide a new implementation of the same method with the same signature. This is called overriding.

Here's a table that sums it up:

method overloading overriding
name same same
signature different same
scope one class hierarchy of classes

How you do it in Java: extends (p.146 top)

Use the extends keyword, like so:

 class MyClass extends AnotherClass { ...body... } 

Note that there are a couple of differences between the way inheritance is done in Java and the way it's done in C++:

Visibility Modifiers (p.163 top)

A visibility modifier (or access modifier) is a keyword that limits the scope and usability of a class member. C++ has 3, Java has 4, listed here in order from broadest access to narrowest access:

  1. public: Visible to everyone, everywhere, anytime.
  2. protected: Visible to all classes in the same package and all subclasses of this class. (Different from C++)
  3. naked: (By "naked", I mean no visibility modifier is specified.) Visible to all classes in the same package.
  4. private: Visible only to the class.

Accessing the parent: super (p.147 bottom)

The this keyword, usable within any instance method (not static methods) is a reference to the current instance of a class. Similarly, the super keyword is a reference to the subclass' parent. Examples:

super.numTries++; // accessing a public, protected (or naked) property
super.getAmount() // accessing a public, protected (or naked) method

Calling a parent's method that is overriden in the child: If you want to access the parent class' implementation in the child you can do it by calling super.method() (as above).

Calling a parent's constructor in a child's constructor: When you are implementing the constructor in a child class, you may want to call the parent class' constructor first to set it up. In java, you can do it with the super keyword, like so:

class Employee {
	public Employee(String name, double salary) {
		this.name = name;
		this.salary = salary;
	}
	...
	String name;
	double salary;
}

class Salesman extends Employee {
	public Salesman(String name, double salary, double commission) {
		super(name, salary);
		this.commission = commission;
	}
	...
	double commission;
}

Halting inheritance (p.155)

Halting inheritance of methods: Put the keyword final in the method declaration and it will not be inherited.

Halting derivation: To prevent child classes from being made from a given class, put the keyword final in the class declaration.

Abstract classes (p.158)

When designing a class hierarchy, you may want to have a class that serves as a "placeholder" or an abstract concept. From an implementation perspective, you may want to make a method stub that has no body and is intended to be overridden in the child classes.

In Java, you can do this with the abstract keyword, like so: (code)

class abstract Dinosaur {
	public abstract void roar();
	// all dinosaurs roar in different ways,
	// so no implementation is defined here
}

class Pteradon extends Dinosaur {
	public void roar() { System.out.println("Squak!"); }
}

class TyranosaurusRex extends Dinosaur {
	public void roar() { System.out.println("ROOAARRR!"); }
}

Rule: any class which contains abstract methods must be declared abstract.

Polymorphism (p.152)

If you're wondering what overriding and abstract methods / classes buy you, the answer is: polymorphism.

The What and The How

What it is: Simple definition: same method call, different results.

Example: Class B is derived from A. A implements a method "foo" and B overrides it. Create a reference to A, load it with a new instance of B (works because A and B have an is-a relationship). Call A's "foo" method, and B's "foo" gets called instead. The DinosaurPolymorphism program shows this in action.

How it works: (p.153) At run-time, the VM looks at the type of an object to determine which method should be called. This is called late binding or dynamic binding.

Why we like polymorphism

Polymorphism prevents us from having to write code like this: (see also p.200)

if (dinosaur.type == TYRANOSAUR) {
	// do something with the tyranosaur
} else if (dinosaur.type == PTERADON) {
	// do something with the pteradon
} else if (... // so on, so forth
This is called "comb code" and is solved by polymorphism. You don't have to know the type, you just have to call the overriden method and the specialized code magically gets called.

Why we don't like polymorphism: Polymorphism can incur more run-time overhead and can make debugging your code a little more tricky. Many folks think that the tradeoffs are worth it.

Casting (p.156)

If you have a reference to a base class loaded with a reference to a derived class, You can only call the base class' methods (implicitly). If you want to call the derived class' methods, you need to cast it to the derived class. Bottom of p.156 to top of p.157 shows how to do this.

Note that there are two types of casting:

Up-casting: casting up the class hierarchy (towards the ancestors -- note that you lose members by doing this).

Down-casting: casting down the class hierarchy (towards the descendants -- note that you gain members by doing this).

If you try to mis-cast an object reference (i.e. casting it to an unrelated class) you will get either a compile-time or run-time error.

The Object class (p.163)

Supreme Superclass: Sitting at the very top of the class hierarchy is the Object class. All classes in Java inherit from Object. (This is a difference from C++.)

Important methods inherited by all classes

The equals method: (p.163 bottom) Override this method to test equivalence between your object and another. (This is done with the String class.) By default, equals only tests to see if two references point to the same place in memory (a la ==), which is kind of unfortunate.

The toString method: (p.165 bottom) Override this method to make your object know how to print itself (similar to overloading the char* operator in C++). This allows you to pass objects to println. Example:

String str = "145";
try {
	i = Integer.parseInt(str);
} catch (NumberFormatException e) {
	System.err.println(e);
}

Using Object references (p.169)

Having Object as a superclass of all other classes allows you to do generic programming because you can assign any reference to an Object reference. (The C approach would be to use void * pointers, the C++ approach would be to use templates.)

Using collections of object references: (p.171) The ArrayList (and Vector) classes are useful for storing lists of things. They have an advantage over normal arrays in that they are resizable (or dynamic, or "growable"). Source code: AL2DTest.java - shows how to make a 2-dimensional array using ArrayLists (an ArrayList of ArrayLists).

Object Wrappers: (p.177) Primitives are not references and as such, cannot be stored in Object references. To get around this, we use "wrapper" classes for all the primitives. They all have obvious names that correspond to the primitive types.

Reflection

A feature added to later versions of Java (1.2, I think) is the ability for any class to describe itself in terms of the methods and properties it contains. This is called reflection.

Real-World Example: Any 5GL IDE that allows you to install components into a toolbar, click-and-drag them onto a "form" and then twiddle with the properties and methods must be able to support reflection, otherwise they would not be able to display the properties of arbitrary classes.

The Class class (p.180)

Use this class to query the information about a class. There are three ways to get any class to return one of these:

Class klass = Employee.getClass(); // method inherited from 'Object'
Class klass = Employee.class; // lazier version of the above
Class klass = Class.forName("Employee"); // doesn't require an instance
Note: All the getClass() and forName() methods are both "factory" methods.

Once you have an instance of Class, you can make a new instance of the object it represents by calling newInstance. (p.181)

Employee emp = klass.newInstance(); // yet another "factory" method

Source code example: ReflectionTest.java - this one comes from your book on p.185. Use it to print the members of any class.

Generic array code (p.192)

As mentioned earlier, you can use an array of Object references to hold references to any kind of object. Performing manipulations on such an array (such as trying to grow it -- yes, it's possible, just a little tricky) can lead to a loss of type because you have up-casted but have not preserved type information.

An intuative but wrong example of growing an array is presented on p.192. The correct way to grow an array is presented on p.193. Note the use of the Class class and the getComponentType() factory method to instantate the class. Note also the newInstance() method used to create, not an array of Objects, but an array of the right type of class. Full Source code : ArrayGrowTest.java (from your book p.193)

Odds & Ends

Method pointers (p.195)

For years, the Java designers fought against including "method pointers" (like "function pointers") into the language. When Microsoft did it in their J++ Java variant, the Java designers shook their heads and wagged their fingers. Nevertheless, when reflection was added to Java, it included the ability not only to analyze an object's methods, but to call them as well with the invoke keyword. See p.195 bottom for an example of how to use (abuse) this feature.

See MethodPointerTest.java (p.197) for an example of how to do this. (It works very similarly to the execvp syscall in Unix.)

Inheritance design hints (p.199)

Once again, there's a lot of wisdom packed into a small space on pgs.199-200