One of the primary design goals for Java is the idea of "write once, run
anywhere." Java is therefore an ideal language choice when faced with the
challenge of developing a platform-independent application.
Many articles have been written on applet development and the use of Java for
bringing more interactivity to pages on the Web. While this is a valid use of
Java, it's equally well suited for developing more traditional standalone or
client/server applications that can run across multiple platforms. When Java is
used to develop each of the tiers of an n-tier client/server application, its
cross-platform nature offers additional benefits, including scalability
(independent of hardware platform), flexibility and vendor independence.
Which JDK? Whichever version you decide to use, be sure to bundle the appropriate Java
Runtime Environment when you distribute your application.
Application development, with its more relaxed security model, allows
lower-level access to the operating system. This access, however, can result in
your application's becoming platform-specific. In this article I'll look at five
primary areas you should pay special attention to when developing cross-platform
Java applications: Good Programming Practices If you use any third-party class libraries, you'll need to distribute their
runtime versions (which may require a separate license). It's also recommended
that you verify that these third-party libraries make use of the core Java API
only and don't use any native calls. Otherwise the code you write may be
cross-platform. However, since your code depends on a third-party product that's
not cross-platform, your resulting application won't be cross-platform either.
Enable all compiler warnings. Avoid deprecated methods Avoid "undocumented features" Similarly, your applications shouldn't depend on the implementation details
of any one particular Java implementation. For example, use of the AWT component
peer interfaces is documented as being "For use by AWT implementers." Peer
classes are highly platform-specific. As a portable application makes use of the
AWT rather than implementing it, you should avoid using peer classes in your
application.
Follow any applicable API protocols. For example, the JDK 1.1 introduced a new AWT event model. According to the
new documentation, the results may be unpredictable if you mix the new event
model with the older 1.0 event model. (Refer to "Updating 1.0 source files to
1.1" in the JDK documentation download bundle for details.)
Several of the AWT methods (Component.Paint and Component.Update, in
particular) accept a Graphics object as one of their arguments. While you can
use and make changes to this object within the method to which it was passed,
you shouldn't store a reference to it for later use. Since the AWT
implementation is allowed to invalidate any Graphics object after the method
returns, storing and later using a reference to it may work on one platform but
isn't guaranteed to work on another. This type of problem is particularly
difficult to debug.
If any of your classes override the Object.equals method, they should also
override the Object.hashCode method so that for any two objects x and y:
x.equals(y) implies that a.hashCode()==b.hashCode()
See the JDK java.lang.Object.hashCode documentation for details.
Use System.exit method sparingly. Since the System.exit method forces termination of all threads in the JVM, it
may, for example, destroy windows containing user data without giving users a
chance to save their work.
Adhere to good practices with thread scheduling and
synchronization Before using Java's multithreaded capabilities, read and understand the JDK
documentation for the java.lang.Thread class and the java.lang.Runnable
interface. Although Java does simplify multithreaded application development,
it's still somewhat of an advanced topic and many of the thread synchronization
issues still exist. Doug Lea's book, Concurrent Programming in Java, covers this
subject in depth.
OS Differences and LimitationsAvoid Native
Methods Before using native code, consider the following alternatives: You may think that confining the native methods to a single class and
providing an implementation of that class for every Java platform is a
sufficient workaround. This is actually a poor "solution" since the number of
Java platforms is ever-increasing. In addition, some Java platforms have no
ability to execute native code.
Exercise caution when using Runtime.exec If you intend to use the Runtime.exec methods in your application, then you
should provide a way in which the user can specify the exact command strings.
Ideally this would be done through a GUI. However, if the command(s) rarely need
changing, you can prompt the user to enter them during installation, then store
them in a properties file for later use.
Don't use hardwired platform-specific constants The AWT also provides ways to help you create a platform-independent GUI.
Details of these features are described in a separate section below.
Issues particular to command line programs and
processing Even with those platforms that support command line programs, command line
processing isn't consistent across these platforms. Although the most portable
solution doesn't use the command line, this may not be acceptable for programs
that need to be executed from within a script. In this case consider using the
POSIX convention in which command line options are indicated with a leading
dash. As an alternative, you may also want to consider reading the options from
a properties file and/or providing a GUI.
Don't assume support for rendering unicode characters Make no assumptions about the hostname format If your application is merely displaying the resulting hostname, this is
unlikely to cause a problem. However, when the name is passed to other systems
or applications, it may be best to provide the IP address in addition to the
hostname. Note that the IP address is available using the InetAddress.getAddress
or InetAddress.getHostAddress methods.
Read/Write Files Alternatively, to dynamically construct a path and filename in a
platform-independent way, use one of the java.io.File constructors. Be aware
that even the format of an absolute path differs between platforms. For example,
on DOS and Windows-based platforms, an absolute path typically begins with a
letter followed by a colon. Under UNIX, absolute paths begin with a forward
slash: "/"
Don't hard-code line termination characters Additionally, though Java internally uses Unicode characters for text,
different platforms have different internal representations.
JDK 1.1 provides several methods to help address both of these problems. To
output text in a platform-independent manner, use any of Java's println methods.
These methods output the end-of-line character sequence appropriate for the
platform on which your Java application is running. If you really need access to
the appropriate end-of-line sequence for the current platform, use the value
returned by java.lang.System.getProperty("line.separator").
Similarly, to input text files, use the java.io.BufferedReader.readLine
method. This reads a line of text terminated by a line feed ("\n"), a carriage
return ("\r") or a carriage return followed immediately by a linefeed, and
returns the contents of the line, excluding any line-termination characters.
Be aware of the maximum length of filenames One workaround to this is to package your classes into a Java archive (JAR).
Alternatively, a ZIP file will work if you're developing for JDK 1.0. Observe
strict case distinctions on all platforms.
Some platforms - most notably DOS and the Microsoft Windows platforms -
ignore case when comparing filenames. However, don't let this affect your
development efforts. Always use the correct case for class names and filenames
throughout your code. That way, your Java application will run as intended on a
wider range of platforms.
Combining all your classes into a JAR or ZIP file, as discussed above, also
helps preserve the case of filenames.
Avoid "special" filenames Be aware of these and other "special" filenames on the different Java
platforms.
GUI Design The JDK 1.1 documentation download bundle contains details about upgrading in
the section titled "Updating 1.0 source files to 1.1." Although it may be
tempting when upgrading your application from JDK 1.0 to 1.1 to do this piece by
piece, resist this temptation. Bite the bullet and do the entire upgrade in one
step.
Use layout managers for sizing elements. Break the habit of laying out components by size and position, and learn how
to use Java's layout managers effectively to achieve the desired results.
Blend your GUI with the desktop To make your colors blend in with the user's desktop, you can use colors from
the java.awt.SystemColor class. Alternatively, you may want to provide the user
some way of customizing the appearance - particularly the colors and fonts - of
your application, either through an Options dialog, a properties file or both.
Rarely should you need to obtain the screen resolution, but if you really
have to, you can use the java.awt.Toolkit.getScreenResolution method.
Don't assume existence of nonstandard fonts Don't hard-code font sizes. Let text components take on their default size
using an appropriate layout manager, and use java.awt.FontMetrics.stringWidth if
you really need to determine the actual displayed width of a string.
Before selecting a nonstandard font, be sure to test for its availability and
provide a suitable default font in the event that it doesn't exist. The default
fonts supported by JDK 1.1 are: "Serif" (usually Times Roman), "SansSerif"
(usually Helvetica) and "Monospaced" (usually Courier).
Other Issues Choosing a distributed framework CORBA, the most generic of these frameworks, supports not only a wide variety
of platforms but also a variety of object development languages. This allows,
for example, a Java client to communicate with a C++ CORBA server on a different
machine, knowing only the interface published by that object.
RMI is specific to Java and makes communication between distributed Java
objects fast and easy. It has a lot in common with CORBA, and there have been
talks about possibly merging the RMI specification into CORBA sometime in the
future.
Finally, DCOM is Microsoft's Distributed Common Object Model. Like CORBA,
it's also language-independent but isn't platform-independent, being supported
only on the newer Microsoft Windows operating systems. Furthermore, DCOM isn't
supported in the standard Java API. This isn't recommended if you're aiming to
develop a truly cross-platform solution.
Loading JDBC drivers Using JDBC may restrict the portability of your application if not
implemented with some forethought. To keep your application as portable as
possible, provide a means for the user to specify the JDBC driver name. This can
be done through either a GUI, the jdbc.drivers system property or a similar
properties file.
Resources
Java and Java-based
marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the
United States and other countries. SYS-CON is independent of Sun Microsystems,
Inc. SYS-CON, JDJEdge 2001 International Java Developer Conference or Java
Developer's Journal is not affiliated with Sun Microsystems, Inc.
www.JavaDevelopersJournal.com
One advantage of application
development over applet development is that you have much more control over
which version of the JDK to use. If you're developing a small application for
deployment in the next couple of months, you should probably use JDK 1.1.
However, you may want to consider using JDK 1.2 (also known as Java 2 SDK,
version 1.2) for slightly longer-term projects. It offers many new features and
improved performance.
1. Good programming practices
2. OS differences and
limitations
3. Read/write files
4. Graphical user interface design
5.
Other issues
Depend
only on the Core APIs.
The Java core API forms a standard foundation
of classes that all Java Virtual Machines must implement to be considered
"standard Java."
By default, the Java compiler
generates warnings for code it considers ambiguous, platform-dependent or
unclear. Although you can disable these warnings using the javac-nowarn option,
it isn't recommended. Good programming practice suggests enabling all warnings -
to their maximum level, if appropriate - in your compiler, then changing your
code to eliminate all warnings that are produced.
In Java application development it
shouldn't be necessary to use deprecated methods. While they may work in the
current release of the JDK, they're no longer the preferred method. Furthermore,
since the plan is to remove them from the JDK, on some platforms they may have
already been removed.
Almost any implementation of
the core Java API will include supporting classes and/or packages that aren't
themselves part of the core API. Such classes/packages are generally
undocumented - although they may provide some quick-and-dirty functionality,
they should be avoided since there's a good chance they won't be available on
other platforms.
Certain methods in the
core API must be called or implemented in a certain pattern. By skipping over
some methods or calling them out of sequence, you may write syntactically
correct code, which is highly nonportable.
You should use this only
for abnormal termination of your application. Generally, you should terminate
your application by stopping all nondaemon threads. This is often as simple as
returning from the main method in your application.
As with any multithreaded language, don't rely on
priorities or luck to synchronize threads. Thread scheduling may differ on
different platforms, and even between different machines running the same
platform.
Occasionally it may appear tempting to use native code to
implement certain functionality. However, the native code is by its very nature
platform-dependent. Although the rest of your Java application may be completely
platform-independent, the fact that you're using native code means that your
entire application is also platform-dependent.
Although the
java.lang.Runtime.exec methods provide a means to execute other applications on
the system in a seemingly platform-independent way, you should use the
Runtime.exec methods with caution. While these methods are part of the core API,
the contents of the string arguments passed to these methods are generally
platform-specific.
The core
Java API provides several ways to help you write a platform-independent
application. For example, instead of printing strings with embedded carriage
returns and/or line feeds, use the System.println method to print a string
followed by an end-of-line character. Alternatively, use
System.getProperty("line.separator") to retrieve the line separator for the
current platform.
Not all Java platforms support the notion of standard
input or standard output streams. Command line programs that use System.in,
System.out or System.err may not run under all platforms. Consider implementing
a GUI to provide the same functionality.
Since
not all platforms can display all Unicode characters, use only ASCII characters
for the default text for messages, menus, buttons and labels. It's acceptable to
use non-ASCII characters in localization resources and in text entered by the
user.
The
java.net.InetAddress.getHostName method returns the fully qualified host name
for the InetAddress object. The format of the String returned by getHostName is,
however, platform-dependent. On some platforms it'll contain a fully qualified
domain name, yet in others it'll contain only the host part of that name.
Don't hard-code file
paths
Hard-coded filenames, and especially hard-coded file paths (a
directory name followed by the filename), frequently present portability
problems in any language. You can address this problem in Java by using
java.lang.System.getProperties("file.separator") to retrieve the file/path
separator or by using a java.awt.FileDialog to prompt the user for a filename.
A common
problem when transferring text files between different operating systems occurs
because of the different ways the platforms represent an end-of-line sequence.
Some platforms, most notably DOS and Windows 3.1/95/98/NT, use the character
sequence "\r\n" (a carriage return followed by a linefeed), whereas other
systems - namely, most varieties of UNIX - use a single linefeed ("\n"). Others
may use a single carriage return character ("\r").
All platforms
have some maximum length on valid filenames. This may be more of an issue during
installation of your application, especially when using the inner classes of JDK
1.1 (which concatenate the class and inner class names to come up with the
resulting filename). However, it can also cause problems at runtime in that the
JVM may have trouble locating some of the required classes.
Some filenames have a "special"
meaning on certain platforms. For example, in DOS and Microsoft Windows
platforms, "LPT1:" refers to the first printer port and "CON:" refers to the
console. Similarly, under UNIX, /dev/null is the name of a special device that
simply absorbs all output directed to it. In other words, don't try saving
anything that you may later want to retrieve to a file with this name.
Don't mix event
models
Don't mix the newer JDK 1.1 event model with the older JDK 1.0
event model - the results may be unpredictable. In particular, you may achieve
the intended results on a single platform with a particular version of the JVM,
but this same code may give different - or unexpected - results on another
platform or with a different JVM.
Don't hard-code
the sizes or positions of any GUI components. Their exact size is almost
guaranteed to differ between platforms, as will the size of the screen and the
default windows.
The size of the screen and
the number of colors available are likely to be different between platforms -
even between users. Additionally, though you can use your own color scheme with
particular RGB values, displays can vary considerably as to the exact color
rendered by a given RGB value.
The availability
and size of fonts varies from platform to platform and even from machine to
machine depending on installation.
Consider international
issues.
The JDK 1.1 provides extensive localization and
internationalization features. It's worthwhile familiarizing yourself with them
and using them where applicable in your Java application. When you first write
your application, you may only expect users from one country, speaking one
language, to use it. However, it's much easier to provide support for multiple
locales at this point than to retrofit these changes at a later date.
When you begin developing
larger applications, you may soon realize the need and/or benefit of distributed
objects, or at least the need to separate out parts of the application in more
of a client/server role. There are various ways to do this, including writing
your own application-specific protocol. The most widely supported distributed
object frameworks include RMI, CORBA and DCOM.
While Java Database Connectivity has
many uses in standalone applications, it's most likely to be used when
developing middleware that communicates client requests to a back-end database.
It provides a way to allow flexibility in driver loading without having to
recompile your code each time you want to change drivers. The
java.sql.DriverManager class is responsible for providing this flexibility. The
two ways in which a driver can be made available to the DriverManager class are
through the jdbc.drivers system property or through a call to
java.lang.Class.forName, which causes the driver to be explicitly loaded.
Updating 1.0 Source Files to
1.1:
http://java.sun.com/products/jdk/1.1/docs/relnotes/update.html
Internationalization
support in JDK 1.1:
http://java.sun.com/products/jdk/1.1/docs/guide/intl/
100% Pure Java home
page: www.javasoft.com/100percent/
InstallAnywhere by ZeroG Software:
www.zerog.com/
InstallShield Java Edition 2 by InstallShield:
www.installshield.com/java/
The Java Developer Connection (JDC):
http://developer.javasoft.com/
D. Lea. Concurrent Programming in Java: Design
Principles and Patterns. Addison Wesley.
Figure 1
Figure 2
Copyright © 2001 SYS-CON Media, Inc.
E-mail: info@sys-con.com