Rules and Standards |
CAA V5 Java Coding RulesRules, hints and tips to write Java code |
Technical Article |
AbstractThis article gives you several mandatory rules to program with Java in order to improve the readability of your code and moreover to enable Dassault Systèmes tools to process your code. Besides, this article provides some tips to make your code profit from the advantages of Java and avoid its drawbacks. These tips are often sum ups of world-wide-web items to which they point by hyperlinks. |
These rules are:
[Top]
Create a separate .java file for each top level class and interface, and put in this file only the declaration of this entity. This file must have the same name than the entity. For example, the BitmapPanel class is written in the BitmapPanel.java file.
However, you can define nested classes, that is to say classes whose declaration occurs within the body of another class or interface if the nested class completely depends on the top level class and is meaningless without it. It avoids namespace pollution by using the enclosing type as namespace.
[Top]
import com.dassault_systemes.CATVisu.CATVisEvent.CreationEvent;
In the files where you insert this declaration, the simple name
CreationEvent refers to the type CreationEvent in the package
com.dassault_systemes.CATVisu.CATVisEvent.CreationEvent.
Otherwise, you would have to use the fully qualified name
com.dassault_systemes.CATVisu.CATVisEvent.CreationEvent to refer to the type
CreationEvent in your code.
Even if the class you need belongs to the current package, insert an import declaration to use it. It is not demanded by Java, but it improves the readability of your code and it is requested by Dassault Systemes tools which use import declarations to detect dependencies.
import com.dassault_systemes.CATVisu.CATVisEvent.CreationEvent;
Don't use the Type-Import-on-Demand declaration: "import com.dassault_systemes.CATVisu.CATVisEvent.* ;" which imports all the types of the com.dassault_systemes.CATVisu.CATVisEvent package. It reduces the readability of the code because when a type is referred by its simple name, one cannot know to which package the type belongs. Whereas, if you always use Single-Type-Import declarations, the type is listed in the the import declarations. Moreover, Dassault Systemes tools demand Single-Type-Import declaration to manage dependencies.
However, you may be compelled to use such a declaration if you need two types with the same simple name but belonging to different packages.
For example:
import com.dassault_systemes.CATDialog.CATDlgWindow.awt.RadioButon; import com.dassault_systemes.CATDialog.CATDlgWindow.swing.RadioButon;
causes a compile-time error. It must be replaced by:
import com.dassault_systemes.CATDialog.CATDlgWindow.awt.RadioButon; import com.dassault_systemes.CATDialog.CATDlgWindow.*
and you have to refer to the
com.dassault_systemes.CATDialog.CATDlgWindow.swing.RadioButon type by its
fully qualified name in the class body.
However, such pieces of code must remain exceptions.
[Top]
If you do so, you give a direct access to your data members to any users of your class instances. This breaks encapsulation. Even if using methods to access private data members doesn't avoid the client application to be rebuilt if you change this data, you keep a control on your data if you enable the client application to access them using these methods.
You can nevertheless do that if performance is an issue, and if there is no added value to add a pair of methods to access your data.
[Top]
An object is eligible for garbage collection when there are no more references to that object. References that are held in a variable are naturally dropped when the variable goes out of scope. Two cases can be distinguished:
For instance, assume that you have a graph of 4 instances
i0, i1, i2, i3. i0. Each instance has one or several instance variables
that hold a reference to another instance like in the diagram near by.
Refij is a variable of instance i which references instance j. In order for i1, i2, i3 to be eligible for garbage collection, i0 must set ref01 to null as soon as i0 is useless. |
[Top]
As soon as you don't want a class to be derived, declare it Final. It is the only way to prevent CAA clients from deriving an exposed class.
[Top]
The tips are:
[Top]
Avoid using an object to access a class (static) variable or method. Use a class name instead. For example:
classMethod(); //OK AClass.classMethod(); //OK anObject.classMethod(); //AVOID!
Class methods are statically bound, so they don't enjoy the flexibility of polymorphism: if you derive a class and create a static method with the same name and arguments as a static method of the superclass, you don't override the superclass's method, you only hide it. When this redefined method is invoked, the JVM will select the method implementation to execute not by the class of the object at runtime, but by the type of the variable at compile-time.
(Source: Designing with Static Members, http://www.artima.com/designtechniques/static.html)
[Top]
They are only useful for non-memory resources such as file handles and sockets. However:
Do not rely on finalizers to release non-memory resources
You don't know when objects will be finalized. You only know that
finalizable objects will be finalized before being garbage collected. But
you don't know when and even whether your objects will be garbage collected.
Moreover, finalizers may be run in any order, sequentially by a single
thread or concurrently by multiple threads.
So you cannot rely on the finalizer to release resources. For example, when
an object opens a file in its constructor, it cannot only close it in its
finalize method. A Java program generally will have only a finite number of
file handles at its disposal. When all those handles are in use, the program
won't be able to open any more files. Instead, the object should provide a
method that will close the file. However, the finalizer can close the file
in case of problem or if the closing method has not been called.
So, you can use finalizers to provide a fallback mechanism for releasing non
memory finite resources such as file handles or sockets.
End your finalizers with:
super.finalize();
The JVM does not automatically invoke superclass finalizers, so you must do so explicitly. Making super.finalize() the last action of a finalizer ensures that subclasses will be finalized before superclasses.
Do not call finalize
Finalize is designed to be called by the garbarge collector. Do not call
it yourself. Define CleanUp methods if you need to deallocate object at
specific moments.
(Source: Object Finalization and Cleanup, http://www.artima.com/designtechniques/cleanup.html)
[Top]
If an initializer or a constructor of a superclass invokes a non-private
method which has been overridden in a subclass, the subclass's implementation of
that method will run. If the subclass's method implementation uses instances
variables explicitly declared in the subclass, those variables will still have
their default initial values (their initializers and the subclass's constructor
will not have been called yet).
If you invoke non-private methods from initializers and constructors, remember
that later some other programmer could extend your class and override those
methods.
Eg: Here is a Building class which has 5 floors of 3 flats and a BigBuilding class with 10 floors of 4 flats.
class Building { protected int flatCount; private int floorCount = 5; Building() { flatCount = initFlatCount(); } int initFlatCount() { return floorCount * 3; } // ... } |
class BigBuilding extends Building { private int flatByFloorCount = 4; int initFlatCount() { return 10 * flatByFloorCount ; } // ... } |
After the constructor, flatCount = 15 | After the constructor, flatCount = 0 ! because flatByFloorCount has not been initialized before the call to BigBuilding::initFlatCount by Building's constructor. |
(Source: Object Initialization in Java, http://www.artima.com/designtechniques/initialization.html)
[Top]
The compiler generates an error if an initializer refers to an instance variable declared textually after the variable being initialized. So you cannot make a direct forward reference from an initializer.
You'll never be allowed to write:
class Building { private int flatCount = 3 * floorCount; private int floorCount = 5; // ... }
But you may inadvertently circumvent the compiler's preventive restriction:
class Building { private int flatCount = initFlatCount(); private int floorCount = 5; private int initFlatCount() { return floorCount * 3; } // ... }
This code compiles without an error but the Runtime result is not the
expected one: flatCount equals 0.
Indeed, when initFlatCount is called, floorCount is not initialized yet, so it
has its default value:0.
(Source: Object Initialization in Java, http://www.artima.com/designtechniques/initialization.html)
[Top]
In Java, the constants must be coded as static final variables. Numerical
constants (literals) should not be coded directly, except for -1, 0, and 1,
which can appear in a for
loop as counter values.
A static final variable:
(Source: Designing Fields and Methods, http://www.artima.com/designtechniques/coupling.html)
Moreover, static final variables enable you to define "enum" in
Java.
For example the C++ statement::
enum Color {RED, GREEN, BLUE, WHITE}
may be replaced in Java by:
public final class Color { private Color() {} public static final Color RED = new Color(); public static final Color GREEN = new Color(); public static final Color BLUE = new Color(); public static final Color WHITE = new Color(); }
(Source: Type safe constants in C++ and Java, http://www.javaworld.com/javaworld/javatips/jw-javatip27.html)
[Top]
If a method returns a reference to a private data member, the client can modify it and corrupt it.
Avoid | Prefer |
import java.awt.Dimension; public class Ground { private Dimension d = new Dimension (0, 0); public Dimension getDimension() { return d; } } |
import java.awt.Dimension; public class Ground { private Dimension d = new Dimension (0, 0); public Dimension getDimension() { return new Dimension (d.x, d.y); } } |
The following code affects only the first implementation
Ground gr = new Ground(); Dimension d = gr.getDimension(); d.height = -5; d.width = -10;
So, if you don't want the caller method to be able to modify the original object, clone it before returning it. However, cloning may take a certain time. So you can disobey this rule if time is a critical point and if you know the possible callers (e.g., if the class is not public, the callers only belong to the same package).
In the same way, when you return an array, not only clone the array but also each object belonging to the array unless you intentionally give the client the access to the original objects.
Avoid | Prefer |
import java.awt.Dimension; public class Grounds { static final public int TOTAL_VALUES = 10; private Dimension[] d = new Dimension[TOTAL_VALUES]; public Dimension[] getDimensions() throws CloneNotSupportedException { return (Dimension[])d.clone(); } } |
public Dimension[] getValues() throws CloneNotSupportedException { Dimension[] copy = (Dimension[])d.clone(); for (int i = 0; i < copy.length; ++i) { if (d[i] != null) { copy[i] = new Dimension (d[i].height, d[i].width); } } return copy; } |
The same problem occurs with multidimensional arrays of atomic types.
(Source: Complement testing with code inspections, http://www.javaworld.com/javaworld/javatips/jw-javatip88.html)
[Top]
Java provides immutable classes, for example:
Immutable objects cannot change, so there is no need to clone them before returning them.
Avoid | Prefer |
public class Rope { private Integer lg = new Integer(0); public Integer getLength() { return new Integer(lg.intValue()); } } |
public class Rope { private Integer lg = new Integer(0); public Integer getLength() { return lg; } } |
A particular type of immutable class is String which represents character strings. All string literals in Java programs, such as "abc" are implemented as instances of this class. Since String objects are immutable, they can be shared. So:
Avoid | Prefer |
String str = new String("abc"); |
String str = "abc"; |
(Source: Complement testing with code inspections, http://www.javaworld.com/javaworld/javatips/jw-javatip88.html)
[Top]
Exception
(or Exception
itself), excluding class RuntimeException
and its subclasses.
Unchecked exceptions are RuntimeException
and any of its
subclasses. If you throw a checked exception (and don't catch it), you will
need to declare the exception in your method's throws
clause.
Client programmers who wish to call your method will then need to either
catch and handle the exception within the body of their methods, or declare
the exception in the throws clause of their methods. Making an exception
checked forces client programmers to deal with the possibility that the
exception will be thrown.Exception
, for example, with a string message
indicating the kind of abnormal condition that caused the exception. Define
or choose an already existing exception class for each kind of abnormal
condition that may cause your method to throw an exception. This way, client
programmers can define a separate catch clause for each kind of exception,
or can catch some but not others, without having to query the object to
determine the kind of abnormal condition that caused the exception.(Source: Designing with Exceptions, http://www.artima.com/designtechniques/desexcept.html)
[Top]
long
s and double
s. This
means that if you have an int
, for example, that is independent
of any other fields in an object, you needn't synchronize code that accesses
that field. Inversely, if two different threads were to attempt to write two
different values to a long
concurrently, you might just end up
with a corrupted value consisting of some bits written by one thread and
other bits written by the other thread. Multithreaded access to long
s
and double
s, therefore, should always be synchronized.(Source: Designing for Thread Safety, http://www.artima.com/designtechniques/threadsafety.html)
[Top]
Version: 1 [Sep 2000] | Document created |
[Top] |
Copyright © 2000, Dassault Systèmes. All rights reserved.