那些年,我們踩過的 Java 坑
Photo?@?Drew Farwell?
文? |??常意
1.前言
中國有句老話叫"事不過三",指一個人犯了同樣的錯誤,一次兩次三次還可以原諒,超過三次就不可原諒了。有人指出這個“三”是虛數(shù),用來泛指多次,所以"事不過三"不包括“三”。至于"事不過三"包不包括“三”,可能跟每個人的底線有關(guān)系,屬于哲學范疇,不在本文的討論范圍之內(nèi)。
1.對象比較方法
1.1.問題現(xiàn)象
Short shortValue = (short)12345;
System.out.println(shortValue == 12345); // true
System.out.println(12345 == shortValue); // true
Integer intValue = 12345;
System.out.println(intValue == 12345); // true
System.out.println(12345 == intValue); // true
Long longValue = 12345L;
System.out.println(longValue == 12345); // true
System.out.println(12345 == longValue); // trueShort shortValue = (short)12345;
System.out.println(Objects.equals(shortValue, 12345)); // false
System.out.println(Objects.equals(12345, shortValue)); // false
Integer intValue = 12345;
System.out.println(Objects.equals(intValue, 12345)); // true
System.out.println(Objects.equals(12345, intValue)); // true
Long longValue = 12345L;
System.out.println(Objects.equals(longValue, 12345)); // false
System.out.println(Objects.equals(12345, longValue)); // false1.2.問題分析
7 ? getstatic java.lang.System.out : java.io.PrintStream [22]
10 ?aload_1 [shortValue]
11 ?invokevirtual java.lang.Short.shortValue() : short [28]
14 ?sipush 12345
17 ?if_icmpne 24
20 ?iconst_1
21 ?goto 25
24 ?iconst_0
25 ?invokevirtual java.io.PrintStream.println(boolean) : void [32]7 ? getstatic java.lang.System.out : java.io.PrintStream [22]
10 ?aload_1 [shortValue]
11 ?sipush 12345
14 ?invokestatic java.lang.Integer.valueOf(int) : java.lang.Integer [28]
17 ?invokestatic java.util.Objects.equals(java.lang.Object, java.lang.Object) : boolean [33]
20 ?invokevirtual java.io.PrintStream.println(boolean) : void [39]在 Java 語言中,整數(shù)的默認數(shù)據(jù)類型是 int ,小數(shù)的默認數(shù)據(jù)類型是 double 。
public static boolean equals(Object a, Object b) {
? ?return (a == b) || (a != null && a.equals(b));
}public boolean equals(Object obj) {
? ?if (obj instanceof Short) {
? ? ? ?return value == ((Short)obj).shortValue();
? }
? ?return false;
}1.3.避坑方法
Short shortValue = (short)12345;
System.out.println(shortValue == (short)12345); // true
System.out.println((short)12345 == shortValue); // true
Integer intValue = 12345;
System.out.println(intValue == 12345); // true
System.out.println(12345 == intValue); // true
Long longValue = 12345L;
System.out.println(longValue == 12345L); // true
System.out.println(12345L == longValue); // true第二段代碼可以這樣寫:
Short shortValue = (short)12345;
System.out.println(Objects.equals(shortValue, (short)12345)); // true
System.out.println(Objects.equals((short)12345, shortValue)); // true
Integer intValue = 12345;
System.out.println(Objects.equals(intValue, 12345)); // true
System.out.println(Objects.equals(12345, intValue)); // true
Long longValue = 12345L;
System.out.println(Objects.equals(longValue, 12345L)); // true
System.out.println(Objects.equals(12345L, longValue)); // trueUnlikely argument type for equals(): int seems to be unrelated to Short
Unlikely argument type for equals(): Short seems to be unrelated to int
Unlikely argument type for equals(): int seems to be unrelated to Long
Unlikely argument type for equals(): Long seems to be unrelated to intCall to Short.equals(Integer) in xxx.Xxx.main(String[]) [Scariest(1), High confidence]
Call to Integer.equals(Short) in xxx.Xxx.main(String[]) [Scariest(1), High confidence]
Call to Long.equals(Integer) in xxx.Xxx.main(String[]) [Scariest(1), High confidence]
Call to Integer.equals(Long) in xxx.Xxx.main(String[]) [Scariest(1), High confidence]2.三元表達式拆包
三元表達式是 Java 編碼中的一個固定語法格式:“條件表達式?表達式 1 :表達式 2 ”。三元表達式的邏輯為:“如果條件表達式成立,則執(zhí)行表達式 1 ,否則執(zhí)行表達式 2 ”。
2.1.問題現(xiàn)象
boolean condition = false;
Double value1 = 1.0D;
Double value2 = 2.0D;
Double value3 = null;
Double result = condition ? value1 * value2 : value3; // 拋出空指針異常2.2.問題分析
17 ?iload_1 [condition]
18 ?ifeq 33
21 ?aload_2 [value1]
22 ?invokevirtual java.lang.Double.doubleValue() : double [24]
25 ?aload_3 [value2]
26 ?invokevirtual java.lang.Double.doubleValue() : double [24]
29 ?dmul
30 ?goto 38
33 ?aload 4 [value3]
35 ?invokevirtual java.lang.Double.doubleValue() : double [24]
38 ?invokestatic java.lang.Double.valueOf(double) : java.lang.Double [16]
41 ?astore 5 [result]
43 ?getstatic java.lang.System.out : java.io.PrintStream [28]
46 ?aload 5 [result]
若兩個表達式類型相同,返回值類型為該類型; 若兩個表達式類型不同,但類型不可轉(zhuǎn)換,返回值類型為Object類型; 若兩個表達式類型不同,但類型可以轉(zhuǎn)化,先把包裝數(shù)據(jù)類型轉(zhuǎn)化為基本數(shù)據(jù)類型,然后按照基本數(shù)據(jù)類型的轉(zhuǎn)換規(guī)則(byte
boolean condition = false;
Double value1 = 1.0D;
Double value2 = 2.0D;
Double value3 = null;
Integer value4 = null;
// 返回類型為Double,不拋出空指針異常
Double result1 = condition ? value1 : value3;
// 返回類型為double,會拋出空指針異常
Double result2 = condition ? value1 : value4;
// 返回類型為double,不拋出空指針異常
Double result3 = !condition ? value1 * value2 : value3;
// 返回類型為double,會拋出空指針異常
Double result4 = condition ? value1 * value2 : value3;2.3.避坑方法
boolean condition = false;
Double value1 = 1.0D;
Double value2 = 2.0D;
Double value3 = null;
Double result;
if (condition) {
? ?result = value1 * value2;
} else {
? ?result = value3;
}boolean condition = false;
double value1 = 1.0D;
double value2 = 2.0D;
double value3 = 3.0D;
double result = condition ? value1 * value2 : value3;3.泛型對象賦值
3.1.問題現(xiàn)象
/** 分頁數(shù)據(jù)VO類 */
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class PageDataVO {
? ?/** 總共數(shù)量 */
? ?private Long totalCount;
? ?/** 數(shù)據(jù)列表 */
? ?private List dataList;
}/** 用戶DAO接口 */
@Mapper
public interface UserDAO {
? ?/** 統(tǒng)計用戶數(shù)量 */
? ?public Long countUser(@Param("query") UserQueryVO query);
? ?/** 查詢用戶信息 */
? ?public List queryUser(@Param("query") UserQueryVO query);
}/** 用戶服務類 */
@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);
? }
}/** 用戶控制器類 */
@Controller
@RequestMapping("/user")
public class UserController {
? ?/** 用戶服務 */
? ?@Autowired
? ?private UserService userService;
? ?/** 查詢用戶 */
? ?@ResponseBody
? ?@RequestMapping(value = "/query", method = RequestMethod.POST)
? ?public Result> queryUser(@RequestBody UserQueryVO query) {
? ? ? ?PageDataVO pageData = userService.queryUser(query);
? ? ? ?return ResultBuilder.success(pageData);
? }
}3.2.問題分析
ArrayList list = new ArrayList();ArrayList list = new ArrayList(); // 第一種情況
ArrayList list1 = new ArrayList();
// 第二種情況
ArrayList list2 = new ArrayList(); 3.3.避坑方法
【推薦】集合泛型定義時,在 JDK7 及以上,使用 diamond 語法或全省略。說明:菱形泛型,即 diamond,直接使用<>來指代前邊已經(jīng)指定的類型。正例: // <> diamond 方式
HashMapuserCache = new HashMap<>(16);
// 全省略方式
ArrayListusers = new ArrayList(10);
return new PageDataVO<>(totalCount, dataList);現(xiàn)在,在 Eclipse 的問題窗口中,我們會看到這樣的錯誤:
Cannot infer type arguments for PageDataVO<>4.泛型屬性拷貝
Spring 的 BeanUtils.copyProperties 方法,是一個很好用的屬性拷貝工具方法。
4.1.問題現(xiàn)象
/** 基礎DO類 */
@Getter
@Setter
@ToString
public class BaseDO {
? ?private T id;
? ?private Date gmtCreate;
? ?private Date gmtModified;
}/** 用戶DO類 */
@Getter
@Setter
@ToString
public class UserDO extends BaseDO{
? ?private String name;
? ?private String description;
}/** 用戶VO類 */
@Getter
@Setter
@ToString
public static class UserVO {
? ?private Long id;
? ?private String name;
? ?private String description;
}/** 用戶服務類 */
@Service
public class UserService {
? ?/** 用戶DAO */
? ?@Autowired
? ?private UserDAO userDAO;
? ?/** 查詢用戶 */
? ?public List queryUser(UserQueryVO query) {
? ? ? ?// 查詢用戶信息
? ? ? ?List userDOList = userDAO.queryUser(query);
? ? ? ?if (CollectionUtils.isEmpty()) {
? ? ? ? ? ?return Collections.emptyList();
? ? ? }
? ? ? ?// 轉(zhuǎn)化用戶列表
? ? ? ?List userVOList = new ArrayList<>(userDOList.size());
? ? ? ?for (UserDO userDO : userDOList) {
? ? ? ? ? ?UserVO userVO = new UserVO();
? ? ? ? ? ?BeanUtils.copyProperties(userDO, userVO);
? ? ? ? ? ?userVOList.add(userVO);
? ? ? }
? ? ? ?// 返回用戶列表
? ? ? ?return userVOList;
? }
}[{"description":"This is a tester.","name":"tester"},...]4.2.問題分析
for (UserDO userDO : userDOList) {
? ?UserVO userVO = new UserVO();
? ?userVO.setId(userDO.getId());
? ?userVO.setName(userDO.getName());
? ?userVO.setDescription(userDO.getDescription());
? ?userVOList.add(userVO);
}Object value = readMethod.invoke(source);
if (Objects.nonNull(value) && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], value.getClass())) {
? ... // 賦值相關(guān)代碼
}4.3.避坑方法
5.Set對象排重
5.1.問題現(xiàn)象
/** 城市輔助類 */
@Slf4j
public class CityHelper {
? ?/** 測試主方法 */
? ?public static void main(String[] args) {
? ? ? ?Collection cityCollection = readCities2("cities.csv");
? ? ? ?log.info(JSON.toJSONString(cityCollection));
? }
/** 讀取城市 */
? ?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("讀取所有城市異常", 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 String code;
? ? ? ?/** 城市名稱 */
? ? ? ?private String name;
? }
}編碼,名稱
010,北京
020,廣州
010,北京[{"code":"010","name":"北京"},{"code":"020","name":"廣州"},{"code":"010","name":"北京"}]但是,并沒有對城市“北京”進行排重。
5.2.問題分析
當向集合 Set 中增加對象時,首先集合計算要增加對象的 hashCode ,根據(jù)該值來得到一個位置用來存放當前對象。如在該位置沒有一個對象存在的話,那么集合 Set 認為該對象在集合中不存在,直接增加進去。如果在該位置有一個對象存在的話,接著將準備增加到集合中的對象與該位置上的對象進行 equals 方法比較:如果該 equals 方法返回 false ,那么集合認為集合中不存在該對象,就把該對象放在這個對象之后;如果 equals 方法返回 true ,那么就認為集合中已經(jīng)存在該對象了,就不會再將該對象增加到集合中了。所以,在哈希表中判斷兩個元素是否重復要使用到 hashCode 方法和 equals 方法。hashCode 方法決定數(shù)據(jù)在表中的存儲位置,而 equals 方法判斷表中是否存在相同的數(shù)據(jù)。
分析上面的問題,由于沒有重寫 City 類的 hashCode 方法和 equals 方法,就會采用 Object 類的 hashCode 方法和 equals 方法。其實現(xiàn)如下:
public native int hashCode();
public boolean equals(Object obj) {
? ?return (this == obj);
}/** 城市類 */
@Getter
@Setter
@ToString
private static class City {
? ?/** 城市編碼 */
? ?private String code;
? ?/** 城市名稱 */
? ?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);
? }
}[{"code":"010","name":"北京"},{"code":"020","name":"廣州"}]5.3.避坑方法
List citySet = new ArrayList<>(1024);
Iterator iterator = parser.iterator();
while (iterator.hasNext()) {
? ?citySet.add(parseCity(iterator.next()));
}
return citySet;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();6.公有方法代理
6.1.問題現(xiàn)象
/** 用戶服務類 */
@Service
public class UserService {
? ?/** 超級用戶 */
? ?private User superUser;
/** 設置超級用戶 */
? ?public void setSuperUser(User superUser) {
? ? ? ?this.superUser = superUser;
? }
? ?/** 獲取超級用戶 */
? ?public final User getSuperUser() {
? ? ? ?return this.superUser;
? }
}/** 公司服務類 */
@Service
public class CompanyService {
? ?/** 公司DAO */
? ?@Autowired
? ?private CompanyDAO companyDAO;
? ?/** 用戶服務 */
? ?@Autowired
? ?private UserService userService;
? ?/** 刪除公司 */
? ?public void deleteCompany(Long companyId, Long operatorId) {
? ? ? ?// 設置超級用戶
? ? ? ?userService.setSuperUser(new User(0L, "admin", "超級用戶"));
? ? ? ?// 驗證超級用戶
? ? ? ?if (!Objects.equals(operatorId, userService.getSuperUser().getId())) {
? ? ? ? ? ?throw new ExampleException("只有超級用戶才能刪除公司");
? ? ? }
? ? ? ?// 刪除公司信息
? ? ? ?companyDAO.delete(companyId, operatorId);
? }
}/** AOP配置類 */
@Slf4j
@Aspect
@Configuration
public class AopConfiguration {
? ?/** 環(huán)繞方法 */
? ?@Around("execution(* org.changyi.springboot.service..*.*(..))")
? ?public Object around(ProceedingJoinPoint joinPoint) {
? ? ? ?try {
? ? ? ? ? ?log.info("開始調(diào)用服務方法...");
? ? ? ? ? ?return joinPoint.proceed();
? ? ? } catch (Throwable e) {
? ? ? ? ? ?log.error(e.getMessage(), e);
? ? ? ? ? ?throw new ExampleException(e.getMessage(), e);
? ? ? }
? }
}6.2.問題分析
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);
? ? ? }
? }
? ......
}6.3.避坑方法
7.公有字段代理
public class ParseConfig {
? ?public final SymbolTable symbolTable = new SymbolTable(4096);
? ......
}7.1.問題現(xiàn)象
/** 用戶服務類 */
@Service
public class UserService {
? ?/** 超級用戶 */
? ?public final User superUser = new User(0L, "admin", "超級用戶");
? ......
}/** 公司服務類 */
@Service
public class CompanyService {
? ?/** 公司DAO */
? ?@Autowired
? ?private CompanyDAO companyDAO;
? ?/** 用戶服務 */
? ?@Autowired
? ?private UserService userService;
? ?/** 刪除公司 */
? ?public void deleteCompany(Long companyId, Long operatorId) {
? ? ? ?// 驗證超級用戶
? ? ? ?if (!Objects.equals(operatorId, userService.superUser.getId())) {
? ? ? ? ? ?throw new ExampleException("只有超級用戶才能刪除公司");
? ? ? }
? ? ? ?// 刪除公司信息
? ? ? ?companyDAO.delete(companyId, operatorId);
? }
}7.2.問題分析
7.3.避坑方法
/** 用戶服務類 */
@Service
public class UserService {
? ?/** 超級用戶 */
? ?public static final User SUPER_USER = new User(0L, "admin", "超級用戶");
? ......
}
/** 使用代碼 */
if (!Objects.equals(operatorId, UserService.SUPER_USER.getId())) {
? ?throw new ExampleException("只有超級用戶才能刪除公司");
}/** 用戶服務類 */
@Service
public class UserService {
? ?/** 超級用戶 */
? ?private User superUser = new User(0L, "admin", "超級用戶");
? ?/** 獲取超級用戶 */
? ?public User getSuperUser() {
? ? ? ?return this.superUser;
? }
? ......
}
/** 使用代碼 */
if (!Objects.equals(operatorId, userService.getSuperUser().getId())) {
? ?throw new ExampleException("只有超級用戶才能刪除公司");
}(1)JavaBean類必須是一個公共類,并將其訪問屬性設置為public,如:public class User{......}(2)JavaBean類必須有一個空的構(gòu)造函數(shù):類中必須有一個不帶參數(shù)的公用構(gòu)造器(3)一個JavaBean類不應有公共實例變量,類變量都為private,如:private Integer id;(4)屬性應該通過一組getter/setter方法來訪問。
后記
?寫在最后:
前線推出學習交流群和大廠面試真題,獲取和加群一定要備注: ?最后給讀者整理了一份大廠面試真題,需要的可掃碼微信加我備注:“大廠面試”獲取。
END 前線推出學習交流群,加群一定要備注:
研究/工作方向+地點+學校/公司+昵稱(如java+上海+上交+可可) 根據(jù)格式備注,可更快被通過且邀請進群,領(lǐng)取一份專屬學習禮包 掃碼加我微信進群 大廠內(nèi)推和技術(shù)交流,和前輩大佬們零距離 歷史推薦
10個實用但非常偏執(zhí)的Java編程技巧 為什么大公司一定要使用微服務? Java 程序員必須掌握的 10 款開源工具! Java程序員必備的11大Intellij插件 點個在看吧



