views:

1984

answers:

25

In the same spirit of other platforms, it seemed logical to follow up with this question: What are common non-obvious mistakes in Java? Things that seem like they ought to work, but don't.

I won't give guidelines as to how to structure answers, or what's "too easy" to be considered a gotcha, since that's what the voting is for.

See also:

+1  A: 

Going first, here's one I caught today. It had to do with Long/long confusion.

public void foo(Object obj) {
    if (grass.isGreen()) {
        Long id = grass.getId();
        foo(id);
    }
}
private void foo(long id) {
    Lawn lawn = bar.getLawn(id);
    if (lawn == null) {
        throw new IllegalStateException("grass should be associated with a lawn");
    }   
}

Obviously, the names have been changed to protect the innocent :)

Alan
+5  A: 

if you have a method that has the same name as the constructor BUT has a return type. Although this method looks like a constructor(to a noob), it is NOT.

passing arguments to the main method -- it takes some time for noobs to get used to.

passing . as the argument to classpath for executing a program in the current directory.

Realizing that the name of an Array of Strings is not obvious

hashCode and equals : a lot of java developers with more than 5 years experience don't quite get it.

Set vs List

Till JDK 6, Java did not have NavigableSets to let you easily iterate through a Set and Map.

anjanb
> Till JDK 6, Java did not have NavigableSets to let you easily iterate through a Set and Map.Regular Sets in Java are easily iterable. NavigableSets just add operations like "find the first element less-than-or-equal to X". Useful, but not necessary to merely iterate.
DrPizza
SortedSet (since Java 1.3) has the headSet and tailSet methods which provide most of that functionality.
finnw
+13  A: 

Try reading Java Puzzlers which is full of scary stuff, even if much of it is not stuff you bump into every day. But it will destroy much of your confidence in the language.

David Plumpton
It's on my todo list of books to read.
Alan
It's a great book but most of the "bugs" in the puzzles are things that just *do not happen* in practice. They are extremely esoteric edge-cases which left me saying "who on earth would have written that anyway!"
oxbow_lakes
Most of the bugs are from bug reports, so clearly people do come across these things, and then get very confused. (I wrote the last program in the book (other than the name), but deliberately.)
Tom Hawtin - tackline
Don't take Java Puzzlers as a knock against Java. Most of the things in there are more like "Java is this far from perfect" rather than "Java is broken." Other languages can give you far more problem-causing confusion.
TREE
+17  A: 

Comparing equality of objects using == instead of .equals() -- which behaves completely differently for primitives.

This gotcha ensures newcomers are befuddled when "foo" == "foo" but new String("foo") != new String("foo").

David
Interestingly Groovy uses '==' as value comparison and not object identity comparison. Be careful when copy-pasting !
Michael Easter
+15  A: 

Overriding equals() but not hashCode()

It can have really unexpected results when using maps, sets or lists.

extraneon
I did something stupid with hashmaps these days, I was using the hash of something as the key for a map, and not the object itself. At my first hash collision I got problems as the equals method for the integer would return true, even when the equals method for the object would return false.
Ravi Wallau
The silly thing was reinventing the wheel (and possibly premature optimisation); java.util.HashMap effectively uses the hash of an object as the key (for efficiency purposes), but handles collisions properly using buckets. Always look for an existing class that has the desired functionality first!
Andrzej Doyle
+11  A: 

I think a very sneaky one is the String.substring method. This re-uses the same underlying char[] array as the original string with a different offset and length.

This can lead to very hard-to-see memory problems. For example, you may be parsing extremely large files (XML perhaps) for a few small bits. If you have converted the whole file to a String (rather than used a Reader to "walk" over the file) and use substring to grab the bits you want, you are still carrying around the full file-sized char[] array behind the scenes. I have seen this happen a number of times and it can be very difficult to spot.

In fact this is a perfect example of why interface can never be fully separated from implementation. And it was a perfect introduction (for me) a number of years ago as to why you should be suspicious of the quality of 3rd party code.

oxbow_lakes
That is also very useful for saving memory, when you are going to be splitting a large String into a number of substrings.
Stephen Denne
The source code for Java's class library is always a fascinating read, whether for better or worse. ;)
Chris Mazzola
One could imagine a VM optimization where such an array could be partially GC:ed if the parts could be proven to be inaccessible.
John Nilsson
+1  A: 

Another one I'd like to point out is the (too prevalent) drive to make APIs generic. Using well-designed generic code is fine. Designing your own is complicated. Very complicated!

Just look at the sorting/filtering functionality in the new Swing JTable. It's a complete nightmare. It's obvious that you are likely to want to chain filters in real life but I have found it impossible to do so without just using the raw typed version of the classes provided.

oxbow_lakes
+7  A: 

Manipulating Swing components from outside the event dispatch thread can lead to bugs that are extremely hard to find. This is a thing even we (as seasoned programmers with 3 respective 6 years of java experience) forget frequently! Sometimes these bugs sneak in after having written code right and refactoring carelessly afterwards...

See this tutorial why you must.

dhiller
+1  A: 

The default hash is non-deterministic, so if used for objects in a HashMap, the ordering of entries in that map can change from run to run.

As a simple demonstration, the following program can give different results depending on how it is run:

public static void main(String[] args) {
    System.out.println(new Object().hashCode());
}

How much memory is allocated to the heap, or whether you're running it within a debugger, can both alter the result.

Stephen Denne
That is not a gotcha, it's expected behaviour for a hash map *and* there's a map class (`LinkedHashMap`) whose main purpose is to preserve iteration order.
finnw
@finnw, it was a gotcha for me, when trying to figure out what my program had done, and the next time I'd run it, with the same input, it gave different output. Eventually I discovered that `Object.hashCode()` can gives different values for even the first object I created. `LinkedHashMap` is useful, but in my case, I didn't need the insertion or access order preserved, and was trying to be extremely memory efficient, so though `Object.hashCode()` was fine for my algorithm, I over-rode it to simply gain determinism.
Stephen Denne
+9  A: 

SimpleDateFormat is not thread safe.

It's also very slow
David Plumpton
+2  A: 

(un)Boxing and Long/long confusion. Contrary to pre-Java 5 experience, you can get a NullPointerException on the 2nd line below.

Long msec = getSleepMsec();
Thread.sleep(msec);

If getSleepTime() returns a null, unboxing throws.

Darron
+6  A: 
List<Integer> list = new java.util.ArrayList<Integer>();
list.add(1);
list.remove(1); // throws...

The old APIs were not designed with boxing in mind, so overload with primitives and objects.

Tom Hawtin - tackline
+1  A: 

Among the common pitfalls, well known but still bitting occasionally programmers, there is the classical if (a = b) which is found in all C-like languages.

One that bites lot of newbies, and even some distracted more experienced programmers (found it in our code), the if (str == "foo"). Note that I always wondered by Sun overrode the + sign for strings but not the == one, at least for simple cases (case sensitive).

And I also saw if (a == b & c == d) or something like that. It works (curiously) but we lost the logical operator shortcut (don't try to write: if (r != null & r.isSomething())!).

PhiLho
if (a = b) causes a compile time error unless a is of type either boolean or Boolean. Yay for strong typing. Boo for bad syntax.
Tom Hawtin - tackline
+6  A: 

There are two that annoy me quite a bit.

Date/Calendar

First, the Java Date and Calendar classes are seriously messed up. I know there are proposals to fix them, I just hope they succede.

Calendar.get(Calendar.DAY_OF_MONTH) is 1-based
Calendar.get(Calendar.MONTH) is 0-based

Auto-boxing preventing thinking

The other one is Integer vs int (this goes for any primitive version of an object). This is specifically an annoyance caused by not thinking of Integer as different from int (since you can treat them the same much of the time due to auto-boxing).

int x = 5;
int y = 5;
Integer z = new Integer(5);
Integer t = new Integer(5);

System.out.println(5 == x);     // Prints true
System.out.println(x == y);     // Prints true
System.out.println(x == z);     // Prints true (auto-boxing can be so nice)
System.out.println(5 == z);     // Prints true
System.out.println(z == t);     // Prints SOMETHING

Since z and t are objects, even they though hold the same value, they are (most likely) different objects. What you really meant is:

System.out.println(z.equals(t));   // Prints true

This one can be a pain to track down. You go debugging something, everything looks fine, and you finally end up finding that your problem is that 5 != 5 when both are objects.

Being able to say

List<Integer> stuff = new ArrayList<Integer>();

stuff.add(5);

is so nice. It made Java so much more usable to not have to put all those "new Integer(5)"s and "((Integer) list.get(3)).intValue()" lines all over the place. But those benefits come with this gotcha.

MBCook
It took me two 8-hour days to figure out why all of our dates were a month off... I was pulling my hair out!
Knobloch
0-based month is a POSIX (and C library) thing. Just because it's a standard, doesn't mean it should always be followed.
Tom Hawtin - tackline
z == t is always false. Two separately-newed objects have different object identity. On the other hand, if you said Integer.valueOf(5) in both cases, then they'd be the same object: values between -128 and 127 are supposed to be cached values.
Chris Jester-Young
If x == z or 5 == z is true, it's because of auto-_unboxing_ on the z, not of any sort of auto-boxing. Integer.valueOf(5) != new Integer(5) (remembering my previous comment about a new object having different identity to any other separately newed object).
Chris Jester-Young
-1. You are wrong about the `z == t` example, as @Chris Jester-Young describes. Maybe you are confusing this with a similar example in .NET where `z == t` *can* be true.
finnw
+7  A: 

this one has trumped me a few times and I've heard quite a few experienced java devs wasting a lot of time.

ClassNotFoundException --- you know that the class is in the classpath BUT you are NOT sure why the class is NOT getting loaded.

Actually, this class has a static block. There was an exception in the static block and someone ate the exception. they should NOT. They should be throwing ExceptionInInitializerError. So, always look for static blocks to trip you. It also helps to move any code in static blocks to go into static methods so that debugging the method is much more easier with a debugger.

anjanb
+5  A: 

Integer division

1/2 == 0 not 0.5
John Nilsson
...along with the general rules about type conversion/promotion/inference with the numbers. E.g. `1/2.0` does come out at 0.5, as does `1d/2`, and I'm sure a few of the Java puzzlers are related to exactly when promotion happens and thus whether overflow occurs or not.
Andrzej Doyle
+3  A: 

Floats

I don't know many times I've seen

floata == floatb

where the "correct" test should be

Math.abs(floata - floatb) < 0.001

I really wish BigDecimal with a literal syntax was the default decimal type...

John Nilsson
A: 

IMHO 1. Using vector.add(Collection) instead of vector.addall(Collection). The first adds the collection object to vector and second one adds the contents of collection. 2. Though not related to programming exactly, the use of xml parsers that come from multiple sources like xerces, jdom. Relying on different parsers and having their jars in the classpath is a nightmare.

questzen
That should be an old issue though. Vecor isn't actually used anymore. And with generic Lists that shouldn't be possible.
John Nilsson
@John, `Vector` is also generic
finnw
+4  A: 

Immutable strings, which means that certain methods don't change the original object but instead return a modified object copy. When starting with Java I used to forget this all the time and wondered why the replace method didn't seem to work on my string object.

String text = "foobar";
text.replace("foo", "super");
System.out.print(text); // still prints "foobar" instead of "superbar"
riadd
+1  A: 

The non-unified type system contradicts the object orientation idea. Even though everything doesn't have to be heap-allocated objects, the programmer should still be allowed to treat primitive types by calling methods on them.

The generic type system implementation with type-erasure is horrible, and throws most students off when they learn about generics for the first in Java: Why do we still have to typecast if the type parameter is already supplied? Yes, they ensured backward-compatibility, but at a rather silly cost.

+3  A: 

This one I just came across:

double[] aList = new double[400];

List l = Arrays.asList(aList);
//do intense stuff with l

Anyone see the problem?


What happens is, Arrays.asList() expects an array of object types (Double[], for example). It'd be nice if it just threw an error for the previous ocde. However, asList() can also take arguments like so:

Arrays.asList(1, 9, 4, 4, 20);

So what the code does is create a List with one element - a double[].

I should've figured when it took 0ms to sort a 750000 element array...

Claudiu
`Doubles.asList` from Guava (http://guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/common/primitives/Doubles.html#asList%28double...%29) does what you want. Similarly `Ints.asList` does the same for `int[]` etc.
finnw
+2  A: 
"a,b,c,d,,,".split(",").length

returns 4, not 7 as you might (and I certainly did expect). split ignores all trailing empty Strings returned. That means:

",,,a,b,c,d".split(",").length

returns 7 (oh, so those empty ones mean something now???) To get what I would think of as the "least astonishing" behaviour, you need to do something quite astonishing:

"a,b,c,d,,,".split(",",-1).length

to get 7.

butterchicken
Do you mean using a negative limit argument? That's the same workaround I've used above...isn't it?
butterchicken
+3  A: 

I think i big gotcha that would always stump me when i was a young programmer, was the concurrent modification exception when removing from an array that you were iterating:

  List list = new ArrayList();
    Iterator it = list.iterator();
    while(it.hasNext()){
      //some code that does some stuff
      list.remove(0); //BOOM!
  }
mkoryak
+2  A: 

Not really specific to Java, since many (but not all) languages implement it this way, but the % operator isn't a true modulo operator, as it works with negative numbers. This makes it a remainder operator, and can lead to some surprises if you aren't aware of it.

The following code would appear to print either "even" or "odd" but it doesn't.

public static void main(String[] args)
{
    String a = null;
    int n = "number".hashCode();

    switch( n % 2 ) {
        case 0:
            a = "even";
            break;
        case 1:
            a = "odd";
            break;
    }

    System.out.println( a );
}

The problem is that the hash code for "number" is negative, so the n % 2 operation in the switch is also negative. Since there's no case in the switch to deal with the negative result, the variable a never gets set. The program prints out null.

Make sure you know how the % operator works with negative numbers, no matter what language you're working in.

Bill the Lizard
Shouldn't you do a ((n%2) == 0) means even else false?
Improfane
+1  A: 

When you create a duplicate or slice of a ByteBuffer, it does not inherit the value of the order property from the parent buffer, so code like this will not do what you expect:

ByteBuffer buffer1 = ByteBuffer.allocate(8);
buffer1.order(ByteOrder.LITTLE_ENDIAN);
buffer1.putInt(2, 1234);

ByteBuffer buffer2 = buffer1.duplicate();
System.out.println(buffer2.getInt(2));
// Output is "-771489792", not "1234" as expected
finnw