5 mistakes that 99% of Java developers make
Source:
Medium In this post, you will learn about the most common mistakes that many Java developers make. As a Java programmer, I know how bad it is to spend a lot of time fixing bugs in your code. Sometimes this takes several hours. However, many errors appear due to the fact that the developer ignores basic rules - that is, these are very low-level errors. Today we'll look at some common coding mistakes and then explain how to fix them. I hope this helps you avoid problems in your daily work.
Comparing objects using Objects.equals
I assume you are familiar with this method. Many developers use it frequently. This technique, introduced in JDK 7, helps you quickly compare objects and effectively avoid annoying null pointer checking. But this method is sometimes used incorrectly. Here's what I mean:
Long longValue = 123L;
System.out.println(longValue==123);
System.out.println(Objects.equals(longValue,123));
Why would replacing
== with
Objects.equals() produce the wrong result? This is because the
== compiler will obtain the underlying data type corresponding to the
longValue packaging type and then compare it to that underlying data type. This is equivalent to the compiler automatically converting constants to the underlying comparison data type. After using the
Objects.equals() method , the default base data type of the compiler constant is
int . Below is the source code
for Objects.equals() where
a.equals(b) uses
Long.equals() and determines the type of the object. This happens because the compiler assumed that the constant was of type
int , so the result of the comparison must be false.
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
public boolean equals(Object obj) {
if (obj instanceof Long) {
return value == ((Long)obj).longValue();
}
return false;
}
Knowing the reason, fixing the error is very simple. Just declare the data type of the constants, like
Objects.equals(longValue,123L) . The above problems will not arise if the logic is strict. What we need to do is follow clear programming rules.
Incorrect date format
In everyday development, you often need to change the date, but many people use the wrong format, which leads to unexpected things. Here's an example:
Instant instant = Instant.parse("2021-12-31T00:00:00.00Z");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss")
.withZone(ZoneId.systemDefault());
System.out.println(formatter.format(instant));
This uses the
YYYY-MM-dd format to change the date from 2021 to 2022. You shouldn't do that. Why? This is because
the Java DateTimeFormatter “YYYY” pattern is based on the ISO-8601 standard, which defines the year as the Thursday of each week. But December 31, 2021 fell on a Friday, so the program incorrectly indicates 2022. To avoid this, you must use the format
yyyy-MM-dd to format the date . This error occurs infrequently, only with the arrival of the new year. But in my company it caused a production failure.
Using ThreadLocal in ThreadPool
If you create
a ThreadLocal variable , then a thread accessing that variable will create a thread local variable. This way you can avoid thread safety issues. However, if you are using
ThreadLocal on
a thread pool , you need to be careful. Your code may produce unexpected results. For a simple example, let's say we have an e-commerce platform and users need to send an email to confirm a completed purchase of products.
private ThreadLocal<User> currentUser = ThreadLocal.withInitial(() -> null);
private ExecutorService executorService = Executors.newFixedThreadPool(4);
public void executor() {
executorService.submit(()->{
User user = currentUser.get();
Integer userId = user.getId();
sendEmail(userId);
});
}
If we use
ThreadLocal to save user information, a hidden error will appear. Because a pool of threads is used, and threads can be reused, when using
ThreadLocal to obtain user information, it may erroneously display someone else's information. To solve this problem, you should use sessions.
Use HashSet to remove duplicate data
When coding, we often have the need for deduplication. When you think of deduplication, the first thing many people think of is using
a HashSet . However, careless use
of HashSet can cause deduplication to fail.
User user1 = new User();
user1.setUsername("test");
User user2 = new User();
user2.setUsername("test");
List<User> users = Arrays.asList(user1, user2);
HashSet<User> sets = new HashSet<>(users);
System.out.println(sets.size());
Some attentive readers should be able to guess the reason for the failure.
HashSet uses a hash code to access the hash table and uses the equals method to determine whether objects are equal. If the user-defined object does not override the hashcode method and
equals method , then the hashcode method and
equals method of the parent object will be used by default. This will cause
the HashSet to assume that they are two different objects, causing deduplication to fail.
Eliminating an "eaten" pool thread
ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.submit(()->{
double result = 10/0;
});
The above code simulates a scenario where an exception is thrown in the thread pool. Business code must assume various situations, so it is very likely that it will throw
a RuntimeException for some reason . But if there is no special handling here, then this exception will be “eaten” by the thread pool. And you won't even have a way to check the cause of the exception. Therefore, it is best to catch exceptions in the process pool.
Strings in Java - inside view
Source:
Medium The author of this article decided to take a detailed look at the creation, functionality and features of strings in Java.
Creation
A string in Java can be created in two different ways: implicitly, as a string literal, and explicitly, using the
new keyword . String literals are characters enclosed in double quotes.
String literal = "Michael Jordan";
String object = new String("Michael Jordan");
Although both declarations create a string object, there is a difference in how both of these objects are located on the heap memory.
Internal representation
Previously, strings were stored in the form
char[] , meaning each character was a separate element in the character array. Since they were represented in the
UTF-16 character encoding format , this meant that each character took up two bytes of memory. This is not very correct, since usage statistics show that most string objects consist of
Latin-1 characters only . Latin-1 characters can be represented using a single byte of memory, which can significantly reduce memory usage—by as much as 50%. A new internal string feature was implemented as part of the JDK 9 release based on
JEP 254 called Compact Strings. In this release,
char[] was changed to
byte[] and an encoder flag field was added to represent the encoding used (Latin-1 or UTF-16). After this, encoding occurs based on the contents of the string. If the value contains only Latin-1 characters, then the Latin-1 encoding is used (the
StringLatin1 class ) or the UTF-16 encoding is used (the
StringUTF16 class ).
Memory allocation
As stated earlier, there is a difference in the way memory is allocated for these objects on the heap. Using the explicit new keyword is pretty straightforward since the JVM creates and allocates memory for the variable on the heap. Therefore, using a string literal follows a process called interning. String interning is the process of putting strings into a pool. It uses a method of storing only one copy of each individual string value, which must be immutable. Individual values are stored in the String Intern pool. This pool is a
Hashtable store that stores a reference to each string object created using literals and its hash. Although the string value is on the heap, its reference can be found in the internal pool. This can be easily verified using the experiment below. Here we have two variables with the same value:
String firstName1 = "Michael";
String firstName2 = "Michael";
System.out.println(firstName1 == firstName2);
During code execution, when the JVM encounters
firstName1 , it looks up the string value in the internal string pool
Michael . If it cannot find it, then a new entry is created for the object in the internal pool. When execution reaches
firstName2 , the process repeats again and this time the value can be found in the pool based on the
firstName1 variable . This way, instead of duplicating and creating a new entry, the same link is returned. Therefore, the equality condition is satisfied. On the other hand, if a variable with the value
Michael is created using the new keyword, no interning occurs and the equality condition is not satisfied.
String firstName3 = new String("Michael");
System.out.println(firstName3 == firstName2);
Interning can be used with
the firstName3 intern() method , although this is not usually preferred.
firstName3 = firstName3.intern();
System.out.println(firstName3 == firstName2);
Interning can also occur when concatenating two string literals using the
+ operator .
String fullName = "Michael Jordan";
System.out.println(fullName == "Michael " + "Jordan");
Here we see that at compile time, the compiler adds both the literals and removes the
+ operator from the expression to form a single string as shown below. At runtime, both
fullName and the “added literal” are interned and the equality condition is satisfied.
System.out.println(fullName == "Michael Jordan");
Equality
From the experiments above, you can see that only string literals are interned by default. However, a Java application will certainly not have only string literals, since it may receive strings from different sources. Therefore, using the equality operator is not recommended and may produce undesirable results. Equality testing should only be performed by the
equals method . It performs equality based on the value of the string rather than the memory address where it is stored.
System.out.println(firstName1.equals(firstName2));
System.out.println(firstName3.equals(firstName2));
There is also a slightly modified version of the equals method called
equalsIgnoreCase . It may be useful for case-insensitive purposes.
String firstName4 = "miCHAEL";
System.out.println(firstName4.equalsIgnoreCase(firstName1));
Immutability
Strings are immutable, meaning their internal state cannot be changed once they are created. You can change the value of a variable, but not the value of the string itself. Each method of the
String class that deals with manipulating an object (for example,
concat ,
substring ) returns a new copy of the value rather than updating the existing value.
String firstName = "Michael";
String lastName = "Jordan";
firstName.concat(lastName);
System.out.println(firstName);
System.out.println(lastName);
As you can see, no changes occur to any of the variables: neither
firstName nor
lastName .
String class methods do not change internal state, they create a new copy of the result and return the result as shown below.
firstName = firstName.concat(lastName);
System.out.println(firstName);
GO TO FULL VERSION