Java Language


Java Pitfalls All Versions

Java SE 1.0
Java SE 1.1
Java SE 1.2
Java SE 1.3
Java SE 1.4
Java SE 5
Java SE 6
Java SE 7
Java SE 8
Java SE 9 (Early Access)

This draft deletes the entire topic.

expand all collapse all

Examples

  • 17

    (This pitfall applies equally to all primitive wrapper types, but we will illustrate it for Integer and int.)

    When working with Integer objects, it is tempting to use == to compare values, because that is what you would do with int values. And in some cases this will seem to work:

    Integer int1_1 = Integer.valueOf("1");
    Integer int1_2 = Integer.valueOf(1);
    
    System.out.println("int1_1 == int1_2: " + (int1_1 == int1_2));          // true
    System.out.println("int1_1 equals int1_2: " + int1_1.equals(int1_2));   // true
    

    Here we created two Integer objects with the value 1 and compare them (In this case we created one from a String and one from an int literal. There are other alternatives). Also, we observe that the two comparison methods (== and equals) both yield true.

    This behavior changes when we choose different values:

    Integer int2_1 = Integer.valueOf("1000");
    Integer int2_2 = Integer.valueOf(1000);
    
    System.out.println("int2_1 == int2_2: " + (int2_1 == int2_2));          // false
    System.out.println("int2_1 equals int2_2: " + int2_1.equals(int2_2));   // true
    

    In this case, only the equals comparison yields the correct result.

    The reason for this difference in behavior is, that the JVM maintains a cache of Integer objects for the range -128 to 127. (The upper value can be overridden with the system property "java.lang.Integer.IntegerCache.high"). For values in this range, the Integer.valueOf() will return the cached value rather than creating a new one.

    Thus, in the first example the Integer.valueOf(1) and Integer.valueOf("1") calls returned the same cached Integer instance. By contrast, in the second example the Integer.valueOf(1000) and Integer.valueOf("1000") both created and returned new Integer objects.

    The == operator for reference types tests for reference equality (i.e. the same object). Therefore, in the first example int1_1 == int2_1 is true because the references are the same. In the second example int2_1 == int2_2 is false because the references are different.

  • 10

    A common mistake for Java beginners is to use the == operator to test if two strings are equal. For example:

    public class Hello {
        public static void main(String[] args) {
            if (args.length > 0) {
                if (args[0] == "hello") {
                    System.out.println("Hello back to you");
                } else {
                    System.out.println("Are you feeling grumpy today?");
                }
            }
        }
    }
    

    The above program is supposed to test the first command line argument and print different messages when it and isn't the word "hello". But the problem is that it won't work. That program will output "Are you feeling grumpy today?" no matter what the first command line argument is.

    When you use == to test strings, what you are actually testing is if two String objects are the same Java object. Unfortunately, that is not what string equality means in Java. In fact, the correct way to test strings is to use the equals(Object) method. For a pair of strings, that will test to see if they consist of the same characters in the same order ... which is what we usually want.

    public class Hello2 {
        public static void main(String[] args) {
            if (args.length > 0) {
                if (args[0].equals("hello")) {
                    System.out.println("Hello back to you");
                } else {
                    System.out.println("Are you feeling grumpy today?");
                }
            }
        }
    }
    

    But it actually gets worse. The problem is that == will give the expected answer in some circumstances. For example

    public class Test1 {
        public static void main(String[] args) {
            String s1 = "hello";
            String s2 = "hello";
            if (s1 == s2) {
                System.out.println("same");
            } else {
                System.out.println("different");
            }
        }
    }
    

    Surprisingly (perhaps), this will print "same", even though we are testing the strings the wrong way. Why is that? Because the Java Language Specification (Section 3.10.5: String Literals) stipulates that any two string >>literals<< consisting of the same characters will actually be represented by the same Java object. Hence, the == test will give true for equal literals. (The string literals are "interned" and added to a shared "string pool" when your code is loaded ... but that is actually an implementation detail.)

    To add to the confusion, the Java Language Specification also stipulates that when you have a compile-time constant expression that concatenates two string literals, that is equivalent to a single literal. Thus:

        public class Test1 {
        public static void main(String[] args) {
            String s1 = "hello";
            String s2 = "hel" + "lo";
            String s3 = " mum";
            if (s1 == s2) {
                System.out.println("1. same");
            } else {
                System.out.println("1. different");
            }
            if (s1 + s3 == "hello mum") {
                System.out.println("2. same");
            } else {
                System.out.println("2. different");
            }
        }
    }
    

    This will output "1. same" and "2. different". In the first case, the + expression is evaluated at compile time and we compare one String object with itself. In the second case, it is evaluated at run time and we compare two different String objects

    In summary, using == to test strings in Java is almost always incorrect, but it is not guaranteed to give the wrong answer.

  • 9

    Every time a program opens a resource, such as a file or network connection, it is important to free the resource once you are done using it. Similar caution should be taken if any exception were to be thrown during operations on such resources. One could argue that the FileInputStream has a finalizer that invokes the close() method on a garbage collection event; however, since we can’t be sure when a garbage collection cycle will start, the input stream can consume computer resources for an indefinite period of time. The resource must be closed in a finally section of a try-catch block:

    Java SE 7
    private static void printFileJava6() throws IOException {
        FileInputStream input;
        try {
            input = new FileInputStream("file.txt");
            int data = input.read();
            while (data != -1){
                System.out.print((char) data);
                data = input.read();
            }
        } finally {
            if (input != null) {
                input.close();
            }
        }
    }
    

    Since Java 7 there is a really useful and neat statement introduced in Java 7 particularly for this case, called try-with-resources:

    Java SE 7
    private static void printFileJava7() throws IOException {
        try (FileInputStream input = new FileInputStream("file.txt")) {
            int data = input.read();
            while (data != -1){
                System.out.print((char) data);
                data = input.read();
            }
        }
    }
    

    The try-with-resources statement can be used with any object that implements the Closeable or AutoClosable interface. It ensures that each resource is closed by the end of the statement. The difference between the two interfaces is, that the close() method of Closeable throws an IOException which has to be handled in some way.

    In cases where the resource has already been opened but should be safely closed after use, one can assign it to a local variable inside the try-with-resources

    Java SE 7
    private static void printFileJava7(InputStream extResource) throws IOException {
        try (InputStream input = extResource) {
            ... //access resource
        }
    }
    

    The local resource variable created in the try-with-resources constructor is effectively final.

  • 3

    The Runtime.exec(String ...) and Runtime.exec(String) methods allow you to execute a command as an external process1. In the first version, you supply the command name and the command arguments as separate elements of the string array, and the Java runtime requests the OS runtime system to start the external command. The second version is deceptively to use, but it has some hidden pit falls.

    First of all, here is an example of using exec(String) being used safely:

    Process p = Runtime.exec("mkdir /tmp/testDir");
    p.waitFor();
    if (p.exitValue() == 0) {
        System.out.println("created the directory");
    }
    

    Spaces in pathnames

    Suppose that we generalize the example above so that we can create an arbitrary directory:

    Process p = Runtime.exec("mkdir " + dirPath);
    // ...
    

    This will typically work, but it will fail if dirPath is (for example) "/home/user/My Documents". The problem is that exec(String) splits the string into a command and arguments by simply looking for whitespace. The command string:

    "mkdir /home/user/My Documents"
    

    will be split into:

    "mkdir", "/home/user/My", "Documents"
    

    and this will cause the "mkdir" command to fail because it expects one argument, not two.

    Faced with this, some programmers try to add quotes around the pathname. This doesn't work either:

    "mkdir \"/home/user/My Documents\""
    

    will be split into:

    "mkdir", "\"/home/user/My", "Documents\""
    

    The extra double-quote characters that were added in attempt to "quote" the spaces are treated like any other non-whitespace characters. Indeed, anything we do quote or escape the spaces is going to fail.

    The way to deal with this particular problems is to use the exec(String ...) overload.

    Process p = Runtime.exec("mkdir", dirPath);
    // ...
    

    This will work if dirpath includes whitespace characters because this overload of exec does not attempt to split the arguments. The strings are passed through to the OS exec system call as-is.

    Redirection, pipelines and other shell syntax

    Suppose that we want to redirect an external command's input or output, or run a pipeline. For example:

    Process p = Runtime.exec("find / -name *.java -print 2>/dev/null");
    

    or

    Process p = Runtime.exec("find source -name *.java | xargs grep package");
    

    (The first example lists the names of all Java files in the file system, and the second one prints the package statements2 in the Java files in the "source" tree.)

    These are not going to work as expected. In the first case, the "find" command will be run with "2>/dev/null" as a command argument. It will not be interpreted as a redirection. In the second example, the pipe character ("|") and the works following it will be given to the "find" command.

    The problem here is that the exec methods and ProcessBuilder do not understand any shell syntax. This includes redirections, pipelines, variable expansion, globbing, and so on.

    In a few cases (for example, simple redirection) you can easily achieve the desired effect using ProcessBuilder. However, this is not true in general. An alternative approach is to run the command line in a shell; for example:

    Process p = Runtime.exec("bash", "-c", 
                             "find / -name *.java -print 2>/dev/null");
    

    or

    Process p = Runtime.exec("bash", "-c", 
                             "find source -name \\*.java | xargs grep package");
    

    But note that in the second example, we needed to escape the wildcard character ("*") because we want the wildcard to be interpreted by "find" rather than the shell.

    Shell builtin commands don't work

    Suppose the following examples won't work on a system with a UNIX-like shell:

    Process p = Runtime.exec("cd", "/tmp");     // Change java app's home directory
    

    or

    Process p = Runtime.exec("export", "NAME=value");  // Export NAME to the java app's environment
    

    There are a couple of reasons why this won't work:

    1. On "cd" and "export" commands are shell builtin commands. They don't exist as distinct executables.

    2. For shell builtins to do what they are supposed to do (e.g. change the working directory, update the environment), they need to change the place where that state resides. For a normal application (including a Java application) the state is associated with the application process. So for example, the child process that would run the "cd" command could not change the working directory of its parent "java" process. Similarly, one exec'd process cannot change the working directory for a process that follows it.

    This reasoning applies to all shell builtin commands.


    1 - You can use ProcessBuilder as well, but that is not relevant to the point of this example.

    2 - This is a bit rough and ready ... but once again, the failings of this approach are not relevant to the example.

Please consider making a request to improve this example.

Syntax

Syntax

Parameters

Parameters

Remarks

Remarks

Still have a question about Java Pitfalls? Ask Question

Topic Outline