Making
local copies
To
review: all argument passing in Java is performed by passing handles. That is,
when you pass “an object,” you’re really passing only a
handle to an object that lives outside the method, so if you perform any
modifications with that handle, you modify the outside object. In addition:
- Aliasing
happens automatically during argument passing.
- There
are no local objects, only local handles.
- Handles
have scopes, objects do not.
- Object
lifetime is never an issue in Java.
- There
is no language support (e.g. const) to prevent objects from being modified (to
prevent negative effects of aliasing).
If
you’re only reading information from an object and not modifying it,
passing a handle is the most efficient form of argument passing. This is nice;
the default way of doing things is also the most efficient. However, sometimes
it’s necessary to be able to treat the object as if it were
“local” so that changes you make affect only a local copy and do
not modify the outside object. Many programming languages support the ability
to automatically make a local copy of the outside object, inside the method.
[49]
Java does not, but it allows you to produce this effect.
Pass
by value
This
brings up the terminology issue, which always seems good for an argument. The
term is “pass
by value,” and the meaning depends on how you perceive the operation of
the program. The general meaning is that you get a local copy of whatever
you’re passing, but the real question is how you think about what
you’re passing. When it comes to the meaning of “pass by
value,” there are two fairly distinct camps:
- Java
passes everything by value. When you’re passing primitives into a method,
you get a distinct copy of the primitive. When you’re passing a handle
into a method, you get a copy of the handle. Ergo, everything is pass by value.
Of course, the assumption is that you’re always thinking (and caring)
that handles are being passed, but it seems like the Java design has gone a
long way toward allowing you to ignore (most of the time) that you’re
working with a handle. That is, it seems to allow you to think of the handle as
“the object,” since it implicitly dereferences it whenever you make
a method call.
- Java
passes primitives by value (no argument there), but objects are passed by
reference. This is the world view that the handle is an alias for the object,
so you
don’t
think about passing handles, but instead say “I’m passing the
object.” Since you don’t get a local copy of the object when you
pass it into a method, objects are clearly not passed by value. There appears
to be some support for this view within Sun, since one of the “reserved
but not implemented” keywords is
byvalue.
(There’s no knowing, however, whether that keyword will ever see the
light of day.)
Having
given both camps a good airing and after saying “It depends on how you
think of a handle,” I will attempt to sidestep the issue for the rest of
the book. In the end, it isn’t
that
important – what is important is that you understand that passing a
handle allows the caller’s object to be changed unexpectedly.
Cloning
objects
The
most likely reason for making a local copy of an object is if you’re
going to modify that object and you don’t want to modify the
caller’s object. If you decide that you want to make a local copy, you
simply use the
clone( )
method to perform the operation. This is a method that’s defined as
protected
in
the base class
Object
and which you must override as
public
in
any derived classes that you want to clone. For example, the standard library
class
Vector
overrides
clone( ),
so we can call
clone( )
for
Vector:
//: Cloning.java
// The clone() operation works for only a few
// items in the standard Java library.
import java.util.*;
class Int {
private int i;
public Int(int ii) { i = ii; }
public void increment() { i++; }
public String toString() {
return Integer.toString(i);
}
}
public class Cloning {
public static void main(String[] args) {
Vector v = new Vector();
for(int i = 0; i < 10; i++ )
v.addElement(new Int(i));
System.out.println("v: " + v);
Vector v2 = (Vector)v.clone();
// Increment all v2's elements:
for(Enumeration e = v2.elements();
e.hasMoreElements(); )
((Int)e.nextElement()).increment();
// See if it changed v's elements:
System.out.println("v: " + v);
}
} ///:~
The
clone( )
method produces an
Object,
which must then be recast to the proper type. This example shows how
Vector’s
clone( )
method
does
not
automatically try to clone each of the objects that the
Vector
contains – the old
Vector
and the cloned
Vector
are aliased to the same objects. This is often called a shallow
copy
,
since it’s copying only the “surface” portion of an object.
The actual object consists of this “surface” plus all the objects
that the handles are pointing to, plus all the objects
those
objects
are pointing to, etc. This is often referred to as the “web
of objects.” Copying the entire mess is called a deep
copy
. You
can see the effect of the shallow copy in the output, where the actions
performed on
v2
affect
v:
v: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
v: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Not
trying to
clone( )
the
objects contained in the
Vector
is
probably a fair assumption because there’s no guarantee that those objects
are
cloneable.
[50]
Adding
cloneability to a class
Even
though the clone method is defined in the base-of-all-classes
Object,
cloning is
not
automatically
available in every class.
[51]
This would seem to be counterintuitive to the idea that base-class methods are
always available in derived classes. Cloning in Java goes against this idea; if
you want it to exist for a class, you must specifically add code to make
cloning work.
Using
a trick with protected
To
prevent default clonability in every class you create, the clone( )
method is
protected
in the base class
Object.
Not only does this mean that it’s not available by default to the client
programmer who is simply using the class (not subclassing it), but it also
means that you cannot call
clone( )
via a handle to the base class. (Although that might seem to be useful in some
situations, such as to polymorphically clone a bunch of
Objects.)
It is in effect a way to give you, at compile time, the information that your
object is not cloneable – and oddly enough most classes in the standard
Java library are not cloneable. Thus, if you say:
Integer x = new Integer(1);
x = x.clone();
You
will get, at compile time, an error message that says
clone( )
is
not accessible (since
Integer
doesn’t override it and it defaults to the
protected
version).
If,
however, you’re in a class derived from
Object
(as
all classes are), then you have permission to call
Object.clone( )
because
it’s protected
and
you’re an inheritor. The base class
clone( )
has
useful functionality – it performs the actual bitwise duplication
of
the derived-class object
,
thus acting as the common cloning operation. However, you then need to make
your
clone operation
public
for it to be accessible. So two key issues when you clone are: virtually always
call super.clone( )
and
make your clone
public. You’ll
probably want to override
clone( )
in
any further derived classes, otherwise your (now
public)
clone( )
will be used, and that might not do the right thing (although, since
Object.clone( )
makes a copy of the actual object, it might). The
protected
trick works only once, the first time you inherit from a class that has no
clonability and you want to make a class that’s cloneable. In any classes
inherited from your class the
clone( )
method
is available since it’s not possible in Java to reduce the access of a
method during derivation. That is, once a class is cloneable, everything
derived from it is cloneable unless you use provided mechanisms (described
later) to “turn off” cloning.
Implementing
the Cloneable interface
There’s
one more thing you need to do to complete the clonability of an object:
implement the Cloneable
interface.
This interface
is a bit strange because it’s empty!
The
reason for implementing this empty
interface
is obviously not because you are going to upcast to
Cloneable
and call one of its methods. The use of
interface
here is considered by some to be a “hack” because it’s using
a feature for something other than its original intent. Implementing the
Cloneable
interface
acts as a kind of a flag, wired into the type of the class.
There
are two reasons for the existence of the
Cloneable
interface.
First, you might have an upcast handle to a base type and not know whether
it’s possible to clone that object. In this case, you can use the
instanceof
keyword (described in Chapter 11) to find out whether the handle is connected
to an object that can be cloned:
if(myHandle
instanceof Cloneable) // ...
The
second reason is that mixed into this design for clonability was the thought
that maybe you didn’t want all types of objects to be cloneable. So
Object.clone( )
verifies that a class implements the
Cloneable
interface. If not, it throws a
CloneNotSupportedException
exception. So in general, you’re forced to
implement
Cloneable
as part of support for cloning.
Successful
cloning
Once
you understand the details of implementing the
clone( )
method, you’re able to create classes that can be easily duplicated to
provide a local copy:
//: LocalCopy.java
// Creating local copies with clone()
import java.util.*;
class MyObject implements Cloneable {
int i;
MyObject(int ii) { i = ii; }
public Object clone() {
Object o = null;
try {
o = super.clone();
} catch (CloneNotSupportedException e) {
System.out.println("MyObject can't clone");
}
return o;
}
public String toString() {
return Integer.toString(i);
}
}
public class LocalCopy {
static MyObject g(MyObject v) {
// Passing a handle, modifies outside object:
v.i++;
return v;
}
static MyObject f(MyObject v) {
v = (MyObject)v.clone(); // Local copy
v.i++;
return v;
}
public static void main(String[] args) {
MyObject a = new MyObject(11);
MyObject b = g(a);
// Testing handle equivalence,
// not object equivalence:
if(a == b)
System.out.println("a == b");
else
System.out.println("a != b");
System.out.println("a = " + a);
System.out.println("b = " + b);
MyObject c = new MyObject(47);
MyObject d = f(c);
if(c == d)
System.out.println("c == d");
else
System.out.println("c != d");
System.out.println("c = " + c);
System.out.println("d = " + d);
}
} ///:~
First
of all,
clone( )
must be accessible so you must make it
public.
Second, for the initial part of your
clone( )
operation you should call the base-class version of
clone( ).
The
clone( )
that’s being called here is the one that’s predefined inside
Object,
and you can call it because it’s
protected
and thereby accessible in derived classes.
Object.clone( )
figures out how big the object is, creates enough memory for a new one, and
copies all the bits from the old to the new. This is called a bitwise
copy
,
and is typically what you’d expect a
clone( )
method to do. But before
Object.clone( )
performs its operations, it first checks to see if a class is
Cloneable,
that is, whether it implements the
Cloneable
interface. If it doesn’t,
Object.clone( )
throws a CloneNotSupportedException
to indicate that you can’t clone it. Thus, you’ve got to surround
your call to
super.clone( )
with a try-catch block, to catch an exception that should never happen (because
you’ve implemented the
Cloneable
interface).
In
LocalCopy,
the two methods
g( )
and
f( )
demonstrate the difference between the two approaches for argument passing.
g( )
shows passing by reference in which it modifies the outside object and returns
a reference to that outside object, while
f( )
clones the argument, thereby decoupling it and leaving the original object
alone. It can then proceed to do whatever it wants, and even to return a handle
to this new object without any ill effects to the original. Notice the somewhat
curious-looking statement:
This
is where the local copy is created. To prevent confusion by such a statement,
remember that this rather strange coding idiom is perfectly feasible in Java
because everything that has a name is actually a handle. So the handle
v
is used to
clone( )
a copy of what it refers to, and this returns a handle to the base type
Object
(because it’s defined that way in
Object.clone( ))
that must then be cast to the proper type.
In
main( ),
the difference between the effects of the two different argument-passing
approaches in the two different methods is tested. The output is:
a == b
a = 12
b = 12
c != d
c = 47
d = 48
It’s
important to notice that the equivalence tests in Java do not look inside the
objects being compared to see if their values are the same. The ==
and
!=
operators are simply comparing the contents of the
handles.
If the addresses inside the handles
are the same, the handles are pointing to the same object and are therefore
“equal.” So what the operators are really testing is whether the
handles are aliased to the same object!
The
effect of Object.clone( )
What
actually happens when Object.clone( )
is called that makes it so essential to call super.clone( )
when you override
clone( )
in your class? The
clone( )
method in the root class is responsible for creating the correct amount of
storage and making the bitwise copy of the bits from the original object into
the new object’s storage. That is, it doesn’t just make storage and
copy an
Object
– it actually figures out the size of the precise object that’s
being copied and duplicates that. Since all this is happening from the code in
the
clone( )
method defined in the root class (that has no idea what’s being inherited
from it), you can guess that the process involves RTTI
to determine the actual object that’s being cloned. This way, the
clone( )
method can create the proper amount of storage and do the correct bitcopy for
that type.
Whatever
you do, the first part of the cloning process should normally be a call to
super.clone( ).
This establishes the groundwork for the cloning operation by making an exact
duplicate. At this point you can perform other operations necessary to complete
the cloning.
To
know for sure what those other operations are, you need to understand exactly
what
Object.clone( )
buys you. In particular, does it automatically clone the destination of all the
handles? The following example tests this:
//: Snake.java
// Tests cloning to see if destination of
// handles are also cloned.
public class Snake implements Cloneable {
private Snake next;
private char c;
// Value of i == number of segments
Snake(int i, char x) {
c = x;
if(--i > 0)
next = new Snake(i, (char)(x + 1));
}
void increment() {
c++;
if(next != null)
next.increment();
}
public String toString() {
String s = ":" + c;
if(next != null)
s += next.toString();
return s;
}
public Object clone() {
Object o = null;
try {
o = super.clone();
} catch (CloneNotSupportedException e) {}
return o;
}
public static void main(String[] args) {
Snake s = new Snake(5, 'a');
System.out.println("s = " + s);
Snake s2 = (Snake)s.clone();
System.out.println("s2 = " + s2);
s.increment();
System.out.println(
"after s.increment, s2 = " + s2);
}
} ///:~
A
Snake
is made up of a bunch of segments, each of type
Snake.
Thus, it’s a singly-linked list. The segments are created recursively,
decrementing the first constructor argument for each segment until zero is
reached. To give each segment a unique tag, the second argument, a
char,
is incremented for each recursive constructor call.
The
increment( )
method recursively increments each tag so you can see the change, and the
toString( )
recursively prints each tag. The output is:
s = :a:b:c:d:e
s2 = :a:b:c:d:e
after s.increment, s2 = :a:c:d:e:f
This
means that only the first segment is duplicated by
Object.clone( ),
so it does a shallow
copy. If you want the whole snake to be duplicated – a deep
copy – you must perform the additional operations inside your overridden
clone( ). You’ll
typically call
super.clone( )
in any class derived from a cloneable class to make sure that all of the
base-class operations (including
Object.clone( ))
take place. This is followed by an explicit call to
clone( )
for
every handle in your object; otherwise those handles will be aliased to those
of the original object. It’s analogous to the way constructors are called
– base-class constructor first, then the next-derived constructor, and so
on to the most-derived constructor. The difference is that
clone( )
is not a constructor so there’s nothing to make it happen automatically.
You must make sure to do it yourself.
Cloning
a composed object
There’s
a problem you’ll encounter when trying to deep copy a composed object.
You must assume that the
clone( )
method in the member objects will in turn perform a deep copy on
their
handles, and so on. This is quite a commitment. It effectively means that for a
deep copy to work you must either control all of the code in all of the
classes, or at least have enough knowledge about all of the classes involved in
the deep copy to know that they are performing their own deep copy correctly.
This
example shows what you must do to accomplish a deep copy when dealing with a
composed object:
//: DeepCopy.java
// Cloning a composed object
class DepthReading implements Cloneable {
private double depth;
public DepthReading(double depth) {
this.depth = depth;
}
public Object clone() {
Object o = null;
try {
o = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
}
class TemperatureReading implements Cloneable {
private long time;
private double temperature;
public TemperatureReading(double temperature) {
time = System.currentTimeMillis();
this.temperature = temperature;
}
public Object clone() {
Object o = null;
try {
o = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
}
class OceanReading implements Cloneable {
private DepthReading depth;
private TemperatureReading temperature;
public OceanReading(double tdata, double ddata){
temperature = new TemperatureReading(tdata);
depth = new DepthReading(ddata);
}
public Object clone() {
OceanReading o = null;
try {
o = (OceanReading)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
// Must clone handles:
o.depth = (DepthReading)o.depth.clone();
o.temperature =
(TemperatureReading)o.temperature.clone();
return o; // Upcasts back to Object
}
}
public class DeepCopy {
public static void main(String[] args) {
OceanReading reading =
new OceanReading(33.9, 100.5);
// Now clone it:
OceanReading r =
(OceanReading)reading.clone();
}
} ///:~
DepthReading
and
TemperatureReading
are quite similar; they both contain only primitives. Therefore, the
clone( )
method can be quite simple: it calls
super.clone( )
and returns the result. Note that the
clone( )
code for both classes is identical.
OceanReading
is composed of
DepthReading
and
TemperatureReading
objects and so, to produce a deep copy, its
clone( )
must clone the handles inside
OceanReading.
To accomplish this, the result of
super.clone( )
must be cast to an
OceanReading
object (so you can access the
depth
and
temperature
handles).
A
deep copy with Vector
Let’s
revisit the Vector
example from earlier in this chapter. This time the
Int2
class is cloneable so the
Vector
can be deep copied:
//: AddingClone.java
// You must go through a few gyrations to
// add cloning to your own class.
import java.util.*;
class Int2 implements Cloneable {
private int i;
public Int2(int ii) { i = ii; }
public void increment() { i++; }
public String toString() {
return Integer.toString(i);
}
public Object clone() {
Object o = null;
try {
o = super.clone();
} catch (CloneNotSupportedException e) {
System.out.println("Int2 can't clone");
}
return o;
}
}
// Once it's cloneable, inheritance
// doesn't remove cloneability:
class Int3 extends Int2 {
private int j; // Automatically duplicated
public Int3(int i) { super(i); }
}
public class AddingClone {
public static void main(String[] args) {
Int2 x = new Int2(10);
Int2 x2 = (Int2)x.clone();
x2.increment();
System.out.println(
"x = " + x + ", x2 = " + x2);
// Anything inherited is also cloneable:
Int3 x3 = new Int3(7);
x3 = (Int3)x3.clone();
Vector v = new Vector();
for(int i = 0; i < 10; i++ )
v.addElement(new Int2(i));
System.out.println("v: " + v);
Vector v2 = (Vector)v.clone();
// Now clone each element:
for(int i = 0; i < v.size(); i++)
v2.setElementAt(
((Int2)v2.elementAt(i)).clone(), i);
// Increment all v2's elements:
for(Enumeration e = v2.elements();
e.hasMoreElements(); )
((Int2)e.nextElement()).increment();
// See if it changed v's elements:
System.out.println("v: " + v);
System.out.println("v2: " + v2);
}
} ///:~
Int3
is inherited from
Int2
and a new primitive member
int
j
is
added. You might think that you’d need to override
clone( )
again to make sure
j
is copied, but that’s not the case. When
Int2’s
clone( )
is called as
Int3’s
clone( ),
it calls
Object.clone( ),
which determines that it’s working with an
Int3
and duplicates all the bits in the
Int3.
As long as you don’t add handles that need to be cloned, the one call to
Object.clone( )
performs all of the necessary duplication, regardless of how far down in the
hierarchy
clone( )
is defined.
You
can see what’s necessary in order to do a deep copy of a
Vector:
after the
Vector
is cloned, you have to step through and clone each one of the objects pointed
to by the
Vector.
You’d have to do something similar to this to do a deep copy of a
Hashtable. The
remainder of the example shows that the cloning did happen by showing that,
once an object is cloned, you can change it and the original object is left
untouched.
Deep
copy via serialization
When
you consider Java 1.1
object serialization (introduced in Chapter 10), you might observe that an
object that’s serialized and then deserialized is, in effect, cloned.
So
why not use serialization
to perform deep copying? Here’s an example that compares the two
approaches by timing them:
//: Compete.java
import java.io.*;
class Thing1 implements Serializable {}
class Thing2 implements Serializable {
Thing1 o1 = new Thing1();
}
class Thing3 implements Cloneable {
public Object clone() {
Object o = null;
try {
o = super.clone();
} catch (CloneNotSupportedException e) {
System.out.println("Thing3 can't clone");
}
return o;
}
}
class Thing4 implements Cloneable {
Thing3 o3 = new Thing3();
public Object clone() {
Thing4 o = null;
try {
o = (Thing4)super.clone();
} catch (CloneNotSupportedException e) {
System.out.println("Thing4 can't clone");
}
// Clone the field, too:
o.o3 = (Thing3)o3.clone();
return o;
}
}
public class Compete {
static final int SIZE = 5000;
public static void main(String[] args) {
Thing2[] a = new Thing2[SIZE];
for(int i = 0; i < a.length; i++)
a[i] = new Thing2();
Thing4[] b = new Thing4[SIZE];
for(int i = 0; i < b.length; i++)
b[i] = new Thing4();
try {
long t1 = System.currentTimeMillis();
ByteArrayOutputStream buf =
new ByteArrayOutputStream();
ObjectOutputStream o =
new ObjectOutputStream(buf);
for(int i = 0; i < a.length; i++)
o.writeObject(a[i]);
// Now get copies:
ObjectInputStream in =
new ObjectInputStream(
new ByteArrayInputStream(
buf.toByteArray()));
Thing2[] c = new Thing2[SIZE];
for(int i = 0; i < c.length; i++)
c[i] = (Thing2)in.readObject();
long t2 = System.currentTimeMillis();
System.out.println(
"Duplication via serialization: " +
(t2 - t1) + " Milliseconds");
// Now try cloning:
t1 = System.currentTimeMillis();
Thing4[] d = new Thing4[SIZE];
for(int i = 0; i < d.length; i++)
d[i] = (Thing4)b[i].clone();
t2 = System.currentTimeMillis();
System.out.println(
"Duplication via cloning: " +
(t2 - t1) + " Milliseconds");
} catch(Exception e) {
e.printStackTrace();
}
}
} ///:~
Thing2
and
Thing4
contain member objects so that there’s some deep copying going on.
It’s interesting to notice that while
Serializable
classes are easy to set up, there’s much more work going on to duplicate
them. Cloning involves a lot of work to set up the class, but the actual
duplication of objects is relatively simple. The results really tell the tale.
Here is the output from three different runs:
Duplication via serialization: 3400 Milliseconds
Duplication via cloning: 110 Milliseconds
Duplication via serialization: 3410 Milliseconds
Duplication via cloning: 110 Milliseconds
Duplication via serialization: 3520 Milliseconds
Duplication via cloning: 110 Milliseconds
Despite
the obviously huge time difference between serialization and cloning,
you’ll also notice that the serialization technique seems to vary
significantly in its duration, while cloning takes the same amount of time
every time.
Adding
cloneability
further
down a hierarchy
If
you create a new class, its base class defaults to
Object,
which defaults to non-clonability (as you’ll see in the next section). As
long as you don’t explicitly add clonability, you won’t get it. But
you can add it in at any layer and it will then be cloneable from that layer
downward, like this:
//: HorrorFlick.java
// You can insert Cloneability at any
// level of inheritance.
import java.util.*;
class Person {}
class Hero extends Person {}
class Scientist extends Person
implements Cloneable {
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
// this should never happen:
// It's Cloneable already!
throw new InternalError();
}
}
}
class MadScientist extends Scientist {}
public class HorrorFlick {
public static void main(String[] args) {
Person p = new Person();
Hero h = new Hero();
Scientist s = new Scientist();
MadScientist m = new MadScientist();
// p = (Person)p.clone(); // Compile error
// h = (Hero)h.clone(); // Compile error
s = (Scientist)s.clone();
m = (MadScientist)m.clone();
}
} ///:~
Before
clonability was added, the compiler stopped you from trying to clone things.
When clonability is added in
Scientist,
then
Scientist
and all its descendants are cloneable.
Why
this strange design?
If
all this seems to be a strange scheme, that’s because it is. You might
wonder why it worked out this way. What is the meaning behind this design? What
follows is not a substantiated story – probably because much of the
marketing around Java makes it out to be a perfectly-designed language –
but it does go a long way toward explaining how things ended up the way they did.
Originally,
Java was designed as a language to control hardware boxes, and definitely not
with the Internet in mind. In a general-purpose language like this, it makes
sense that the programmer be able to clone any object. Thus,
clone( )
was placed in the root class
Object,
but
it was a
public
method so you could always clone any object. This seemed to be the most
flexible approach, and after all, what could it hurt?
Well,
when Java was seen as the ultimate Internet programming language, things
changed. Suddenly, there are security issues, and of course, these issues are
dealt with using objects, and you don’t necessarily want anyone to be
able to clone your security objects. So what you’re seeing is a lot of
patches applied on the original simple and straightforward scheme:
clone( )
is now
protected
in
Object.
You must override it
and
implement
Cloneable
and
deal with the exceptions.
It’s
worth noting that you must use the
Cloneable
interface
only
if
you’re going to call
Object’s
clone( ),
method, since that method checks at run-time to make sure that your class
implements
Cloneable.
But for consistency (and since
Cloneable
is empty anyway) you should implement it.
[49]
In C, which generally handles small bits of data, the default is pass-by-value.
C++ had to follow this form, but with objects pass-by-value isn’t usually
the most efficient way. In addition, coding classes to support pass-by-value in
C++ is a big headache.
[50]
This is not the dictionary spelling of the word, but it’s what is used in
the Java library, so I’ve used it here, too, in some hopes of reducing
confusion.
[51]
You can apparently create a simple counter-example to this statement, like this:
public
class Cloneit implements Cloneable {
public static void main (String[] args)
throws CloneNotSupportedException {
Cloneit a = new Cloneit();
Cloneit b = (Cloneit)a.clone();
}
}
However,
this only works because
main( )
is a method of
Cloneit
and thus has permission to call the
protected
base-class method
clone( ).
If you call it from a different class, it won’t compile.