ZeePedia

B: The Java Native Interface (JNI):Calling a native method, the JNIEnv argument

<< A: Passing & Returning Objects:Aliasing, Making local copies, Cloning objects
Java Programming Guidelines:Design, Implementation >>
img
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
img
}
}
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
img
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
img
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
img
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
img
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
img
}
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
img
}
}
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
img
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
img
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
img
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
img
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:
  1. Introduction to Objects:The progress of abstraction, An object has an interface
  2. Everything is an Object:You manipulate objects with references, Your first Java program
  3. Controlling Program Flow:Using Java operators, Execution control, true and false
  4. Initialization & Cleanup:Method overloading, Member initialization
  5. Hiding the Implementation:the library unit, Java access specifiers, Interface and implementation
  6. Reusing Classes:Composition syntax, Combining composition and inheritance
  7. Polymorphism:Upcasting revisited, The twist, Designing with inheritance
  8. Interfaces & Inner Classes:Extending an interface with inheritance, Inner class identifiers
  9. Holding Your Objects:Container disadvantage, List functionality, Map functionality
  10. Error Handling with Exceptions:Basic exceptions, Catching an exception
  11. The Java I/O System:The File class, Compression, Object serialization, Tokenizing input
  12. Run-time Type Identification:The need for RTTI, A class method extractor
  13. Creating Windows & Applets:Applet restrictions, Running applets from the command line
  14. Multiple Threads:Responsive user interfaces, Sharing limited resources, Runnable revisited
  15. Distributed Computing:Network programming, Servlets, CORBA, Enterprise JavaBeans
  16. A: Passing & Returning Objects:Aliasing, Making local copies, Cloning objects
  17. B: The Java Native Interface (JNI):Calling a native method, the JNIEnv argument
  18. Java Programming Guidelines:Design, Implementation
  19. Resources:Software, Books, My own list of books
  20. Index