In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-02-24 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/02 Report--
This article is mainly about "what are the pitfalls in writing Java". Interested friends may wish to take a look. The method introduced in this paper is simple, fast and practical. Let's let the editor take you to learn "what are the pitfalls in writing Java?"
1. Object comparison method
The Objects.equals method provided by JDK 1.7 conveniently realizes the comparison of objects and effectively avoids the tedious null pointer check.
Problem phenomenon
Before JDK1.7, when judging whether a short, long, or long integer 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
After JDK1.7, the Objects.equals method is provided and functional programming is recommended. The change code is 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 does directly replacing = = with the Objects.equals method cause the output to be different?
Analysis of problems
By decompiling the first piece of code, we get the bytecode instruction for the statement System.out.println (shortValue = = 12345):
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 will judge the basic data type corresponding to the wrapper data type and compare it with the instructions of this basic data type (such as sipush and if_icmpne in the bytecode instruction above), which is equivalent to the forced conversion of the constant data type by the compiler automatically.
Why doesn't the compiler automatically cast data types on constants after using the Objects.equals method? By decompiling the second piece of code, we get the bytecode instruction for the statement System.out.println (Objects.equals (shortValue, 12345)) as follows:
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 believes that the default basic data type of constant 12345 is int, so it is automatically converted 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 source code of the Objects.equals method, we can see that the statement System.out.println (Objects.equals (shortValue, 12345)), because the two parameter object types of Objects.equals are inconsistent, one is the wrapper data type Short, the other is the wrapper data type Integer, so the final comparison result must be false. On the other hand, the statement System.out.println (Objects.equals (intValue, 12345)), because the two parameter objects of Objects.equals are of the same type, both wrap the data type Integer with the same value, so the final comparison result must be true.
Pit avoidance method
1) maintain good coding habits to avoid automatic conversion of data types
In order to avoid automatic conversion of data types, a more scientific way to write is to directly declare the constant as the corresponding basic data type.
The first piece of code can be written as follows:
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 can be written as follows:
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) early detection of data type mismatch with the help of development tools or plug-ins
In the questions window of Eclipse, we will 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 testing and try to find the problem at the stage of research and development.
"Don't give up doing good even if it's not important." Don't do unit testing just because the changes are small. Bug often appears in your overconfident code. Problems like this can be found by conducting a unit test.
Note: perform the necessary unit tests and apply to all of the following cases, so I will not repeat them below.
2. Unpacking with ternary expression
A ternary expression is a fixed syntax format in Java coding:
Conditional expression? Expression 1: expression 2
The logic of the ternary expression is: if the conditional expression is valid, execute expression 1, otherwise execute expression 2.
Problem phenomenon
Boolean condition = false; Double value1 = 1.0D; Double value2 = 2.0D; Double value3 = null; Double result = condition? Value1 * value2: value3; / / throws a null pointer exception
When the conditional expression condition is equal to false, the Double object value3 is assigned directly to the Double object result. According to reason, there is no problem. Why would a null pointer exception be thrown?
Analysis of problems
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]
On line 9, load the Double object value 3 into the Operand stack; on line 10, call the doubleValue method of the Double object value 3. At this point, because value 3 is an empty object null, calling the doubleValue method must throw a null pointer exception. But why convert an empty object value 3 to the underlying data type double?
Consult the relevant data to get the type conversion rules of the ternary expression:
If two expressions are of the same type, the return value type is that type
If the two expression types are different, but the type is not convertible, the return type is Object.
If the two expression types are different, but the type can be converted, first convert the wrapper data type to the basic data type, and then follow the conversion rules of the basic data type (byte
< short(char)< int < long < float < double) 来转化,返回值类型为优先级最高的基本数据类型。 根据规则分析,表达式 1(value1 * value2)的类型为基础数据类型 double,表达式 2(value 3)的类型为包装数据类型 Double,根据三元表达式的类型转化规则判断,最终的表达式类型为基础数据类型 double。所以,当条件表达式 condition 为 false 时,需要把空 Double 对象 value 3 转化为基础数据类型 double,于是就调用了 value 3 的 doubleValue 方法进行拆包,当然会抛出空指针异常。 避坑方法 1)尽量避免使用三元表达式,可以采用 if-else 语句代替 如果三元表达式中有包装数据类型的算术计算,可以考虑利用 if-else 语句代替。改写代码如下: if (condition) { result = value1 * value2; } else { result = value3; } 2)尽量使用基本数据类型,避免包装数据类型的拆装包 如果在三元表达式中有算术计算,尽量使用基本数据类型,避免包装数据类型的拆装包。改写代码如下: boolean condition = false; double value1 = 1.0D; double value2 = 2.0D; double value3 = 3.0D; double result = condition ? value1 * value2 : value3; 3、泛型对象赋值 Java 泛型是 JDK 1.5 中引入的一个新特性,其本质是参数化类型,即把数据类型做为一个参数使用。 问题现象 在做用户数据分页查询时,因为笔误编写了如下代码: 1)PageDataVO.java /** 分页数据VO类 */ @Getter @Setter @ToString @NoArgsConstructor @AllArgsConstructor public class PageDataVO { /** 总共数量 */ private Long totalCount; /** 数据列表 */ private List dataList; } 2)UserDAO.java /** 用户DAO接口 */ @Mapper public interface UserDAO { /** 统计用户数量 */ public Long countUser(@Param("query") UserQueryVO query); /** 查询用户信息 */ public List queryUser(@Param("query") UserQueryVO query);} 3)UserService.java /** 用户服务类 */@Service public class UserService { /** 用户DAO */ @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 does not have any compilation problems, but returns some secret fields in UserDO to the front end. Careful readers may have found that in the statement return new PageDataVO (totalCount, dataList) of the queryUser method of the UserService class, we assign the List object dataList to the List field dataList of PageDataVO.
The question is: why don't development tools report compilation errors?
Analysis of problems
For historical reasons, parameterized types and original types need to be compatible. Let's take ArrayList as an example to see how it is compatible.
The previous way of writing:
ArrayList list = new ArrayList ()
The way it is now written:
ArrayList list = new ArrayList ()
Considering compatibility with previous code and passing values between various object references, the following is bound to occur:
/ / ArrayList list1 = new ArrayList () in the first case; / / ArrayList list2 = new ArrayList () in the second case
Therefore, the Java compiler is compatible with the above two types, and there will be no compilation errors, but compilation alarms will occur. However, there are really no alarms in my development tools at compile time.
When we analyze the problems we encounter, we actually hit two situations at the same time:
Put List
Object is assigned to List, which hits the first case
Assign a PageDataVO object to PageDataVO
Hit the second case.
The end result is that we magically assign the List object to List.
The root of the problem is that when we initialize the PageDataVO object, we do not require type checking to be enforced.
Pit avoidance method
1) diamond syntax is recommended when initializing generic objects
In the Java Development Manual, there is a recommended rule:
[recommended] when defining collection generics, use diamond syntax or omission on JDK7 or above.
Description: Diamond generics, that is, diamond, are directly used to refer to the previously specified types.
Positive example:
/ / diamond mode HashMap userCache = new HashMap (16); / / omission mode ArrayList users = new ArrayList (10)
In fact, when initializing generic objects, the whole province is not recommended. This avoids type checking, causing the above problems.
When initializing a generic object, it is recommended to use the diamond syntax as follows:
Return new PageDataVO (totalCount, dataList)
Now, in the question window of Eclipse, we will see an error like this:
Cannot infer type arguments for PageDataVO
So, we know that we forgot to convert the List object to the List object.
4. Copy of generic attributes
Spring's BeanUtils.copyProperties method is an easy-to-use attribute copy tool method.
Problem phenomenon
According to the database development specification, the database table must contain three fields of id,gmt_create,gmt_modified. The id field may be of int or long type depending on the amount of data.
First, a BaseDO base class is defined:
/ * * basic DO class * / @ Getter @ Setter @ ToString public class BaseDO {private T id; private Date gmtCreate; private Date gmtModified;}
For the user table, a UserDO class is defined:
/ * * user DO * / @ Getter @ Setter @ ToString public static class UserDO extends BaseDO {private String name; private String description;}
For the query interface, a UserVO class is defined:
/ * * user VO class * / @ Getter @ Setter @ ToString public static class UserVO {private Long id; private String name; private String description;}
Implement the query user service API. The implementation code is as follows:
/ * * user service class * / @ Service public class UserService {/ * * user DAO * / @ Autowired private UserDAO userDAO; / * * query user * / public List queryUser (UserQueryVO query) {/ / query user information List userDOList = userDAO.queryUser (query) If (CollectionUtils.isEmpty ()) {return Collections.emptyList ();} / / convert user list List userVOList = new ArrayList (userDOList.size ()); for (UserDO userDO: userDOList) {UserVO userVO = new UserVO () BeanUtils.copyProperties (userDO, userVO); userVOList.add (userVO);} / / return user list return userVOList;}}
Through the test, we will find a problem-calling the query user service interface, the value of the user ID is not returned.
[{"description": "This is a tester.", "name": "tester"},...]
Analysis of problems
Run in Debug mode, go inside the BeanUtils.copyProperties tool method, and get the following:
It turns out that the getId method return type of the UserDO class is not a Long type, but is restored to an Object type by a generic type. The following ClassUtils.isAssignable tool method, which determines whether the Object type can be assigned to the Long type, will of course return false so that the attribute copy cannot be made.
Why doesn't the author consider "get the attribute value first, and then judge whether it can be assigned or not"? The recommended code is as follows:
Object value = readMethod.invoke (source); if (Objects.nonNull (value) & & ClassUtils.isAssignable (writeMethod.getParameterTypes () [0], value.getClass () {. / / assignment related code}
Pit avoidance method
1) do not blindly trust third-party toolkits, any toolkit may have problems
In Java, there are many third-party toolkits, such as Apache's commons-lang3, commons-collections,Google 's guava. Are easy-to-use third-party toolkits. However, do not blindly trust third-party toolkits, any toolkit may have problems.
2) if there are few attributes to be copied, you can manually encode the attribute copy.
Using BeanUtils.copyProperties to reflect copy attributes, the main advantage is to save the amount of code, and the main disadvantage is that it leads to the decline of program performance. Therefore, if you need to copy fewer attributes, you can manually encode the attribute copy.
5. Set object weight
In Java language, Set data structure can be used for object weighting, and the common Set classes are HashSet, LinkedHashSet and so on.
Problem phenomenon
A city helper class is written to read city data from the CSV file:
/ * * City Auxiliary Class * / @ Slf4j public class CityHelper {/ * * read City * / 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 () } / * * analyze the city * / private static City parseCity (CSVRecord record) {City city = new City (); city.setCode (record.get (0)); city.setName (record.get (1)); return city } / * * City Class * / @ Getter @ Setter @ ToString private static class City {/ * * City Code * / private String code; / * * City name * / private String name;}}
The HashSet data structure is used in the code to avoid duplication of city data and to force the weight of the read city data.
When the input file 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, there is no weight arrangement for the city "Beijing".
Analysis of problems
When adding objects to the collection Set, the collection first calculates the hashCode of the objects to be added, and according to this value, we get a location to store the current object. If no object exists at that location, the collection Set assumes that the object does not exist in the collection and is added directly. If there is an object at that location, then compare the object you are going to add to the collection with the object at that location with the equals method: if the equals method returns false, then the collection thinks that the object does not exist in the collection and places the object after it; if the equals method returns true, it is considered that the object already exists in the collection and will no longer be added to the collection. Therefore, the hashCode method and the equals method are used to determine whether the two elements are repeated in the hash table. The hashCode method determines where the data is stored in the table, while the equals method determines whether the same data exists in the table.
Analyzing the above problems, since the hashCode method and equals method of the City class are not overridden, the hashCode method and equals method of the Object class are adopted. The implementation is as follows:
Public native int hashCode (); public boolean equals (Object obj) {return (this = = obj);}
You can see that 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 whether the objects are equal. Therefore, for two identical pieces of Beijing data, different City objects are initialized during parsing, resulting in different values of hashCode method and equals method, which must be considered as different objects by Set, so there is no ranking.
So, let's rewrite the hashCode and equals methods of the City class, as follows:
/ * * Getter class * / @ Getter @ Setter @ ToString private static class City {/ * * City Code * / private String code; / * * City name * / private String name; / * * determine equality * / @ 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) } / * * Hash code * / @ Override public int hashCode () {return Objects.hashCode (this.code);}}
The test program is supported again. The parsed JSON results are as follows:
[{"code": "010", "name": "Beijing"}, {"code": "020", "name": "Guangzhou"}]
The result is correct, and the weight of the city "Beijing" has been ranked.
Pit avoidance method
1) when determining that the data is unique, you can use List instead of Set
When it is determined that the parsed city data is unique, there is no need for reweight operation, but can be stored directly using List.
List citySet = new ArrayList (1024); Iterator iterator = parser.iterator (); while (iterator.hasNext ()) {citySet.add (parseCity (iterator.next ();} return citySet
2) when determining that the data is not unique, you can use Map instead of Set
When it is determined that the parsed city data is not unique, you need to install the city name for scheduling operation, which can be stored directly using Map. Why not recommend implementing the hashCode method of the City class, and then using HashSet to achieve weighting? First of all, you don't want to put the business logic in the model DO class; second, put the priority fields in the code so that the code can be read, understood, and maintained.
Map 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, rewrite hashCode method and equals method
Custom classes that do not override hashCode and equals methods should not be used in Set.
6. Public method agent
The proxy class generated by the SpringCGLIB proxy is an inherited proxy class that implements the proxy by overriding non-final methods in the proxied class. Therefore, the class of a SpringCGLIB proxy cannot be a final class, and the method of a proxy cannot be a final method, which is limited by the inheritance mechanism.
Problem phenomenon
Here is a simple example. Only superusers have permission to delete a company, and all service functions are intercepted by AOP to handle exceptions. The example code is as follows:
1) UserService.java
/ * * user service class * / @ Service public class UserService {/ * * superuser * / private User superUser; / * * set superuser * / public void setSuperUser (User superUser) {this.superUser = superUser;} / * * get superuser * / public final User getSuperUser () {return this.superUser;}}
2) CompanyService.java
/ * * Company service class * / @ Service public class CompanyService {/ * * Company DAO * / @ Autowired private CompanyDAO companyDAO; / * * user service * / @ Autowired private UserService userService / * * Delete company * / public void deleteCompany (Long companyId, Long operatorId) {/ / set superuser 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 company information companyDAO.delete (companyId, operatorId);}}
When we call the deleteCompany method of CompanyService, we also throw a null pointer exception (NullPointerException), because the superuser obtained by calling the getSuperUser method of the UserService class is null. However, in the deleteCompany method of the CompanyService class, we force the superuser to be specified every time through the setSuperUser method of the UserService class, and the superuser obtained through the getSuperUser method of the UserService class should not be null. In fact, this problem is also caused by the AOP agent.
Analysis of problems
When using the SpringCGLIB proxy class, Spring creates a file called UserService$$EnhancerBySpringCGLIB$$? The proxy class of the. Decompile the proxy class to get the following main code:
Public class UserService$$EnhancerBySpringCGLIB$$a2c3b345 extends UserService implements SpringProxy, Advised, Factory {. Public final void setSuperUser (User var1) {MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if (var10000 = = null) {CGLIB$BIND_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) }}. }
As you can see, this proxy class inherits the UserService class and proxies only 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 modifiers of these two methods are interchanged, there is also a problem of getting the superuser as null.
Pit avoidance method
1) strictly follow the CGLIB proxy specification, and do not add final modifiers to the proxied classes and methods
Strictly follow the CGLIB proxy specification, the proxied classes and methods do not add final modifiers to avoid dynamic proxy operation object instances (original object instance and proxy object instance), resulting in data inconsistency or null pointer problem.
2) narrow the scope of CGLIB proxy classes so that classes that do not need to be proxied will not be proxied.
Narrow the scope of the CGLIB proxy class, so that the class that does not need to be proxied is not proxied, which can not only save memory overhead, but also improve the efficiency of function calls.
7. Public field agent
Step through a hole when fastjson is forced to upgrade to 1.2.60. in order to develop quickly, the author defines it in ParseConfig:
Public class ParseConfig {public final SymbolTable symbolTable = new SymbolTable (4096);. }
We inherited this class in our project and were dynamically proxied by AOP at the same time, so a single line of code caused a "murder".
Problem phenomenon
The example in the previous chapter is still used, but the get and set methods are deleted and a public field is defined. The example code is as follows:
1) UserService.java
/ * * user service class * / @ Service public class UserService {/ * * superuser * / public final User superUser = new User (0L, "admin", "superuser");. }
2) CompanyService.java
/ * * Company service class * / @ Service public class CompanyService {/ * * Company DAO * / @ Autowired private CompanyDAO companyDAO; / * * user service * / @ Autowired private UserService userService / * * Delete company * / public void deleteCompany (Long companyId, Long operatorId) {/ / verify superuser if (! Objects.equals (operatorId, userService.superUser.getId ()) {throw new ExampleException ("only superuser can delete company");} / delete company information companyDAO.delete (companyId, operatorId);}}
When we call CompanyService's deleteCompany method, we actually throw a null pointer exception (NullPointerException). After debugging and printing, it is found that the superUser variable for UserService is null. If the agent is deleted, there will be no null pointer exception, indicating that the problem is caused by the AOP agent.
Analysis of problems
When using the SpringCGLIB proxy class, Spring creates a file called UserService$$EnhancerBySpringCGLIB$$? The proxy class of the. 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 method 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: one is the original UserService object instance, and the other points to the proxy object instance of 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, as soon as the public member variable is obtained through this proxy class object instance, a default value, null, is returned.
Pit avoidance method
1) when it is determined that the field is immutable, it can be defined as a public static constant
When it is determined that the field is immutable, it can be defined as a public static constant and accessed with the class name + field name. The class name + field name accesses the public static constant, regardless of the dynamic proxy of the class instance.
2) when it is determined that the field is immutable, it can be defined as a private member variable
When it is determined that the field is immutable, it can be defined as a private member variable, providing a public Getter method to get the value of the variable. When an instance of this class is dynamically proxied, the proxy method calls the proxied Getter method, which returns the value of the member variable of the proxied class.
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:
The JavaBean class must be a public class with its access property set to public, such as public class User {.}
The JavaBean class must have an empty constructor: there must be a common constructor without arguments in the class
A JavaBean class should not have public instance variables. Class variables are all private, such as private Integer id
Properties should be accessed through a set of getter / setter methods
At this point, I believe you have a deeper understanding of "what are the pitfalls in writing Java?" you might as well do it in practice. Here is the website, more related content can enter the relevant channels to inquire, follow us, continue to learn!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.