<Previous    Back to Start  Contents   Next>


Overloading, Overriding, Runtime Type, and Object Orientation


State the benefits of encapsulation in object oriented design and write code that implements tightly encapsulated classes and the relationships "is a" and "has a".

Encapsulation

Encapsulation is the principal of keeping the internal details of a classes state and behaviours hidden from the other classes that use it. This allows you to change those details where necessary, without breaking compatibility. The interconnectness between pieces of code is called 'coupling', and minimising it makes for more reusable and maintainable classes. The expectated behaviour of a class or method is referred to as its 'contract'. You do not want to expose any more than the minimum required to support the contract, or you can end up with tangled interdependent code with poor maintainabilty, and in all likelyhood, poor quality.

Encapsulation also aids polymorphism and inheritance - if the internal details of a class are exposed and used, it is very hard to substitute a different implementation of the same contract.

To achieve good encapsulation, make everything have the tightest possible access control so if something should not be used in a given context, limit the access to prohibit it. In particular, class instance variables should be private, and accessed only through 'get'/'set' methods (a.k.a accessors and mutators). This would allow you to do some work or checking before reading/writing a value to the variable. You could remove the variable entirely and derive it or obtain it from another source. If the variable was public, and was accessed directly, should changes would break client code.

Write classes that implement object oriented relationships specified using the clauses 'is a' and 'has a'.

Briefly, 'is a' should be implemented using inheritance, 'has a' should be implemented using containment (i.e. 'composition' or sometimes 'aggregation'). This objective is conceptual, consult a textbook for more.


Determine at run time if an object is an instance of a specified class or some subclass of that class using the instanceof operator.
SomeObject instanceof SomeClass

evaluates as true if SomeObject is an instance of SomeClass, or is an instance of a subclass of Someclass. Otherwise it evaluates as false.

SomeObject instanceof SomeInterface

evaluates as true if SomeObject is an instance of a class which implements SomeInterface. Otherwise it evaluates as false.


Write code to invoke overridden or overloaded methods and parental or overloaded constructors; and describe the effect of invoking these methods.

Overloaded and overridden methods.

What identifies a method in the Java is not merely the name of the method, rather it is the name of the method and the arguments it takes. This is called the methods signature. You must watch for any differences in the arguments list. If the arguments are different, you are dealing with a separate method. This is overloading. The fact that they have the same name is only of significance to humans.

The return type is not part of a methods signature. You are only overriding if there is a method in the parent class with same name which takes exactly the same arguments. Then the method replaces the one from the superclass.

You cannot define two methods within a class with the same name and the same arguments.

A related point is that you cannot override variables or static methods, rather you hide them when you declare, in a subclass, a variable of the same name or static method with the same signature. This is one of these things that occasionally surprises experienced developers. It is an important distinction as the polymorphic behaviour which happens when overriding (see below) does not apply here.

Legal return types for an overloading method

Overloaded methods are really completely different and separate methods, so they can return completely different types.

State legal return types for an overriding method given the original method declaration.

The overriding method must return the same type as the method in the superclass. This is because in the subclass, the overriding method replaces the method in superclass, and polymorphism or "upcasting" would not work they returned different types (i.e. you should get the same return type as you would with the parent class, or else you can't treat the subclass as the superclass).

Invoking overridden method in base and derived classes.

If the object is an instance of the derived class, then the overridden version defined in the derived class is the one that is invoked. Look at this example:

class Animal {
	void sayHello() {
		System.out.println("Hello, I'm an animal.");
	}
}
class Dog extends Animal {
	void sayHello() {
		System.out.println("Hello, I'm a dog.");
	}
}

We are overriding the sayHello method with behaviour more specific to a dog.

The straightforward method calls are as follows:

Animal i = new Animal;
i.sayHello();

prints "Hello, I'm an animal."

Dog j = new Dog();
j.sayHello();

prints "Hello, I'm a dog." because we have overridden sayHello, and this instance is a dog.

However:

Animal k = new Dog();
k.sayHello();

prints "Hello, I'm a dog." Here we are creating an instance of Dog, but we are assigning it to an Animal reference [referring to an object as the base class is called "upcasting" and is useful as you can write methods that deal with instances of any of the subclasses]. Because the underlying object is really a Dog, the method in the derived class is the one that executes.

Note: This behavior only applies to instance (i.e. not static) methods. Variables and static methods do not "override", instead they "hide" the variable/method in the parent class. This means that it is the class of the reference that matters, rather than that of the underlying object.

Write code for any overridden method that invokes the parental method, using super.

super.someMethod() will call the version of someMethod() in the immediate super class. You cannot use something like super.super.someMethod() to call the method two steps up the object hierarchy.


Write code to construct instances of any concrete class including normal top level classes and nested classes.

This is fairly straight forward, just use the new operator. Nested classes are covered in another section.

Creating constructors which use this() and super() to access overloaded or parent-class constructors.

These calls must be at the very start of the code in the constructor, therefore you can only make one of these types of calls. A call to the default constructor in the parent class is made by default, so super(arguments) is useful when you want to call a version of the constructor which takes an argument instead (and there may not be a default constructor in the parent class, in which case this call is necessary, or the code won't compile).

this() with or without arguments in brackets is used to call any of the other (overloaded) constructors defined in the class, before performing actions specific to current constructor being defined.


<Previous    Back to Start  Contents   Next>

©1999, 2000, 2002 Dylan Walsh.
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; with the Invariant Sections being the disclaimer, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".