Saturday, September 28, 2019

10 Common Mistakes Every Beginner Java Programmer Makes


1. Accessing non-static members from static methods (mostly from the main method)

Novice programmers often make this mistake as they don’t fully understand the differences between static and non-static stuffs in Java. And interestingly, this mistake mostly happens in the static method main()where one attempts to access an instance variable or method. Consider the following code snippet:
1
2
3
4
5
6
7
public class StaticExample1 {
    public int number;  // instance variable

    public static void main(String[] args) {
        number = 30;    // compile error
    }
}
Java compiler will issue this error:
1
error: non-static variable number cannot be referenced from a static context
Here, number is an instance variable which means that we can only access it via an object of its class, as shown in the following correction:
1
2
3
4
5
6
7
8
public class StaticExample1 {
    public int number;  // instance variable

    public static void main(String[] args) {
        StaticExample1 obj = new StaticExample1();
        obj.number = 30;
    }
}
Another solution is making the variable become static, but it depends on the program’s purpose. For example, it’s fine for the following program to access a constant (static final field):
1
2
3
4
5
6
7
8
public class StaticExample2 {
    public static final int BUFFER_SIZE = 4096; // a constant

    public static void main(String[] args) {

        byte[] data = new byte[BUFFER_SIZE];
    }
}
The same applies for an instance method being accessed by a static method. For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class StaticExample3 {

    // instance method:
    public int sum(int a, int b) {
        return a + b;
    }

    public static void main(String[] args) {
        int a = 399;
        int b = 256;

        int c = sum(a, b);  // compile error
    }
}

And here’s a solution:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class StaticExample3 {

    // instance method:
    public int sum(int a, int b) {
        return a + b;
    }

    public static void main(String[] args) {
        int a = 399;
        int b = 256;

        StaticExample3 obj = new StaticExample3();
        int c = obj.sum(a, b);
    }
}
So to avoid this mistake, remember the following rules:
·         Instance variables and methods must be accessed via object reference. They cannot be accessed from static context.
·         Static variables and methods can be accessed from both static and non-static context.
To fully understand the differences between static and non-static stuffs, consult the tutorial: Differences between static and non-static stuffs in Java

2. Missing closing curly braces
This is also a common mistake which one writes an opening curly brace (the { ) but forget to put a closing one (the ). A closing curly brace is often missed due to improper indentation of the code, or due to putting the open brace without closing it immediately. Can you spot the missing curly brace in following code snippet?
1
2
3
4
5
6
7
8
9
10
Thread stopper = new Thread(new Runnable() {
    public void run() {
    try {
        Thread.sleep(RECORD_TIME);
    } catch (InterruptedException ex) {
        ex.printStackTrace();
    }
    recorder.finish();

});
Yes, you can see that there is a missing closing brace for the method run() although the code looks seemingly fine due to the last two lines are indented improperly. Here’s the correction:
1
2
3
4
5
6
7
8
9
10
Thread stopper = new Thread(new Runnable() {
    public void run() {
    try {
        Thread.sleep(RECORD_TIME);
    } catch (InterruptedException ex) {
        ex.printStackTrace();
    }
    recorder.finish();
    }
});
Although this mistake is detected quickly by the compiler and instantly by modern IDEs, programmers still often make it. So to avoid this mistake:
·         Always write the closing brace immediately after the opening one, and then put the code inside.
·         Indent/format the code properly.

3. Missing break in switch case construct
Novice programmers often misses a break in the switch case construct which does not cause compile error but makes the code runs wrongly. It’s because the switch case construct in Java has a feature called ‘fall through’ in which the code execution continues to the next case if there is no break in the current case. Let’s look at the following example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Scanner scanner = new Scanner(System.in);
int option = scanner.nextInt();

switch (option) {
    case 1:
        System.out.println("1 -> New");
        break;
    case 2:
        System.out.println("2 -> Edit");
        break;
    case 3:
        System.out.println("3 -> Delete");
        break;
    case 4:
        System.out.println("4 -> View");
        break;
    case 5:
        System.out.println("5 -> Quit");
    default:
        System.out.println("Unknown command");

}
Can you spot the missing break in this code? Let run the code and enter 5 as input and you will get the following output:
1
2
5 -> Quit
Unknown command
Oops! It’s because there is a missing break in the 5th case, so the execution falls through to the default case. Here’s the correction:
1
2
3
4
5
case 5:
    System.out.println("5 -> Quit");
    break;
default:
    System.out.println("Unknown command");
This mistake is a little bit difficult to detect because it doesn’t cause compile error. We only realize the problem when the program runs not as expected.
However, sometimes we do need to remove the breaks, as shown in the following method:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static int getDaysOfMonth(int month, int year) {
    switch (month) {
        case 1:
        case 3:
        case 5:
        case 7:
        case 8:
        case 10:
        case 12:
            return 31;
        case 4:
        case 6:
        case 9:
        case 11:
            return 30;
        case 2:
            return (year % 4 == 0) ? 29 : 28;
        default:
            throw new IllegalArgumentException();
    }
}
As you can see, this method returns number of days for a given month and year, and the fall-through feature of the switch case is a great help.
Read this article to fully understand about the switch case construct in Java.

4. Confusing assignment with comparison (= and ==)
The is an assignment operator whereas the == is a relational operator, and sometimes one mistakenly uses = instead of ==. Take a look at this code snippet:
1
2
3
4
5
6
7
8
Scanner scanner = new Scanner(System.in);
int number = scanner.nextInt();

if (number = 1) {
    System.out.println("Choose 1");
} else {
    System.out.println("Choose other");
}
Here, number = 1 is an assignment not a comparison, thus the compiler detects this mistake easily. Correction:
1
if (number == 1) {
But watch out for boolean assignment which the compiler cannot detect, as shown in the following example:
1
2
3
4
5
public void openFile(String path, boolean readOnly) {
    if (readOnly = true) {
        // this is always true
    }
}
The if (readOnly = true) seems to be a comparison but it is an assignment and the variable is of type boolean so the code compiles normally. The problem is, code inside the if block always get executed regardless of the value of the argument. Correction:
1
if (readOnly == true) {
Consult this article to understand more about operators in Java.

5. Confusing object comparison (== instead of .equals)
The == operator compares two objects physically (their memory addresses) whereas the equals() method compares two objects semantically (their information). And in most of the cases, we should compare two objects meaningfully using the equals() method. Novice programmers often make a mistake by comparing two String objects like this:
1
2
3
4
5
6
7
public void getMeat(String type) {
    if (type == "beef") {
        System.out.print("Choose beef");
    } else if (type == "pork") {
        System.out.print("Choose pork");
    }
}
This seems to work correctly with the following call:
1
getMeat("pork");
This works because Java caches String literals in String constant pool so that the == works here. But this won’t work:
1
2
String meatType = new String("pork");
obj.getMeat(meatType);
Why? Because meatType is now a different String object so the == operator returns false. The rule is always using the equals() method to compare two objects, unless you have reason to compare two objects directly.
Thus here’s the correction for the above method:
1
2
3
4
5
6
7
public void getMeat(String type) {
    if (type.equals("beef")) {
        System.out.print("Choose beef");
    } else if (type.equals("pork")) {
        System.out.print("Choose pork");
    }
}
Also pay attention when comparing two Integer wrapper objects. Consider the following code:
1
2
3
4
Integer i1 = 100;
Integer i2 = 100;

System.out.println(i1 == i2);
This will print true, but the following code prints false:
1
2
3
4
Integer i1 = 500;
Integer i2 = 500;

System.out.println(i1 == i2);
Why? It’s because Java caches integer numbers ranging from -128 to 127. So remember the rule of using equals() method to compare objects.
Refer to this article to understand more about the equals() method.

6. Confusing about 0-based or 1-based index
Like many other programming languages, array index in Java are 0-based, which means the first element in the array starts at the index 0, the second at 1, and so on. However, sometimes we mistakenly treat the first element at index 1. For example:
1
2
String[] fruits = {"Apple", "Banana", "Carrot", "Grape"};
String firstFruit = fruits[1];  // should be: fruits[0]
The problem is even worse if the array has only one element, then an ArrayIndexOutOfBoundsException will occur. For example:
1
2
int[] numbers = {256};
int firstNumber = numbers[1];   // cause ArrayIndexOutOfBoundsException
And it’s worth paying attention to some methods whose index parameters are both 0-based and 1-based. Let’s take the method substring(beginIndex, endIndex) of the String class for instance: the beginIndex is 0-based, but the endIndex is 1-based. 
1
2
3
String title = "JavaProgramming";
String subTitle = title.substring(0, 4);
System.out.println(subTitle);
This gives “Java” as the result.
Likewise, the method subList()of List collections has fromIndex starts from 0 and toIndex starts from 1. For example:
1
2
3
4
List<String> names = Arrays.asList("Andy", "Bob", "Carol", "David", "Eric");
List<String> subNames = names.subList(0, 3);

System.out.println(subNames);
This gives the result: [Andy, Bob, Carol]
So you can infer the rule for such methods: begin index starts from 0 and end index starts from 1.
Also be aware of the Calendar class which returns date on 1-based but returns month in 0-based. For example:
1
2
3
4
5
6
7
8
9
Calendar calendar = Calendar.getInstance();

// current date is November, 2nd 2016
calendar.setTimeInMillis(System.currentTimeMillis());

int date = calendar.get(Calendar.DATE);
int month = calendar.get(Calendar.MONTH);

System.out.printf("date = %d, month = %d", date, month);
This prints: date = 2, month = 10
So bear in mind these rules to avoid this beginner mistake.

7. Using less restrictive access modifiers
This is another very common mistake of all beginners: using access modifiers without taking care of its visibility. The Java language provides 4 access modifiers for protecting members of a class:
public > protected > default > private
These access modifiers are sorted from the least restrictive (public) to the most restrictive (private). However, novice programmers do not fully understand this and often use the public and default access modifiers which make the code less secured.
Consider the following class:
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Person {
    public String name;
    int age;

    public Person(String name, int age) {
        if (name == null || age < 1 || age > 100) {
            throw new IllegalArgumentException("Invalid name or age");
        }

        this.name = name;
        this.age = age;
    }
}
It seems to be fine as the constructor validates the name and age to make sure they are properly set. For example, this instantiation looks fine:
1
Person p1 = new Person("Alex", 30);
And this instantiation causes an exception to be thrown, as expected by the logic in the constructor:
1
Person p2 = new Person(null, 0);
But the problem is, the member variables name and age are not protected (name can be accessed everywhere and age can be accessed in the same package), one can easily bypass the check logic in the constructor by writing code like this:
1
2
3
Person p1 = new Person("Alex", 30);
p1.name = null; // valid
p1.age = -1;    // valid
So to protect member variables from unwanted changes, we need to use more restrictive access modifier such as private (cannot be modified outside the class) or protected (can be modified only by subclasses or classes in the same package). Thus the following is the correction for the Person class:
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        if (name == null || age < 1 || age > 100) {
            throw new IllegalArgumentException("Invalid name or age");
        }

        this.name = name;
        this.age = age;
    }
}
So remember this rule: always use the most restrictive access modifiers if possible.
Consult this article to fully understand about access modifiers in Java.

8. Forgetting to free resources
This is also a common mistake which one easily forgets to close resources after use, such as network connections, database connections, file streams, etc. This mistake may lead to resources leaked or memory occupied by no longer used objects.
Let’s look at this example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void copyFile(File sourceFile, File destFile) {

    FileChannel sourceChannel = null;
    FileChannel destChannel = null;

    try {

        sourceChannel = new FileInputStream(sourceFile).getChannel();
        destChannel = new FileOutputStream(destFile).getChannel();
        sourceChannel.transferTo(0, sourceChannel.size(), destChannel);

    } catch (IOException ex) {
        ex.printStackTrace();
    }
}
As you can see, this code misses the statements to close the sourceChannel and destChannel after using them. A solution to this is using the try-with-resources structure available since Java 7, which automatically closes the resources. For example, the above code can be re-written like the following code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void copyFile(File sourceFile, File destFile) {

    try (

        FileChannel sourceChannel = new FileInputStream(sourceFile).getChannel();

        FileChannel destChannel = new FileOutputStream(destFile).getChannel();
    ) {

        sourceChannel.transferTo(0, sourceChannel.size(), destChannel);

    } catch (IOException ex) {
        ex.printStackTrace();
    }
}
Therefore, to never forget freeing resources, use the try-with-resources structure on the resources implement the AutoCloseable interface.

9. Ignoring Exceptions
This is another common mistake of beginners who are too lazy to write code for handling exceptions. It seems to be of no harms if the code runs fine without exceptions. However, in case the exceptions occurred, the code can fail silently which adds difficulty in finding the problem.
Let’s take a look at the following program:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Sum {
    public static void main(String[] args) {
        int a = 0;
        int b = 0;

        try {
            a = Integer.parseInt(args[0]);
            b = Integer.parseInt(args[1]);

        } catch (NumberFormatException ex) {
        }

        int sum = a + b;

        System.out.println("Sum = " + sum);
    }
}
As you can see, this program calculates the sum of two numbers passed via command-line arguments. Note that the catch block is left empty. If we try to run this program by the following command line:
1
java Sum 123 456y
It will fail silently:
1
Sum = 123
It’s because the second argument 456y causes a NumberFormatException to be thrown, but there’s no handling code in the catch block so the program continues with incorrect result.
So to avoid such potential problem, always handle exceptions at least by printing the stack trace to inform the error when it happens:
1
2
3
4
5
6
7
try {
    a = Integer.parseInt(args[0]);
    b = Integer.parseInt(args[1]);

} catch (NumberFormatException ex) {
    ex.printStackTrace();
}
Having said that, don’t be lazy to ignore exceptions, as writing a simple a print stack trace statement takes only few seconds and it can save you hours of debugging later if the problem occurs.

10. Modifying a collection while iterating it
Sooner or later, anyone who starts programming with Java makes this common mistake: attempt to modify (remove) elements in a collection while iterating over it. Consider the following example:
1
2
3
4
5
6
7
8
List<String> fixedList = Arrays.asList("Apple", "Banana", "Carrot", "Grape");
List<String> listFruit = new ArrayList<>(fixedList);

for (String fruit : listFruit) {
    if (fruit.contains("e")) {
        listFruit.remove(fruit);
    }
}
This code attempts to remove String elements containing “e” letter but it causes ConcurrentModificationException at runtime, even in a single-threaded program. Why? There are two main reasons that Java prohibits this kind of operation:
·         The list’s size is changed dynamically when you remove an element. But it also causes the iteration behaves unpredictable as the content of the list gets changed.
·         Imagine in a multi-threaded program, while one thread is accessing this list and another thread is removing elements from this list, the result will be unpredictable.

So is there any solution?
Yes, there’s a few.
First, we can build a list contains elements that needs to be removed, then iterate on this list to remove elements in the original list. For example:
1
2
3
4
5
6
7
8
9
10
List<String> fruitToRemove = new ArrayList<>();
for (String fruit : listFruit) {
    if (fruit.contains("e")) {
        fruitToRemove.add(fruit);
    }
}

for (String fruit : fruitToRemove) {
    listFruit.remove(fruit);
}
Second, a more compact solution is using an iterator which is designed for modifying a collection while iterating its elements. The following example works well:
1
2
3
4
5
6
7
8
9
Iterator<String> iterator = listFruit.iterator();

while (iterator.hasNext()) {
    String next = iterator.next();

    if (next.contains("e")) {
        iterator.remove();
    }
}
Third, Java 8 makes thing much simpler with the Streams API. For example, the following code produces same result as the above code:
1
2
3
List<String> result = listFruit.stream()
            .filter(fruit -> !fruit.contains("e"))
            .collect(Collectors.toList());
That’s the 10 common mistakes in Java programming which every novice programmer makes in his or her life. Note that this is not a top 10 because we don’t have reliable statistics data.
We hope you find this article helpful and we are welcome for suggestions for other common mistakes in Java.


No comments:

Post a Comment

How to DROP SEQUENCE in Oracle?

  Oracle  DROP SEQUENCE   overview The  DROP SEQUENCE  the statement allows you to remove a sequence from the database. Here is the basic sy...