Everyone makes mistakes, not just beginners, but even professionals. This article goes over a dozen common mistakes that Java newbies and newcomers make and how to avoid them. Have you or your colleagues made any of these common Java mistakes early in your career? Everyone makes mistakes, not only learners or beginners but professionals. As a programming course, the team often collects mistakes of newbies to improve our auto validator. This time we decided to interview experienced programmers about mistakes in Java they made closer to their careers start or noticed them among their young colleagues. CodeGym We collected their answers and compiled this list of dozen popular mistakes Java beginners make. The order of errors is random and does not carry any special meaning. #1. OOP: Incorrect construction of the object hierarchy. In particular, a misunderstanding of where to apply an interface, and where an abstract class. An abstract class lets you create functionality that subclasses can implement or override. In an interface, you just define functionality, but not implement it. Although a class can only extend one abstract class, it can use multiple interfaces. The choice between interface and abstract class depends on many factors. Java does not support multiple inheritances, so each class can inherit only one superclass. However, any class can implement multiple interfaces. If you decide to write many methods, then an abstract class is the best solution for you, because you can provide default implementations for some of the methods that are common to all subclasses. If you need to add or remove a method from the interface, it could be a problem, because you can’t declare additional methods in an interface without changing all classes that implement this interface. Let’s look at the example below. Here we have incorrect construction of the object hierarchy. In particular, a misunderstanding of when to apply the interface, and when should be an abstract class. &nbsp; { ; ; } { ; ; } { id; String name; String avatarUrl; { id; } { .id = id; } { name; } { .name = name; } { avatarUrl; } { .avatarUrl = avatarUrl; } } { id; String name; String description; { id; } { .id = id; } { name; } { .name = name; } { description; } { .description = description; } } { id; String content; { id; } { .id = id; } { content; } { .content = content; } } public interface BaseEntity long getId () void setId ( id) long public interface NamedEntity extends BaseEntity String getName () void setName (String name) public class User implements NamedEntity private long private private @Override public long getId () return @Override public void setId ( id) long this @Override String public getName () return @Override public void setName (String name) this String public getAvatarUrl () return public void setAvatarUrl (String avatarUrl) this public class Group implements NamedEntity private long private private @Override public long getId () return @Override public void setId ( id) long this @Override String public getName () return @Override public void setName (String name) this String public getDescription () return public void setDescription (String description) this public class Comment implements BaseEntity private long private @Override public long getId () return @Override public void setId ( id) long this String public getContent () return public void setContent (String content) this Here the beginner uses an interface, in spite of the fact that class is much more appropriate for this task. The reason is that you can keep duplicated code into a class. If it is necessary, you can add Interfaces on top of parent classes. So here is the right decision (using abstract classes instead of interfaces): &nbsp; { id; { id; } { .id = id; } } { String name; { name; } { .name = name; } } { String avatarUrl; { avatarUrl; } { .avatarUrl = avatarUrl; } } { String description; { description; } { .description = description; } } { String content; { content; } { .content = content; } } public abstract class BaseEntity private long public long getId () return public void setId ( id) long this public abstract class NamedEntity extends BaseEntity private String public getName () return public void setName (String name) this public class User extends NamedEntity private String public getAvatarUrl () return public void setAvatarUrl (String avatarUrl) this public class Group extends NamedEntity private String public getDescription () return public void setDescription (String description) this public class Comment extends BaseEntity private String public getContent () return public void setContent (String content) this #2. OOP: mess with order of calling constructors Newbies often forget about the order that constructors are called when objects are created. The rule is simple: constructors are called in inheritance order. When you think about the logic, it becomes clear that executing constructors in order of inheritance makes some sense. The superclass knows nothing about its subclasses. Thus, any initialization must be done in the superclass completely independently of any initialization performed by the subclass. Therefore, this should be done first. &nbsp; { { System.out.println( ); } } { { System.out.println( ); } { Cat cat = Cat(); } } The Output is: Animal constructor has worked off Cat constructor has worked off! public class Animal public Animal () "Animal constructor has worked off" public class Cat extends Animal public Cat () "Cat constructor has worked off!" public static void main (String[] args) new As you can see, when creating child elements, the base class constructors are implicitly called first. In addition, it is important to remember to call the correct parent constructor, otherwise, the parent default constructor will be called, as in the following example: { Collection<Object> authorities; { (authorities == ) { .authorities = Collections.emptyList(); ; } (Object a : authorities) { (a == ) { IllegalArgumentException( ); } } ArrayList<Object> temp = ArrayList<>(authorities.size()); temp.addAll(authorities); .authorities = Collections.unmodifiableList(temp); } { .authorities = Collections.emptyList(); } } { userId; { .userId = user.getId(); } } { id; { id; } public abstract class AbstractToken private final public AbstractToken (Collection<Object> authorities) if null this return for if null throw new "Authorities collection cannot contain any null elements" new this public AbstractToken () this public class MyToken extends AbstractToken private int public MyToken (User user, Object... authority) this public class User private int public int getId () return The constructor of the parent class is not explicitly called in the constructor of MyToken. So the constructor of AbstractToken with no parameters will be called. This constructor is missing the required object initialization part. Don’t forget to call a particular constructor you really need: { userId; { (Arrays.asList(authority)); .userId = user.getId(); } } public class MyToken extends AbstractToken private int public MyToken (User user, Object... authority) super this #3. OOP: Mess with overriding and overloading Overriding and Overloading are two very important concepts in Java. They could be really confusing for Java novice programmers. Simply put, overriding allows you to take a method of the parent class and write its own implementation of this method in each inherited class. The new implementation will “replace” the parent in the child class. Here is an example. We have an Animal class with the voice() method. { { System.out.println( ); } } public class Animal public void voice () "Speak!" Let’s say we need to override the behavior of the method in the derived classes. For example, we will implement 4 inheritor classes, which will have their own implementation of the voice method. { { System.out.println( ); } } { { System.out.println( ); } } { { System.out.println( ); } } { { System.out.println( ); } } public class Bear extends Animal @Override public void voice () "Grrr!" public class Cat extends Animal @Override public void voice () "Meow!" public class Dog extends Animal @Override public void voice () "Bow-wow!" public class Snake extends Animal @Override public void voice () "Hiss-hiss!" Now let’s check how it works. { { Animal animal1 = Dog(); Animal animal2 = Cat(); Animal animal3 = Bear(); Animal animal4 = Snake(); animal1.voice(); animal2.voice(); animal3.voice(); animal4.voice(); } } The output is: Bow-wow! Meow! Grrr! Hiss-hiss! public class Main public static void main (String[] args) new new new new In addition to overriding, in the program we can use methods with the same name, but with different types and/or a number of parameters. This mechanism is called method overloading. { { System.out.println(sum( , )); System.out.println(sum( , )); System.out.println(sum( , , )); } { x + y; } { x + y; } { ukx + y + z; } } public class Program public static void main (String[] args) 2 3 // 5 4.5 3.2 // 7.7 4 3 7 // 14 static int sum ( x, y) int int return static double sum ( x, y) double double return static int sum ( x, y, z) int int int return Three options or three overloads of the sum() method are defined here, but when it is called, depending on the type and number of parameters passed, the system will choose the version that is most suitable. There is another option where a newbie can make a mistake when overriding. You can simply remember in which case which method is called: { { System.out.println( ); } } { { System.out.println( ); } { MyParent o1 = MyParent(); MyParent o2 = MyChild(); MyChild o3 = MyChild(); o1.testPrint(); o2.testPrint(); o3.testPrint(); } } The output is: parent child child public class MyParent public void testPrint () "parent" public class MyChild extends MyParent @Override public void testPrint () "child" public static void main (String[] args) new new new // MyChild o4 = (MyChild) new MyParent(); // ClassCastException #4. Wrong work with exceptions Very often novice programmers don’t know how to work with exceptions correctly. To begin with, they simply ignore them, especially those that have moved from other programming languages. However, exceptions are thrown for some reason, so don’t ignore them. There are also cases of careless handling of exceptions. For example, a newbie writes code, and their IDE starts underlining it in red and explains that certain exceptions may be thrown during its execution. In this case, an inexperienced programmer often prefers to wrap all the code in a try-catch and do nothing in catch block: { { String urlString = Scanner(System.in).nextLine(); URL url = URL(urlString); String content = Scanner(url.openStream(), StandardCharsets.UTF_8).useDelimiter( ).next(); System.out.println(content); } (IOException ignored) { } } public static void main (String[] args) try new new new "\\A" catch The problem is that in this case, we will not know what kind of exception happened, for what reason, and whether it happened at all. There are different options for how to fix the situation. For example: display the stack trace: { { String urlString = Scanner(System.in).nextLine(); URL url = URL(urlString); String content = Scanner(url.openStream(), StandardCharsets.UTF_8).useDelimiter( ).next(); System.out.println(content); } (IOException e) { e.printStackTrace(); } } public static void main (String[] args) try new new new "\\A" catch throw the exceptions above: { String urlString = Scanner(System.in).nextLine(); URL url = URL(urlString); String content = Scanner(url.openStream(), StandardCharsets.UTF_8).useDelimiter( ).next(); System.out.println(content); } IOException public static void main (String[] args) throws new new new "\\A" handle each exception separately: { URL url = getUrl(); String content = getContent(url, ); System.out.println(content); } { ( ) { { Scanner(url.openStream(), StandardCharsets.UTF_8).useDelimiter( ).next(); } (IOException e) { (attempts == ) { RuntimeException(e); } attempts--; } } } { ( ) { String urlString = Scanner(System.in).nextLine(); { URL(urlString); } (MalformedURLException e) { System.out.println( ); } } } public static void main (String[] args) 3 String private static getContent (URL url, attempts) int while true try return new "\\A" catch if 0 throw new URL private static getUrl () while true new try return new catch "URL is incorrect. Please try again." #5. Problems with choosing the right Collections Beginners are often confused about choosing the right collection. Sometimes they choose this or that collection by mistake because they do not deeply understand data structure. The wrong choice can affect the effectiveness of your program. On the other hand, if the collection is selected correctly, your program will look simpler and more logical, and the solution will be more efficient. To find out which type of collection is right for your task, find out the characteristics and behavior of each one, as well as the differences between them. You need to be clear about the pros and cons of each specific implementation (ArrayList vs LinkedList, treeMap vs HashMap, and so on). I recommend the first steps: Explore not just Сollections framework, but theoretical data structures. Create a table with all the collections. Give a brief definition of what data structure is at its core, what are its features. Ask yourself some basic questions. Should your collection allow access to items by index? Is null accepted in it? Is it allowed to duplicate elements? Do quick elements addition and quick removal important for your solution? Should it support concurrency? #6. Ignorance of Java libraries, reinvention of wheels A huge number of libraries have been written for Java, but beginners often do not notice all these gems. Don’t try to reinvent the wheel, first learn the existing developments on the issue of interest. Many libraries have been perfected by developers over the years, and you can use them for free. For example, Google Guava, or Log4j. Let’s have an example. This is what the code looks like without using libraries: { Map<String, Integer> result = HashMap<>(); (String word : words) { count = ; (String s : wordsList) { (word.equals(s)) { count++; } } result.put(word, count); } result; } Map<String, Integer> private static getFrequencyMap (Set<String> words, List<String> wordsList) new for int 0 for if return Here we use Collections.frequency() library: { Map<String, Integer> result = HashMap<>(); (String word : words) { result.put(word, Collections.frequency(wordsList, word)); } result; } Map<String, Integer> private static getFrequencyMap (Set<String> words, List<String> wordsList) new for return Of course, here we’ve got just a short example, so the complexity of the code has changed only slightly. However, the readability of the code has significantly increased. In large projects with many classes, using built-in and third-party libraries can significantly speed up development, improve readability, and testability. So don’t forget to explore Java libraries. #7. Ignoring JUnit and wrong testing of your own code Very often, novice programmers “test” their code incorrectly. For example, using System.out.println(), substituting and printing different values to the console. Seriously, from the very first steps, you should learn how to use the excellent JUnit library and write tests for your programs. Moreover, you will definitely need it in your work. Unit testing your own code is a good practice for developers. #8. Forgetting to free resources Every time your program opens a file or sets a network connection, you need to free up the resources it uses. It is also true for cases of exceptions when working with resources. Of course, FileInputStream has a finalizer that calls the close() method to collect garbage. However, you can’t be sure about the beginning of the build cycle. Thus, there is a risk that the input stream may consume resources indefinitely. The problem for beginners is that not freeing resources does not lead to compile-time or run-time errors. So it’s easy to forget about it. Let’s give an example. { propertiesPath != ; DirectoryStream<Path> directoryStream = Files.newDirectoryStream(propertiesPath, ); (Path entry : directoryStream) { Properties properties = Properties(); properties.load(Files.newBufferedReader(entry)); validateAndSave(properties); } } IOException private void populateConfigs (Path propertiesPath) throws assert null "*.properties" for new The program works, but we’ll better do it the right way. Let’s close the resource as follows: { propertiesPath != ; (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(propertiesPath, )) { (Path entry : directoryStream) { Properties properties = Properties(); properties.load(Files.newBufferedReader(entry)); validateAndSave(properties); } } } IOException private void populateConfigs (Path propertiesPath) throws assert null try "*.properties" for new #9. Equals and hashcode problems The Object class is the parent class for all Java objects. This class has methods equal() and hashCode (). The equals () method, as its name suggests, is used to simply check if two objects are equal. hashCode() method that allows you to get a unique integer number for a given object. Often newbies don’t feel like these methods need to be overridden for their objects. The default implementation of the equals() method simply checks the two objects by reference to see if they are equivalent. For example, you need to compare two points on the coordinate plane, let’s try to override the equals method: { x; y; { .x = x; .y = y; } public class Point private int private int public Point ( x, y) int int this this Now, without overriding the equals() method for the Point class, let’s try to compare the points. To do this, let’s create a PointDemo class and in it, there are three points, two according to the logic of Cartesian coordinates — equal (with the same abscissa and ordinate) and the third, which differs from them. { { Point point1 = Point( , ); Point point2 = Point( , ); Point point3 = Point( , ); (point1.equals(point2)) System.out.println( ); System.out.println( ); (point1.equals(point3)) System.out.println( ); System.out.println( ); } } The output is: and are not equal and are not equal public class PointDemo public static void main (String[] args) new 2 3 new 2 3 new 2 5 if "1 and 2 are equal" else "1 and 2 are not equal" if "1 and 3 are equal" else "1 and 3 are not equal" 1 2 1 3 More often, a practicing beginner forgets not to override equals(), but to override it correctly. For example, they forget that the object can be null or that it needs to be checked for equivalence to itself. Let’s write the correct equals() method to compare two points on the plane: { (obj == ) ; (obj == ) ; (obj.getClass() == .getClass()) { Point point = (Point) obj; (point.x == .x && point.y == .y) ; } ; } } public boolean equals (Object obj) if null return false // checking if the passed object is null if this return true //checking if the passed object is equal to itself if this // checking if the passed object has the same result of the getClass () method as the current one, on which the equals method was called // now we can definitely convert the passed object to type Point if this this // if the coordinates match, then return true, otherwise false return true return false Now if we run the main() method of the PointDemo() class we get the following result: and are equal and are not equal 1 2 1 3 #10. Working with uninitialized objects This newbie mistake leads to a NullPointerEception that is thrown when they try to use an uninitialized object. Don’t be confused about declaring an object variable, this does not mean that it is initialized. So if you write something like: String name; { System.out.println(name.length()); } private static public static void main (String[] args) you’ll get an exception when you try to call the length method because the name field is null. You should always initialize them before working with variables. #11. Wrong work with Wrappers You need to be careful to remember that Wrapper classes such as Integer or Boolean are reference data types and the type of variable can be null. In this case, it is better to avoid operations in which the null doesn’t work well. Often newbies work with uninitialized variables of type Integer or Boolean somewhere as with int or bool, which can cause NullPointerException errors. For example, if we have any Boolean s, which is Null by default, and they try to call it in some if (s), we will get an error. Also, autoboxing of variables of primitive types requires an exact match of the type of the original primitive — the type of the “wrapper class”. Attempting to autopack a variable of type byte into Short without first explicitly casting byte-> short will cause a compilation error. Here is an example: { String name; Integer age; { name; } { age; } { .age = age; } { Man man = Man(); System.out.println(man.getAge()); } } public static class Man private private String public getName () return public int getAge () return public void setAge (Integer age) this public static void main (String[] args) new In this example, when we run the main method, we will receive a NullPointerException error. This happens because we return int instead of Integer in the getAge getter. As we said before, int can’t be null and auto-unboxing occurs, in such cases, you need to be careful and pay attention that the field is not null or the getAge method returns Integer. #12. Work with asynchronous code Experts said that the most common mistake among developers, in general, is dealing with asynchronous code (concurrency, threads, etc.). If in a real project it becomes necessary to work with code asynchronously, you don’t need to use low-level multithreaded programming. It could be beneficial for learning issues or some kind of experimentation. However, in a real project, this leads to unnecessary complexity and many potential errors. Therefore, when working with multithreading, it is better to use classes from the java.util.concurrent package or other ready-made third-party libraries (Guava, etc.) Here is an example of the code: { monitoringPeriod; active; { } { monitoringPeriod; } { .monitoringPeriod = monitoringPeriod; } { active; } { .active = active; } } { { ScheduledExecutorService scheduler = Executors.newScheduledThreadPool( ); Collection<MyTask> tasks = generateTasks(); (MyTask task : tasks) { (task.isActive()) { scheduler.scheduleWithFixedDelay(task, , task.getMonitoringPeriod(), TimeUnit.SECONDS); } } } { Collection<MyTask> result = HashSet<>(); result; } } public class MyTask implements Runnable private int private boolean @Override public void run () // do work public int getMonitoringPeriod () return public void setMonitoringPeriod ( monitoringPeriod) int this public boolean isActive () return public void setActive ( active) boolean this public class Solution public static void main (String[] args) 8 for if 0 Collection<MyTask> private static generateTasks () new // tasks generation return If you try to do the same, but manually start the threads, you might end up with hard-to-read and difficult-to-maintain code. It’s also about the invention of the wheel. Conclusion We tried to describe the most popular mistakes made by Java newbies, according to experts, experienced programmers. Many examples are intended in the article so that it is convenient for beginners to get rid of these errors as soon as possible. In fact, these are not really errors. These are certain stages of the practical use of the language, through which most of the beginning Java programmers go. Errors of the very first stage (for example, incorrect placement of curly braces or semicolons we will omit) and start with those that Java Trainee and Java Junior often do. Sure, it is advisable not to prolong this stage of “popular mistakes” for a long time. This is exactly what we wish you for in your Java developer way. Previously published at https://jaxenter.com/java-mistakes-174395.html