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. They are ordered. The first ones are for rank novices. The later ones for more advanced programmers.
I spoke on this topic in 1997 November at the Colorado Summit Conference.
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();
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.
setCharAt(int index, char c) but setElementAt(Object o, int index).
The conversion and I/O functions are impossible to memorise there is so little pattern to them.
Java is quite inconsistent about caps, e.g. instanceof but isInstanceOf, and Hashtable but HashCode.
// 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() { return ("Bessie"); } public static int age = 70; String recipe() { return("light a fire,..."); } public int cups = 20; public int getCups() { return cups; } } // end class Grandma class Mom extends Grandma { public static String name() { return ("Rhonda"); } public static int age = 30; public String recipe() { return("Open a can ..."); } public int cups = 1; } // end class MomFor 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:
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.
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:
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.
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.
"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?
String s; String t;
s.compareTo(t) will return:
Novices might be surprised by the following results:
int i1 = (b2 & 0xff);
byte b2 = (byte) (b2 + 1);
Floored division is what you normally want when trying to figure out which bin an item belongs in. You can compute floored division as:
For computing how many fixed-size bins you need to contain N items, you want ceiled division, also known as the covered quotient. You can compute the covered quotient as:
Signs | Division | Modulus |
---|---|---|
+ + | +7/+4=+1 | +7%+4=+3 |
- + | -7/+4=-1 | -7%+4=-3 |
+ - | +7/-4=-1 | +7%-4=+3 |
- - | -7/-4=+1 | -7%-4=-3 |
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.
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.
// 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++) v[i] = i*2+999;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);
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.
// 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[3][]; 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; }When you have a matrix with all columns the same size, you can allocate all the space at once this way.
int[][] mat = new int[3][5]; for (int i=0; i<3; i++) 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 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.
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 subsclasses) 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.
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.
static { calcPriceTab(); }
Newbies just stick such code anywhere inside the class { } sandwich and are baffled by the misleading error messages.
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.
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.
GridBagLayou 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.
It does the same thing allocating extra space to rows by using weighty.
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.
int BufferedReader.read(char[] m, int offset, int len) has a similar gotcha.
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; }
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.
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. |
int a=4; int b=5; a^=b; // a=1, b=5 b^=a; // a=1, b=4 a^=b; // a=5, b=4You 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.
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.
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 |
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 implments the class when it is fully qualified. |
Array a may not have been initialized. | You forgot to initialise an array with new int[5]. |
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.
|
Class WindowAdapter not found in type declaration. | You for got 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 fulfil 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 |
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. |
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 visibily 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 }. |
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.
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.
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.
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@supercede.com |
Patricia Shanahan | pats@abcm.org |
Joseph Bowbeer | jozart@csi.com |
Charles Thomas | cftoma1@facstaff.wisc.edu |
Joel Crisp | Joel.Crisp@gb.swissbank.com |
![]() |
![]() |
|
Canadian Mind Products | You can get an updated copy of this page from http://mindprod.com/gotchas.html |