Alexei B Khorev
March 2013
What we will do in this part:
OO paradigm: Classes and Objects
Constructors
Scope and access modifiers
Inheritance
Polymorphism and dynamic binding
Abstract classes and Interfaces
Overriding and hiding
equals() and clone()
Wrapper classes and Auto In-boxing/Un-boxing
Class Design
The procedural (structural) programming paradigm, which is what we follow when writing separate methods and then calling them from the main method, divide the virtual world into three distinct parts:
Supreme Being — the program control logic (coded main method)
Data (our variables of the primitive data type, and data structures like arrays)
Operations acting on Data (our methods)
Data and Operations are very different:
Data are passive: they have no “free will” and they change only as a result of Operations acting on them at the behest of Supreme Being
Operations are “angelic” (non-substantive) beings: they have no life in the virtual world; without lasting state, their role is only to change Data, but how and when is decided by the Supreme Being (in this sense their use is optional — Supreme Being could change Data every time by Himself, but He created Operations as His little helpers)
The result: Supreme Being is overburdened, He must be truly omniscient, know and control everything ever happening in Virtual World. This is hard, and getting harder (complexity of software grows). Supreme Being is unwell (software industry crisis).
Poor old man (or is it “Poor us programmers”?)
|
“This division [between data and operations] is grounded in the way computers work, so it’s not one that you can easily ignore or push aside. Like the equally pervasive distinctions between matter and energy and between nouns and verbs, it forms the background against which we work. At some point, all programmers — even object-oriented programmers — must lay out the data structures that their programs will use and define the functions that will act on the data.” (from The Objective-C Programming Language manual, © Apple Computer, Inc.)
“Object-oriented programming doesn’t so much dispute this view of the world as restructure it at a higher level. It groups operations and data into modular units called objects and lets you combine objects into structured networks to form a complete program. In an object-oriented programming language, objects and object interactions are the basic elements of design.”
The first idea of OO programming was due to Ole-Johan Dahl and Kirsten Nygaard (Simula-67); it was duly embraced by the industry only in the late 1980s, after a successful implementation in Smalltalk-80 (a language + run-time environment; Java has repeated the same approach 15 years later).
OO programming creates objects in an attempt to model real-world entities:
|
Not every time an object can be constructed from the above list, but at least everything which goes as a noun or noun phrase can be considered to be modelled by an object. When deciding to model something by an object, one has to establish:
By answering “yes” to both question, a virtual counterpart to the real one can be defined (whether it will be featured as an object in the final model, depends on design and other considerations, as we may see later in the course).
Benefits of building your software system as a collection of objects include (by citing The Java Tutorial):
Yet the dynamics — the run-time properties — of an OO system can be much more complex than the dynamics of a procedural system. The relevant simile would be to compare an assembly line with the life cycle in a habitat populated by numerous species living and eating (incl. each other) side by side. Which way is better to build complex system? No simple answer is possible. And (some) software developers still question the overall benefit which OO has brought into the industry.
Software objects (well, most of them) in OO paradigm are virtual counterparts of objects in the real world. Just like the real-world objects of the same kind (Cars, Students, Homes,…) can be given a common, abstract, characterisation, so the OO programming objects of the same kind can be collectively described as examples of one type, which is formalised in a concept of class.
Philosophical Parallel
Observing all kinds of tables around him — office desks, coffee tables, kitchen benches; round, rectangular, oddly shaped; wooden, plastic, metallic; brown, green, shiny… — a philosopher comes with the idea of Table, which abstracts out all accidental, transient, non-essential characteristics, and retains only those which make every table Table, those which constitute “tableness”.
Philosophers are not alone: Carpenter also needs this idea to build real tangible tables.
An OO programming language achieves a similar abstraction: it provides the syntax rules for defining Class as a data type with state and behaviour, and provides mechanism to create instances of such type.
The objects which are introduced in a program in this way are known as variables of the reference type. Together with the primitive types, they represent the second kind of data in Java programs.
Apart from primitive type variables, stuff which can live inside a Java program, can be of reference type. References are just names of the entities which have something inside them. Unlike primitive data types, these new entities are not “atomic”, they carry a number of constituents to hold data (which can be of primitive type, or be references themselves) and functions which may operate on those data, or on data which are passed as parameters to those functions. These complex entities are called objects. Primitive type variables are declared with their type, objects are declared with complex data type called classes. What lives inside objects and is defined to be the structure of classes are called class members, which in turn are divided into fields and methods.
[<modifier>]* class ClassName [extends ParentClass] [implements AnInterface]* {
[<access-modifier>] [<scope-modifier>] returnType fieldName;
... repeated as many times as there are fields ...
[<access-modifier>] [<scope-modifier>] returType methodName([arguments]) {
body-of-method definition;
}
[<access-modifier>] [<scope-modifier>] ClassName {
... body of an inner class definition ...
}
... more methods and inner classes ...
}
The class Planet, describing a celestial body — a planet, a satellite, an asteroid, a comet…
class Planet {
public long idNum; public String name; // two declaration on one line, sorry!
public Planet orbits; // not all fields are nouns
public long id; // the catalog number of the new planet
private static long nextID = 0; //the total planet counter
public Planet(String s) { //one constructor
this.name = s;
id = nextID++; // here the post-increment matter!
}
public static long getNumberOfPlanets() { return nextID; }
public Planet(String s, Planet p) { //another constructor
this.name = s;
this.orbits = p;
id = nextID++;
}
}
Planet moon; // variable declaration
Planet sun = new Planet("Sun"); // declaration and instantiation
Planet venus = new Planet("Venus", sun); // another instantiation
A class can define several ways to create its objects: special “methods” (but not real methods), constructors, are called with operator new which allocates a memory for an object. A constructor looks like a class method without a return value and it requires the operator new. Multiple constructors in one class differ by type and number of parameters (and by exceptions they throw), but all have the same name as the class. When no constructors are defined, the default parameterless constructor is available (it’s inherited from parent class). If at least one constructor is defined, the default constructor is no longer available by default and must be defined explicitly (if needed).
class Student {
String name;
Degree degree;
ArrayList<Course> courses = new ArrayList<Course>();
public Student(String n) {…}
public Student(String n, Degree d) {…}
public void enrollCourse(Course c) { courses.add(c); }
public void study() {…}
public Mark sitExam(Course c) {…}
... ...
}
A defined class can be out to use to create and manage object of that class type:
Course comp6700 = new Course("comp6700"); // creating a new course
Degree bit = new Degree("MITS"); // new degree
Student stud = new Student("John Doe", bit); // start a degree
stud.enrollCourse(comp6700); // enrolling in a course
stud.study(); // "studying hard"
comp6700.setMark(stud,stud.sitExam(comp6700)); // earning a mark
...
stud.receiveGrades(comp6700);
...
stud.completeDegree();
It is useful to visualise an object memory layout:
With every constructor call, a memory block structured in accordance with the class definition is allocated, as illustrated by the object diagram. The fields of a newly created object are initialised first to their default or initial values (the latter are the values which are assigned during the fields declaration, outside the constructors), and then the fields are assigned values as defined by the constructor.
The memory layout for (non-static) fields and methods is, actually different: the object methods aren’t grouped with the fields in memory (that would be wasteful). Memory is allocated for the instance fields of each new object, but there’s no need to allocate memory for methods. All an instance needs is access to its methods, and all instances of the same class share access to the same set of methods. There’s only one copy of the methods in memory, no matter how many instances of the class are created. However, whether access to the methods requires a reference to an object, or the methods can be called by the class name — this depends on the scope identifier in the methods declaration:
Static methods can be called without even a single instance of the class in existence
Static fields can be read (and reset, if their are not final) without a single object of the class being created. There is a unique copy of a static field — one per class; for multiple instances, the value of their static field is the same.
If a method makes use of a static member, it itself must be declared static. (Think “why?”)
Fields and methods of a class (collectively known as members) are accessed by referencing the object name:
System.out.println("The name of this planet is " + sun.name);// non-static members
or by the class name:
System.out.println("The total number of planets is " + Planet.nextID); // static members
The access modifiers regulate how and which class member is visible — can be read, (re-)assigned or called (for methods):
public — visible from anywhere outside"friend" (implicit, assumed if no access modifier is used; no such keyword) — visible within the same packageprotected — visible only to objects which are instances of subclasses of the classprivate — totally invisible from outside, can be only used inside the class; Different objects of the same class can access the other’s private members, see Shtuka.javaThe general policy is to declare fields private and (non-auxiliary) methods public, but it really depends on design considerations. The values of a private field is read by calling a “getter” method, while its value is controllably changed with a “setter” method (not every class provides setter methods, immutable classes data, like in String, are not meant to be altered).
Class members can have an object scope, or they can have a class scope, when they are declared static. Non-static members are associated with individual instances of the class (objects), and two values of the same field but from two different objects will have uncorrelated values:
public class Parameter {
private int x; // declared static in StaticParameter class
public Parameter(int x) { this.x = x; }
public int getX() { return x; }
public void setX(int y) { this.x = y; }
public String toString() { return "" + x; }
}
Parameter p1 = new Parameter(1);
Parameter p2 = new Parameter(1);
System.out.println("p1 has " + p1.getX() +
" and p2 has " + p2.getX()); // prints "... 1 ... and ... 1 ..."
p1.setX(10);
System.out.println("p1 has " + p1.getX() +
" and p2 has " + p2.getX()); // prints "... 10 ... and ... 1 ..."
The modified Parameter (now StaticParameter) class will produce different output (study!).
The nature of class members can be also controlled by (non-)presence of modifier **final**.
final field makes the value unchangeable, constant; its initialisation can be delayed (so called blank finals), but once initialised, further attempt to change its — even to the same value! — value will result in a compile time error:
final int i = 0;
i = 1; // Error: This object cannot be modifiedfinal method cannot be overridden in a subclass; this will be clearer when we discuss inheritance
Normally, all modifiers which can be used in a method declaration, can be also used in a class declaration, with the similar meaning. So far unmentioned modifiers include:
abstract (for classes and methods) — will learn soonnative (for methods) — indicates non-Java implementationstrctfp (for methods) — used to indicated strict floating point arithmetic (not discussed)annotation (for classes and methods) — will be mentioned in a later week (time permitting)Some modifiers cannot be used together, eg final and abstract together “do not compute”.
Taste of OO power
The method println(), which we have used multiple times already, seems was one and the same despite the arguments were different (values of all types which we wanted to be printed). The sameness of the println() was an illusion — those were, in fact, different methods! Different by the type (and number) of parameter which one can pass to them. But they all can have the same name! A method whose name used in other methods which differ by the type and the number of their parameters is called overloaded. Below is the code from The Java Tutorial:
public class DataArtist {
...
public void draw(String s) { ... }
public void draw(int i) { ... }
public void draw(double f) { ... }
}
But it’s illegal to declare more than one method with the same name and the same number and type of arguments, because the compiler cannot tell them apart. Since the compiler does not consider return type when differentiating methods, you cannot declare two methods with the same parameter set if they have a different return type. General advice — not overuse the overloading, since it hinders the code readability. Especially, do not overload methods with the same number (but different type) of parameters, including varags.
this and all thatThe constructor definitions can contain any code, but their primary purpose is to initialise the class fields. Fields which are not initialised by the constructor explicitly, are given default values:
+0.0)false'\u0000'nullstatic fields can sometimes be assigned in the constructor:
public Planet(String s, Planet p) {
this.name = s; // the self-reference this is optional
this.orbits = p; // it's a good practice to avoid errors
idNum = nextID++; // counting number of created objects
}
this is used for object self-referencing (eg, if it is passed as a parameter).
public ArrayList<Planet> satellites; // ArrayList is like an array
public Planet(String s, Planet p) {
this.name = s;
this.orbits = p;
p.sattellites.add(this);
}
Three stages — when class is loaded, then when its constructor is called but before it’s executed, and finally after the constructor executed:
| The class is loaded | before | after |
|---|---|---|
|
Notice the appearance of an additional String object at the second stage due to presence of new operator inside the Dummy constructor which results in a creation of new String object. If the assignment of str value was different: str = "abcdefg"; — then no new String object was created, but the reference str was given a value of the reference to the constant string “abcdefg” (remember String’s pool).
The ability to re-use existing code (in the form of class definitions), or inherit previous definitions in Java is realised via the mechanism of inheritance. A new subclass (child, derived) can be defined to inherit the definition of an existing class (superclass, parent) and also to add new class members and change the inherited ones.
class A {
protected int oldField;
public void oldMethod();
}
class B extends A {
... A's public/protected members are included
... their access modifiers can be only changed to
... raise their visibility, eg protected -> public
public B(..) { // this is B's constructor
super(..); // call to parent constructor
newField = some_value; }
private int newField;
public void newMethod();
}
Everything (fields, methods, constructors) from the parent class can be accessed in the child class using the parent reference super (an example of constructor’s re-use is Manager.java. Call to super() is always a good practice in every child constructor.
The Object-Orientation paradigm is like a pie (using Peter van der Linden’s simile):
The most important aspects of the OO paradigm:
Abstraction — deal with multitude of object types as if they were one
Polymorphism —use objects of various type as if they were same
Inheritance — reuse and change a type and still retaining one
Encupsulation — hide behind an interface allowing changes in object’s internals
What happens when the constructor is called?
When an object is created, memory is allocated for all its fields, including those which are inherited from superclasses (parent and all the ancestors all the way up to the Object class — Abraham of Java’s class hierarchy). The class fields are set to their respective default values before the constructor’s phases begin:
The superclass constructor — either default super() or one with parameters super(...) — is invoked;
The class fields are initialised using their initialisers and initialisation blocks (the blocks of code outside the constrictors or class methods which initialise the fields of the object; see the example Body.java) in the same order in which they are declared in the class
The rest of the constructor body — everything after super() — is executed (that’s why super() should be the first statement inside the constructor body, or be absent altogether);
Explicit constructor invocation: One class constructor can invoke another constructor of the same class by calling this(...). super(), or this() must be the first constructor statement. this() helps to write a reusable constructor code only once, and then invoke it by calling this(arg1,arg2,…) in other constructors when same initialisation is required.
Important: if a class can be extended, do not call any non-final non-private methods inside its constructor!
This table shows the values which the Y subclass (of the class X) fields have at different stages of the Y() constructor executions. The source code for the parent class X.java and its child Y.java is good for understanding what is gong on:
| Step | What Happens | xMask | yMask | fullMask | |
|---|---|---|---|---|---|
| 0 | Fields set to defaults | 0 | 0 | 0 | |
| 1 | Y constructor invoked | 0 | 0 | 0 | |
| 2 | X constructor invoked | 0 | 0 | 0 | |
| 3 | Object constr. invoked | 0 | 0 | 0 | |
| 4 | X field initialised | 0x00ff | 0 | 0 | |
| 5 | X constructor executed | 0x00ff | 0 | 0x00ff | |
| 6 | Y field initialised | 0x00ff | 0xff00 | 0x00ff | |
| 7 | Y constructor executed | 0x00ff | 0xff00 | 0xffff |
|
In an inheritance hierarchy, every class from a sub-tree represents a subtype of the type represented by the root of sub-tree. Type and its subtype are in “IsA” relationship.
Person p = new Student("Jack Sparrow"); // upcasting, OK: Student IsA Person
Student st = new Person("Bill Turner"); // illegal, class cast exception
Student st = p; // unsafe, p may not be Student type
p.study(); // illegal, downcasting, needs explicit cast
((Student)p).study(); // OK if p IsA Student, or class cast exception is thrown
Employee e = new Student("Barbossa"); // illegal, incompatible types
Every class in Java inherits, either explicitly via extends, or implicitly from the Object class. Hence, all public and protected methods from the Object class are available to all instances and classes which can be defined and used in any Java program. Two such methods, equals() and hashCode(), and how and why they should be overridden will be discussed later. The inheritance does not only allow to re-use existing code. It also let you modify an inherited definition to meet new requirements. This is achieved through overriding of inherited methods — providing a new implementation without changing the signature and the contract (to achieve the polymorphism, see next slide) of the inherited method.
|
|
Parent’s attributes are accessed via super reference anywhere inside the child class. Multiple parent constructors differ by their parameters, as ordinary methods. Eg, the Manager constructor which calls super(String,String,double) refers to Employee(String,String,double).
The mechanism of extending an old type isn’t just good in terms of code re-use. It allows to write shorter, yet more flexible and expressive programs. Namely, if the client of your code can use an object of some class, it can also use objects of its subclasses. This feature is called polymorphism, meaning that an object of a given class can, in fact, have multiple forms — of its own class or of any class it extends. Put it another way: polymorphism is the ability for different objects to respond to identical messages.
All fields and methods of a given class, which are visible outside (1), together with the conditions when they are used (2) and the effects they produced (3), are collectively known as the class contract — it represents the declared raison d’etre of the class. Class extension provides two kinds of inheritance:
inheritance of contract or type — here the subclass is endowed with the type of the superclass and can be used where the superclass can be used;
inheritance of implementation — here the subclass acquires the implementation of the superclass in the form of its (non-private) fields and methods.
Example of the payroll() method in the EmployeeTest.java program. Depending on the class of the Employee object which is used in the payroll() method, different getSalary() methods are used. This binding of the call (ie, method invocation) to a particular method is known as dynamic binding. Another set of examples are shapes and their area.
A method can also return a reference type. For instance, a method for calculating a Number type value which is what the method returns:
|
If the class Number is extended to ComplexNumber and you want to override calculateTheNumber() to return, quite appropriately, the ComplexNumber value, this is possible. This technique, called covariant return type, means that when overriding, the return type is allowed to vary in the same direction as the subclass (see covariant overriding example).
Abstract classes allow to capture an abstraction — a state or behavioural feature which is common in a family of types, which they all have, but in somewhat different (particular) way. The whole family is characterised by declaring that something is done, but not how it is done. The “how”, ie, the concrete implementation, differs from one type to another. In Java, an abstract class usually has one or more declared, but not implemented methods which are marked with the keyword abstract (one can declare a class abstract without having a single abstract method in it). Abstract classes cannot be instantiated. The classes which provide implementation of abstract methods (concrete subclasses) must extend the abstract class. The opposite is also possible: a class can extend a concrete superclass and “override” a normal (implemented) method to make it abstract (why would it be useful?).
|
Multiple inheritance — an ability to define a type which combine properties of more than one existing is a double-edge sword: powerful and dangerous.
It would be often beneficial for a new class to inherit at once from more than one parent, eg, a HollywoodActor object can go on trial for a (real life) crime, like tax evasion or murder. In such case, a class AccusedActor can be created which needs to inherit from both the HollywoodActor and Civilian (to account for attributes like SS number, or payTax() method). (Less queer example is the SwissArmyKnife class.)
The diamond of death problem The problem with multiple inheritance occur when two (or more) parents of the same class have common ancestry. Who is Some OO languages (C++, Eiffel) allow multiple inheritance, but resolving DDP is complex and makes design and use of class hierarchy more complicated. Java constrains the multiple inheritance by allowing only single inheritance of implementation and multiple inheritance of contract. |
The way around the multiple inheritance problems in Java is to introduce interface. It can be thought as the abstract class stripped of any implementation and variable fields. No implementation! Pure contract. The only members allowed are non-private method declarations (no need to call them abstract), and constants —— static (no instantiation!) and final (not blank! with assigned constant values) fields — public static final modifiers can be omitted in declarations. The interface declaration is marked by interface keyword instead of class:
interface Verbose {
int SILENT = 0; // interface constants have fully CAPITALISED identifiers
int TERSE = 1;
int NORMAL = 2;
int VERBOSE = 3;
void setVerbosity(int level);
int getVerbosity();
}
In the manner of extending abstract class to get complete implementation and object creation, an interface must be implemented into the concrete class via implementation of every method declared in the interface.
class MyConcreteClass implements Verbose { .... }
The diamond of death phenomenon cannot occur if the implementing class inherits only one (abstract or concrete, does not matter) class, and implements a number (I couldn’t find the max number, even in The Java Language Specification book, but I guess it’s 256; imaging a class which implements that many interfaces!) of interfaces, because there is no competing implementations which vie to become the one in the derived class (there is at most one).
If a class implements multiple interfaces, they are declared in a comma separated list following the implements keyword:
class ClassD extends ClassA implements InterfaceB, interfaceC {
... adding own fields and methods ...
... possibly overriding other methods form ClassA ...
void doSmth(){
... providing unique implementation (perhaps overriding) ...
}
}
Interfaces allow to decouple the behaviour which classes from one group have (because they implement a particular interface) from classes of another group which use this behaviour (they are provided with an interface to the behaviour).
Use interface type as a parameter for a method — any method promised by the interface parameter can be invoked:
doSmth(type param) { // at compile time type is an interface
param.foo(); // at run time param is an object of an implementing class
param.bar();
}
When the method is called, an object of a real class, which implemented the type interface, is passed.
An interface name can be used anywhere a type can be used.
Interfaces can be extended (NewInterface extends OldInterface) to add new method declarations to the interface type without breaking existing classes which implement the OldInterface type. Unlike class extension, the interface can extend multiple interfaces (no implementation is provided, and declarations do not clash; the parent interface constants get hidden).
Which one to choose
Repeat the difference:
Interfaces provide a form of multiple inheritance without complex semantics. For abstract classes, the multiple inheritance is forbidden, even if all methods are abstract.
An abstract class can contain partial implementation, protected fields and methods, static methods and other standard elements, while interfaces can only have public constants and public methods without implementation.
Therefore:
If multiple inheritance is envisaged, interfaces are used.
But abstract classes give easier re-use through available (partial) implementation.
Abstract classes allow to constrain modification (via use of final modifier) of some aspects of behaviour.
If a major class is meant to be extended, make it to implement an interface, and make clients of this class to refer to the interface instead.
The idea that object encapsulates its implementation and reveals its interface represents only the technical aspect. A deeper and more powerful idea is that Object is its Interface.
If an object is an idea of computation, then it is more stable and has more longevity if it has less accidental (implementation specific) characteristics. In its ideal, purified form an object is defined by operations which one can perform with it, and by nothing else.
The watch measures time, it does not count it!
![]() |
||
| (Courtesy of Apple Inc.) |
Three reference types — classes, abstract classes, and interfaces — seems excessive, isn’t?
The most important OO idea: “Program to interface!”
When writing a program, try — as much as possible — to program to interface. The client code of an object (a class which has it as a field, or a method which uses it as an actual parameter, or a code block which is given access to the object) should be only revealed that part of its interface which they actually use, not more! If the object has many types (ie, it instantiated to a class which implements multiple interfaces), and the client needs only one of them, it is only with that type the object should be declared to its client.
Physics analogy: motion of a bunch of electrically charged particles (electrons) in an external electro-magnetic field. Electron’s internal structure needs not to be known to calculate the trajectories; only their “interface”: charge, mass, position and velocity. Future advances in Physics may reveal electron internal structure, but the laws of motion will remain intact.
Ideas, concepts and knowledge in general (including software) — they are most useful, stable and valuable if they are expressed in abstract terms.(How many ideas and how many functioning artefacts from Antiquity are around today?) In software, this abstraction of object properties is their interface.
What do we need to know to fully describe the motion of charged particles in external electro-magnetic field? Only charges and masses, not the inner structure.
![]() |
||
Signals and methods in OO programs
(time t1, t2…): Objects o1… o6 are created at various stages of execution
(t2): An object o1 sends a signal to o4 with data containing a reference to an object o2
(t3 >= t2): The object o4 receives this signal and executes a method (or, several methods); the received signal contains a reference to o2 (a parameter to one of o4 methods), and o4 may invoke methods of o2 as a part of its response; the state of o4 may change, and it may emit a signal to another object o3 passing some data; the data which are passed are obtained as return values of methods which the object o4 executed between receiving the signal from o1 and sending the signal to o3
(t5): All references to the object o2 are lost, this object “dies” (is garbage-collected)
States of all objects undergo evolution during the program execution (red lines), which depends both on the global program logic (“external force”), and on signals they receive from other objects (including their own).
When a class is extended and an implementation of its method is changed, it’s said then the method is overridden (not confuse with method overloading). When we are dealing with instances of a class, it is the actual class of the object which determines which implementation is used. Apart from changing implementation, the overriding can widen the access modifier, ie, make protected method public, but it cannot make it private. If the parental implementation need to be used, it can be done by using the reference to the superclass: super.do().
When designing a class for extension, one must consider which access modifier to give to fields (private or protected, the last choice can be good for performance but must be exercises carefully) and to methods (protected or public). As every design aspect, this is a complex issue and requires experience and care, but the rule of thumb is that if you do not trust the class’s possible children to preserve the integrity of the class contract, you should limit the access of its members. If the class method should not be overridden no matter what, it must be declared final (this will disallow any possible future overriding of the method by its descendants). If the whole class is not meant to be extended, it itself must be declared final (all methods in such a class are implicitly final).
final class NonExtendableClass { .... }
Attempts to declare a class which extends NonExtendableClass will be met with obstinate compiler error.
One tricky issue
If the inherited instance methods can be overridden, the inherited fields (if the child class introduces a field with the name but not type identical to a field in the parent class) and static methods are hidden. For a field in the subclass with the same name as a field in the superclass, the latter still exists, but it’s no longer accessible by its simple name. The reference must be cast to the superclass type to access it.
class SuperShow {
public String str = "SuperString";
public void show() { System.out.println("Super.show: " + str);}
}
class ExtendShow extends SuperShow {
public String str = "ExtendString";// hiding the field
public void show() { // overriding the method
System.out.println("Extend.show: " + str);
}
}
Run InheritanceTest class (which involves the parent-child pair SuperShow and ExtendShow):
Extend.show: ExtendString // method is selected by the object class
Extend.show: ExtendString
sup.str = SuperString // field is selected by the reference type
ext.str = ExtendString
Class (static) methods behave similarly to fields
Some overriding does not make sense: overriding a class method into instance method (stripping static) doesn’t make sense, and vice-versa — overriding an instance method into a static one. Both attempts result in the compile errors.
Defining a Method with the Same Signature as a Superclass’s Method
| Kind of Inheritance | Superclass Instance Method | Superclass Static Method |
| Subclass Instance Method | Overrides | Illegal (Compile Error) |
| Subclass Static Method | Illegal (Compile Error) | Hides |
For examples, see overrideStatic three class code.
Note: In a subclass, you can overload methods inherited from the superclass. Such overloaded methods neither hide nor override the superclass methods—they are new methods, unique to the subclass.
Note: When overriding a method, you might want to use the @Override annotation that instructs the compiler that you intend to override a method from the superclass. If, for some reason, the compiler detects that the method does not exist in one of the superclasses, it will generate an error.
superThe super can be invoked in any non-static methods. It acts as a reference to the current object as an instance of its superclass. When you need to select a parental implementation even if the reference is attached to an instance of the child class, use super.
class That {
protected String getName() { return "That"; } //return the class name
}
class More extends That {
protected String getName() { return "More"; } //overrides the superclass method
void printName() {
That sref = (That) this; // no need to do the cast, though
System.out.println("1 this.getName() = " + this.getName());
System.out.println("2 sref.getName() = " + sref.getName());
System.out.println("3 super.getName() = " + super.getName());
}
public static void main(String[] args) {
(new More()).printName(); }
}
Both sref and super refer to the same object of the type That, but super will ignore the real class of the object and use the superclass implementation.
1 this.getName() = More
2 sref.getName() = More
3 super.getName() = That
When two objects of the same class can considered equal but independent? This depends on how we define the object equality.
|
|
|
equals()Objects of the same type are often compared on equality with one another. The method Object.equals(Object o) returns true only if the objects are one and the same (the default implementation is the test o == this). Some classes do require this kind of behaviour (like Thread, which represents a process, not a value). But often equals() is required as the test of logical equality, when two instances of a value class are considered equal not only when they not refer to the same object, but also when the objects can be substituted for one another without altering the computational environment. Such equals() methods are important for search and placement of elements in instances of Collection classes. Demo with two versions of equals() in the A class (the test running program is TestingEquals.java).
To work correctly, the overridden equals() must satisfy the equivalence relations:
be reflexive, x.equals(x) returns true
be symmetric, y.equals(x) and x.equals(y) return the same value
be transitive and consistent (returns the same value over the two objects life if they are subjected the same manipulations)
x.equals(null) returns false
equals()Overriding equals() can occur in two ways:
The above equivalence relations cannot be satisfied all at once if is is done on the way of inheritance — there is simply no way to extend a class and add an aspect (a new field) while preserving the equals() contract" (for proof see Joshua Bloch’s book “The Effective Java”).
However, equals() can be defined with the above properties on the way of composition.
class ColourPoint {
Point point;
Colour colour;
public boolean equals(Object o) {
if (!(o instanceof ColourPoint)) return false;
ColourPoint cp = (ColourPoint) o;
return cp.point.equals(point) && cp.colour.equals(colour);
}
}
One case, when there is no need to override equals, is when the a class is defined in such a way that at most one object of it can be instantiated (Singleton pattern). Another example of types for which equals() is equivalent to == is Enum (they allow only a finite number of instances which are defined as a part of the enum type declaration).
clone()Sometimes, the client code needs to create a copy of an object which has the same state as the prototype object. This procedure is called cloning. The method which can do such creation, clone(), is defined in the Object class; it is a native method. The Object.clone() method returns a reference to the Object type object which must be appropriately cast. However, the returned object must be otherwise independent from the original one such that subsequent changes to the newly cloned object do not affect the original object (deep clone). This task cannot be achieved by simply calling the inherited clone() — the Object.clone() is declared protected, and every subclass needs to explicitly override it, and either keep it protected, or promote it to public (not always a good idea). When overriding the clone() method in a derived class, one should:
CloneNotSupportedException is thrown); Object.clone() checks whether the object on which it was invoked implements the Cloneable interface and throws CloneNotSupportedException if it does not; clone() always returns an object of the ambient classclone() must be supplemented by additional statements insuring that all reference type fields are appropriately initialisedclone() to throw no CloneNotSupportedException (this is simplification — the decision to implement Cloneable and to (not) throw the exception depends of the class policy)clone()An example, given in B.java class, is demonstrated with both — naive (incorrect) and correct versions of clone(). Class B uses a private buffer field (a simple array of int) to provide a stack type data structure (for details, see Block-4) which allows to push() a value into the stack, pop() the latest added vale, and read the latest added value with getTop(). What if we attempt to clone an existing stack object of B which then could be used as independent stack? A very important aspect of cloning is to make sure that buffer is correctly cloned too:
public B clone() {
try {
// recreating the old object with shared reference fields
B tmp = (B) super.clone();
// calling the corresponding field's clone
tmp.buffer = buffer.clone(); /* omit this and you're in trouble! */
return tmp; // provided buffer.clone() is already correct
} catch (CloneNotSupportedException e) {
// Cannot happen -- 'cause we supported the clone
throw new InternalError(e.toString());
}
}
The cloning problem is a delicate one and it is dealt with differently by languages:
copy to support both shallow and deep cloning (best decision?)![]() |
![]() |
Implementing clone() is a messy business (in Java). Often, a much better way to program object creation in a given state is to define a copy constructor; this provides a simpler alternative (eg, it can deal with final fields).
Presence of primitive data types in Java was a performance “hack”, rather then a necessary feature (Smalltalk, or Eiffel which predated Java were pure OO, without primitives): primitive type variables do not incur initialisation overhead compared to objects. Also a factor was to maintain the type system familiar by making it similar to C/C++. The trade-off was to sacrifice the expressiveness and uniformity of type system. This artificial division between two kinds of type was not just illogical, but resulted in practical limitation (eg, some collection types, like the Vector class, can be only “stuffed” with objects). To breach this logical divide, Java provided wrapper classes for each primitive type: Boolean, Character and the abstract Number (with concrete subclasses to represent the number types.)
This classes can be instantiated to carry the data which the corresponding primitive type do. They also provide additional services, like conversion, parsing values, etc.
int i = 10;
Integer j = new Integer(11); // wrapping a primitive value
i = j.intValue(); // getting it back from an object
double k = j.doubleValue();
Integer l = Integer.decode("0xAAA"); //decodes string representation of hex number
i = Integer.parseInt((new Scanner(System.in)).next());
Purists can argue that coexistence of reference and non-reference types is a flaw in language design (“considered harmful” by Nick Ourusoff, Comm. ACM 45 (8) 2002), because the expression evaluation for primitive types breaks the OO paradigm, data representation is confused with object encapsulation, the machine domain is confused with the application domain.
Prior to Java 1.5 there was awkward practical aspect to existence of primitives, too. If numbers object (instance of ArrayList<Integer>) is used to store values, every element in it is an Integer object, and the extracted element must be converted if to be assigned to an int variable:
int i = 10;
numbers.add(new Integer(i));
Integer j = numbers.get(4); // getting the copy of the element at index 4
int k = j.intValue();
Java 1.5 has made this explicit conversion unnecessary:
Integer val = 3; // in-boxing conversion
int i = numbers.get(4); // un-boxing conversion
The class Freq created the word-frequency map reading from the command line:
public class Freq {
public static void main(String[] args) {
Map m = new TreeMap();
for (String word : args) {
Integer freq = m.get(word);
m.put(word, (freq == null ? 1 : freq + 1));
}
System.out.println(m);
}
}
Basic OO terms and concepts
Interface (user perspective) — set of methods which can be called on an object
Encapsulation — concealing implementation details behind object’s interface
Polymorphism — ability to treat objects of different class uniformly by the type of their reference
Dynamic binding — choosing method implementation at run time based on the class of an object
Overloading — using same name for multiple methods
Overriding — changing method’s implementation in a subclass
Hiding — using same name for a field added in a subclass
Abstract class — class with incomplete implementation
Interface (code construct) — type which only declares behaviour and no implementation
Extension (subclassing, specialising) — reusing existing class in defining a new one
Implementation — turing an interface type into concrete class
Don’t use too many basic types in a class — multiple related uses of basic types should probably be another class:
private String street;
private String city;
private String town;
private int postcode;
should be made part of a separate class Address
Consider carefully which fields need individual accessors and mutators — some of them may be immutable (declare them final ?)
Use standard (consistent) form for class definitions — introduce non-static fields, then static fields (if any), then constructors, then public methods, then private methods, then mutator methods, then accessor methods, then inner classes (if any)
Break up classes with “too many” responsibilities — classes should be small! (try to adhere to Single Responsibility Principle)
Make names of your classes and methods reflect their responsibilities (Noun, AdjectiveNoun or GerundNoun)
What is more important for OOP — classes or objects? A class is an accumulation of properties which are common to instances. When a computation is conceived “from the top”, defining classes is an adequate approach to capture abstraction, and class based implementation for creating objects is appropriate. Since Java is a statically typed compiled language, once an object is created, its structure and behaviour will not change (unless you are using the reflections or some other “black art”, that is). This is good since it guarantees that object’s contract will not change (which gives a layer of security to be expected in statically-typed languages), but sometimes one can benefit from the ability to change object’s properties while it is alive. Dynamic OO languages (like Python) allow just that, but they still begin with classes (in Python, there are ways to restrain object’s variability via the slot-mechanism).
What if the problem in question does not yield to easy classification: multiple objects do exist, but finding common features to define their class is not possible. (In philosophy, this problem was emphasised by Ludwig Wittgenstein). There is an alternative paradigm which is known as prototype based (OO) programming.
In languages like Self, Lua, Smalltalk (which allows classes, too) and (most popular) JavaScript, objects are created by cloning from a set of predefined object literals which are called prototypes, with the ability to add new attributes and behaviour during the object lifetime. In the prototype-based languages, the notion of type hierarchy does not exist.