The Java programming course


Lecture 3: Extending Classes

One of the major benefits of object orientation is the ability to extend, or subclass, the behavior of an existing class and continue to use code written for the original class when acting on an instance of the subclass. The original class is known as the superclass. When you extend a class to create a new class, the new extended class inherits fields and methods of the superclass.

If the subclass does not specifically override the behavior of the superclass, the subclass inherits all the behavior of its superclass because it inherits the fields and methods of its superclass. In addition, the subclass can add new fields and methods and so add new behavior.

An extended class often overrides the behavior of its superclass by providing new implementations of one or more of the inherited methods. To do this the extended class defines a method with the same signature and return type as a method in the superclass.

Extending Classes

A class can be extended, or subclassed, and an object of an extended class can be used wherever the original class is required. The term for this capability is polymorphism, meaning that an object of a given class can have multiple forms, either as its own class or as any class it extends. The new class is a subclass or extended class of the class it extends; the class that is extended is its superclass.

The collection of methods and fields that are accessible from outside a class, together with the description of how those members are expected to behave, is often referred to as the class's contract. The contract is what the class designer has promised that the class will do. Class extension provides two forms of inheritance:

  • inheritance of contract or type, whereby the subclass acquires the type of the superclass and so can be used polymorphically wherever the superclass could be used; and
  • inheritance of implementation, whereby the subclass acquires the implementation of the superclass in terms of its accessible fields and methods.

Class extension can be used for a number of purposes. It is most commonly used for specialization where the extended class defines new behavior and so becomes a specialized version of its superclass. Class extension may involve changing only the implementation of an inherited method, perhaps to make it more efficient. Whenever you extend a class, you create a new class with an expanded contract. You do not, however, change the part of the contract you inherit from the class you extended. Changing the way that the superclass's contract is implemented is reasonable, but you should never change the implementation in a way that violates that contract.

An Extended Class

To demonstrate subclassing, we start with a basic attribute class designed to store name-value pairs. Attribute names are human-readable strings, such as "color" or "location". Attribute values are determined by the kind of attribute; for example, a "location" may have a string value representing a street address, or it may be a set of integer values representing latitude and longitude.

public class Attr { private final String name; private Object value = null; public Attr (String name) { this.name = name; } public Attr (String name, Object value) { this.name = name; this.value = value; } public String getName () { return name; } public Object getValue () { return value; } public Object setValue (Object newValue) { Object oldVal = value; value = newValue; return oldVal; } public String toString () { return name + "='" + value + "'"; } }

An attribute must have a name, so each Attr constructor requires a name parameter. The name must be immutable (and so is marked final) because it may be used, for example, as a key into a hashtable or sorted list. In such a case, if the name field were modified, the attribute object would become "lost" because it would be filed under the old name, not the modified one. Attributes can have any type of value, so the value is stored in a variable of type Object. The value can be changed at any time. Both name and value are private members so that they can be accessed only via the appropriate methods. This ensures that the contract of Attr is always honored and allows the designer of Attr the freedom to change implementation details in the future without affecting clients of the class.

Every class you have seen so far is an extended class, whether or not it is declared as such. A class such as Attr that does not explicitly extend another class implicitly extends the Object class. Object is at the root of the class hierarchy. The Object class declares methods that are implemented by all objects - such as the toString method. Variables of type Object can refer to any object, whether it is a class instance or an array.

The next class extends the notion of attribute to store color attributes, which might be strings that name or describe colors. Color descriptions might be color names like "red" or "ecru" that must be looked up in a table, or numeric values that can be decoded to produce a standard, more efficient color representation we call ScreenColor (assumed to be defined elsewhere). Decoding a description into a ScreenColor object is expensive enough that you would like to do it only once. So we extend the Attr class to create a ColorAttr class to support a method to retrieve a decoded ScreenColor object. We implement it so the decoding is done only once:

class ColorAttr extends Attr { private ScreenColor myColor; // the decoded color public ColorAttr (String name) { this(name, "transparent"); } public ColorAttr (String name, Object value) { super(name, value); decodeColor(); } public ColorAttr (String name, ScreenColor value) { super(name, value.toString()); myColor = value; } public Object setValue (Object newValue) { // do the superclass's setValue work first Object retval = super.setValue(newValue); decodeColor(); return retval; } /** Set value to ScreenColor, not description */ public ScreenColor setValue (ScreenColor newValue) { // do the superclass's setValue work first super.setValue(newValue.toString()); ScreenColor oldValue = myColor; myColor = newValue; return oldValue; } /** Return decoded ScreenColor object */ public ScreenColor getColor () { return myColor; } /** set ScreenColor from description in getValue */ protected void decodeColor () { if (getValue() == null) myColor = null; else myColor = new ScreenColor(getValue()); } }

We first create a new ColorAttr class that extends the Attr class. The ColorAttr class does everything the Attr class does and adds new behavior. Therefore, the Attr class is the superclass of ColorAttr, and ColorAttr is a subclass of Attr.

The extended ColorAttr class does three primary things: it provides three constructors: two to mirror its superclass and one to directly accept a ScreenColor object; it both overrides and overloads the setValue method of its superclass so that it can set the color object when the value is changed; it provides a new getColor method to return a value that is the color description decoded into a ScreenColor object. The use of the protected access modifier on the decodeColor method. By making this method protected it can be accessed in the current class or in a subclass but is not externally visible.

Constructors in Extended Classes

An object of an extended class contains state variables (fields) that are inherited from the superclass as well as state variables defined locally within the class. To construct an object of the extended class, you must correctly initialize both sets of state variables. The extended class's constructor can deal with its own state but only the superclass knows how to correctly initialize its state such that its contract is honored. The extended class's constructors must delegate construction of the inherited state by either implicitly or explicitly invoking a superclass constructor.

A constructor in the extended class can directly invoke one of the superclass's constructors using another kind of explicit constructor invocation, which uses the super construct. This is shown in the first constructor of the ColorAttr class:

public ColorAttr (String name, Object value) { super(name, value); decodeColor(); }

You can defer the choice of which superclass constructor to use by explicitly invoking one of your class's own constructors using this instead of super, as shown in the second constructor of ColorAttr:

public ColorAttr (String name) { this(name, "transparent"); }

If you do not invoke a superclass constructor or one of your own constructors as your constructor's first executable statement, the superclass's no-arg constructor is automatically invoked before any statements of the new constructor are executed. That is, your constructor is treated as if

super();

were its first statement. If the superclass doesn't have a no-arg constructor, you must explicitly invoke another constructor.

Constructors are not methods and are not inherited. If the superclass defines a number of constructors and an extended class wishes to have constructors of the same form, then the extended class must explicitly declare each constructor, even if all that constructor does is invoke the superclass constructor of the same form.

Constructor Order Dependencies

When an object is created, memory is allocated for all its fields, including those inherited from superclasses, and those fields are set to default initial values for their respective types (zero for all numeric types, false for boolean, '\u0000' for char, and null for object references). After this, construction has three phases:

  1. Invoke a superclass's constructor.
  2. Initialize the fields using their initializers and any initialization blocks.
  3. Execute the body of the constructor.

If an exception is thrown during the construction process, the new expression terminates by throwing that exceptionno reference to the new object is returned. Because an explicit constructor invocation must be the first statement in a constructor body, it is impossible to catch an exception thrown by another constructor.

Inheriting and Redefining Members

When you extend a class you can both add new members to a class and redefine existing members. Exactly what effect redefining an inherited member has depends on the kind of member.

Overriding

In our new ColorAttr class we have both overridden and overloaded the instance method setValue:

  • Overloading a method is what you have already learned: providing more than one method with the same name but with different signatures to distinguish them.
  • Overriding a method means replacing the superclass's implementation of a method with one of your own. The signatures must be identicalbut the return type can vary in a particular way, as discussed below.

Overloading an inherited method simply means that you have added a new method, with the same name as, but a different signature from, an inherited method. In ColorAttr we have gone from having one setValue method to having two overloaded forms of the method.

public Object setValue (Object newValue) { // ... } public ScreenColor setValue (ScreenColor newValue) { // ... }

Overriding a method means that you have replaced its implementation so that when the method is invoked on an object of the subclass, it is the subclass's version of the method that gets invoked. In the ColorAttr class, we overrode the Attr class's setValue(Object) by providing a new setValue(Object) method in the ColorAttr class that uses the super keyword to invoke the superclass's implementation and then invokes decodeColor. The super reference can be used in method invocations to access methods from the superclass that are overridden in this class.

public Object setValue (Object newValue) { Object retval = super.setValue(newValue); decodeColor(); return retval; }

When you're overriding methods, the signature must be the same as in the superclassif they differ then it is an overload, not an override. The return type of an overriding method is allowed to vary in a specific way: if the return type is a reference type then the overriding method can declare a return type that is a subtype of that declared by the superclass method. Because a reference to a superclass can always hold a reference to an instance of a subclass, this variation is perfectly safe - it is referred to as being a covariant return type. We'll see a good example of this when we look at object cloning. If the return type is a primitive type, then the return type of the overriding method must be identical to that of the superclass method. It is an error if two methods differ only in return type and the compiler will reject your class.

The overriding methods have their own access specifiers. A subclass can change the access of a superclass's methods, but only to provide more access. A method declared protected in the superclass can be redeclared protected (the usual thing to do) or declared public, but it cannot be declared private or have package access.

The overriding method is also allowed to change other method modifiers. The synchronized, native, and strictfp modifiers can be freely varied because they are implementation concerns. The overriding method can be final but obviously the method it is overriding cannot. An instance method cannot have the same signature as an inherited static method, and vice versa. The overriding method can, however, be made abstract, even though the superclass method was not.

A subclass can change whether a parameter in an overriding method is final; a final modifier for a parameter is not part of the method signature - it is an implementation detail. Also, the overriding method's throws clause can be different from that of the superclass method's as long as every exception type listed in the overriding method is the same as or a subtype of the exceptions listed in the superclass's method. That is, each type in the overriding method's throws clause must be polymorphically compatible with at least one of the types listed in the throws clause of the supertype's method. This means that the throws clause of an overriding method can have fewer types listed than the method in the superclass, or more specific types, or both. The overriding method can even have no throws clause, which means that it results in no checked exceptions.

Hiding Fields

Fields cannot be overridden; they can only be hidden. If you declare a field in your class with the same name (regardless of type) as one in your superclass, that other field still exists, but it can no longer be accessed directly by its simple name. You must use super or another reference of your superclass's type to access it.

Accessing Inherited Members

When a method accesses an object's member that has been redefined in a subclass, to which member will the method referthe superclass member or the subclass member? The answer to that depends on the kind of member, its accessibility, and how you refer to it.

When you invoke a method through an object reference, the actual class of the object governs which implementation is used. When you access a field, the declared type of the reference is used. The following example will help explain:

class SuperShow { public String str = "SuperStr"; public void show () { System.out.println("Super.show: " + str); } } class ExtendShow extends SuperShow { public String str = "ExtendStr"; public void show () { System.out.println("Extend.show: " + str); } } public class Main { public static void main (String[] args) { ExtendShow ext = new ExtendShow(); SuperShow sup = ext; sup.show(); ext.show(); System.out.println("sup.str = " + sup.str); System.out.println("ext.str = " + ext.str); } }

There is only one object, but we have two variables containing references to it: one variable has type SuperShow (the superclass) and the other variable has type ExtendedShow (the actual class). Here is the output of the example when run:

Extend.show: ExtendStr Extend.show: ExtendStr sup.str = SuperStr ext.str = ExtendStr

For the show method, the behavior is as you expect: the actual class of the object, not the type of the reference, governs which version of the method is called. When you have an ExtendShow object, invoking show always calls ExtendShow's show even if you access it through a reference declared with the type SuperShow. This occurs whether show is invoked externally (as in the example) or internally within another method of either ExtendShow or SuperShow.

For the str field, the type of the reference, not the actual class of the object, determines which class's field is accessed. In fact, each ExtendShow object has two String fields, both called str, one of which is hidden by ExtendShow's own, different field called str. The field that gets accessed is determined at compile time based on the type of the reference used to access it.

Accessibility and Overriding

A method can be overridden only if it is accessible. If the method is not accessible then it is not inherited, and if it is not inherited it can't be overridden. For example, a private method is not accessible outside its own class. If a subclass defines a method that coincidentally has the same signature and return type as the superclass's private method, they are completely unrelated - the subclass method does not override the superclass's private method.

What does this mean in practice? An external invocation of the subclass method (assuming it is accessible outside its class) results in the subclass implementation being invoked. This is normal behavior. But notice that in the superclass, any invocations of the private method result in the superclass's implementation of the method being invoked, not any like-named method in a subclass. In short, invocations of private methods always invoke the implementation of the method declared in the current class.

When a method is inaccessible because the superclass and subclass are in different packages things are more complicated.

Hiding Static Members

Static members within a class, whether fields or methods, cannot be overridden, they are always hidden. The fact that they are hidden has little effect, however, each static field or method should always be accessed via the name of its declaring class, hence the fact that it gets hidden by a declaration in a subclass is of little consequence. If a reference is used to access a static member then, as with instance fields, static members are always accessed based on the declared type of the reference, not the type of the object referred to.

The super Keyword

The super keyword is available in all non-static methods of a class. In field access and method invocation, super acts as a reference to the current object as an instance of its superclass. Using super is the only case in which the type of the reference governs selection of the method implementation to be used. An invocation of super.method always uses the implementation of method the superclass defines (or inherits). It does not use any overriding implementation of that method further down the class hierarchy.

Type Compatibility and Conversion

The Java programming language is strongly typed, which means that it checks for type compatibility at compile time in most casespreventing incompatible assignments by forbidding anything questionable. Now that you understand the basic type relationship defined by subclasses and superclasses, we can revisit a few details regarding the compatibility of reference types within assignments (implicit or explicit) and conversion between types.

Compatibility

When you assign the value of an expression to a variable, either as part of an initializer, assignment statement, or implicitly when an argument value is assigned to a method parameter, the type of the expression must be compatible with the type of the variable. For reference types this means that the type of the expression must be the same type as, or a subtype of, the declared type of the variable or can be converted to such a type. For example, any method that expects an Attr object as a parameter will accept a ColorAttr object because ColorAttr is a subtype of Attr. This is called assignment compatibility. But you cannot assign an Attr object to a variable of type ColorAttr, or pass an Attr object as an argument when a ColorAttr is expected.

The same rule applies for the expression used on a return statement within a method. The type of the expression must be assignment compatible with the declared return type of the method.

The null object reference is a special case in that it is assignment compatible with all reference types, including array types - a reference variable of any type can be assigned null.

The types higher up the type hierarchy are said to be wider, or less specific, than the types lower down the hierarchy. The lower types are said to be narrower, or more specific, than their supertypes. When you are expecting a supertype and receive a subtype, a widening conversion takes place. Such a conversion causes the subtype object to be treated as an instance of the supertype and can be checked at compile time. No action is needed by the programmer in a widening conversion. Going the other waytaking a reference to a supertype and converting it to a reference to a subtypeis known as a narrowing conversion. Narrowing conversions must be explicitly requested using the cast operator.

A variable, or an expression, of primitive type can be automatically converted to a reference type using an instance of the wrapper class corresponding to that primitive type - such as an int value becoming an Integer object. Conversely, a reference to a wrapper object can be converted to the primitive value that is being wrapped. These primitive-to-wrapper conversions is called boxing conversions. The existence of the boxing conversions means that a variable of type Object can be assigned the value of any expression that you can form in the Java programming language.

Explicit Type Casting

A cast is used to tell the compiler that an expression should be treated as having the type specified by the cast, and when applied to primitive types can also affect the value of the expression. A cast consists of a type name within parentheses, applied to an expression.

Base sref = (Base) this;

This cast was unnecessary but emphasized that we really wanted the current object to be treated as an instance of its superclass. If we then try to assign sref back to a reference of the narrower More type, an explicit cast is essential.

More mref = (More) sref;

Even though we know the object referred to is of the right type, the compiler still requires an explicit cast.

A widening conversion is also known as an upcast because it casts from one type to another further up the type hierarchyit is also a safe cast because it is always valid. A narrowing conversion is also known as a downcast because it casts from one type to another, further down the inheritance hierarchyit is also an unsafe cast because it may not be valid.

When a cast is used to request a conversion, the compiler does not assume that the conversion is correct. If the compiler can tell that is cast is correct, then it can apply the cast. If the compiler can tell that a cast is incorrect then a compile time error can occur. If the compiler cannot ascertain that the cast is correct at compile time, then a run time check will be performed. If the run time check fails because the cast is incorrect, then a ClassCastException is thrown.

Testing for Type

You can test the class of an object by using the instanceof operator, which evaluates to true if the expression on its left is a reference type that is assignment compatible with the type name on its right, and false otherwise. Because null is not an instance of any type instanceof for null always returns false. Using instanceof you can safely downcast a reference, knowing that no exception will be thrown. For example:

if (sref instanceof More) mref = (More) sref;

Note that we still have to apply the cast - that's to convince the compiler that we really meant to use the object as a subclass instance.

Type testing with instanceof is particularly useful when a method doesn't require an object of a more extended type but if passed such an object it can make use of the extended functionality. For example, a sort method may accept a general List type as an argument, but if it actually receives a SortedList then it doesn't have to do anything:

public static void sort (List list) { if (list instanceof SortedList) return; // else sort the list ... }
What protected Really Means

We noted briefly that making a class member protected means it can be accessed by classes that extend that class, but that is loose language. More precisely, beyond being accessible within the class itself and to code within the same package, a protected member can also be accessed from a class through object references that are of at least the same type as the class - that is, references of the class's type or one its subtypes.

Marking Methods and Classes final

Marking a method final means that no extended class can override the method to change its behavior. In other words, this is the final version of that method. Entire classes can also be marked final. A class marked final cannot be extended by any other class, and all the methods of a final class are themselves effectively final.

public final class NoExtending { // ... }

Final classes and methods can improve security. If a class is final, nobody can declare a class that extends it, and therefore nobody can violate its contract. If a method is final, you can rely on its implementation details (unless it invokes non-final methods, of course).

Marking a method or class final is a serious restriction on the use of the class. If you make a method final, you should really intend that its behavior be completely fixed. You restrict the flexibility of your class for other programmers who might want to use it as a base from which to add functionality to their code. Marking an entire class final prevents anyone else from extending your class, limiting its usefulness to others. If you make anything final, be sure that you want to create these restrictions.

Another ramification of final is that it simplifies optimizations. When a non-final method is invoked, the runtime system determines the actual class of the object, binds the method invocation to the correct implementation of the method for that type, and then invokes that implementation.

Abstract Classes and Methods

An extremely useful feature of object-oriented programming is the concept of the abstract class. Using abstract classes, you can declare classes that define only part of an implementation, leaving extended classes to provide specific implementation of some or all of the methods. The opposite of abstract is concrete - a class that has only concrete methods, including implementations of any abstract methods inherited from superclasses, is a concrete class.

Abstract classes are helpful when some of the behavior is defined for most or all objects of a given type, but other behavior makes sense only for particular classes and not for a general superclass. Such a class is declared abstract, and each method not implemented in the class is also marked abstract.

For example, suppose you want to create a benchmarking harness to provide an infrastructure for writing benchmarked code. The class implementation could understand how to drive and measure a benchmark, but it couldn't know in advance which benchmark would be run.

public abstract class Benchmark { abstract void benchmark (); public final long repeat (int count) { long start = System.nanoTime(); for (int i=0; i<count; i++) benchmark(); return (System.nanoTime()-start); } }

The repeat method provides the benchmarking expertise. It can time a run of count repetitions of the benchmark. The method System.nanoTime returns a timestamp in nanoseconds. The abstract method benchmark must be implemented by each subclass that is not abstract itself. This is why it has no implementation in this class, just a declaration.

public class MethodBenchmark extends Benchmark { /** Do nothing, just return. */ void benchmark () {} public static void main (String[] args) { int count = Integer.parseInt(args[0]); long time = new MethodBenchmark().repeat(count); System.out.println(count+" methods in "+time+" nanoseconds"); } }

This class times how long it takes to invoke an empty method benchmark, plus the loop overhead. You can now time method invocations by running the application MethodBenchmark with the number of times to repeat the test. The count is taken from the program arguments and decoded using the Integer class's parseInt method on the argument string.

Any class with any abstract methods must be declared abstract. Any class can override methods from its superclass to declare them abstract, turning a concrete method into an abstract one at that point in the type tree. This technique is useful, for example, when a class's default implementation is invalid for a part of the class hierarchy.

The Object Class

The Object class is the root of the class hierarchy. Every class directly or indirectly extends Object and so a variable of type Object can refer to any object, whether a class instance or an array. For example, the Attr class can hold an attribute of any type, so its value field was declared to be of type Object. Such a class cannot hold primitive types directly, but can hold references to the associated wrapper class.

The Object class defines a number of methods that are inherited by all objects. These methods fall into two categories: general utility methods and methods that support threads. This section describes the utility methods and how they affect classes. The utility methods are:

public boolean equals (Object obj)
Compares the receiving object and the object referenced by obj for equality, returning true if they have the same value and false if they don't. If you want to determine whether two references refer to the same object, you can compare them using == and !=. The equals method is concerned with value equality. The default implementation of equals in Object assumes that an object is equal only to itself, by testing if this==obj.
public int hashCode ()
Returns a hash code for this object. Each object has a hash code for use in hashtables. The default implementation returns a value that is usually different for different objects. It is used when storing objects in hashed collections.
protected Object clone () throws CloneNotSupportedException
Returns a clone of this object. A clone is a new object that is a copy of the object on which clone is invoked.
public final Class<?> getClass ()
Returns the type token that represents the class of this object. For each class T there is a type token Class<T> (read as "class of T") that is an instance of the generic class Class. When invoked on an instance of Object this method will return an instance of Class<Object>; when invoked on an instance of Attr it will return an instance of Class<Attr>, and so forth.
protected void finalize () throws Throwable
Finalizes the object during garbage collection.
public String toString ()
Returns a string representation of the object. The toString method is implicitly invoked whenever an object reference is used within a string concatenation expression as an operand of the + operator. The Object version of toString constructs a string containing the class name of the object, an @ character, and a hexadecimal representation of the instance's hash code.
Cloning Objects

The Object.clone method helps you write clone methods for your own classes. A clone method returns a new object whose initial state is a copy of the current state of the object on which clone was invoked. Subsequent changes to the new clone object should not affect the state of the original object.

Strategies for Cloning

There are three important factors in writing a clone method:

  • The empty Cloneable interface, which you must implement to provide a clone method that can be used to clone an object.
  • The clone method implemented by the Object class, which performs a simple clone by copying all fields of the original object to the new object. This method works for many classes but may need to be supplemented by an overriding method.
  • The CloneNotSupportedException, which can be used to signal that a class's clone method shouldn't have been invoked.

Object.clone checks whether the object on which it was invoked implements the Cloneable interface and throws CloneNotSupportedException if it does not. Otherwise, Object.clone creates a new object of exactly the same type as the original object on which clone is invoked and initializes the fields of the new, cloned object to have the same values as the fields of the original object. When Object.clone is finished, it returns a reference to the new object.

The simplest way to make a class that can be cloned is to declare that it implements the Cloneable interface, and override the clone method, redeclaring it to be public:

public class MyClass extends HerClass implements Cloneable { public MyClass clone () throws CloneNotSupportedException { return (MyClass)super.clone(); } // ... }

Any other code can now make a clone of a MyClass object. In this simple case, all fields of MyClass will be assigned by Object.clone into the new object that is returned. Note that the overriding implementation declares that it returns an instance of MyClass, not Object, utilizing the ability to specify a covariant return type - this saves the calling code from having to supply a cast, but we have to supply it internally when returning the value from super.clone().

Correct Cloning

Objects of most classes can be cloned in principle. Even if your class does not support the Cloneable interface, you should ensure that its clone method is correct. In many classes, the default implementation of clone will be wrong because it duplicates a reference to an object that shouldn't be shared. In such cases, clone should be overridden to behave correctly. The default implementation assigns each field from the source to the same field in the destination object.

If, for example, an object has a reference to an array, a clone of one of the objects will refer to the same array. If the array holds read-only data, such a shared reference is probably fine. But if it is a list of objects that should be distinct for each of your objects, you probably don't want the clone's manipulation of its own list to affect the list of the original source object, or vice versa. Here is an example of the problem. Suppose you have a simple integer stack class:

public class IntegerStack implements Cloneable // dangerous { private int[] buffer; private int top; public IntegerStack (int maxContents) { buffer = new int[maxContents]; top = -1; } public void push (int val) { buffer[++top] = val; } public int pop () { return buffer[top--]; } public IntegerStack clone () { try { return (IntegerStack) super.clone(); } catch (CloneNotSupportedException ev) { // cannot happen (we support clone) throw new InternalError(ev.toString()); } } // ... }

Here we override clone to make it public, but we use the default implementation from the Object class. Now let's look at some code that creates an IntegerStack object, puts some data onto the stack, and then clones it:

IntegerStack first = new IntegerStack(2); first.push(2); first.push(9); IntegerStack second = first.clone();

Now consider what happens when future code invokes first.pop(), followed by first.push(17). The top element in the stack first will change from 9 to 17, which is expected. The programmer will probably be surprised, however, to see that the top element of second will also change to 17 because there is only one array that is shared by the two stacks. The solution is to override clone to make a copy of the array:

public IntegerStack clone () { try { IntegerStack newIntSt = (IntegerStack)super.clone(); newIntSt.buffer = buffer.clone(); return newIntSt; } catch (CloneNotSupportedException ve) { // cannot happen (we support clone, and so do arrays) throw new InternalError(ev.toString()); } }

Object.clone initializes each field in the new clone object by assigning it the value from the same field of the object being cloned. You then need only write special code to deal with fields for which copying the value is incorrect. IntegerStack.clone doesn't need to copy the top field, because it is already correct from the "copy values" default. It must, however, make a copy of the buffer array, which is done by cloning the array - all arrays can be cloned, with clone returning a reference of the same type as the array reference on which clone was invoked.

Shallow versus Deep Cloning

The default implementation of clone provides what is known as a shallow clone or copy - it simply performs a field by field copy. A deep clone would clone each object referred to by a field and each entry in an array. This would apply recursively and so deep cloning an object would clone all of the objects reachable from that object. In general, clone is overridden to perform a deeper clone whenever a shallow clone is not appropriatesuch as in the IntegerStack example.

The object serialization mechanism allows you to write entire object graphs to a stream of bytes and, using that generated stream of bytes, create an equivalent copy of the original object graphs. Serialization can provide a way to make deeper copies than those provided by Object.clone.


References
  • K.Arnold, J.Gosling, D.Holmes: The Java programming language. Fourth edition. Section 3: extending classes.