CATEGORII DOCUMENTE |
Asp | Autocad | C | Dot net | Excel | Fox pro | Html | Java |
Linux | Mathcad | Photoshop | Php | Sql | Visual studio | Windows | Xml |
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. Java does not, but it allows you to produce this effect.
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.
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
public void increment()
public String toString()
}
public class Cloning
} ///:~
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.
Even though the clone method is defined in the base-of-all-classes Object, cloning is not automatically available in every class. 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.
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.
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!
interface Cloneable
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.
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
public Object clone() catch (CloneNotSupportedException e)
return o;
}
public String toString()
}
public class LocalCopy
static MyObject f(MyObject v)
public static void main(String[] args)
} ///:~
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:
v = (MyObject)v.clone();
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!
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
void increment()
public String toString()
public Object clone() catch (CloneNotSupportedException e)
return o;
}
public static void main(String[] args)
} ///:~
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.
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
public Object clone() catch (CloneNotSupportedException e)
return o;
}
}
class TemperatureReading implements Cloneable
public Object clone() catch (CloneNotSupportedException e)
return o;
}
}
class OceanReading implements Cloneable
public Object clone() catch (CloneNotSupportedException e)
// Must clone handles:
o.depth = (DepthReading)o.depth.clone();
o.temperature =
(TemperatureReading)o.temperature.clone();
return o; // Upcasts back to Object
}
}
public class DeepCopy
} ///:~
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).
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
public void increment()
public String toString()
public Object clone() catch (CloneNotSupportedException e)
return o;
}
}
// Once it's cloneable, inheritance
// doesn't remove cloneability:
class Int3 extends Int2
}
public class AddingClone
} ///:~
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.
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
class Thing3 implements Cloneable catch (CloneNotSupportedException e)
return o;
}
class Thing4 implements Cloneable catch (CloneNotSupportedException e)
// Clone the field, too:
o.o3 = (Thing3)o3.clone();
return o;
}
public class Compete catch(Exception e)
}
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.
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 catch (CloneNotSupportedException e)
}
}
class MadScientist extends Scientist
public class HorrorFlick
} ///:~
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.
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.
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.
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.
You can apparently create a simple counter example
to this statement, like this:
public class Cloneit implements Cloneable
}
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.
Politica de confidentialitate | Termeni si conditii de utilizare |
Vizualizari: 700
Importanta:
Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved