|
|||||
Here's
an example that shows the
various ways cloning can
be
implemented
and then, later in the
hierarchy, "turned
off":
//:
appendixa:CheckCloneable.java
// Checking to see
if a reference can be cloned.
// Can't clone this
because it doesn't
//
override clone():
class
Ordinary {}
//
Overrides clone, but doesn't implement
//
Cloneable:
class
WrongClone extends Ordinary {
public
Object clone()
throws
CloneNotSupportedException {
return
super.clone(); // Throws exception
}
}
//
Does all the right things for cloning:
class
IsCloneable extends Ordinary
implements
Cloneable {
public
Object clone()
throws
CloneNotSupportedException {
return
super.clone();
}
}
//
Turn off cloning by throwing the exception:
class
NoMore extends IsCloneable {
public
Object clone()
throws
CloneNotSupportedException {
throw
new CloneNotSupportedException();
}
}
class
TryMore extends NoMore {
public
Object clone()
throws
CloneNotSupportedException {
//
Calls NoMore.clone(), throws exception:
return
super.clone();
1038
Thinking
in Java
}
}
class
BackOn extends NoMore {
private
BackOn duplicate(BackOn b) {
//
Somehow make a copy of b
//
and return that copy. This is a dummy
//
copy, just to make the point:
return
new BackOn();
}
public
Object clone() {
//
Doesn't call NoMore.clone():
return
duplicate(this);
}
}
//
Can't inherit from this, so can't override
//
the clone method like in BackOn:
final
class ReallyNoMore extends NoMore {}
public
class CheckCloneable {
static
Ordinary tryToClone(Ordinary ord) {
String
id = ord.getClass().getName();
Ordinary
x = null;
if(ord
instanceof Cloneable) {
try
{
System.out.println("Attempting
" + id);
x
= (Ordinary)((IsCloneable)ord).clone();
System.out.println("Cloned
" + id);
}
catch(CloneNotSupportedException e) {
System.err.println("Could
not clone "+id);
}
}
return
x;
}
public
static void main(String[] args) {
//
Upcasting:
Ordinary[]
ord = {
new
IsCloneable(),
new
WrongClone(),
new
NoMore(),
Appendix
A: Passing & Returning
Objects
1039
new
TryMore(),
new
BackOn(),
new
ReallyNoMore(),
};
Ordinary
x = new Ordinary();
//
This won't compile, since clone() is
//
protected in Object:
//!
x = (Ordinary)x.clone();
//
tryToClone() checks first to see if
//
a class implements Cloneable:
for(int
i = 0; i < ord.length; i++)
tryToClone(ord[i]);
}
}
///:~
The
first class, Ordinary,
represents the kinds of
classes we've seen
throughout
this book: no support for
cloning, but as it turns
out, no
prevention
of cloning either. But if
you have a reference to an
Ordinary
object
that might have been
upcast from a more derived
class, you can't
tell
if it can be cloned or
not.
The
class WrongClone
shows
an incorrect way to implement
cloning. It
does
override Object.clone(
) and
makes that method public,
but it
doesn't
implement Cloneable,
so when super.clone(
) is
called (which
results
in a call to Object.clone(
)),
CloneNotSupportedException
is
thrown so the cloning
doesn't work.
In
IsCloneable
you
can see all the
right actions performed for
cloning:
clone(
) is
overridden and Cloneable
is
implemented. However,
this
clone(
) method
and several others that
follow in this example
do
not
catch
CloneNotSupportedException,
but
instead pass it through
to
the
caller, who must then
put a try-catch block around
it. In your own
clone(
) methods
you will typically
catch
CloneNotSupportedException
inside
clone( )
rather
than passing it
through.
As you'll see, in this
example it's more
informative to pass
the
exceptions
through.
Class
NoMore
attempts
to "turn off" cloning in the
way that the
Java
designers
intended: in the derived
class clone(
),
you throw
CloneNotSupportedException.
The clone(
) method
in class
1040
Thinking
in Java
TryMore
properly
calls super.clone(
),
and this resolves to
NoMore.clone(
), which
throws an exception and
prevents cloning.
But
what if the programmer
doesn't follow the "proper"
path of calling
super.clone(
) inside
the overridden clone(
) method?
In BackOn,
you
can see how this
can happen. This class
uses a separate
method
duplicate(
) to
make a copy of the current
object and calls this
method
inside
clone( )
instead
of
calling super.clone(
).
The exception is
never
thrown
and the new class is
cloneable. You can't rely on
throwing an
exception
to prevent making a cloneable
class. The only sure-fire
solution
is
shown in ReallyNoMore,
which is final
and
thus cannot be
inherited.
That
means if clone(
) throws
an exception in the final
class,
it cannot
be
modified with inheritance
and the prevention of
cloning is assured.
(You
cannot explicitly call
Object.clone(
) from
a class that has an
arbitrary
level of inheritance; you
are limited to calling
super.clone(
),
which
has access to only the
direct base class.) Thus, if
you make any
objects
that involve security
issues, you'll want to make
those classes
final.
The
first method you see in
class CheckCloneable
is
tryToClone(
),
which
takes any Ordinary
object
and checks to see whether
it's cloneable
with
instanceof.
If so, it casts the object
to an IsCloneable,
calls
clone(
) and
casts the result back to
Ordinary,
catching any
exceptions
that
are thrown. Notice the
use of run-time type
identification (see
Chapter
12) to print the class
name so you can see
what's happening.
In
main(
),
different types of Ordinary
objects
are created and upcast
to
Ordinary
in
the array definition. The
first two lines of code
after that
create
a plain Ordinary
object
and try to clone it.
However, this code
will
not
compile because clone(
) is
a protected
method
in Object.
The
remainder
of the code steps through
the array and tries to
clone each
object,
reporting the success or
failure of each. The output
is:
Attempting
IsCloneable
Cloned
IsCloneable
Attempting
NoMore
Could
not clone NoMore
Attempting
TryMore
Could
not clone TryMore
Attempting
BackOn
Appendix
A: Passing & Returning
Objects
1041
Cloned
BackOn
Attempting
ReallyNoMore
Could
not clone ReallyNoMore
So
to summarize, if you want a
class to be cloneable:
1.
Implement
the Cloneable
interface.
2.
Override
clone(
).
3.
Call
super.clone(
) inside
your clone(
).
4.
Capture
exceptions inside your
clone(
).
This
will produce the most
convenient effects.
The
copy constructor
Cloning
can seem to be a complicated
process to set up. It might
seem like
there
should be an alternative. One
approach that might occur to
you
(especially
if you're a C++ programmer) is to
make a special
constructor
whose
job it is to duplicate an object. In
C++, this is called the
copy
constructor.
At first, this seems like
the obvious solution, but in
fact it
doesn't
work. Here's an
example:
//:
appendixa:CopyConstructor.java
//
A constructor for copying an object of the same
//
type, as an attempt to create a local copy.
class
FruitQualities {
private
int weight;
private
int color;
private
int firmness;
private
int ripeness;
private
int smell;
//
etc.
FruitQualities()
{ // Default constructor
//
do something meaningful...
}
//
Other constructors:
//
...
//
Copy constructor:
FruitQualities(FruitQualities
f) {
1042
Thinking
in Java
weight
= f.weight;
color
= f.color;
firmness
= f.firmness;
ripeness
= f.ripeness;
smell
= f.smell;
//
etc.
}
}
class
Seed {
//
Members...
Seed()
{ /* Default constructor */ }
Seed(Seed
s) { /* Copy constructor */ }
}
class
Fruit {
private
FruitQualities fq;
private
int seeds;
private
Seed[] s;
Fruit(FruitQualities
q, int seedCount) {
fq
= q;
seeds
= seedCount;
s
= new Seed[seeds];
for(int
i = 0; i < seeds; i++)
s[i]
= new Seed();
}
//
Other constructors:
//
...
//
Copy constructor:
Fruit(Fruit
f) {
fq
= new FruitQualities(f.fq);
seeds
= f.seeds;
//
Call all Seed copy-constructors:
for(int
i = 0; i < seeds; i++)
s[i]
= new Seed(f.s[i]);
//
Other copy-construction
activities...
}
//
To allow derived constructors (or other
//
methods) to put in different qualities:
protected
void addQualities(FruitQualities q) {
fq
= q;
Appendix
A: Passing & Returning
Objects
1043
}
protected
FruitQualities getQualities() {
return
fq;
}
}
class
Tomato extends Fruit {
Tomato()
{
super(new
FruitQualities(), 100);
}
Tomato(Tomato
t) { // Copy-constructor
super(t);
// Upcast for base copy-constructor
//
Other copy-construction
activities...
}
}
class
ZebraQualities extends FruitQualities
{
private
int stripedness;
ZebraQualities()
{ // Default constructor
//
do something meaningful...
}
ZebraQualities(ZebraQualities
z) {
super(z);
stripedness
= z.stripedness;
}
}
class
GreenZebra extends Tomato {
GreenZebra()
{
addQualities(new
ZebraQualities());
}
GreenZebra(GreenZebra
g) {
super(g);
// Calls Tomato(Tomato)
//
Restore the right qualities:
addQualities(new
ZebraQualities());
}
void
evaluate() {
ZebraQualities
zq =
(ZebraQualities)getQualities();
//
Do something with the qualities
//
...
1044
Thinking
in Java
}
}
public
class CopyConstructor {
public
static void ripen(Tomato t) {
//
Use the "copy constructor":
t
= new Tomato(t);
System.out.println("In
ripen, t is a " +
t.getClass().getName());
}
public
static void slice(Fruit f) {
f
= new Fruit(f); // Hmmm... will this work?
System.out.println("In
slice, f is a " +
f.getClass().getName());
}
public
static void main(String[] args) {
Tomato
tomato = new Tomato();
ripen(tomato);
// OK
slice(tomato);
// OOPS!
GreenZebra
g = new GreenZebra();
ripen(g);
// OOPS!
slice(g);
// OOPS!
g.evaluate();
}
}
///:~
This
seems a bit strange at
first. Sure, fruit has
qualities, but why not
just
put
data members representing
those qualities directly
into the Fruit
class?
There are two potential
reasons. The first is that
you might want to
easily
insert or change the
qualities. Note that
Fruit has
a protected
addQualities(
) method
to allow derived classes to do
this. (You might
think
the logical thing to do is to
have a protected
constructor
in Fruit
that
takes a FruitQualities
argument,
but constructors don't
inherit so it
wouldn't
be available in second or greater
level classes.) By making
the
fruit
qualities into a separate
class, you have greater
flexibility, including
the
ability to change the
qualities midway through the
lifetime of a
particular
Fruit object.
The
second reason for making
FruitQualities
a
separate object is in
case
you
want to add new qualities or
to change the behavior via
inheritance
and
polymorphism. Note that for
GreenZebra
(which
really
is a
type of
Appendix
A: Passing & Returning
Objects
1045
tomato--I've
grown them and they're
fabulous), the constructor
calls
addQualities(
) and
passes it a ZebraQualities
object,
which is
derived
from FruitQualities
so
it can be attached to the
FruitQualities
reference
in the base class. Of
course, when GreenZebra
uses
the
FruitQualities
it
must downcast it to the
correct type (as seen
in
evaluate(
)),
but it always knows that
type is ZebraQualities.
You'll
also see that there's a
Seed
class,
and that Fruit
(which
by
definition
carries its own
seeds)4 contains
an array of Seeds.
Finally,
notice that each class
has a copy constructor, and
that each copy
constructor
must take care to call
the copy constructors for
the base class
and
member objects to produce a
deep copy. The copy
constructor is
tested
inside the class CopyConstructor.
The method ripen(
) takes
a
Tomato
argument
and performs copy-construction on it in
order to
duplicate
the object:
t
= new Tomato(t);
while
slice( )
takes
a more generic Fruit
object
and also duplicates
it:
f
= new Fruit(f);
These
are tested with different
kinds of Fruit
in
main(
).
Here's the
output:
In
ripen,
t
is
a
Tomato
In
slice,
f
is
a
Fruit
In
ripen,
t
is
a
Tomato
In
slice,
f
is
a
Fruit
This
is where the problem shows
up. After the
copy-construction that
happens
to the Tomato
inside
slice(
),
the result is no longer a
Tomato
object,
but just a Fruit.
It has lost all of its
tomato-ness. Further,
when
you
take a GreenZebra,
both ripen(
) and
slice( )
turn
it into a
Tomato
and
a Fruit,
respectively. Thus, unfortunately,
the copy
constructor
scheme is no good to us in Java
when attempting to make
a
local
copy of an object.
4
Except for the
poor avocado, which has been
reclassified to simply "fat."
1046
Thinking
in Java
Why
does it work in C++ and not
Java?
The
copy constructor is a fundamental
part of C++, since it
automatically
makes
a local copy of an object.
Yet the example above
proves that it does
not
work for Java. Why? In
Java everything that we
manipulate is a
reference,
while in C++ you can
have reference-like entities
and you can
also
pass
around the objects directly.
That's what the C++
copy
constructor
is for: when you want to
take an object and pass it
in by value,
thus
duplicating the object. So it
works fine in C++, but
you should keep
in
mind that this scheme
fails in Java, so don't use
it.
Read-only
classes
While
the local copy produced by
clone( )
gives
the desired results in
the
appropriate
cases, it is an example of forcing
the programmer (the
author
of
the method) to be responsible
for preventing the ill
effects of aliasing.
What
if you're making a library
that's so general purpose
and commonly
used
that you cannot make
the assumption that it will
always be cloned in
the
proper places? Or more
likely, what if you
want
to
allow aliasing for
efficiency--to
prevent the needless
duplication of objects--but you
don't
want
the negative side effects of
aliasing?
One
solution is to create immutable
objects which
belong to read-only
classes.
You can define a class
such that no methods in the
class cause
changes
to the internal state of the
object. In such a class,
aliasing has no
impact
since you can read
only the internal state, so
if many pieces of
code
are
reading the same object
there's no problem.
As
a simple example of immutable
objects, Java's standard
library
contains
"wrapper" classes for all
the primitive types. You
might have
already
discovered that, if you want
to store an int
inside
a container such
as
an ArrayList
(which
takes only Object
references),
you can wrap
your
int inside
the standard library
Integer
class:
//:
appendixa:ImmutableInteger.java
//
The Integer class cannot be changed.
import
java.util.*;
public
class ImmutableInteger {
Appendix
A: Passing & Returning
Objects
1047
public
static void main(String[] args) {
ArrayList
v = new ArrayList();
for(int
i = 0; i < 10; i++)
v.add(new
Integer(i));
//
But how do you change the int
//
inside the Integer?
}
}
///:~
The
Integer
class
(as well as all the
primitive "wrapper"
classes)
implements
immutability in a simple fashion:
they have no methods
that
allow
you to change the
object.
If
you do need an object that
holds a primitive type that
can be modified,
you
must create it yourself.
Fortunately, this is
trivial:
//:
appendixa:MutableInteger.java
//
A changeable wrapper class.
import
java.util.*;
class
IntValue {
int
n;
IntValue(int
x) { n = x; }
public
String toString() {
return
Integer.toString(n);
}
}
public
class MutableInteger {
public
static void main(String[] args) {
ArrayList
v = new ArrayList();
for(int
i = 0; i < 10; i++)
v.add(new
IntValue(i));
System.out.println(v);
for(int
i = 0; i < v.size(); i++)
((IntValue)v.get(i)).n++;
System.out.println(v);
}
}
///:~
Note
that n
is
friendly to simplify
coding.
1048
Thinking
in Java
IntValue
can be
even simpler if the default
initialization to zero is
adequate
(then you don't need
the constructor) and you
don't care about
printing
it out (then you don't
need the toString(
)):
class
IntValue { int n; }
Fetching
the element out and
casting it is a bit awkward,
but that's a
feature
of ArrayList,
not
of IntValue.
Creating
read-only classes
It's
possible to create your own
read-only class. Here's an
example:
//:
appendixa:Immutable1.java
//
Objects that cannot be modified
//
are immune to aliasing.
public
class Immutable1 {
private
int data;
public
Immutable1(int initVal) {
data
= initVal;
}
public
int read() { return data; }
public
boolean nonzero() { return data != 0; }
public
Immutable1 quadruple() {
return
new Immutable1(data * 4);
}
static
void f(Immutable1 i1) {
Immutable1
quad = i1.quadruple();
System.out.println("i1
= " + i1.read());
System.out.println("quad
= " + quad.read());
}
public
static void main(String[] args) {
Immutable1
x = new Immutable1(47);
System.out.println("x
= " + x.read());
f(x);
System.out.println("x
= " + x.read());
}
}
///:~
All
data is private,
and you'll see that
none of the public
methods
modify
that data. Indeed, the
method that does appear to
modify an
Appendix
A: Passing & Returning
Objects
1049
Table of Contents:
|
|||||