Java GotchasThe Java language has several gotchas to trap
by Lee Fesperman
|
| This enum Goes to Eleven |
|---|
| Although Java syntax borrowed heavily from C++, there are many C++ features that Java chose to omit. At times, the lack of a particular feature makes Java programs cumbersome to implement. One such feature is the ability to define enumerated types, such as those declared with the C++ enum keyword. |
finalize(), create user-defined destructors for objects and classes, as needed, and ensure proper use by application code.
Function Pointers
In C/C++, you can pass a pointer to a function to other functions. Java does not have this capability. You need to use methods in derived classes, often a more clumsy way to deal with the problem.
In Java, you pass a base class or interface as a parameter to methods instead of a function pointer. The base class or interface defines an abstract method as a prototype for the function to be used. Each derived class supplies the appropriate method body.
A Java method might use an interface in place of a function pointer:
void processLines(
BufferedReader in, ProcessLine out)
throws IOException
{
String line;
while (
(line = in.readLine()) != null)
out.process(line);
}
processLines() reads lines from a stream and passes each line to a method for processing. It uses the ProcessLine interface to declare the processing method:
interface ProcessLine
{
void process(String line);
}
A derived class implementing the processing routine:
class Printline implements ProcessLine
{
void process(String line)
{
System.out.println(line);
}
}
The PrintLine class prints each line. This code uses PrintLine() to print a stream as lines:
processLines(new BufferReader(in),
new PrintLine());
The introduction of inner classes and anonymous classes in Java 1.1 makes emulation of function pointers more convenient:
processLines(new BufferReader(in),
new ProcessLine()
{
void process(String line)
{
System.out.println(line);
}
});
This eliminates the need for a derived class like PrintLine. The anonymous class can reference variable and methods in the containing class.
Summary: In places where you might normally need function pointers, use an abstract method in a base class or interface as a prototype and implement appropriate methods in derived classes.
Multiple Inheritance
Without true multiple inheritance, Java forces some difficult design choices. A Java class may inherit from only one base class. Any additional inheritance is through implementation of interfaces. When a class is has more than one distinct parent, you must select one parent to be the single base class. The other parents must be interfaces. This is not an easy choice.
In larger projects, there are usually several situations where classes require multiple inheritance. Often, this involves the major classes in the application, producing difficult design decisions that you need to make early in the process. Potential future code re-use is a consideration, too. Improper choices for base classes versus interfaces can complicate later enhancements, at worst forcing a rewrite of the base system.
Interfaces have advantages over classes because of improved information hiding and code re-use. A very clean design might use interfaces instead of base classes. This would provide the maximum flexibility in code re-use. However, as we've discussed, interfaces do have limitations versus base classes.
Interfaces also solve the nasty virtual class problem in C++. Virtual classes are a counterintuitive solution to the problem of a class inheriting from the same class more than once. This is equivalent in Java to the situation where a base class and derived class implement the same interface. Interfaces that extend other interfaces may also produce duplicate references. Java merges the duplicate references into one without any fuss.
In choosing which parent to model as a class (if any), you must consider a number of aspects. See the sidebars, "Guidelines for Multiple Inheritance" and "Designing a class with Multiple Inheritance."
You can use multiple inheritance to extend standard Java classes:
class LinkedVectors extends
Vector implements Linked
{
Linked next = null;
Linked getNext()
{
return next;
}
void setNext(Linked next)
{
this.next = next;
}
int count()
{
int count = 1;
if (next != null)
count += next.count();
return count;
}
}
The LinkedVectors class provides a linked list of vectors. It extends the Vector class and implements the Linked interface, defined in the "Interfaces" section.
Summary: To properly design multiple inheritance, follow guidelines in the Sidebar.
Class Identity
Java has limited facilities for determining the identity of an object. You use the instanceof operator to test if an object is a subclass of a specific class or implements a specific interface. However, when you need to test an object for membership in a set of distinct classes and interfaces, you get a cumbersome chain of if statements:
if (obj instanceof class1)
...
else
if (obj instanceof class2)
...
else
if (obj instanceof class3)
...
There is no direct way to extract the identity of an object and use it as a value in a switch, for instance. The only general solution for testing a set of classes and interfaces is to define a common base class or interface. The common base entity includes a variable or method that gives an identifying value for each derived entity.
For externally defined classes, you can define derived classes that implement a common interface for identification. However, many of the basic classes, such as the wrapper classes for primitive typesInteger, Double, and so onare final and aren't extensible. You must use instanceof to test for membership in these classes.
There is a technique to retrieve an integer value representing the class of an object. You can obtain the hash code of the Class object for use as a unique id:
int id = obj.getClass().hashCode();
Unfortunately, this won't work for switch. However, you can build a HashTable for class identity with getClass(). Each HashTable entry could contain a value identifying the class for use with switch.
Summary: To test membership in a set of classes and interfaces, use a common method or variable to retrieve object identity as an integer value where possible, otherwise use multiple instanceof tests.
Class/Interface Source Files
Java requires that each public class or interface declaration be placed in a separate file. The file name must be the public class or interface name. A Java source file may contain any number of class declarations and interface declarations but only one can be public.
This organization leads to a proliferation of source files in a Java package. Also, the work-arounds for many of the Java traps described in this article require the creation of additional classes and interfaces producing even more source files. A large number of source files causes organizational and comprehension problems.
Larger applications can reduce the problem by breaking packages up into smaller subsystems. Names for public classes should be descriptive and preferably multi-word.
Summary: To reduce the impact of many source files, break up larger packages into smaller packages and use descriptive names for public classes.
By Reference Parameters
Normally, the only way a method can pass back a result is as a return value. Java passes parameters by value to methods. It does not provide by reference or by address parameters, as do other languages. You can't use method parameters directly for retrieving results.
By reference parameters can be emulated using container objects:
class StringContainer
{
String string;
void setString(String string)
{
this.string = string;
}
String getString()
{
return string;
}
}
Container objects have a field variable for receiving results.
int myMethod(StringContainer con)
{
int x;
...
con.setString("some text");
return x;
}
A method using containers stores result values in objects passed as parameters.
StringContainer con =
new StringContainer();
int x = myMethod(con);
System.out.println(
"myMethod returns " + x +
" and " + con.getString());
Code calling the method uses the container to receive the string.
Defining additional classes as containers increases the complexity of applications by adding more source files; see section entitled, "Class/Interface Source Files. " A better choice is to use an array as a container:
int myMethod(String string[])
{
int x;
...
string[0] = "some text";
return x;
}
myMethod() returns an int result and passes back a String object through an array:
String string[] = new String[1];
int x = myMethod(string);
System.out.println(
"myMethod returns " + x +
" and " + string[0]);
Code calling the method uses the array to receive the string.
Summary: To emulate by reference parameters, use container objects or arrays.
Conclusion
Java is a comparatively clean language but has aspects that perplex even the most experienced developer. Most of the traps in the language have acceptable workarounds. However, the nature of multiple inheritance in Java will continue to be a stumbling block, especially in larger projects and in code re-use.
Java is a powerful, general-purpose language. You can use it on most programming taskssystems and applications, large and small. The main problem with Java is performance. Certain truly time-critical applications may never be feasible in Java.
Much of the concern today about Java performance centers on the speed of byte code interpreters and JIT compilers. This is likely to change in the near future as full-fledged native code compilers appear. Performance of compiled Java will not be better than closer-to-the-metal languages like C++ but will be quite acceptable.
For time-critical applications, the real performance issue is with Java garbage collection. Garbage collection overhead will bring time-critical applications to their knees. While there will be significant improvements in garbage collection-technology to deal with some of the problems, it won't cover all situations. Certain applications will not be appropriate for Java or, at best, will require significant reliance on native methods, written in a more efficient language.
Lee is the developer of the FirstSQL RDBMS and is currently rewriting the entire RDBMS in pure Java. He can be reached at firstsql@ix.netcom.com. His company's Web site is at www.firstsql.com.
| © 1999 FAWCETTE TECHNICAL PUBLICATIONS, all rights reserved. Java Pro is an independent publication not affiliated with Sun Microsystems. Sun Microsystems is not responsible in any way for the editorial policy or other contents of the publication. All contents of Java Pro are copyright © 1999 Fawcette Technical Publications, unless otherwise noted. "VBITS" and "Interactive Developer" are trademarks of Fawcette Technical Publications, a California Corporation, James E. Fawcette, President. Java, all Java-based marks and logos, and the Duke Logo are trademarks of Sun Microsystems Inc. The Duke Logo is used by permission. Rather than put a trademark symbol in every occurrence of other trademarked names, we state that we are using the names only in an editorial fashion with no intention of infringement of the trademark. Although all reasonable attempts are made to ensure accuracy, the publisher does not assume any liability for errors or omissions anywhere in the publication. |