preface

There is an old Chinese saying, “If you make the same mistake once or twice, you can forgive it, but if you make it again, you can’t forgive it.” Writing code is the same, the same code “pit”, step on the first time called “long experience”, step on the second time called “deepen impression”, step on the third time called “not long memory”, step on more than three times called “hopeless”. In this paper, the author summarizes some Java pits, describes the phenomenon of the problem, analyzes the problem, and gives the method to avoid the pit. I hope you can avoid this kind of Java pit in advance in your daily work.

1 Object comparison method

The objects.equals method in JDK 1.7 makes it easy to compare Objects and avoid tedious null-pointer checking.

Phenomenon of the problem

Before JDK1.7, when determining whether a short, int, or long wrapper data type is equal to a constant, we used to write:

Short shortValue = (short)12345; System.out.println(shortValue == 12345); // true Integer intValue = 12345; System.out.println(intValue == 12345); // true Long longValue = 12345L; System.out.println(longValue == 12345); // true

Since JDK1.7, the objects.equals method has been provided and functional programming is recommended. The code changes as follows:

Short shortValue = (short)12345; System.out.println(Objects.equals(shortValue, 12345)); // false Integer intValue = 12345; System.out.println(Objects.equals(intValue, 12345)); // true Long longValue = 12345L; System.out.println(Objects.equals(longValue, 12345)); // false

Why would replacing == with objects.equals result in a different output?

Problem analysis

By decomcompiling the first code, we get the statement System.out.println(shortValue == 12345); The bytecode instructions of

getstatic java.lang.System.out : java.io.PrintStream [22] aload_1 [shortValue] invokevirtual java.lang.Short.shortValue() : short [28] sipush 12345 if_icmpne 24 iconst_1 goto 25 iconst_0 invokevirtual java.io.PrintStream.println(boolean) : void [32]

It turns out that the compiler determines the basic data type corresponding to the wrapper data type and compares the instructions of this basic data type (sipush and IF_ICmpne in the bytecode instructions above, etc.), which is equivalent to the compiler automatically forcing the data type of the constant.

Why doesn’t the compiler automatically cast constants to their data types after objects.equals? By decomcompiling the second code, we get the statement System.out.println(objects.equals (shortValue, 12345)); The bytecode instructions of

getstatic java.lang.System.out : java.io.PrintStream [22] aload_1 [shortValue] sipush 12345 invokestatic java.lang.Integer.valueOf(int) : java.lang.Integer [28] invokestatic java.util.Objects.equals(java.lang.Object, java.lang.Object) : boolean [33] invokevirtual java.io.PrintStream.println(boolean) : void [39]

It turns out that the compiler literally thinks the constant 12345 defaults to an int, so it automatically converts to the wrapper data type Integer.

In the Java language, the default data type for integers is int and the default data type for decimals is double.

By analyzing the objects. equals method source code, we know that: System.out.println(objects.equals (shortValue, 12345)); The other is the wrapper data type Integer, so the final comparison must be false; System.out.println(objects.equals (intValue, 12345))); So the final comparison must be true.

Avoid pit method

1) Keep good coding habits and avoid automatic conversion of data types

To avoid automatic conversion of data types, it is more scientific to declare constants as corresponding base data types.

The first piece of code could look like this:

Short shortValue = (short)12345; System.out.println(shortValue == (short)12345); // true Integer intValue = 12345; System.out.println(intValue == 12345); // true Long longValue = 12345L; System.out.println(longValue == 12345L); // true

The second piece of code could be written like this:

Short shortValue = (short)12345; System.out.println(Objects.equals(shortValue, (short)12345)); // true Integer intValue = 12345; System.out.println(Objects.equals(intValue, 12345)); // true Long longValue = 12345L; System.out.println(Objects.equals(longValue, 12345L)); // true

2) Use development tools or plug-ins to detect data type mismatches early

In Eclipse’s Problems window, we see the following prompt:

Unlikely argument type for equals(): int seems to be unrelated to Short Unlikely argument type for equals(): int seems to be unrelated to Long

3) Conduct routine unit tests and try to find problems in the development phase

Don’t make small changes so you don’t need to unit test. Bugs tend to show up in your overconfident code. Problems like this can be found with a single unit test.

Note: The necessary unit tests are applicable to all of the following cases, so they are not covered below.

2 Unpacking ternary expressions

Ternary expressions are a fixed syntax in Java encoding:

Conditional expression? Expression 1: expression 2

The logic of a ternary expression is: if the conditional expression is true, expression 1 is executed, otherwise expression 2 is executed.

Phenomenon of the problem

boolean condition = false; Double value1 = 1.0 D; Double value2 = 2.0 D; Double value3 = null; Double result = condition ? value1 * value2 : value3; // Throw a null pointer exception

When condition = false, value3 is assigned to result. There is no problem.

Problem analysis

By decompiling the code, we get the statement:

Double result = condition ? value1 * value2 : value3;

The bytecode instructions of

iload_1 [condition] ifeq 33 aload_2 [value1] invokevirtual java.lang.Double.doubleValue() : double [24] aload_3 [value2] invokevirtual java.lang.Double.doubleValue() : double [24] dmul goto 38 aload 4 [value3] invokevirtual java.lang.Double.doubleValue() : double [24] invokestatic java.lang.Double.valueOf(double) : java.lang.Double [16] astore 5 [result]

At line 9, the Double object value 3 is loaded into the operand stack; At line 10, the doubleValue method of the Double object value 3 is called. If value 3 is null, the doubleValue method must throw a null-pointer exception. But why convert the empty object value 3 to the underlying data type double?

Refer to relevant information and get the type conversion rules of ternary expressions:

If the two expressions are of the same type, the return type is that type.

If the two expressions are of different types but cannot be converted, the return type is Object.

If two expressions have different types but can be converted, the wrapper data type is first converted to the basic data type and then converted according to the basic data type conversion rules (byte < short(char)< int < long < float < double). The return value type is the highest priority basic data type.

According to the rule analysis, the type of expression 1 (value1 * value2) is the basic data type double, and the type of expression 2 (value 3) is the wrapper data type double. According to the type conversion rules of ternary expressions, the final expression type is the basic data type double. So, when condition is false, we need to convert the empty Double object value 3 to the underlying data type Double, so we call the doubleValue method of value 3.

Avoid pit method

1) Avoid ternary expressions and use if-else statements instead

If a ternary expression has arithmetic calculations that wrap data types, consider using if-else statements instead. Rewrite the code as follows:

if (condition) { result = value1 * value2; } else { result = value3; }

2) Try to use basic data types and avoid unpacking data types

If there is an arithmetic calculation in a ternary expression, try to use basic data types and avoid unpacking data types. Rewrite the code as follows:

boolean condition = false; Double value1 = 1.0 D; Double value2 = 2.0 D; Double value3 = 3.0 D; double result = condition ? value1 * value2 : value3;

3 Generic object assignment

Java generics, a new feature introduced in JDK 1.5, are essentially parameterized types, which use a data type as a parameter.

Phenomenon of the problem

When doing user data paging query, the following code was written because of a clerical error:

1) PageDataVO. Java

@getter@setter@toString @noargsconstructor @allargsconstructor Public Class PageDataVO private Long totalCount; /* private List dataList; }

2) UserDAO. Java

@mapper public interface UserDAO {public Long countUser(@param (“query”) UserQueryVO query); Public List queryUser(@param (“query”) UserQueryVO query); }

3) UserService. Java

/* public class UserDAO / @autowired private UserDAO UserDAO; Public PageDataVO queryUser(UserQueryVO query) {List dataList = null; Long totalCount = userDAO.countUser(query); if (Objects.nonNull(totalCount) && totalCount.compareTo(0L) > 0) { dataList = userDAO.queryUser(query); } return new PageDataVO(totalCount, dataList); }}

The above code doesn’t have any compilation problems, but it does return some of the confidential fields in UserDO to the front end. Return New PageDataVO(totalCount, dataList); return PageDataVO(totalCount, dataList); PageDataVO = PageDataVO; PageDataVO = PageDataVO; PageDataVO = PageDataVO;

The question is: why don’t development tools report compilation errors?

Problem analysis

For historical reasons, parameterized types and primitive types need to be compatible. Let’s take ArrayList as an example to see how this works.

Previously written:

ArrayList list = new ArrayList();

Now written:

ArrayList list = new ArrayList();

For compatibility with previous code, passing values between various object references must result in the following:

ArrayList list1 = new ArrayList(); ArrayList list2 = new ArrayList();

As a result, the Java compiler is compatible with both types, and compilation errors do not occur, but compilation alarms do occur. However, my development tools compile without any alarms.

To analyze the problem we encountered, we actually hit both:

Assigning a List object to a List hits the first case;

Assigning PageDataVO to PageDataVO hits the second case.

The net effect is that we magically assign a List object to a List.

The root of the problem is that we did not enforce type checking when initializing the PageDataVO object.

Avoid pit method

1) The diamond syntax is recommended when initializing generic objects

In the Java Development Manual, there is a recommended rule:

In JDK7 and above, use the diamond syntax or skip all generic definitions.

Note: Diamond generics, known as diamond, use <> directly to refer to the previously specified type.

Is:

// <> diamond mode HashMap<String, String> userCache = new HashMap<>(16); ArrayList users = new ArrayList(10);

In fact, it is not recommended to initialize generic objects. This avoids type checking, which causes the problem above.

When initializing generic objects, it is recommended to use the diamond syntax as follows:

return new PageDataVO<>(totalCount, dataList);

Now, in Eclipse’s Problems window, we see an error like this:

Cannot infer type arguments for PageDataVO<>

So, we know we forgot to convert the List object to the List object.

Generic property copy

Spring’s beanutils.copyProperties method is a useful property copy utility method.

Phenomenon of the problem

According to the database development specification, the database table must contain the ID, GMT_CREATE, gmT_MODIFIED three fields. Where, the id field, depending on the amount of data, may be int or long.

First, we define a BaseDO base class:

/** BaseDO class */ @getter@setter@toString public BaseDO {private T id; private Date gmtCreate; private Date gmtModified; }

For the user table, we define a UserDO class:

@get@setter@toString public static class UserDO extends BaseDO {private String name; private String description; }

For the query interface, a UserVO class is defined:

@getter@setter@toString public static class UserVO {private Long id; private String name; private String description; }

To realize the query user service interface, the implementation code is as follows:

/** @service public class UserService {

/** @autowired private UserDAO UserDAO;

Public List<UserVO> queryUser(UserQueryVO query) {// List<UserDO> userDOList = userDAO.queryUser(query); if (CollectionUtils.isEmpty()) { return Collections.emptyList(); } List<UserVO> userVOList = new ArrayList<>(userdolist.size ()); for (UserDO userDO : userDOList) { UserVO userVO = new UserVO(); BeanUtils.copyProperties(userDO, userVO); userVOList.add(userVO); } // Return userVOList; }Copy the code

}

Through testing, we found a problem — the value of the user ID was not returned by calling the query user service interface.

[{“description”:”This is a tester.”,”name”:”tester”},…]

Problem analysis

Run in Debug mode, go inside the beanutils.copyProperties tool method and get the following:

The return type of UserDO’s getId method is not Long, but Object. The classutils. isAssignable utility method determines whether an Object can be assigned to a Long, and of course returns false so that the property cannot be copied.

Why doesn’t the author consider “get the value of the attribute first and decide whether to assign it later”? The suggested code is as follows:

Object value = readMethod.invoke(source); if (Objects.nonNull(value) && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], value.getClass())) { … // assign code}

Avoid pit method

1) Don’t blindly trust third party toolkits, any toolkit can have problems

In Java, there are many third-party toolkits, such as Apache’s Commons-Lang3, Commons-Collections, Google’s Guava… These are all good third party toolkits. However, don’t blindly trust third-party toolkits; any toolkit can have problems.

2) If the number of properties to be copied is small, you can manually encode the properties to be copied

The main advantage of using beanutils.copyProperties is that it saves code, but the main disadvantage is that it degrades program performance. Therefore, if fewer attributes need to be copied, you can manually encode the attributes for copying.

5 The Set object is weighted

In the Java language, a Set data structure can be used for object reweighting. Common Set classes include HashSet, LinkedHashSet, and so on.

Phenomenon of the problem

Write a city helper class to read city data from a CSV file:

Public static Collection readCities(String fileName) {try (FileInputStream stream = new FileInputStream(fileName); InputStreamReader reader = new InputStreamReader(stream, “GBK”); CSVParser parser = new CSVParser(reader, CSVFormat.DEFAULT.withHeader())) { Set citySet = new HashSet<>(1024); Iterator iterator = parser.iterator(); while (iterator.hasNext()) { citySet.add(parseCity(iterator.next())); } return citySet; } catch (IOException e) {log.warn(” Read all city exceptions “, e); } return Collections.emptyList(); }

Private static City parseCity(CSVRecord record) {City City = new City(); city.setCode(record.get(0)); city.setName(record.get(1)); return city; } @getter@setter@toString private static class City {/** private static class City code */ private String code; /** city name */ private String name; }Copy the code

}

The HashSet data structure is used in the code to avoid the duplication of city data and to enforce the reweighting of the read city data.

If the file content is as follows:

Code, name 010, Beijing 020, Guangzhou 010, Beijing

The parsed JSON result is as follows:

[{” code “:” 010 “, “name” : “Beijing”}, {” code “:” 020 “, “name” : “guangzhou”}, {” code “:” 010 “, “name” : “Beijing”}]

However, the city of “Beijing” was not ranked.

Problem analysis

When adding an object to a Set, the collection first computes the hashCode of the object to be added, using that value to get a location for the current object. If no object exists at that location, the Set assumes that the object does not exist in the Set and adds it to the Set. If an object exists at that location, then the equals method compares the object being added to the collection with the object at that location. If the equals method returns false, the collection considers that the object does not exist in the collection and places the object after it. If equals returns true, the object is considered to already exist in the collection and will not be added to it. Therefore, the hashCode and equals methods are used to determine whether two elements are duplicated in the hash table. The hashCode method determines where data is stored in the table, and the Equals method determines whether the same data exists in the table.

The hashCode and equals methods of the Object class are used instead of overriding the hashCode and equals methods of the City class. Its implementation is as follows:

public native int hashCode(); public boolean equals(Object obj) { return (this == obj); }

The hashCode method of the Object class is a local method that returns the address of the Object. The equals method of the Object class only compares objects for equality. Therefore, for the two identical Beijing data, because different City objects were initialized during parsing, the values of hashCode and Equals methods were different, which must be considered as different objects by Set. Therefore, no weighting was carried out.

We rewrite the hashCode and equals methods of the City class as follows:

@getter@setter@toString private static class City {/* private String code; /* City name */ private String name;

Override public Boolean equals(Object obj) {if (obj == this) {return true; } if (Objects.isNull(obj)) { return false; } if (obj.getClass() ! = this.getClass()) { return false; } return Objects.equals(this.code, ((City)obj).code); } @override public int hashCode() {return objects.hashCode (this.code); }Copy the code

}

Support the test program again, and the JSON result after parsing is as follows:

[{” code “:” 010 “, “name” : “Beijing”}, {” code “:” 020 “, “name” : “guangzhou”}]

The result is correct and the city “Beijing” has been ranked.

Avoid pit method

1) A List can be used instead of a Set when the data is unique

When it is determined that the parsed city data is unique, there is no need to perform the rearrangement operation and the List can be used to store the data directly.

List citySet = new ArrayList<>(1024); Iterator iterator = parser.iterator(); while (iterator.hasNext()) { citySet.add(parseCity(iterator.next())); } return citySet;

2) Map can be used instead of Set when certain data is not unique

If the resolved city data is not unique, you need to install the city name to perform reassignment. You can use Map to store the data. Why not implement the hashCode method of the City class, and then use HashSet to implement weights? First, you don’t want to put business logic in model DO classes; Secondly, put the weight field in the code, easy to read, understand and maintain the code.

Map<String, City> cityMap = new HashMap<>(1024); Iterator iterator = parser.iterator(); while (iterator.hasNext()) { City city = parseCity(iterator.next()); cityMap.put(city.getCode(), city); } return cityMap.values();

3) Follow the Java language specification and override the hashCode and equals methods

Custom classes that do not override the hashCode and Equals methods should not be used in sets.

Public method proxy

The SpringCGLIB agent generates a proxy class that inherits the proxyed class and implements the proxy by overriding non-final methods in the proxyed class. Therefore, the SpringCGLIB proxy class cannot be final, nor can the proxy method be final, due to the inheritance mechanism.

Phenomenon of the problem

As a simple example, only the superuser has permission to delete the company, and all service functions are intercepted by AOP to handle exceptions. Example code is as follows:

1) UserService. Java

/ @service public class UserService {/* private User superUser;

Public void setSuperUser(User superUser) {this.superUser = superUser; } public final User getSuperUser() {return this.superuser; }Copy the code

}

2) CompanyService. Java

/** Public class CompanyService {/* Autowired private CompanyDAO CompanyDAO; @autowired private UserService UserService;

Public void deleteCompany(Long companyId, Long operatorId) {// setSuperUser userservice.setsuperuser (new User(0L, "admin", "superuser ")); // Verify superuser if (! Objects.equals(operatorId, userService.getsuperUser ().getid ())) {throw new ExampleException(" Only superusers can delete companies "); } // Delete companydao.delete (companyId, operatorId); }Copy the code

}

When CompanyService deleteCompany is called, a NullPointerException is thrown. The superuser obtained by calling the getSuperUser method of the UserService class is NULL. However, each time we force a superuser in the CompanyService deleteCompany method through the setSuperUser method of the UserService class, The superuser obtained from the getSuperUser method of the UserService class should not be null. In fact, this problem is caused by AOP proxies.

Problem analysis

When using SpringCGLIB proxy classes, Spring will create a named UserService EnhancerBySpringCGLIBEnhancerBySpringCGLIBEnhancerBySpringCGLIB?????????? Proxy class of. Decompiling the proxy class yields the following main code:

public class UserService
E n h a n c e r B y S p r i n g C G L I B EnhancerBySpringCGLIB
a2c3b345 extends UserService implements SpringProxy, Advised, Factory { …… public final void setSuperUser(User var1) { MethodInterceptor var10000 = this.CGLIBCALLBACK_0; if (var10000 == null) { CGLIBBIND_CALLBACKS(this); var10000 = this.CGLIB$CALLBACK_0; }

if (var10000 ! = null) { var10000.intercept(this, CGLIB$setSuperUser$0$Method, new Object[]{var1}, CGLIB$setSuperUser$0$Proxy); } else { super.setSuperUser(var1); }}...Copy the code

}

As you can see, this proxy class inherits the UserService class and only proxies the setSuperUser method, but not the getSuperUser method. So, when we call the setSuperUser method, we set the superUser field value of the original object instance; When we call the getSuperUser method, we get the superUser field value of the proxy object instance. If the final modifier of the two methods is interchanged, there is also the problem of getting null for the superuser.

Avoid pit method

1) Follow the CGLIB proxy specification strictly and do not add final modifiers to propped classes and methods

In strict accordance with the CGLIB proxy specification, proxyed classes and methods should not have final modifiers to avoid dynamic proxying that operates on different object instances (the original object instance and the proxy object instance), resulting in data inconsistency or null pointer problems.

2) Narrow the scope of CGLIB proxy classes, and do not delegate classes that can be avoided

By narrowing the scope of CGLIB proxy classes, we can save memory and improve the efficiency of function calls.

Public field proxy

– there was a pit in fastjson forcing an upgrade to 1.2.60.

public class ParseConfig { public final SymbolTable symbolTable = new SymbolTable(4096); . }

Inheriting this class in our project, while being dynamically proxied by AOP, one line of code caused a bloodbath.

Phenomenon of the problem

We still use the example from the previous chapter, but remove the get and set methods and define a public field. Example code is as follows:

1) UserService. Java

@service public class UserService {/* superUser */ public final User superUser = new User(0L, “admin”, “superUser “); . }

2) CompanyService. Java

/** Public class CompanyService {/* Autowired private CompanyDAO CompanyDAO; @autowired private UserService UserService;

Public void deleteCompany(Long companyId, Long operatorId) {// Verify superuser if (! Objects. The equals (operatorId, userService. SuperUser. GetId ())) {throw new ExampleException (only the super user can delete "company"); } // Delete companydao.delete (companyId, operatorId); }Copy the code

}

A NullPointerException was thrown when CompanyService deleteCompany was called. After debugging, the superUser variable of UserService is null. If the proxy is removed, there are no null pointer exceptions, indicating that the problem is caused by an AOP proxy.

Problem analysis

When using SpringCGLIB proxy classes, Spring will create a named UserService EnhancerBySpringCGLIBEnhancerBySpringCGLIBEnhancerBySpringCGLIB?????????? Proxy class of. This proxy class inherits the UserService class and overrides all non-final public methods in the UserService class. However, this proxy class does not call the methods of the super base class; Instead, it creates a member userService and points to the original userService class object instance. There are now two object instances in memory: the original UserService object instance and a proxy object instance pointing to UserService. This proxy class is just a virtual proxy that inherits the UserService class and has the same fields as UserService, but it never initializes and uses them. Therefore, a default value of NULL is returned whenever a public member variable is retrieved from this proxy object instance.

Avoid pit method

1) When a field is determined to be immutable, it can be defined as a public static constant

When a field is determined to be immutable, it can be defined as a public static constant and accessed with the class name + field name. Class name + field name accesses public static constants, independent of the dynamic proxy for the class instance.

2) When a field is determined to be immutable, it can be defined as a private member variable

When a field is determined to be immutable, it can be defined as a private member variable that provides a public Getter method to get its value. When an instance of the class is dynamically proxied, the proxied method calls the proxied Getter method, which returns the proxied class’s member variable value.

3) Follow the JavaBean coding specification and do not define public member variables

Follow the JavaBean coding specification and do not define public member variables. The JavaBean specification is as follows:

A JavaBean class must be a public class with its access property set to public, for example: public Class User{…… }

JavaBean classes must have an empty constructor: There must be a public constructor with no arguments in the class

A JavaBean class should not have public instance variables. Class variables are private, such as private Integer ID.

Properties should be accessed through a set of getter/setter methods

Author: Ali Technology Link: mp.weixin.qq.com/s?src=11&ti…