Java Gotchas
Last updated by Roedy Green
©1997-1999 Canadian Mind Products.
Java Gotchas are quirks in the language or standard libraries. Some
might call them bugs, some features, some nasty surprises. Here is a chart
of the some dangerous waters.
I spoke on this topic in 1997 November at the Colorado
Summit Conference.
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();
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.
Inconsistencies
The designers of Java never took the time to clean up all the methods and
make them use consistent conventions. Here are some examples:
// ways to set text in a component
TextField.setText();
Label.setText();
Button.setLabel();
Frame.setTitle();
// ways to change element
setCharAt(int index, char c);
setElementAt(Object o, int index);
// ways to get length
lenOfArray = myArray.length;
lenOfString = myString.length();
The conversion and I/O functions are impossible to memorise there
is so little pattern to them. See the conversion table of methods.
Java is quite inconsistent about caps, e.g. java.util.TimeZone but
Date.getTimezoneOffset,
instanceof but
Beans.isInstanceOf,
Hashtable but
HashCode, StreamTokenizer.whitespaceChars,
System.arraycopy
Color.RGBtoHSBand
GridBagConstraints.gridwidth.
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:
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, 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 returns an object. You may not even
specify a void return type.
Even though it is effectively a static method, you may not
declare it static. If you do any of theses 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.
If you don't provide any constructors at all, Java will
automatically provide you with a default constructor the form:
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: cmp.MyPackage.MyApplet.class can't be instantiated.
java.lang.InstantiationException: cmp/MyPackage/MyApplet
In English, it means you are missing the default constructor for
your Applet.
Missing Hex
In Java strings, you can no longer say "\xf2",
"\a" or "\v".
Happily "\n", "\r",
"\\", "\'" and
"\"" still work. To encode control characters
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 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.
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 cvtDegToRad = Math.PI/180;
double x = 90*cvtDegToRad;
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.
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 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 files with AudioClip.play.
See sound in the Java 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();
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.
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.
Shiftless Shift
The shift operators can give some surprising results. You can't
shift ints by more than 31 bits. If you try it, you will shift
by that amount modulo 32. Similarly you can't shift longs my more
than 63 bits. If you try it, you will shift by that amount modulo
64. This applies to <<< << and >> So:
int i = -1;
i = i >>> 32;
// i is -1, not 0 as you might expect.
long k = 1;
k = k << 65;
// k is 2, not 0 as you might expect.
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.
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 Djikstra (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.
String Comparison
Unless Strings have been interned, with String.intern(),
you cannot compare them for equality with ==. You have to use equals()
instead. The compiler will not warn you if you inadvertently use ==. Unfortunately,
the bug may take a long time to surface if your compiler or virtual machine
is doing transparent interning. If you want to compare for < or > you
cannot use the usual comparison operators, you have to use compareTo()
instead.
String s;
String t;
s.compareTo(t);
will return:
- some positive number if string s lexically comes after t.
- 0 if s is the same as t.
- some negative number if s sorts earlier than t.
You can think of it roughly like treating the strings as numbers and returning
s-t.
Novices might be surprised by the following results:
- "abc".compareTo("ABC")
returns "abc" > "ABC".
compareTo is case sensitive.
- "abc ".compareTo("abc")
returns "abc " > "abc".
Blanks are treated like any other character.
- "".compareTo(null)
raises a java.lang.NullPointerException. ""
is not the same thing as null. Most String functions will be happy to handle
"", but very few will accept null.
- The comparison is done my straightforward Unicode numeric character by
character comparison. There is no adjustment for locale collating sequence.
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:
// Recipe.java -- demonstrate effects of overriding methods
// and shadowing variables, with and without casting.
public class Recipe
{
public static void main (String[] args)
{
// check behaviour of static functions and variables.
System.out.println(Grandma.name());
// Bessie
System.out.println(Mom.name());
// Rhonda
System.out.println(Grandma.age);
// 70
System.out.println(Mom.age);
// 30
Grandma grandma = new Grandma();
Mom mom = new Mom();
Grandma confuser = new Mom();
// check behaviour of static functions and variables
// called in a permitted, but not very wise, manner
System.out.println(grandma.name());
// Bessie
System.out.println(mom.name());
// Rhonda
System.out.println(confuser.name());
// Bessie !
System.out.println(grandma.age);
// 70
System.out.println(mom.age);
// 30
System.out.println(confuser.age);
// 70 !
// check out instance behaviour of
instance functions and variables.
System.out.println(grandma.recipe());
// light a fire
System.out.println(mom.recipe());
// open a can
System.out.println(confuser.recipe());
// open a can
System.out.println(grandma.cups);
// 20
System.out.println(mom.cups);
// 1
System.out.println(confuser.cups);
// 20 !
// check out instance behaviour of
casted
// instance functions and
variables.
System.out.println(((Grandma)mom).recipe());
// open a can !!!
System.out.println(((Grandma)mom).cups);
// 20 !!
System.out.println(((Mom)confuser).recipe());
// open a can !
System.out.println(((Mom)confuser).cups);
// 1 !
// check out instance behaviour
of
// variables accessed internally
System.out.println(grandma.getCups());
// 20
System.out.println(mom.getCups());
// 20 !!!
System.out.println(confuser.getCups());
// 20 !
System.out.println(((Grandma)mom).getCups());
// 20 !
System.out.println(((Mom)confuser).getCups());
// 20 !
} // end main
} // end class Recipe
class Grandma
{
public static String name()
public static int age = 70;
String recipe()
{
return( "light a fire,..." );
}
public int cups = 20;
public int getCups()
} // end class Grandma
class Mom extends Grandma
{
public static String name()
public static int age = 30;
public String recipe()
{
return( "Open a can ..." );
}
public int cups = 1;
} // end class Mom
For more discussion, see shadowing variables and
overriding methods in the Java 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 subclasses 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.
"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 truncated division.
Floored division is what you normally want when trying to figure
out which bin an item belongs in. You can compute floored division as:
(dividend >=
0) ? (dividend /
divisor) :
((dividend-divisor+1) / divisor);
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:
(dividend >=
0) ? ((dividend+divisor-1) / divisor) : (dividend / divisor);
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.
Array Initialisation
A fixed length block of primitives or references. Java never stores blocks
of repeating structures. It always creates contiguous blocks of references
to separately stored structures. Novices make two common errors: failing
to allocate space for the array and failing to allocate objects for each
cell. Java automatically initialises arrays of primitives to 0 and arrays
of references to null. It won't create any objects for you automatically.
Here is how you would allocate an array of primitives:
// note how you never specify the array's size
in its type declaration.
int[] v;
// allocate space for the array
v = new int[100];
for (int i=0;
i<v.length; i++)
Here is how you would allocate an array of objects.
Cell[] v =
new Cell[100];
for (int i=0;
i<v.length; i++)
{
v[i] =
new Cell(i, 999);
}
Matrix
Initialisation
In Java, a matrix is an array of arrays. Each row of the array can even
have a different number of columns. It is not stored in one contiguous
block
Under the hood, to find an element, you first index by row into a contiguous
block of pointers. That points you to the start of one of another contiguous
block of pointers. You index into that block by column, which points you
to associated object. If you had a 3x5 matrix of objects you would have
1+3+(3*5)=19 separate allocation regions for the matrix and its objects.
Here is the generalized way you would use declare and initialize a 3x5
rectangular matrix.
// This is the fully general way to initialise a matrix.
// There are shorter ways to specify initialisation,
// but this is always what happens under the covers.
// Note how you never specify the array's size in its type declaration.
int[][] mat;
// for each row, allocate a slot for a pointer to an array
mat = new int[][];
for (int i=0; i<3; i++)
{
// allocate an array for each row
mat[i] = new int[5];
for (int j=0; j<5; j++)
{
mat[i][j] = i*j+100;
}
}
If you fail to initialise the array, Java automatically initialises it
for you to zeroes. If you have a matrix of objects, and you fail to
initialise, Java initialises it to nulls for you. It does not allocate
empty objects at each grid point for you. You have to allocate the objects
yourself like this:
Cell[][] mat = new Cell[3][5];
for (int i=0; i<3; i++)
{
for (int j=0; j<5; j++)
{
mat[i][j] = new Cell(i, j, 100);
}
}
Here is how you could create a triangular matrix:
int[][] mat;
// for each row, allocate a slot for a pointer to an array
mat = new int[100][];
for (int i=0; i<100; i++)
{
// allocate an array for each row
mat[i] = new int[i+1];
for (int j=0; j<=i; j++)
{
mat[i][j] = i * j + 100;
}
}
You can initialise a matrix to a list of values this way:
int[][]
mat = { { 11, 12, 13
} , { 21, 22, 21
} , { 31, 32, 33
} };
You might think you could similarly assign a matrix constant to an
array like this:
mat = { { 11, 12, 13
} , { 21, 22, 21
} , { 31, 32, 33
} };
However, the syntax needed (introduced with JDK 1.1) is more verbose:
mat = new int[][]
{ { 11, 12, 13 } ,
{ 21, 22, 21 } ,
{ 31, 32, 33 } } ;
In all these examples, you can use mat.length and
mat[i].length to avoid repeating the constants that define the
matrix's dimensions.
ArrayStoreException
Arrays are like virgins. They are very careful about what they allow in
themselves. When you construct an array you declare what sorts of Object
you are going to allow into the array. You cannot violate that sacred trust:
For example:
Dog[] mutts = new Dalmatian[20];
mutts[2] = new PitBull();
/* Prang!! */
This will get you an ArrayStoreException since the array, as constructed,
only allows Dalmatians (or their subclasses) in it, even though the declaration
says it will allow any Dog in.
however this:
Dog[] mutts = new Dog[20];
mutts[2] = new PitBull();
/* is perfectly ok */
Whenever you store an object into an array, at run time, the
JVM checks to make sure the object is of a compatible type. There are a
few cases where this check is not necessary, e.g. if Dog had no subclasses.
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.
Instance Initialisers
You have the option of initialising an instance variable in the
declaration or in the constructor.
int n = 100; // init on the
declaration
MyClass() { n = 100; } // init in the
constructor
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.
Casts
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.
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 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, you can't do the same thing explicitly with
a (String) cast.
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 |
Strings |
immutable |
16 |
unsigned |
Unicode |
StringBuffer |
mutable |
16 |
unsigned |
Unicode |
char |
mutable |
16 |
unsigned |
individual Unicode character. |
Character |
immutable |
16 |
unsigned |
Unicode character object. |
char[] |
mutable |
16 |
unsigned |
array of Unicode characters. |
byte |
mutable |
8 |
signed |
individual ASCII char. |
Byte |
immutable |
8 |
signed |
ASCII char object. |
byte[] |
mutable |
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.
Cascaded Assignment Operators
Consider the following correct code to swap a and b without using an intermediate
temporary:
int a=4;
int b=5;
a^=b; // a=1,
b=5
b^=a; // a=1,
b=4
a^=b; // a=5,
b=4
You might think you could collapse that program like this, as you can
in some C++ compilers. It may not be legitimate C++, but some compilers
allow it. However, in Java it does not work. You just get a=0.
int a=4;
int b=5;
a^=b^=a^=b;
Even adding parentheses does not help:
int a=4;
int b=5;
a^=(b^=(a^=b));
As a general rule, avoid cascading any of the Java combo assign/compute
operators such as ^= += -= *= /= %= &= |= <<= >>= >>>= or =,
especially when you use the same variable both on the left and right of
an assignment operator.
Random Numbers
I have seen dozens of routines posted on the Internet for generating
uniformly distributed random numbers. Almost none of them worked. If
you want 100 evenly distributed random integers 0..10 here is how you
would generate them:
// Generate random integers 0 .. 10
import java.util.Random;
// To get exactly the same results each run
// seed the generator with a repeatable value.
// Create the Random object only once.
Random wheel = new Random(149L);
// ...
for (int i=0; i<100; i++)
{
// generate another random integer, mask off sign bit, take modulus
int m = (wheel.nextInt() & Integer.MAX_VALUE) % 11;
System.out.println(m);
}
To get an evenly distributed random number between integers low and high
inclusive use:
int m = (wheel.nextInt() & Integer.MAX_VALUE) % (high-low+1) + low;
Using nextInt for generating random integers is faster than using
nextDouble, multiplying and converting to int as many authors suggest. In
solving problems of this sort, you must be mindful that nextInt returns a
positive or negative integer, including MIN_VALUE (whose absolute value
cannot be represented) and MAX_VALUE, and that Java division/modulus has
unexpected results for negative operands. You must also be careful to
maintain even distribution. Consider the following code that also
produces a random integer in the range 0 .. 10.
int m = wheel.nextInt() % 6 + 5; // not
recommended!!
However that code would generate 5 twice as frequently as the other
values.
If you wanted a random double between 0.0 and 10.0 here is how you would
generate it.
// Generate random doubles 0.0 <= d < 10.0
import java.util.Random;
// This time, to get different results each run,
// seed the generator with the current time in milliseconds.
Random wheel = new Random();
// ...
for (int i=0; i<100; i++)
{
// generate a number between 0.0 and 1.0, then scale
double d = wheel.nextDouble() * 10.0d ;
System.out.println(d);
}
nextDouble() can return a 0.0, but never a 1.0. However, if you
want a random number in the interval [0, N), taking the result returned by
nextDouble() and multiplying by N will not always work; for some values of
N, the final result can be N (the high bound). nextDouble() works by
taking 53 random bits divided by (double) (1 << 53). So, the closest
it can get to 1.0 is 0.99999999999999988897. When you multiply by a
sufficiently large number, the tiny difference from 1.0 gets lost, and the
result is the same as if you had started with 1.0 not a number just less
than 1.0. According to Merlin Hughes, any number that the generator
produces will occur 32 times more commonly than for a perfect
distribution; 31 of every 32 numbers just won't ever be produced.
Math.Random is an alternate method create random doubles that does not
require you to create a Random object, but it does not give you control
over the seed.
About every four days someone will post the following code as a way to
generate a random integer 0 .. 10:
int m = (int) (Math.random() * 10.0d);
// not recommended!!
There are four things wrong with the technique:
- It will generate a number 0 to 9, not 0 to 10.
- However the technique in general may once in a blue moon generate
"10". It won't actually do this with 10, but it will hit the
upper bound with larger ranges, so I think it wise a avoid the technique
on general principles.
- It requires two int <=> double floating point conversions and a
floating point multiply. These are slow operations, and completely
unnecessary.
- Math.random gives you no power over the seed. It is hard to debug
if you program behaves a totally different way every time you run it.
The random number generator is portable and repeatable. If two Random
objects are created with the same seed and the same sequence of method
calls is made for each, they will generate and return identical sequences
of numbers in all Java Implementations.
The Java random number generator does very poorly at generating the low
order bit. It tends to repeat itself every 128 times you call nextInt. It
tends to get stuck for long runs of 0s or 1s, if you look only at every
128th result. In other words it is somewhat predictable. Bit 1 tends to
repeat on a 256 cycle. The following code to generate simulate a heads or
tail coin flip will thus not work very well.
boolean heads = (wheel.nextInt() & 1) != 0;
You can avoid the sticky low order bits by picking a bit out of the middle
of the result this way:
boolean heads = ((wheel.nextInt() >> 15) & 1) != 0;
Sun must have heard me (and others) screaming every time somebody
posted yet another piece of unworking code to generate random
numbers. In JDK 1.2 they have built in two new generators
nextBoolean() which avoids the low order sticky bit problem and
nextInt(10) which will generate a positive number 0 .. 9.
One final caveat. Seed the random number generator only once. If
you keep restarting it (by doing new
Random() more than once) using the system time default or some
chosen seed, your numbers are not going to be very random. Since the clock
does not tick over all that fast you may even create generators with
identical default seeds, which then generate identical streams of numbers.
java.util.Date
The java.util.Date class is crawling with gotchas. It is a
disgrace. It is the lemon of Java and deserves a giant string of raspberries.
Inconsistent capitalization. java.util.TimeZone, but Date.getTimezoneOffset.
Documentation is never clear on when you are using local and when UTC.
Months are numbered starting at 0, rather than 1 as everyone else on the
planet does. Yet days start at 1.
Dates prior to 1970 are not handled in JDK 1.0 but are handled in JDK 1.1 and later.
Monday is day 1, (Half the world expects that; half expects Monday to be
day 0). This is not properly documented.
Year 0=1900, year 100=2000.
There is no reserved value for a null date.
Dates are stored internally with a date and time in GMT, and automatically
converted to local time. If you are not careful a date stored as Saturday
can come back a day earlier or later.
DateFormat uses PST (Pacific Standard Time) as the default not GMT or
local time as you might expect. To make it use local time you must do
DateFormat.setTimeZone( TimeZone.getDefault() );
You must configure your TimeZone property for TimeZone.getDefault
to work.
The method names are misleading. getDay gets you the
day of the week. getDate gets you the day of the month.
getTime gets you a date/time stamp milliseconds since
1970.
Within the Date class, even though the dates are stored in
GMT, they come out field by field in local time. There is a way
to find out which local time it is using with
getTimezoneOffset, but there is no way to set the
timezone you want to use. You can set the default local timezone
when formatting, but it does not apply to your calculations based
on getMonth etc.
The list of possible timezones is
incomplete and ambigously defined. For example, "BST"
as a timezone refers to Bangkok Standard Time, not British Summer
Time. In Solaris, when you want to express your date in the MET
timezone (continental european timezone), you get GMT + 3h30 :
Teheran time.
One way out is to use my BigDate class which handles
dates from 999,999 BC to 999,999 AD. Sun has deprecated most of
the Date methods and replaced them with GregorianCalendar. It has
not nearly as many limitations as Date, it has got rid of those
ridiculous 1900-based years, however it is obscenely complicated,
still relies on the old Date class and maintains a lot of the
Date lunacy such 0-based months. Happily the documentation in JDK
1.2 is better, though ambiguity whether local or UTC parameters
are wanted still plagues. Sun still refuses to document units of
measure. For example, are TimeZone offsets in milliseconds,
seconds, minutes?
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. One way of thinking about it is the AWT thinks it is
drawing lines infinitely thin, but they smudge a bit, one pixel down
and to the right.
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.
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 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.
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 cold 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 erroneously
presume 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 readFully if you want to get
all the bytes you asked for.
The read routine has another problem. It does 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:
// author Jef Poskanzer jef@acme.com
http://www.acme.com/jef/
// Read into an array of bytes. This is a fixed
version
// of java.io.InputStream.read(byte[], int,
int). The
// standard version catches and ignores
IOExceptions from
// below; this version sends them on to the
caller.
public int
read( byte[]
b, int
off, int len ) throws IOException
{
if ( len <= 0
) return 0;
int c = read();
if ( c == -1 ) return -1;
if ( b != null ) b[off]
= (byte) c;
int i;
for ( i = 1; i < len ; ++i
)
{
c = read();
if ( c == -1 ) break;
if ( b != null ) b[off +
i] = (byte) c;
}
return i;
}
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!
- 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 Serialised Objects
The process of serialisation and reconstituting objects is fraught
with problems.
- 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
intializers. They should 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 beginners 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 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.
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.
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."
Sometimes a single syntax error starts off an avalanche of baffling
compiler error messages. As a general rule, look slightly ahead of where
the compiler is complaining. Fix any problems there and recompile. Most
of the time the other errors will disappear.
When you start using a compiler, it is a good idea to
deliberately make some common errors, and see what the
compiler says. Then you can make a table to help you later when
you inadvertently make that error. It also helps to have two or
three compilers on tap. When you get a baffling error message,
try some other compilers. With three variants on the error
message, you have extra clues what it really means.
For example here is a table for Symantec's
Visual Cafe Family. Please email me versions of this
table for your compiler to post here.
What compiler says |
real error |
';' expected. 'else' without if. statement expected. invalid expression. |
missing semicolon |
All class files and packages you reference in import statements must
be accessible via the CLASSPATH, or be part of the project. |
class Mypackage.Myclass not found in an import |
Applet not inited (sic) |
Missing package statement. These erroneous Applets will often
work with the Java Plug-in, or when run locally, but will fail
with native Javas when run on the web. There can also be problems
with your jar file having too much or too little qualification of
class names. Your <APPLET CODE= must have nothing but the
classname, with the .class. Make sure the case and name
exactly match the name of the *.java file, *.class file, and
class name. For a class in a package this would have dots in it,
e.g. cmp.MyPackage.Myclass.class, but it would not have
any directory qualification. Your CODEBASE= parameters must have
an absolute http://-style reference to the base
directory where the code is stored. For a local hard disk, the
only thing I could get to work reliably on NT with all browsers
and Appletviewers is leaving the CODEBASE out entirely. You may
find for your platform you have to code it something like this:
///C|//MyDir/ or C:\MyDir\. Your ARCHIVE=
parameter must have a list of the jar files, separated by commas.
If you have too little or too much qualification, or if you fail
to use the file naming conventions of your server, you will be in
trouble. |
ambiguous class x.y.SomeClass and a.b.SomeClass |
import x.y.SomeClass;
import a.b.SomeClass;
should be changed to:
import x.y.*;
import a.b.*;
Some compilers may complain about the clash in SomeClass, even
if you never reference it. And of course all references to Someclass
should be disambiguated to either x.y.SomeClass or a.b.Someclass.
Alternatively, you can throw out all the imports, and fully qualify
all classes in x.y and a.b. This approach makes code easier to
maintain, because it is easier to find the code that implements the
class when it is fully qualified. |
Array a may not have been initialized. |
You forgot to initialise an array with new int[5]. |
Bad magic number |
The first few bytes of class file are supposed to say
CAFEBABE in hex. They don't. Most likely the problem is
you uploaded your class file to your Web server using FTP ASCII
mode instead of BINARY mode which mangled the class
file. |
Can't access MyPackage.MyClass. Only classes and interfaces in
other packages can be accessed. |
You forgot to make the class
public. |
Can't make a static reference to nonstatic variable x in MyClass. |
using an instance variable in a static method |
Class not found |
This can occur at compile or run time.
- Some other syntax error ahead of the class declaration is preventing the
compiler from seeing the class declaration.
- The class is not in the proper file in the proper directory.
- The class is not public.
- The class does not have the correct case, either in the class name or
the file name.
- The corresponding class or java file is not on the CLASSPATH
(considering the package name.)
|
Class WindowAdapter not found in type declaration. |
You forgot to import java.awt.event.* or to fully
qualify java.awt.event.WindowAdapter. |
ClassFormatError |
You mangled the class file FTP upload
by doing it as ASCII instead of binary. Further your web server must be
configured to send *.class files as binary rather than ASCII. It needs a
MIME configuration entry to define *.class files as type
application/octet-stream instead of text/plain. Sometimes it is actually a
CLASSPATH problem. It can't find the class. |
Duplicate method declaration. |
duplicate method
declaration |
Error: MyClass is an abstract class. It can't be
instantiated. |
missing method to fulfill an interface
implementation |
Exception java.io.IOException is never thrown in the body of the
corresponding try statement. |
This usually means there is some
syntax error in the code that would throw the exception. Fix other errors
and this one will go away. |
Identifier expected. '}' expected. |
forgetting static { }
around class init code |
Illegal use of nonvirtual function call |
An inner class is not permitted to call private methods of
the enclosing outer class. It is permitted to call protected
methods. This error message appears in Symantec 2.5a only when
you have debugging turned off. |
IllegalAccessError |
- Failing to use both the <APPLET ARCHIVE= parameter to
specify a comma-delimited list of jar file names and the
CODEBASE= paramter in the form of an absolute
http://-style reference to where the jar file is.
- Generally problems with CLASSPATH, CODEBASE, and ARCHIVE.
- Classes in jar have the wrong amount of qualification stored
with them.
- Classes in packages are not stored in the correspondingly
named directories. See CLASSPATH and java.exe in the Java
glossary for a fuller discussion.
|
Incompatible type for =. Explicit cast needed to convert int to
byte. |
missing a cast such as (byte) |
IncompatibleClassChangeError |
You forgot the static on
your main method. |
Invalid method declaration; return type required. |
forgetting void return type on a method declaration. |
load: cmp.MyPackage.MyApplet.class can't be
instantiated. java.lang.InstantiationException:
cmp/MyPackage/MyApplet |
You are missing the default constructor for your Applet. See
the section earlier in the gotchas on The Case of the
Disappearing Constructor. |
main must be static and void |
An application's main class must have a method public
static void main (String[] args). Under Project Option Main Class,
you must declare the name of that class, without a terminating .class. |
Method x() not found in MyClass. |
undefined method |
Method MyClass() not found in MyClass |
You wrote MyClass x = Myclass(); instead of MyClass x =
new Myclass(); |
myClass.class does not contain myClass as expected, but MyClass.
Please remove the file. Class myClass not found in new. |
missing caps on new MyClass() reference. |
myClass.class does not contain myClass as expected, but MyClass.
Please remove the file. Class myClass not found in new. |
missing caps on MyClass() obj declaration. |
No error while developing. Security Violation in
production. |
Applets can only access the server they were loaded from. |
No error while developing. Security Violation in
production. |
Applets are not permitted to read or write local files. |
No method matching myMethod() found in MyClass |
You have the wrong number of parameters or the wrong parameter types
for the method. It can also mean you defined a method with the wrong
visibility modifier, e.g. none, private or protected when you meant
public. |
no warning. |
missing caps on a class name declaration. |
no warning. |
caps on a variable declaration |
no warning. Case fall through is the default. |
missing break |
no warning. The array is automatically initialised to
null. This will likely soon lead to a java.lang.NullPointerException when
you try to apply some method to one of the elements of the array. Note
NullPointerException, not NullReferenceException. |
You forgot to initialise an array of strings or objects to some
value. |
no warning. |
In debug mode, if you forget to make
your main method public, you will not be warned. You won't discover the
problem until later. main must be public static void. |
no warning. The array is automatically initialised to
zeroes. |
You forgot to initialise an int array to some value. |
no warning. Constructor treated as a method. |
specifying a void return type on a constructor |
NoClassDefFoundError |
One of your static initialisers invokes a function that uses a static
not yet initialised.
Alternatively, if you have classes included in your project that don't
live in the same directory as the project, you must also do a
Project | Options | Directory | Classes to include their directories in
the invisible CLASSPATH for the project. Simply including the java or
class files in the project is not sufficient.
Somehow your runtime is having trouble finding classes it could find at
compile time. Check your CLASSPATH. Make sure all the classes are
available.
Consider combining class files in to a single jar file.
Check that your browser supports jar files.
You can get this error if you try to run a JDK 1.1 Applet on a 1.0 browser
since it does not have the 1.1 classes. |
Return required at end of MyClass Myclass(..). |
specifying a MyClass return type on a constructor |
'return' with value from constructor: MyClass(..). |
specifying a return this in a constructor |
Statement expected. |
missing } in a method |
Type expected. identifier expected. |
extra }, or literally a missing type, especially in constant
declarations like: public static final SOMETHING=3; instead of
public static final int SOMETHING=3; |
Unable to load MyClass.class for debugging. |
Symantec support suggests copying all the DDLs in VCP\JAVA\BIN to
VCP\BIN to correct stability problems. If you do this, all debugging
will cease to function. Delete any DDL in VCP\BIN that also exists
is VCP\JAVA\BIN.
Check that you fully qualified the name of your class with its
package name, e.g. cmp.business.TestDate in the Project section
of Visual Cafe, no trailing ".class" and that the upper and lower case is
precisely correct. Read the section in the glossary on CLASSPATH and under
java.exe on how packages, classnames, the directory structure and the
CLASSPATH all interact. If that does not solve it, I have bad news.
Windows 95 has the most incredibly inept scheme for dealing with
DLLs from different vendors that just happen to have the same 8+3
name, (even when they are stored in totally different directories).
Whichever application starts first gets its DLLs installed, and every
other app that comes aftward has to use them. If you start some app
before VC, it may load incompatible DLLs. To avoid this, load VC
first. Then the other apps may stop working. Phttt! Mr. Gates has
guaranteed himself a seat in hell for this (and for the confounded
registry where all configuration information for all applications is
lumped in one unrestorable basket). There should be separate system
DLLs and application DLLs with no possibility of DLLs provided by one
vendor being foisted on some other. Try starting Visual Cafe before any
other app and keep it running. It stands a better chance then of getting
its own DLLs loaded. |
Unable to run MyClass.class: Check that you have properly
specified name of the class containing main in your project settings. |
Your vep project file is not in the same directory with the
*.java and *.class files that compose the class containing Main. |
Undefined variable x; or Variable x in SomeOtherClass not
accessible from MyClass Incompatible type for =. |
caps on a variable reference |
Undefined variable: x. |
undefined variable |
UnsatisiedLinkError |
Missing native class. Class name not properly qualified. |
Variable 'x' is already defined in this method. |
duplicate variable declaration |
Variable x may not have been initialized. |
missing initialisation for a temporary variable |
Warning: Public MyClass must be defined in a file called
'MyClass.java'. |
class name does not match source filename |
Warning: Public class MyClass must be defined in a file called
'MyClass.java'. |
Putting more than one public class per file. Also getting the
capitalisation wrong in the filename on the javac command line or in the
filename itself. |
} expected. Type expected. Identifier expected. |
missing } at the end of a class. Perhaps the missing } is on a line
containing // before the }. |
Bugs
There are also outright bugs in the various Java compilers and runtimes.
Sun maintains a searchable list at http://developer.java.sun.com.
Don't be put off by the login id and password. It is free to register.
You can also submit bugs, and vote on which bugs you feel should be given
highest priority. They also accept feature requests and gotchas as bugs.
You can also use
http://java.sun.com/cgi-bin/bugreport.cgi.
Use search engines, DejaNews,
the newsgroups such as comp.lang.java.programmer, and the Java Developer
Connection web pages to see if others have reported a similar bug.
Similarly you can contact Netsape via http://developer.netscape.com.
You can report bugs in Microsoft's SDK via
http://www.microsoft.com/java/misc/sdkbug.htm.
If you think you have found a new bug, build a small test case
that clearly demonstrates the bug. Most reported bugs are not bugs at all,
but coding errors. If you can keep your example small enough, you will
be much more convincing that you truly have found a bug. Ask a friend to
test the code on another platform to help decide if the problem is with
the compiler or the JVM or a native library.
Finally, if you would just like to complain about the design of Java
or its implementation, you can expound on the comp.lang.java.advocacy newsgroup
or send email to feedback@java.sun.com.
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.
Tov Are |
tovj@stud.ntnu.no |
Paul van Keep |
paul@sumatra.nl |
Mike Cowlishaw |
mfc@vnet.ibm.com |
Pierre Baillargeon |
pierre@jazzmail.com |
Bill Wilkinson |
billw@chilisoft.com |
Patricia Shanahan |
pats@abcm.org |
Joseph Bowbeer |
jozart@csi.com |
Charles Thomas |
cftoma1@facstaff.wisc.edu |
Joel Crisp |
Joel.Crisp@gb.swissbank.com |
Eric Nagler |
epn@eric-nagler.com |
Daniel Leuck |
dan@pretium.com |
William Brogden |
wbrogden@bga.com |
Yves Bossu | ybossu@fisystem.fr |