A gotcha is a nasty surprise in the Java language or the standard libraries.
Some might call them bugs, some features. Sometimes they are the result of
incompetence or carelessness on the part of the language designers and sometimes
they are just quirky things that cannot be helped. Here is a chart of the some
dangerous waters.
I spoke on this topic in 1997-11 at the Colorado
Summit Conference, and again in 1999 November.
Inconsistent Extensions
Sometimes the extension should be obvious, e.g. .java
when compiling, or .class when executing. You would
think the extension would be optional. It is not. Sometimes you must specify it,
and sometimes you must not. And sometimes you can get away with doing it either
way, on some platforms. Here are the rules that will always work.
| Where |
Extension Mandatory? |
Example |
| compiling |
mandatory |
CD \MyDir
javac.exe -classpath . HelloWorld.java |
| executing |
must exclude |
CD \MyDir
java.exe -classpath . HelloWorld |
| Applet |
mandatory |
<applet
code="HelloWorld.class"
width="230"
height="240">
<img src="image/NoJava4U.jpg">
</applet> |
We Aren’t In Kansas Anymore
C/C++ programmers will attempt to write code like this:
if ( width )
{
widen( width );
}
if ( myDog )
{
myDog.bark();
}
You need to spell these out longhand in Java:
if ( width != 0 )
{
widen( width );
}
if ( myDog != null )
{
myDog.bark();
}
However, for boolean variables you can say things like:
if ( tooWide )
{
widen( width );
}
Beware, you can also inadvertently say things like like:
if ( tooWide = true )
{
widen( width );
}
Note: that the = above is an assignment operator,
not an == comparison operator. The if is always
true.
Default Fall Through
C programmers are familiar with this, but those coming from languages designed
by Professor Wirth will gasp in astonishment that Java would allow such
unstructured code.
switch ( k )
{
case 1:
System.out.println( "hello" );
case 2:
System.out.println( "hi" );
}
When k is 1, the program will print out both "hello "
and "hi". Case clauses fall though by
default. You won’t get a syntax error or even a warning if you leave out
the break after each case.
Because the syntax for defining cases and labels is so similar, it is easy to
make an error like this: The programmer left out a space and, instead of
handling n == 3, he defined a goto label called case3 in his switch statement.
switch ( n )
{
case 1:
...
case 2:
...
case3:
...
case 4:
...
}
In JDK 1.4+ you can use the javac.exe -Xswitchcheck to
get the compiler to warn you if you do this. However, there is no syntax to tell
Java when you meant to do it deliberately. e.g. a break
case or fallthrough keyword.
String.replace
String aa = "peal";
String bb = aa.replace( 'a', 'e' );
System.out.println ( aa + " " + bb );
String. replace does
not modify the input String. It could not, even if
it wanted to, since Strings are immutable. Further,
the Javadocs for replace explain that replace
creates a new String.
Sun’s Javadoc on
String.
replace : available:
This gotcha is not Sun’s fault. The same applies
to String. toUpperCase
and String. loLowerCase.
Microscopic Fonts
Sometimes fonts will come out too tiny to see. Likely it means you have reversed
the last two parmameters when you created the font, a error the compiler cannot
detect since Font does not use enums,
just enumerated int constants.
component.setFont( new Font( "Dialog", 12, Font.BOLD ));
component.setFont( new Font( "Dialog", Font.BOLD, 12 ));
Font.createFont
produces a font 1 point high, too tiny to see. You must use Font.
deriveFont before you can use it.
double Double Toil and Trouble
The compiler for languages such as Eiffel goes through Houdiniesque contortions
to let you treat primitives as objects while simultaneously giving primitives
express treatment when you don’t need the objectness. In such languages,
you can write code analogous to this:
int ii = -42;
int jj = ii.abs();
String s = ii.toString();
But in Java, you can’t use instance methods on primitives. However, you
still need set of standard methods to deal with bytes,
shorts, chars, ints,
floats and doubles. So
Sun created a set of classes called Byte, Short,
Character (not Char), Integer
(not Int), Float and Double.
They wrote everything to fiddle with primitives as static
methods! What else could they do?
You need to treat primitives as objects for another purpose — so that you
can put them into containers such as ArrayLists or Vectors.
In Java, you must manually wrap your primitive up in an object. Sun provides a
set of immutable object wrapper classes called: Byte,
Short, Character (not
Char), Integer (not Int), Float
and Double, the very same classes that house your static
methods for fiddling with primitives! It might have been clearer had they called
these classes names like ImmutableByte or ByteWrapper,
but they didn’t. You can’t change the value of the primitive inside
any of these wrapper objects. All you can do is create a new object with a new
immutable primitive sitting inside.
To make matters all the more confusing, the Double
class then has double duty, to deal with both double
primitives and Double wrapper objects. The instance
methods for Double manipulate Double
objects and the static methods for Double
manipulate double primitives. These methods
naturally have similar if not identical names! The same problem occurs for all
the other primitive/class pairs, but most newbies seem to trip over it first
with
double d;
Double dd;
String g;
g = Double.toString( d );
g = dd.toString();
See the conversion for help in interconverting the
various primitives and wrappers.
toString Blues
toString is defined on every object to allow you to
convert it to a String. However, the String
you get is often nothing like you would expect.
With the default toString, you just get the class
name and hashcode of the object, which looks like gibberish, e.g. [C@ad3ba4.
char[] c = { 'a' , 'b' , 'c'};
System.out.println( c.toString() );
will print out the address of c, not the characters that compose it. You must
use new String
(char[]
) to convert a char
array to a String.
Label.toString() is not an alias for Label.getText().
It will print out a summary of the Label fields like
this: java.awt.Label[label0,0,0,0x0,invalid,align=left,text=def]
How then do you convert to String? With a hodgepodge
of techniques that rival French verbs for irregularity.
Is char a character or a number?
char can be thought of as a character or an
unsigned 16 bit integer. This ambiguity catches up with us in a statement like
this:
String x = "foo" + 's';
In JDK 1.1, x is "foos".
In early JDK 1.2 it is "foo115", but was
fixed for the release version.
Octal
I have not seen octal used since the days of the Univac 1106 (except for Unix
file permissions), however Java has carried on the C and C++ tradition. Any
numeric literal with a lead zero on it is considered
base 8, octal. Beware, programmers are used to using lead zeros simply to align
numbers in columns.
The Case Of The Disappearing
Constructors
When you define a constructor, you must not
specify a return type, even though it behaves much like a static factory method
that returns an object. You may not even specify a void
return type. It is best to think of constructors as like parameterless instance
methods that operate on the current object.
Even though a constructor is similar to static method that returns a new object,
you may not declare the constructor static. It is not static, since it works on
specific default object. this is defined in the
constructor.
If you do any of these things, the compiler will think your constructor is just
an ordinary method. You will be baffled by the compiler’s complaints that
you have not defined a suitable constructor to match the arguments you provide.
It may help if you realise that new allocates/creates
the object and the constructor initializes it. Therefore the constructor has no
need to return an object.
If you don’t provide any constructors at all, Java will automatically
provide you with a default constructor the form:
public MyClass()
{
super();
}
However, if you get ambitious and write an extra constructor, say like this:
private MyClass( int fudgicles )
{
this.fudgicles = fudgicles;
}
The default constructor will disappear! You have to then provide it yourself
explicitly.
You will most likely come across this problem when you see the following error
message:
load: com.mindprod.mypackage.MyApplet.class can’t be instantiated. java.lang.InstantiationException:
com/mindprod/mypackage/MyApplet
In English, it means you are missing the default public constructor for your
Applet. See constructor.
Missing Hex
In Java Strings, you can no longer say "\xf2",
"\a" or "\v"
as you can in C++. Happily "\n", "\r
", "\\", "\'"
and "\"" still work. To encode
control characters without specific abbreviations you must now use octal, e.g. "\012"
for a linefeed, formerly "\x0a ". Octal
character constants must be exactly 3 digits. See literal
in the Java & Internet Glossary.
The new hex Unicode technique "\u003f" has
a catch. It is not suitable for encoding control characters. It is evaluated in
a pre-processor pass and converted to a character, then the code is compiled. If
for example you wrote "\u000a", this gets
converted to:
"
"
Since the \u000a gets converted to a newline
character. It definitely won’t work for Cr and Lf. It might work for some
of the other control chars in some compilers. I suggest using the octal forms
for safety.
Math.sin
Every novice tries code like this:
double x = 90;
double y = sin( x );
double z = tan( x + PI );
And stares and stares at it wondering why it won’t work. You need to write
it this way:
static final double CONVERT_DEGREES_TO_RADIANS = Math.PI / 180;
double x = 90 * CONVERT_DEGREES_TO_RADIANS;
double y = Math.sin( x );
double z = Math.tan( x + Math.PI );
There are three places to trip up:
- the Math package works in radians, not degrees. 360 degrees = 2 pi radians.
- You need the Math.sin instead of plain sin because sin, cos, tan etc. are static
functions. Compared with other languages you may have used, Java is picky and
verbose. It wants the "Math." even when there is no name clash with a
sin function in some class other than Math or some package other than java.lang.
Java wants to avoid even the possibility of an eventual name clash by making you
qualify with Math.sin now. In a similar way, you must precede all static
function and method invocations with the classname.
- You need the Math.PI instead of plain PI because PI is a static constant. In a
similar way you must precede all static constants with the class name.
- JDK 1.2 has added two convenience methods:
public static double toDegrees ( double angleInRadians );
public static double toRadians ( double angleInDegrees );
Math.sin(Double.PI) != 0 since Double.PI
isn’t precisely equal to the irrational number PI. So Math.sin(Double.PI)
shouldn’t be expected to be exactly equal to sin(PI). Similarly Math.cos(
Math.toRadians( 90 ) ) is not bang on zero either.
Further, every time you do a floating point
calculation you loose a little precision. It all adds up.
In Java 5.0 you no longer need the Math. in Math.
sin() so long as you add an import
like this:
import static java.lang.Math;
Where’s the Beep?
Java does not have a built-in set of sounds. It ignores '\a'
in console output, though you can use \007.
In JDK 1.1 You can make a simple beep with j java.awt.Toolkit.beep().
I have seen reports that beep does not work properly on some platforms.
In JDK 1.0.2 you can use
System.out.print ( "\007" );
System.out.flush();
You can also play AU, wav, midi and
aiff files with AudioClip.play.
See sound in the Java & Internet Glossary for more details.
Where’s the Root Directory?
You might look a long time through java.io.* trying
to find the directory operations, before you find them hiding in the File
class e.g. list a directory to find out the files in it. However,
File dir = new File( "C:\\" );
String[] files = dir.list();
won’t cut it. To look at the root. You must say:
File dir = new File( "C:\\." );
String[] files =dir.list();
Be aware that "\" in Java Strings has to
be written as "\\".
This makes reading Strings representing filenames confusing. Unix systems use
the "/" path separator character instead of "\". Macintoshes
use ":" and have interesting twists like "::" for up instead
of "/../" as in Unix. To write platform independent code, you should
use the system file.separator and path.separator, or use the File methods that
construct filenames for you from parts.
Then you might well ask, how do you find out which drives are available? In JDK
1.2 there is a new method:
File[] roots = File.listRoots();
For Each Gaps
for : pronounced for each
is a shortand extension to the regular for. It lets
you iterate over Iterators, Collections and arrays with a terse syntax. However,
it will not let you iterate over the characters of a String
or let you look at the index while you are iterating
Unstoppable for
for ( long i=Long.MAX_VALUE -2; i<=Long.MAX_VALUE; i++ )
{
}
How many times will that for loop execute? Until
you kill the process! It will loop endlessly because i
can never get bigger than Long.MAX_VALUE to
terminate the loop. There are similar problems with Integer.MAX_VALUE,
Long.MIN_VALUE and Integer.MIN_VALUE.
You get used to thinking of the upper limit as the logical stopping point, where
canonical for loops actually stop on one past that value.
Upper Case Surprises
Take a look at the source of java.lang.String.toUpperCase().
You might expect it to contain only some very simple code of the form:
if ( 'a' <= theChar && theChar <= 'z' ) theChar -= ('a' -'A' );
However, you will discover the code is quite elaborate. For example, it tests if
the current locale is Turkish to call special code to cope with the dotless i.
It tests for the German ß and converts it to a pair of letters "SS
"! If you are only working with English, you might want to roll your own
more streamlined version. If you take a string to lower case then back to upper
case then back to lower case, you won’t necessarily end up where you
started.
String.substring
In other languages, to extract a substring, you give an offset where the string
starts and a length in characters. In Java, you provide two zero-based offsets.
The first points to the start of the string as you might expect, and the second
points one past the end of the string.
"abcdefg".substring( 3, 5 ) gives "de".
"abcdefg".substring( 3, 7 ) gives "defg".
"abcdefg".substring( 3, 8 ) gives StringIndexOutOfBoundsException.
If you specify offsets that are outside the span of the string, you don’t
get a truncated or null string; you raise a StringIndexOutOfBoundsException.
One way to remember the way it works is that you specify the first character to
include and the first character to exclude. But not quite. You are allow to be
just barely past the end.
"emptiness".substring( 9 )
returns "" (an empty string)
"emptiness".substring( 10 )
gives a StringIndexOutOfBoundsException
Why do it this way?
- Often you have an index scooting along a string, pointing the beginnings of each
token in succession. To pull out the token you need only the old and new values
of this index.
- Edsger Dijkstra (one of the earlier pioneers of computer languages) put up a
logical argument for having range defined by their first value and value-after-the-end.
- the difference between the end and begin is the length.
- checking for empty range is done simply by checking if begin
== end.
There is another way of looking at the indexing that fits the Java behaviour in
a more natural way. The index does not identify a character in the string, it
identifies a position between the characters of the string, starting at 0
and ending at the string length. For example, "APPLE"
has indexing like this:
(0) A (1) P (2) P (3) L (4) E (5)
Then:
s = "APPLE".substring( 1, 4 );
s = "APPLE".substring( 3, 3 );
s = "APPLE".substring( 0, 5 );
Beware of using substring with only one argument.
String tail = x.substring( endindex );
gets you the tail end of a string starting at endindex.
It does not get you the beginning of the string ending at endindex.
head = x.substring( 0, length );
gets you the first part of the string, the first length characters.
Incomparable NaN
When you divide by zero with double, take the square root of a negative number,
overflow the maximum representable value etc. the result is a magic number
called Double.NaN, Double.POSITIVE_INFINITY
or Double.NEGATIVE_INFINITY. You check the result
with:
if ( Double.isNaN( d ) )
or with Double.isInfinite.
You can’t test it directly with:
if ( d == Double.NaN )
Though for some bizarre reason you can use == to
compare with Double.POSITIVE_INFINITY.
There is a corresponding Float.NaN
and Float.isNaN. The
theory is making NaN not equal to itself allows a quick and dirty way to test
for a calculation going haywire.
if ( result != result )
{
System.out.println( "oops" );
}
ValueOf and null
int[] intArray = null;
String.valueOf( intArray );
char[] charArray = null;
String.valueOf( charArray );
Final vs Const
Java has a keyword final and C++ has a keyword const.
They are similar, but don’t fall into the trap of thinking they are
identical. Declaring a variable final will prevent
the value of that variable from being changed after initialisation. However, if
that variable is a reference to an object, it will not necessarily
prevent the various fields in that object from being changed. e.g.
final Thing thing = new Thing( 7 );
thing = otherThing;
thing.setSize( 10 );
thing.girth = 6;
This also applies to parameters declared final.
Overflow
Java is cavalier about overflow. There are no compile-time warnings or run-time
exceptions to let you know when your calculations have become too big to store
back in an int or long. There is no warning for float or double overflow either.
One place you often get nailed is when some calculations are done as int, high
order parts are truncated, then promoted to long.
Watch out for chains of multiplied integer constants:
IBM’s BigDecimal and Archimath
BigDecimal have provisions for detecting overflow and generating an
exception. You can roll your own with code like this in standard Java.
or this one to detect overflow in multiplication:
If you know both values are positive, you can use a simpler overflow check. You
could check if the sum is less than 0 or less than one of the two operands. I’m
not sure if this catches all overflows, however.
Floating point arithmetic uses the IEEE error propagating scheme similar to that
used in a spreadsheet to propagate invalid value flags to cells that depend on
invalid values. In Java, though there is no overflow interrupt or notification,
there is a good chance you will eventually find out about an overflow error when
you see a java.lang.Double.POSITIVE_INFINITY, java.lang.Double.NEGATIVE_INFINITY
or java.lang.Double.NaN showing up in one of your
double variables. You can’t use == to test for NaN, you must use java.lang.Double.isNan().
Override vs Shadow
What happens when you reuse a method or variable name in a subclass? It depends.
There are four cases to consider:
- static method
- instance method
- static variable
- instance variable
Do you inherit the superclass version or get subclass version? This is all so
confusing, I suggest you perform some experiments. Here a little program I wrote
to discover the various shadowing and overriding behaviours:
For more discussion, see shadowing variables and overriding
methods in the Java & Internet Glossary. My general advice is never to
shadow variables. There is no need for it. It just causes confusion. In summary:
- Static methods are never selected dynamically; they are always selected based on
the type information known at compile time.
- Instance methods always are selected dynamically based on the type of the object.
The type of the reference or any casts are irrelevant. In C++ terms, all (non-final)
methods are virtual. The main place this gets you in trouble is if you use a
method in a constructor that is later overridden in some subclass. That method
may use the subclass’s fields that have not yet been initialised by code
in the subclass’s constructor. That method will just see subclass
variables initialised to 0 or null.
- Static variables are always selected based on the type information known at
compile time, taking any casts into consideration.
- Instance variables are always selected based on the type information known at
compile time, taking any casts into consideration.
- There is no way to get at grandma’s method if mom has overridden it.
Either grandma or mom would have had to given you a backdoor to that method with
a wrapper around it by another name that was not overridden. Mom can of course
use super to get at Grandma’s methods.
"broken" setLocation,
setSize, setBackground, setForeground
People often complain they can’t get setLocation,
setSize, setBounds, setBackground
or setForeground to work. The problem is usually
that something else is setting them and overriding your settings:
Culprits include:
- Layout Managers. They do resize() and move() (the deprecated method names) on
the contents of each container. Only the null layout manager will leave your
sizes intact.
- Generated code in Visual Cafe will do a move() and show() (the deprecated names)
in an overridden show() method.
- Your own code using deprecated names like move() or resize().
Unsigned Bytes
Back when the earth was still molten, when characters still had only 7 bits,
somebody thought it would be a good idea if characters were signed. This caused
a schism in the C world when 8-bit characters later appeared. Java added
unsigned 16-bit Unicode characters, but decided to support only signed 8-bit
characters, known as bytes. Perhaps Java’s designers wanted to encourage
migration to Unicode by making handling unsigned bytes awkward. In any case, you
most often want unsigned 8-bit characters, not signed. How do you fake them?
int i1 = b2 & 0xff;
byte b2 = (byte)( b2 + 1 );
byte b3 = b2;
- On every reference, you must mask off the generated sign-extending high order
bits with & 0xff. Keep in mind that almost any operation on a byte will
promote the result to an int.
- On every store, you must cast from int back to byte.
- The only time you don’t need the (byte) cast is when the right hand side
is already a byte.
Modulus
In Java you take the remainder with the % operator.
In Java, the sign of the remainder follows the dividend, not the divisor. Java
division has the Euclidean property. When you multiply the quotient by the
divisor and add the remainder you get back to the dividend. Java division is truncateddivision.
Floored division is what you normally want when trying to figure out
which bin an item belongs in. You can compute floored division as:
For computing how many fixed-size bins you need to contain N items, you want ceiled
division, also known as the covered quotient. You can compute the covered
quotient as:
| Signs |
Division |
Modulus |
| + + |
+7/+4=+1 |
+7%+4=+3 |
| - + |
-7/+4=-1 |
-7%+4=-3 |
| + - |
+7/-4=-1 |
+7%-4=+3 |
| - - |
-7/-4=+1 |
-7%-4=-3 |
I have a general rule to avoid writing code that depends on the expected sign of
the modulus. It is often a source of bugs since people testing have their own
ideas of how the answers should be. For example the Microsoft JIT gives wrong
signs even for division, but the interpreter gives correct ones.
Static Initialisers
You have to enclose any initialisation code for static (class) variables inside
a sandwich like this:
static { calcPriceTab();}
Newbies just stick such code anywhere inside the class { } sandwich and are
baffled by the misleading error messages.
The order of your variables that are statically initialised matters. Consider
this little program:
The output with Java 1.4.1 was no compile error and 61 0 61,
YMMV.
The order of those three static finals is crucial to the results! You must put
them in the order you want the calculations done. Java is not smart like a
spreadsheet to do natural order recalcs for you. It will notice and either
handle or reject such forward references when only literals and simple variables
are involved, but as soon as you do things inside methods, it throws up its
hands and says on your own head be it.
Instance Initialisers
You have the option of initialising an instance variable in:
- the declaration
- the constructor
- an instance initializer block
The advantage of putting it on the declaration is that you need to specify it
only once, not once for each constructor. This means there is less likelihood of
error if its value is ever changed. The other safe approach is to put all your
initialisation code in one method or one constructor and have all the
constructors call it.
Constructor Initialisation
The order that fields are initialised is subtle.
However, there is one particularly tricky problem with initialisation order. In
general you must avoid calling any non-final methods in a constructor. The
problem is that instance initialisers / variable initialisation in the derived
class is performed after the constructor of the base class. This can
cause a problem if the base class constructor calls a method polymorphically,
since that method will be presuming its derived class fields have all been
initialised when they have not.
So base class constructors can safely call private or final methods of the base
class provided those methods directly or indirectly call only private or final
methods of the base class. If you call methods in your constructor
polymorphically, the compiler will not stop or warn you of the initialisation
pitfalls awaiting.
Casts
Java is a strongly typed language. You not only need to be aware of what type
each of your variables were declared, you must also keep track of the type of
object each is currently pointing to, which may be a subclass of the declared
class.
There are four sorts of cast:
- to expand a value:
byte b = -42;
int i = (int)b;
This cast is nugatory, though you might want to use the cast as a documentation
aid. It does some conversion work — sign extension.
- to trim a value:
int i = -16411;
byte b = (byte)i;
This style of cast actually may do some real conversion work — zeroing out
high order bits.
- to treat a reference as its superclass:
Dog myDog = (Dog)aDalmatian;
This cast is nugatory, though you might want to use the case as a documentation
aid. All Dalmatians automatically have all the Dog fields, so this cast has no
run-time overhead.
- to treat a reference as one of its descendants:
Dalmatian myDalmatian = (Dalmatian)aDog;
At run time, this cast actually checks that aDog truly is already a Dalmatian,
and raises a ClassCastException if it does not. It does not make any attempt to
convert a Dog to a Dalmatian.
- Casts with abstract classes and interfaces work the same way as classes.
So where are the gotchas?
- Casts sometimes mean convert something to something else. Other times they mean,
don’t convert, just treat something as if it already were something else.
Casting objects is a misleading terminology. Any actual object has a definite
class, set when it was instantiated. Nothing can change that during the lifetime
of the object.
- You can’t use casts the way you can in C++ to look at the same physical
storage in two different ways, e.g. to overlay a short on top of a pair of bytes,
and sometimes address the storage as if it were a short and other times as if it
were two bytes. Java is cleverly designed so that you can’t write a Pure
Java program that depends on the big-endian or little-endian format of internal
storage. (All external representations are big-endian).
To break a short up into bytes you have to shift and mask.
- You might logically presume that casts are for converting one type into another.
You might attempt code like this:
String s1 = (String) i;
int i = (int) s2;
String s3 = (String) myDalmatian;
Yet casting only works for two primitives. When there is a primitive and an
object involved, there is a system of conversion
functions with about as much regularity as French verbs.
- Whenever you store into an array of references, there is a type check done to
make sure the object you are inserting is of the correct type. These checks can
be quite slow.
- (String)is not smart enough to invoke the toString()
method of an object.
- You can cast null into anything without raising a
ClassCastException. Generally, this is a good thing. Otherwise every cast would
need an if to specially handle the null case. You
might be tempted to count on ClassCastExceptions to filter out nulls for you.
The following code will not raise a java.lang.ClassCastException:
Cat c = null;
Object o = c;
Dalmatian d = (Dalmatian)o;
In other words, there is one universal representation for null, not a special
one for each class.
In contrast if ( null instanceof
Dog ) is always false. instanceof does
filter out nulls.
- What’s wrong with this code?
Dog fido = new Dalmatian();
int spots = (Dalmatian)fido.spotCount();
Hint: What are you trying to cast to Dalmatian, the Dog fido or the int result
of the spotCount method? When you are casting, you often need a forest of
parentheses. This is what you should have written:
Dog fido = new Dalmatian();
int spots = ((Dalmatian)fido).spotCount();
- Parenthesis Forests:
You think you have the hang of it? Look at this:
(B)( ( (A)va.get( 0 )).b ).c = x;
What does that mean? Get the first element of vector va (an Object), cast it to
an A object which then, using field b, points to a another object. Cast that
object to class B. In that object is a string reference c that you want set to x.
That would be so much easier to read as:
va.get(0):A.b.c:B = x;
In getting those ()((()()))) right, count +1 for each ( and -1 for each ).
You should get back to 0 at the end and any place you think you should be
outside all (). You should never go negative. In that case above you count:
1 0 1 2 3 2 3 2 1 0
This is better than merely counting the number of ( and ) and
seeing if they match, though it may take more mental effort.
The problem is the infernal mix of prefix, postfix and infix operators that Java
inherited from C. (At least the goofiness of String x [] is sort of gone).
In a language with postfix casts and flatter precedence that might read:
va.get(0) (A) .b (B) .c = "hello";
You can similarly balance {} and [], though {} is harder since they can be
widely separated. Best to use a smart editor. See parentheses
(), brackets [], braces {}.
Implicit Casts
Conversions and promotions occur both when you explicitly request them, and
sometimes automatically.
- Automatic Assignment Conversion converts an expression’s type to a
variable’s type (ex. short value = 26). This type of conversion is allowed
when converting from a type to that same type (identity conversion), when
performing a widening conversion, or when performing a narrowing conversion
which assigns an int to a byte, short, or char variable where the int is
representable by the (byte, short, or char) variable. Note that this form of
conversion occurs only in assignments that preclude exceptions by definition.
- Automatic Numeric Promotion homogenates operands to allow an operation (ex. 1.0f
+ 2.0 will cause 1.0f to be promoted to a double).
- Automatic Method Invocation Conversion occurs when passing arguments during a
method invocation (ex. calling methodA(45) on a method defined as methodeA(long
value)). Except for disallowing implicit narrowing of integer constants, this
form of conversion’s behavior is identical to that of automatic assignment
conversion. Note that this form of conversion occurs only when the argument
types passed to the method can be automatically converted to those specified in
the method signature in a manner which precludes exceptions by definition.
- Automatic String Conversion allows any type to be converted to type String. This
occurs when the "+" String concatenating operator is used (ex. String
resultString = "the answer is:" + result, where result can be of any
type)
Concatenation
Java uses the + operator to mean both addition and concatenation. Parsers can
unambiguously figure out which your intent is from the context, but humans can
be easily fooled. For example:
System.out.println(" x+y " + x+y );
System.out.println( x+y + " x+y " );
Which + are addition? Which are concatenation?
The way you most commonly get caught is is code like this where the last + is
treated as concatenation.
System.out.println( "value: " + v + 1 );
The concatenation operator has the magic power of being able to implicitly
coerce an int into a String
by automatically invoking the
static String
Integer.toString(int)
method, however, oddly, you can’t do the same thing explicitly
with a (String) cast.
What do you think this little code snippet produces?
System.out.println( 'A' );
System.out.println( 'A' + 'B' );
You might naively expect: A AB, or perhaps 65
131, however, the answer is: A 131.
The problem is Java’s design blunder of using +
to mean both addition and concatenation. Addition also promotes to int,
which println displays differently from char.
I really want this fixed. Concatenation should get a new operator symbol and
using + for concatenation should be deprecated.
The current scheme leads to code too easy to misread. +
with int can mean either addition or concatenation.
Deciding which depends too much on subtle context clues.
M y O u t p u t L o o k s L i k e T
h i s
There are 9 common character handling types in Java
| Type |
mutable? |
size in bits |
signed? |
Description |
| String |
immutable |
16 |
unsigned |
Unicode |
| StringBuffer |
mutable both in value and size |
16 |
unsigned |
Unicode |
| char |
mutable value |
16 |
unsigned |
individual Unicode character. |
| Character |
immutable |
16 |
unsigned |
Unicode character object. |
| char[] |
mutable value |
16 |
unsigned |
array of Unicode characters. |
| byte |
mutable value |
8 |
signed |
individual ASCII char. |
| Byte |
immutable |
8 |
signed |
ASCII char object. |
| byte[] |
mutable value |
8 |
signed |
array of ASCII chars. |
| UTF |
immutable |
8/16 |
unsigned |
16-bit length, 7 bit chars, multibyte codes for 16-bit chars with high bit
on. |
Especially when you are doing I/O. you need to be very clear whether you have 8
or 16 bit characters internally and 8 or 16 bit characters externally. Some I/O
methods convert, some do not. A hex file viewer will help you track down such
problems. An ASCII character when converted to Unicode has a high order 0 byte
prepended, since all Java I/O is big-endian.
Finalizers Are Not Destructors
C++ programmers tend to think that the finalize() method is equivalent to a
destructor. This is not true, for two reasons:
- There is no guarantee that a finalizer will ever be called.
- Finalizers do not automatically propagate up the inheritance chain like
destructors. In particular, you should manually call super.finalize() from all
your finalize methods. There is no guarantee about the order of invocation of
finalizers.
There are two System methods you should be aware of:
- System.runFinalization() Runs the finalization
methods of any objects pending finalization.
- System.runFinalizersOnExit(boolean value)
Deprecated. It causes all finalizer methods to be invoked before exiting. This
method is inherently unsafe. It may result in finalizers being called on live
objects while other threads are concurrently manipulating those objects,
resulting in erratic behavior or deadlock.
Finally
Finally can be confusing. The finally keyword is Java’s answer to C++
destructors. In C++, when automatic objects go out of scope (even as a result of
a thrown exception), the objects’ destructors are called in a well-defined
order. Java has garbage collection and no destructors, so there needs to be some
way to ensure that certain things happen before exiting the scope. finally lets
you achieve this, but if you put certain kinds of statements in finally blocks,
you can confuse yourself. The following example always returns 0:
public int square( int n )
{
try
{
return n*n;
}
finally
{
return 0;
}
}
Also, the behavior of exceptions thrown from within finally blocks is not
obvious.
Thread Safety
Thread safety is underspecified, in particular:
- Implications of finalization on concurrent programs. In the Sun VM, there is a
garbage collection thread that is responsible for freeing unreferenced objects
and calling their finalizers. If your finalizer calls synchronized methods, you
can wind up with very hard to debug deadlocks.
- Should the implementation of method clone() from interface Cloneable be thread
safe or not?
- A rule of thumb is: never assume that JDK objects are thread safe. Do not think,
"Oh, I bet I can guess the implementation, and it must be thread safe."
For example, java.util.SimpleDateFormat is not thread
safe, so things like this will cause strange formatting errors in multithreaded
programs:
- Swing components are not thread-safe. You must use javax.swing.SwingUtilities.invokeLater()/invokeAndWait()
or EventQueue.invokeLater.
in any code which might possibly be called by a thread other than the main event-dispatching
thread. This crops up frequently in writing custom ListModels and TreeModels
which respond to non-local or non-GUI events.
- java.util.Hashtable and java.util.Vector
are two examples of JDK objects whose every method is synchronized. This imposes
a large runtime cost on applications that iterate over these structures. When
you are not using threads, prefer java.util.HashMap
and java.util.ArrayList respectively. If you are
using JDK 1.1, it may pay to cannibalise the Hashtable code and remove the
synchronisation.
- Thread.sleep(5000) is supposed to sleep for 5
seconds. However, if somebody changes the system time, you may sleep for a very
long time or no time at all. The OS records the wake up time in absolute form,
not relative.
- Thread scheduling is not guaranteed to be round-robin. A task may totally hog
the cpu at the expense of threads of the same priority. You can use Thread.yield()
to have a conscience. You can use Thread.setPriority(Thread.NORM_PRIORITY-1)
to lower a thread’s priority. In Applets you need security clearance to
even lower thread priority.
java.math.BigDecimal
BigDecimal provides for immutable arbitrary-precision signed decimal numbers. A
BigDecimal consists of an arbitrary precision integer unscaled value (a
BigInteger 2-two’s complement variable length array of bytes) and a non-negative
32-bit integer scale, which represents the number of digits to the right of the
decimal point.
For greater efficiency, you can often use a long or int, and keep track of the
scaling yourself, and inserting a decorative decimal point on output.
BigDecimal is a travesty and deserves raspberries just like Date. Briefly, it
slow, difficult to use, uses native methods, and it quietly drops off
significant digits during conversion.
Also, java.text.DecimalFormat.parse returns either a Long or a Double. There is
no built-in way to define custom number formatting for BigDecimalor
BigInteger objects.
Fortunately, IBM has made available its proposed replacement for Sun’s
class, which is 23 times faster, smaller, uses no native methods, and implements
standard ANSI X3.274 floating arithmetic. For the spec see: IBM’s
Decimal Arithmetic For Java and AlphaWorks.
Dirk Bosmans has implemented the IBM spec as ArciMath.
Another implementation is PSPDec.
java.awt.Graphics.drawRect
java.awt.Graphics.drawRect( int x, int y , int width , int height )
draws a rectangle one pixel bigger than the specified width and height. I am
told if you understand the abstract drawing model the AWT uses, it turns out
this extra pixel is deliberate and unavoidable. The rationale is that you
specify the path of an idealised box drawn with infinitely thin lines, and the
pen hangs down and to the right, at least one pixel thick.
java.awt.Graphics.drawString
All graphics routines expect x,y to represent the upper left corner of a
bounding box. However for Graphics.drawString() x,y
refers to the to the baseline (which is distinct yet again from the lower left
corner). This inconsistency is traditional in drawing packages. You need to take
into account the font metrics:
g.drawString( "Hello World" , 0, getFontMetrics(getFont()).getAscent() );
GridBagLayout
Whenever you use any layout manager, other than null, it is going to decide the
sizes and placement of the components. Your setLocation(),
setBounds() and setSize()
calls will all be overridden. Some ways you can get finer control are:
- Write your own layout manager. It is not as hard as you might think.
- Override the getPreferredSize and getMinimumSize
methods of your components. See the Deprecation Blues section. These methods
used to be called preferredSize and minimumSize,
and there are problems overriding deprecated methods.
- For Swing components, you don’t need any overriding. Have the object send
itself a setPreferredSize() in its constructor or
the client can send it before it is laid out, and the right thing will happen.
GridBagLayout sometimes behaves strangely, generating oddly asymmetric layouts.
The problem can usually be traced to trying to put two components into the same
grid cell. You won’t get any error message when you do this.
GridBagLayout will generate goofy layouts when components provide incorrect
numbers for minimum and preferred size. For example TextFields don’t take
into consideration setColumns or the size of the current font. All you can do is
fudge using the ipadx and ipady
parameters to inflate the minimum size.
GridBayLayout does not mind if you have a row or
column with nothing in it. It will take no space. You might consider leaving
some empty rows and columns in your layouts for future expansion.
weightx and weighty control
where the extra space goes if the container is expanded. Think of them as
percentages that don’t have to add up to 100%. They are automatically
normalised. To figure out which column should get the most space, GridBagLayout
examines each component in the column, and looks at its weightx. It saves the
biggest weightx of all the components in that column as the weight for the
entire column. It does not average them, or add them. Then it proportionately
assigns the extra space based on the column weights. The component with a lot of
weight does not necessarily grow, just the column that component is in. Giving
equal but non-zero weight to columns tends to equalize their size.
GridBagLayout does the same thing allocating extra space to rows by using
weighty.
The Insets(top, left, bottom, right) can be used to
build a border around a component. The four numbers are measured in pixels.
If you want to fix the size of some element, you must use all three methods: setMinimumSize,
setMaxiumSize and setPreferredSize.
Make sure you supply some non-zero x and y weights in your GridBag.
Otherwise when you squeeze the frame down too small the components will act as
if they had infinite space and will scoot madly offscreen to the right.
If you use GridBagConstraints.BOTH, forcing fill, that
will override any setMaximumSize you may have
specified on your panel.
Null Layout
It is possible to handle layout manually with a null
layout manager. To do absolute positioning, in theory all you need to do is Container.setLayout(
null ), and the position your components with Component.setLocation(
x , y ). However when you do this, you often find yourself looking at a
blank screen. Here are some things to check.
- Has your enclosing container shrunk down to a point size 0,0? If so you can
buttress it with Container.setMinimumSize( x, y ),
Container.setSize(x, y); , Container.setPreferredSize( x,
y ), Container.setMaximumSize( x, y ).
- Have your Components all got locations and sizes? I
found, for example, that creating a JButton with an Icon
was not sufficient to set the size of the button to the image size. you must do
a Dimension d = jbutton.getPreferredSize(); jbutton.setSize(
d );
- Have you positioned Components offscreen?
- Have you arranged for repaints?
Oddly, it may be simpler to write your own custom LayoutManager
to do your positioning. The advantages of writing a custom layout are:
- The code that uses the LayoutManager is
perfectly normal.
- You can reuse the layout in other situations.
- Instead of doing absolute positioning, you might create something slightly more
general purpose. You can still force the size of the container to a fixed value,
avoiding all the comlicated parts of writing a LayoutManager.
- It segregates the positioning and sizing logic, thus making your code simpler to
read.
- You can create more maintainable code. The LayoutManager
does the calculations the programmer would otherwise have to do manually.
For a simple LayoutManager or LayoutManager2,
many of the methods can be dummies.
Deprecation Blues
With JDK 1.1, Sun brought more order to the naming of various methods,
particularly in the AWT. The old names are still supported but deprecated (discouraged
from use pending complete removal). Deprecated names are not aliases the
compiler translates to the new names. They are full fledged methods in their own
right. I wondered why vendors like Sun and Symantec were so reluctant to abandon
the old names entirely and convert completely to the new scheme. I have
discovered why.
setVisible() calls the deprecated show(),
the reverse of that you might expect. You would think the deprecated method
should bear the speed penalty of another layer of indirection. Yet consider what
happens if you write a new setVisible() method to
override one of the built-in ones. Users of the original show()
method will be unaffected. They will continue to use the old code. Only those
who directly call setVisible() will use your new
routine. Now, consider what happens if you write a new deprecated show()
method to override one of the built-in ones. All works properly; everyone will
use your new method. You are thus stuck writing new deprecated
methods if you want your code to work properly.
Let us say the AWT were redesigned so that instead show()
called setVisible(). Then old code that used the
deprecated methods would suddenly stop working.
This problem is general and applies to all deprecated methods. Let us hope Sun
will soon get rid of the deprecated methods entirely, then this problem will go
away. Most of the deprecated names are just name changes to fit the JavaBeans
get/set conventions. Such deprecations could be handled as pure aliases by
translation to the new names inside the compiler, and do away with the old
classes entirely. However, that would cause a political problem of JDK 1.0.2
code no longer running under JDK 1.1 without recompilation or some translation
process. You could not then have code that would run both under JDK 1.02 and
1.1. We would need to support the translation process in the JVM to have old
code automatically use the new names. Sun is very reluctant to make any changes
to the JVM.
The JDK 1.0.2 event handling routines are also deprecated. It is quite a bit
bigger job to convert those. They could not be handled by a simple alias.
java.io.BufferedReader &
BufferedInputStream
int BufferedInputStream.read( byte[] m, int offset, int len )
is advertised to block until some input is available. It returns the number of
bytes read, or -1 for EOF. You might erroneouslypresume that it blocks
either:
- until at least len bytes are available.
- until a buffer full of bytes are available.
Not so. You might get as little as one-byte back, even when you are nowhere near
the EOF. len just controls the maximum amount you
are prepared to accept.
int BufferedReader.read ( char[] m, int offset, int len )
has a similar gotcha. You must use java.io.DataInputStream.readFully
if you want to get all the bytes you asked for.
The read routine has another problem. It traps and ignores IOExceptions rather
than passing them on to you. To get around both the above problems, you can use
your own read routine like this:
For more elaborate code to deal with the problem download
the com.mindprod.http package and download
the com.mindprod.filetransfer package. You can
also view some of the code at the http entry.
Applets Can’t Use The
Local Hard Disk
The whole idea of an Applet is to protect the user from you putting any files or
meddling with any files on his hard disk, so you are going to have to cheat if
you want your Applet to be able to write or read his local hard disk. Here are
seven possibilities:
- Give the user a new security manager that has to be installed specially that
gives permission to just your Applet to write to disk. Unfortunately,
this won’t work if anybody else does the same thing. Security managers are
still a black art. I have not yet seen any documentation on just how you would
do this.
- Convert your Applet to an application. The user has to download and install it,
find and install some sort of standalone Java system for his platform, then run
it. Whew!
- Write a native class to cheat and do the I/O behind the security manager’s
back. You will need to write such a native class for each different platform,
then arrange to have it installed separately ahead of time. Ouch! Even then you
still need security clearance to run the native class.
- Use JavaScript or Active-X or some other gonzo scheme that cares not a fig for
security.
- Join the ranks of other programmers with their torches and pitchforks demanding
some sort of chimera — half Applet/half application. It would be allowed
access to a limited amount of disk space, and would not have access to any files
it did not create. It could run inside a browser. This would have general
applicability. You could do off-line data entry for example then upload, or
retain application preference information, cache server data,…
- Using the preferences of Internet Explorer, if you list an application’s
site as a "Trusted Site", then if you set the security zone for "Trusted
Sites" to "Custom" and change the settings such that Java
permissions are "Unrestricted" and "Launch applications and files"
is enabled, whew!, you will be able to write/read files from the local hard
drive from within an Applet. Unfortunately Netscape has no equivalent feature.
- Lobby for a generic user-configurable security manager, that lets users OK
various naughty behaviours from specific Applets. The Applet would have an
interface to request permission for special dispensation with minimum and ideal
requirements.
Reconstituted Serialized Objects
The process of serialization and reconstituting objects is fraught with problems.
- You may not append to the end of a serialized stream once it has been closed and
reopened in append mode. The writes will appear to work, but when you go to read
the file later you will get a java.io.StreamCorruptedException.
- In the creation of the serial stream, Java uses recursion. This limits you to a
chain of about 1000 elements long.
- If you output an object twice to the stream because it has changed, only the
first copy will actually go to the stream, unless you use the atomic bomb
technique of resetting to make it forget all past history.
- When an object is reconstituted, the default constructor of any non-serialisable
superclass is run, not the constructor that was actually used to originally
create the object. However, no constructor of the serialised class itself
is run, not even the default constructor. Further, the initialisation in the
field instance declarations is ignored. The logic behind this would be, why
bother to initialise when you are about to overlay with reconstituted fields?
That is fine for ordinary fields, but it leaves transient fields
uninitialised.
- Code to initialise variables as part of the declaration e.g. int
q = 8; is ignored, (except in non-serialisable superclasses).
- Transient variables will just be set to 0 or null. Any initialisation code in a
constructor or instance declarations will be ignored.
- Fields are serialized and reconstituted in alphabetical order, not necessarily
the same order they appear in the object. This can lead to bizarre effects in
reconstituting when you make a forward reference to a field alphabetically later
than the current one since it has not yet been reconstituted.
- Static fields are neither serialised nor reconstructed.
- If you have a static singleton object that is also referred to by instance
fields, an unwanted extra copy of the singleton object will be created on
reconstitution. Each time you serialise and reconstitute you get more and more
copies of the singleton object.
What could you do to ensure transient fields in reconstituted objects are
properly initialised?
- Don’t use any transient variables. Serialise everything. Yet, when you
have a peek at just how bulky serialized objects are, you won’t think much
of this technique. You want to avoid serialising anything you don’t have
to.
- Code so that the transient variables work even when they have default null or
zero values. This is the simplest.
- Use the Externalizable interface, which is an
extension of Serializable that add the readExternal
and writeExternal methods. When Externalizable
objects are reconstituted, their default constructor is used. The
constructor also gives you a place to insert code to reconstitute static fields.
- Classes that implement Serialization should not use initializers. They
should do all the equivalent work in an initTransient
method and call it from both the constructor and readObject.
This is ugly but safe.
- Check on every method entry, not just readObject
and initialize the transient (yeach!).
- Call an initTransient method after reading your
object (but it then has to be forwarded to the sub-objects, etc. Make it an
interface.
- Derive the ObjectInputStream and make it call the initTransient
(or check for the interface) in either the resolveObject
or the validateObjects method.
"Broken" Repaint
A very common beginner’s problem is failure of repaint()
to redraw the screen. repaint() works by putting an
object in a queue to remind the system to schedule the paint()
later. It will never get around to servicing the queue if you don’t
quickly return from your init method, or handling the keystroke or button press
event. Calling Thread.sleep() just makes matters
worse, since the current thread is the one that will have to later do the paint().
Hidden Components Won’t Stay
Hidden
setEnabled( false )
disables a component by graying it out. setVisible( false
) (a.k.a hide()) makes the component totally
disappear, with an invalidate(), which marks all
containing components as needing a repack(), so
that surrounding components will be shuffled to grow into the space it vacates. setVisible(
true ) (a.k.a. show())
also marks visible all contained subcomponents. This means a component you have
hidden will infuriatingly unhide itself the next time you setVisible(
true ) the enclosing window. This is fixed
in JDK1.1+.
I know of no method that will let you hide a
component, that does not invalidate, thus leaving
its space reserved, with no shuffling of sibling components.
Dialog.setBackground Does Not Work
The Dialog background gets reset when the dialog is being prepared for display,
so your call to setBackground (or setForeground)
in the constructor won’t have an effect. You can fix it with a hook into
the addNotify method of your Dialog
class:
public void addNotify()
{
super.addNotify();
setBackground( Color.red );
}
Add notify creates the peer object. You are hooking your code in right after the
peer gets created.
You also need to explicitly control the background of each component. Inheriting
it from the Dialog will just give gray.
One further warning. In JDK 1.0, the modal feature of dialogs does not work.
Socket To Me
The Socket() constructor doesn’t allow you to
specify a timeout. The default of 1.5 minutes is usually quite excessive.
Until nio, there was no select()
like functionality in sockets (or to be more flexible, in InputStreams) and
there was no timeout in the InputStream.read()
method. This makes it impossible to program a Socket
server having a number of threads less than the number of users. However, I am
told that if you use the available() method
cleverly you can fudge it.
You have some control over timeouts with the networking system properties.
Sun’s JDK Technote Guide on
Networking properties : available:
JSP Import Blues
The problem is in the include mechanism used by Tomcat 4.0.3 and possibly other
JSP server products. The language uses a <%@page import="package.package.class"
%> notation to import classes.
If you use an include for boilerplate headers and footers then you may get a ClassNotFoundException
if the included JSPs uses a custom class.
ASP developers are used to source code being dropped in by the ASP preprocessor
and interpretted as one big file. According to this logic an import statement
would be expected to appear at the top of the "page", where most ppl
declare variables and define functions are in ASP. However in JSP, things tend
to be split up and stay split up into many small files. You must thus make sure
the import goes in each file that uses it, not just at the beginning.
Misleading Error Messages
A compiler looks at source code from quite a different perspective that humans
do. You gradually get to know what your compiler really means when it
says baffling things like "{ expected."
See the table of error messages, now a separate
document.
Credits
As you might guess, a great many people helped compile this list. I have only
recently started giving credit. If you would like to be added to this list,
please tell me.
Unfortunately, the email addresses below are not clickable. Further you cannot
copy/paste them into your email program. You must manually re-type them. The
email addresses are graphic *.png images created by Masker .
I inconvenience you this way to discourage spammers from harvesting email
addresses from the website with automated website spidering.
| Tov Are |
 |
| Paul van Keep |
 |
| Mike Cowlishaw |
 |
| Pierre Baillargeon |
 |
| Bill Wilkinson |
 |
| Patricia Shanahan |
 |
| Joseph Bowbeer |
 |