Java Gotchas
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.
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 |