JavaRush /Java Blog /Random EN /Coffee break #146. 5 mistakes that 99% of Java developers...

Coffee break #146. 5 mistakes that 99% of Java developers make. Strings in Java - inside view

Published in the Random EN group

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. Coffee break #146.  5 mistakes that 99% of Java developers make.  Strings in Java - inside view - 1As 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); //true
System.out.println(Objects.equals(longValue,123)); //false
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));//2022-12-31 08:00:00
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());// the size is 2
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(()->{
            //do something
            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. Coffee break #146.  5 mistakes that 99% of Java developers make.  Strings in Java - inside view - 2

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);             //true
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);           //false
Interning can be used with the firstName3 intern() method , although this is not usually preferred.
firstName3 = firstName3.intern();                      //Interning
System.out.println(firstName3 == firstName2);          //true
Interning can also occur when concatenating two string literals using the + operator .
String fullName = "Michael Jordan";
System.out.println(fullName == "Michael " + "Jordan");     //true
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.
//After Compilation
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));       //true
System.out.println(firstName3.equals(firstName2));       //true
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));  //true

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);                       //Michael
System.out.println(lastName);                        //Jordan
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);                      //MichaelJordan
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION