J D C T E C H T I P S
TIPS, TECHNIQUES, AND SAMPLE CODE
WELCOME to the Java Developer Connection(sm) (JDC) Tech Tips,
June 12, 2001. This issue covers:
* Abstract Classes
* Using Peer Classes With the Java(tm) Native
Interface
These tips were developed using Java 2 SDK, Standard Edition,
v 1.3.
You can view this issue of the Tech Tips on the Web at
http://java.sun.com/jdc/JDCTechTips/2001/tt0612.html
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ABSTRACT CLASSES
If you've done much work in the Java programming language, you've
probably seen programs that use the "abstract" keyword to declare
classes and methods. What is an abstract class, and when would
you want to use one? This tip discusses some of the basics of
these classes, and then presents a couple of examples.
The key idea with an abstract class is that it's useful when
(a) there is common functionality that you'd like to implement in
a superclass, and (b) some behavior is unique to specific classes
and cannot be factored into the superclass. So you implement the
superclass as an abstract class, and define methods that
subclasses have in common. Then you implement each subclass by
extending the abstract class, and add in the methods unique to
that class. The abstract class provides a "contract" of sorts that
specifies behavior that must be implemented in a subclass.
Let's consider an example of an abstract class:
abstract class AbstractClass1 {
protected AbstractClass1() {
System.out.println(
"AbstractClass1 constructor called");
}
public abstract void distinct_method();
public void common_method() {
System.out.println("common_method called");
distinct_method();
}
};
class ConcreteClass extends AbstractClass1 {
public void distinct_method() {
System.out.println("distinct_method called");
}
}
public class AbstractDemo1 {
public static void main(String args[]) {
AbstractClass1 ref;
//ref = new AbstractClass1();
ref = new ConcreteClass();
ref.common_method();
}
}
AbstractClass1 is an abstract class, with a constructor and two
methods. One of the methods, common_method, is defined in the
class, and represents shared functionality applicable to
subclasses of AbstractClass1. The other method, distinct_method,
is not defined in the class, and must be implemented in
a subclass. Note that common_method calls distinct_method.
It's not possible to create a new instance of an abstract class.
If you uncomment "new AbstractClass1()" above, you get a compile
error. Since some of the methods in the abstract class are not
defined, there would be no implementation to rely on if you had
an object of such a class. The same consideration applies if you
try to use Class.newInstance to create a new instance of an
abstract class represented by a Class object. You can, however,
use the abstract class type to hold a reference to an object of
the subclass type, similar to the way you use an interface type.
When you define an abstract class, you need to use "abstract" for
the class declaration and each abstract method. Also, notice that
in the AbstractDemo1 example, the constructor in the abstract
class is explicit. Otherwise, the usual rules would apply about
implicit and generated constructors up through the class
hierarchy.
Here's another example of an abstract class:
abstract class AbstractClass2 {
public abstract void runtest();
public void driver() {
System.out.println("driver called");
runtest();
}
}
public class AbstractDemo2 extends AbstractClass2 {
public void runtest() {
System.out.println("runtest called");
}
public static void main(String args[]) {
new AbstractDemo2().driver();
}
}
This example shows how you can use abstract classes to build
a software testing framework. You have an abstract class with
a driver method that handles issues such as test case timing and
error reporting. The user of your class extends it to actually
provide the method that runs the test.
Let's look at one final example that's a little more involved.
Suppose you're working on an application where you need to
represent geometric rectangles, and you're using legacy data with
several different data representations. One approach to
representing a rectangle uses the X,Y origin plus the width and
height. Another approach uses two X,Y pairs for the origin and the
maximum value at the opposite corner of the rectangle. You want to
to have a common interface to these various representations. How
can you do that?
One approach to this problem is similar to that taken by the Java
collection classes such as List and ArrayList. The approach is to
define an interface, define an abstract class that implements the
interface and has common functionality, and then code (all
methods implemented) subclasses of the abstract class that define
representation-specific behavior.
The code looks like this:
// Rect interface, describing methods that must be
// implemented
interface Rect {
int getXLo();
int getXHi();
int getYLo();
int getYHi();
int getWidth();
int getHeight();
boolean contains(int x, int y);
String toString();
};
// abstract class that implements common methods and
// leaves other methods to be implemented in subclasses
abstract class AbstractRect implements Rect {
// these methods are implemented in subclasses
abstract public int getXLo();
abstract public int getYLo();
abstract public int getXHi();
abstract public int getYHi();
// get the width of the rectangle
public int getWidth() {
return getXHi() - getXLo();
}
// get the height of the rectangle
public int getHeight() {
return getYHi() - getYLo();
}
// see if rectangle contains a particular point
public boolean contains(int x, int y) {
return x >= getXLo() && x <= getXHi() &&
y >= getYLo() && y <= getYHi();
}
// convert to [xlo,ylo,xhi,yhi]
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("[");
sb.append(getXLo());
sb.append(",");
sb.append(getYLo());
sb.append(",");
sb.append(getXHi());
sb.append(",");
sb.append(getYHi());
sb.append("]");
return sb.toString();
}
}
// implementation of AbstractRect that represents
// a rectangle as an upper left origin plus width/height
class RectWidthHeight extends AbstractRect {
private int x;
private int y;
private int w;
private int h;
public RectWidthHeight(int x, int y, int w, int h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
public int getXLo() {
return x;
}
public int getYLo() {
return y;
}
public int getXHi() {
return x + w;
}
public int getYHi() {
return y + h;
}
}
// implementation of AbstractRect that represents
// a rectangle as low and high X,Y points
class RectTwoPoints extends AbstractRect {
private int xlo;
private int ylo;
private int xhi;
private int yhi;
public RectTwoPoints(int xlo, int ylo, int xhi, int yhi) {
this.xlo = xlo;
this.ylo = ylo;
this.xhi = xhi;
this.yhi = yhi;
}
public int getXLo() {
return xlo;
}
public int getYLo() {
return ylo;
}
public int getXHi() {
return xhi;
}
public int getYHi() {
return yhi;
}
}
// driver
public class AbstractDemo3 {
public static void main(String args[]) {
Rect r1 = new RectWidthHeight(10, 10, 10, 15);
System.out.println(r1);
System.out.println(r1.getWidth() + " " +
r1.getHeight());
System.out.println(r1.contains(10, 10));
System.out.println(r1.contains(20, 25));
System.out.println(r1.contains(9, 9));
System.out.println(r1.contains(21, 26));
Rect r2 = new RectTwoPoints(10, 10, 20, 25);
System.out.println(r2);
System.out.println(r2.getWidth() + " " +
r2.getHeight());
System.out.println(r2.contains(10, 10));
System.out.println(r2.contains(20, 25));
System.out.println(r2.contains(9, 9));
System.out.println(r2.contains(21, 26));
}
}
The abstract class implements shared functionality. Subclasses
implement the four methods, such as getXLo, that are used to get
the low and high X,Y values for the corners of the rectangle.
Using these four methods, it's possible to implement common methods
such as getWidth and toString in the abstract class.
When you run this program, the output is:
[10,10,20,25]
10 15
true
true
false
false
[10,10,20,25]
10 15
true
true
false
false
In the driver program, the Rect interface type is used to hold
references to the concrete subclasses. One reason why you want
to do this is that additional representation classes might be
added at a later time. These additional classes might extend the
AbstractRect class, as already done above, or implement the Rect
interface directly -- and not use the abstract class at all. In
either case, programming with interface types makes it easy to
change and mix representation classes.
Both abstract classes and interfaces allow you to specify a
"contract" as a type. In other words, you can require that a
subclass of an abstract class, or the class that implements an
interface, define particular methods. So there is some overlap
between these two mechanisms. Interfaces allow for a form of
multiple inheritance, because you can implement more than one
interface when you define a class. Interfaces allow you to
specify only public methods and constants, with no
implementation allowed within the interface.
By comparison, abstract classes do not provide multiple
inheritance, but they do allow you to build up a partial
implementation of a class (as illustrated above). Also, you
have more flexibility than interfaces in areas such as
protected members.
Using abstract classes and interfaces together often makes
sense. For example, suppose that you're extending a class,
and you also want to use the AbstractRect class defined above.
You can't extend from two classes at one time (that is, multiple
inheritance). However, in such a case you can implement the Rect
interface, possibly by forwarding the methods of the interface to
a concrete subclass of AbstractRect. Or you might want to
reimplement the interface completely, perhaps for correctness or
performance reasons.
For more information about Abstract classes, see
Section 3.7, Abstract Classes and Methods, and Section 4.6,
When to Use Interfaces, in "The Java(tm) Programming Language
Third Edition" by Arnold, Gosling, and Holmes
http://java.sun.com/docs/books/javaprog/thirdedition/
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
USING PEER CLASSES WITH THE JAVA NATIVE INTERFACE
Suppose that you're developing an application in the Java
Programming Language, and you find that you need to make use of
a C++ class that you previously developed. You've weighed the
various pros and cons of this decision, such as complexity issues
and portability, and have decided that you really need to use
the C++ class. So how can you use C++ classes from a Java
program, and keep C++ objects around while the program is
running? This tip shows a simple example.
The basic idea is that you define a Java class, called a "peer
class," that corresponds to the C++ class. Each instance of the
peer class corresponds to a C++ object, tracking the state of the
C++ object. The peer class and driver program look like this:
// PeerDemo.java
class Peer {
// used to hold the C++ object pointer
private long peerobj;
// create native object
private native long create(int i);
// destroy native object
private native synchronized void destroy(long p);
// get value of native object
private native int getvalue(long p);
// constructor - call create() to create native object
public Peer(int i) {
peerobj = create(i);
System.out.println("create peerobj = " + peerobj);
}
// destroy native object if not already done
public synchronized void destroy() {
if (peerobj != 0) {
System.out.println("destroy peerobj = " + peerobj);
destroy(peerobj);
peerobj = 0;
}
}
// get value from native object
public int getValue() {
if (peerobj == 0) {
throw new IllegalStateException(
"getValue called on destroyed object");
}
return getvalue(peerobj);
}
// destroy native object if it's still around
// when finalize called from garbage collection
public void finalize() {
destroy();
}
}
public class PeerDemo {
// load the native library
static {
System.loadLibrary("PeerLib");
}
// driver
public static void main(String args[]) {
Peer p1 = new Peer(37);
Peer p2 = new Peer(47);
System.out.println("p1 value = " + p1.getValue());
System.out.println("p2 value = " + p2.getValue());
p1.destroy();
p2.destroy();
p1.destroy();
}
}
Before explaining this code, let's examine the C++ class. This is
a simple class that saves with each created object an integer
value passed to the constructor. This allows the value to be
retrieved at a later point from the object. There's also a
destructor for the class that is invoked when objects of the class
go out of scope, or are explicitly deleted (remember that C++ has
no garbage collection). The class is defined in a single header
file, with inline functions:
// PeerClass.h
#ifndef _PEERCLASS_
#define _PEERCLASS_
#include <iostream.h>
using namespace std;
// C++ class that stores an integer value in an object
class PeerClass {
int val;
public:
// constructor
PeerClass(int i) {
val = i;
cout << "PeerClass::PeerClass called" << endl;
}
// destructor
~PeerClass() {
cout << "PeerClass::~PeerClass called" << endl;
}
// return value stored in object
int getValue() {
cout << "PeerClass::getValue called" << endl;
return val;
}
};
#endif
Now let's explain the peer class and driver program,
PeerDemo.java. Notice that there are three native methods in the
program, create, destroy, and getvalue, that correspond to the
constructor, destructor, and getValue functions in the C++
class. These native methods are called within the peer class, and
are the link between the peer class and the C++ class. For
example, the constructor in the peer class calls the native
method create. The constructor passes as an argument the integer
value to be stored in the C++ object. In a moment you'll learn
how to actually connect the native methods with the C++ class
functions.
Suppose that an instance of a C++ class is created. How is the
pointer (reference) to the instance saved so that it can be
manipulated through the peer class? This is done by defining
a long (64-bit) field in the peer class; the field is used to
save the pointer. In other words, the create native method
returns this value, and the value is saved in objects of the peer
class. When other native methods, such as destroy, are called,
this value is retrieved and passed as an argument to the method.
The value is then cast into a C++ pointer value.
Another important point about peer classes concerns finalization
and destroying objects. As stated earlier, C++ has no garbage
collection, so it's necessary to worry about how objects are
reclaimed when they're no longer in use. When objects are
dynamically created with the new operator, you must explicitly
call delete. In the peer class, this behavior is modeled using
the destroy method.
PeerDemo also defines a finalize method that calls destroy.
But you can't rely on destroy being called in a timely way. So
you must explicitly call destroy to invoke the destructor for
the C++ class, and avoid memory leaks. Instances of the Java peer
class are garbage collected, but not instances of the C++ class.
The destroy method is synchronized to avoid race conditions. The
create method is called from the constructor, which can execute
in only one thread for a given object. Also the getvalue method
is read-only, so there are no issues with synchronization for
these two methods.
Given all of this discussion, how do you actually turn the code
above into a running application? The first step is to compile
the Java code:
javac PeerDemo.java
Then run the "javah" tool that is part of the JDK distribution:
javah -jni -classpath . -o PeerLib.h Peer
This step creates a header file that declares C++ function
prototypes for the native methods. The created header file looks
like this:
// PeerLib.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Peer */
#ifndef _Included_Peer
#define _Included_Peer
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Peer
* Method: create
* Signature: (I)J
*/
JNIEXPORT jlong JNICALL Java_Peer_create
(JNIEnv *, jobject, jint);
/*
* Class: Peer
* Method: destroy
* Signature: (J)V
*/
JNIEXPORT void JNICALL Java_Peer_destroy
(JNIEnv *, jobject, jlong);
/*
* Class: Peer
* Method: getvalue
* Signature: (J)I
*/
JNIEXPORT jint JNICALL Java_Peer_getvalue
(JNIEnv *, jobject, jlong);
#ifdef __cplusplus
}
#endif
#endif
Note that 'extern "C"' is used to declare the function
prototypes. This means that the C++ functions will have their
external names encoded using C rules, instead of C++ ones.
C++ name mangling gets complicated, for example, to represent
overloaded function types, and it's not at all portable, so
C-style names are used.
Given the header file, PeerLib.h, you can define PeerLib.cpp, the
C++ implementation of the native functions:
// PeerLib.cpp
#include "PeerLib.h"
#include "PeerClass.h"
// create an object by calling constructor of C++ class
JNIEXPORT jlong JNICALL
Java_Peer_create(JNIEnv *, jobject, jint i) {
PeerClass* ptr = new PeerClass(i);
return (jlong)ptr;
}
// destroy an object by calling destructor of C++ class
JNIEXPORT void JNICALL
Java_Peer_destroy(JNIEnv *, jobject, jlong objptr) {
PeerClass* ptr = (PeerClass*)objptr;
delete ptr;
}
// get the value of an object
JNIEXPORT jint JNICALL
Java_Peer_getvalue(JNIEnv *, jobject, jlong objptr) {
PeerClass* ptr = (PeerClass*)objptr;
return ptr->getValue();
}
If you study this code, you will see how the native methods are
implemented. For example, the destroy function has passed to it
a parameter called "objptr" which is the 64-bit value of the
"peerobj" field in the peer class. This value is cast to type
"PeerClass*". Then the delete operator is called on the pointer.
The next step is to compile PeerLib.cpp, and create a shared
library or DLL from it. The exact steps to do this will vary, but
here's an example using Borland C++ on Win32:
bcc32 -c -I/jdkbase/include -I/jdkbase/include/win32 PeerLib.cpp
bcc32 -tWD PeerLib.obj
In the Solaris(tm) Operating Environment, using the
Sun WorkShop(tm) Compiler C++ 5.0,
CC -G -o libPeerLib.so -Ijdkbase/include \
-Ijdkbase/include/solaris PeerLib.cpp -lCstd -lCrun
Or using the GNU C++ Compiler:
g++ -G -o libPeerLib.so -Ijdkbase/include \
-Ijdkbase/include/solaris PeerLib.cpp
"/jdkbase" should be replaced with the path of your JDK
distribution, and "win32" replaced as appropriate. The result of
this step is a shared library with a name like "PeerLib.dll" or
"PeerLib.so".
Note that for the Solaris Operating Environment, you need to set
the environment variable LD_LIBRARY_PATH to the directory
containing libPeerLib.so. Otherwise you might get an
UnsatisfiedLinkError exception.
Once you've created the shared library, you invoke the demo
program by saying:
java PeerDemo
Typical output will look like this:
PeerClass::PeerClass called
create peerobj = 149302092
PeerClass::PeerClass called
create peerobj = 149302136
PeerClass::getValue called
p1 value = 37
PeerClass::getValue called
p2 value = 47
destroy peerobj = 149302092
PeerClass::~PeerClass called
destroy peerobj = 149302136
PeerClass::~PeerClass called
The big numbers are 64-bit values stored in the "peerobj" field
in objects of the Java peer class, and represent C++ pointers.
The Java Native Interface has other mechanisms you can use to
combine Java and C/C++ code, for example, if you're trying to use
C functions from an existing DLL.
For more information about using peer classes with JNI, see
Section 9.5, Peer Classes, in "The Java(tm) Native Interface" by
Sheng Liang http://java.sun.com/docs/books/jni/index.html
. . . . . . . . . . . . . . . . . . . . . . .
- NOTE
Sun respects your online time and privacy. The Java Developer
Connection mailing lists are used for internal Sun Microsystems(tm)
purposes only. You have received this email because you elected
to subscribe. To unsubscribe, go to the Subscriptions page
http://developer.java.sun.com/subscription/
uncheck the appropriate checkbox, and click the Update button.
- SUBSCRIBE
To subscribe to a JDC newsletter mailing list, go to the
Subscriptions page
http://developer.java.sun.com/subscription/
choose the newsletters you want to subscribe to, and click
Update.
- FEEDBACK
Comments? Send your feedback on the JDC Tech Tips to:
jdc-webmaster@sun.com
- ARCHIVES
You'll find the JDC Tech Tips archives at:
http://java.sun.com/jdc/TechTips/index.html
- COPYRIGHT
Copyright 2001 Sun Microsystems, Inc. All rights reserved.
901 San Antonio Road, Palo Alto, California 94303 USA.
This document is protected by copyright. For more information, see:
http://java.sun.com/jdc/copyright.html
- LINKS TO NON-SUN SITES
The JDC Tech Tips may provide, or third parties may provide,
links to other Internet sites or resources. Because Sun has no
control over such sites and resources, You acknowledge and agree
that Sun is not responsible for the availability of such external
sites or resources, and does not endorse and is not responsible
or liable for any Content, advertising, products, or other
materials on or available from such sites or resources. Sun will
not be responsible or liable, directly or indirectly, for any
damage or loss caused or alleged to be caused by or in connection
with use of or reliance on any such Content, goods or services
available on or through any such site or resource.
This issue of the JDC Tech Tips is written by Glen McCluskey.
JDC Tech Tips
June 12, 2001
Sun, Sun Microsystems, Sun Workshop, Java, Java Developer
Connection, and Solaris are trademarks or registered
trademarks of Sun Microsystems, Inc. in the United States and
other countries.
==================================
¢º¢¸
//'''' ||'''||
q¢¾.¢¾p q¢¯.£¿p
( v ) ( v )
___oOo___oOo___oOo____oOo__
¢Ï : 019-548-1115
e-mail:bceum@yahoo.co.kr
==================================
|