This issue presents tips, techniques, and sample code for the following topics:
Program Assertions
The languages C and C++ have something called an assert macro, which
you can use to check program assertions. For example, a sequence like:
char* p = (char*)malloc(10);
assert(p);
checks whether the pointer returned by the storage allocator is non-zero
and terminates the program if it is not.
It's interesting to consider one way of adding a similar facility to the
Java programming language, for example:
public class Assert {
private static void fail()
{
System.err.println("assertion failed:");
Throwable e = new Throwable();
e.printStackTrace();
System.exit(1);
}
public static void assert(boolean b)
{
if (!b)
fail();
}
public static void assert(long lng)
{
if (lng == 0L)
fail();
}
public static void assert(double dbl)
{
if (dbl == 0.0)
fail();
}
public static void assert(Object ref)
{
if (ref == null)
fail();
}
}
With this class definition, you can write things like the following:
int i = 0;
Assert.assert(i < 10);
// checks whether i < 10
Object p = f();
Assert.assert(p);
// checks that p is non-null
If one of these assertion checks fails, the program will terminate
with a stack traceback.
Note that there are several versions of the assert method. This is
necessary because the Java language type system is not as loose as
C/C++ in converting to/from logical values. For example, i < 10 has
a boolean value, which is not convertible to an integer. So one version
of assert is given for booleans, another for byte, char, short, int, and
long, another for float and double, and a final one for all reference
types.
Within the fail method, a new object of type Throwable, the superclass
of all exception types, is created as a means of obtaining a current
stack traceback. The traceback is printed, and the program terminates.
So if you want to check "should never fail" conditions, and
terminate an application gracefully in such an event, try this technique.
You should find it useful.
Serialization and Transient Values
The Java programming language incorporates a feature known as
serialization, which is used to convert objects (including complex
data structures such as lists and trees) into a stream of bytes,
for writing to a file or across a network. The stream can later
be deserialized and converted back into an object. This feature
is very useful for giving objects persistence, and for transmitting
them to a remote location.
A simple example of serialization looks like this:
// write.java
import java.io.*;
public class write {
public static void main(String args[])
{
try {
FileOutputStream fos =
new FileOutputStream("file.out");
ObjectOutputStream oos =
new ObjectOutputStream(fos);
oos.writeObject(new Test("testing", 37));
oos.flush();
fos.close();
}
catch (Throwable e) {
System.err.println(e);
}
}
}
// read.java
import java.io.*;
public class read {
public static void main(String args[])
{
Test testobj = null;
try {
FileInputStream fis =
new FileInputStream("file.out");
ObjectInputStream ois =
new ObjectInputStream(fis);
testobj = (Test)ois.readObject();
fis.close();
}
catch (Throwable e) {
System.err.println(e);
}
System.out.println(testobj.str);
System.out.println(testobj.ivalue);
}
}
// Test.java
public class Test implements java.io.Serializable {
public String str;
public transient int ivalue;
public Test(String s, int i)
{
str = s;
ivalue = i;
}
}
There are two programs in this example: one that serializes an Test
object instance, and the other that reads back the serialized bytes
and reconstitutes the object. ObjectOutputStream and ObjectInputStream
are layered on top of FileOutputStream and FileInputStream to effect
the actual serialization and deserialization, using the writeObject
and readObject methods.
The Test class implements java.io.Serializable. Serializable is used
as a marker interface, that is, it simply flags the fact that a class
that implements it has some specific property. Without implementation
of this interface, objects of class Test would not be serializable.
This example illustrates an interesting aspect of serialization. A
field of a class may be declared as transient, meaning that the field
is not serialized. In other words, it's not part of the persistent
state of an object. An example of where this situation matters is
java.util.Hashtable. When a Hashtable object is serialized, the keys
and values are written out as pairs of values, rather than written out
as the actual table. This is because the underlying hash codes (see
Object.hashCode) may differ when the table is reconstructed. In the
above example, ivalue has the default value 0 when the saved object is
deserialized.
Finally, serialization is an important technique to understand,
especially as it's used by Remote Method Invocation to transmit
objects across a network.
|