1.常量&變量
1.1.直接賦值常量值分尸,禁止聲明新對(duì)象
直接賦值常量值,只是創(chuàng)建了一個(gè)對(duì)象引用歹嘹,而這個(gè)對(duì)象引用指向常量值箩绍。
反例:
Long i = new Long(1L);String s = new String("abc");
正例:
Longi = 1L;Strings ="abc";
1.2.當(dāng)成員變量值無需改變時(shí),盡量定義為靜態(tài)常量
在類的每個(gè)對(duì)象實(shí)例中尺上,每個(gè)成員變量都有一份副本材蛛,而成員靜態(tài)常量只有一份實(shí)例。
反例:
public class HttpConnection {? ? private final long timeout = 5L;? ? ...}
正例:
public class HttpConnection {? ? private static final long TIMEOUT = 5L;? ? ...}
1.3.盡量使用基本數(shù)據(jù)類型怎抛,避免自動(dòng)裝箱和拆箱
Java 中的基本數(shù)據(jù)類型double卑吭、float、long马绝、int豆赏、short、char富稻、boolean掷邦,分別對(duì)應(yīng)包裝類Double、Float椭赋、Long抚岗、Integer、Short哪怔、Character宣蔚、Boolean。JVM支持基本類型與對(duì)應(yīng)包裝類的自動(dòng)轉(zhuǎn)換认境,被稱為自動(dòng)裝箱和拆箱胚委。裝箱和拆箱都是需要CPU和內(nèi)存資源的,所以應(yīng)盡量避免使用自動(dòng)裝箱和拆箱叉信。
反例:
Integer sum =0;int[] values = ...;for(intvalue: values) {sum +=value;// 相當(dāng)于result = Integer.valueOf(result.intValue() + value);}
正例:
int sum = 0;int[] values = ...;for (int value : values) {? ? sum += value;}
1.4.如果變量的初值會(huì)被覆蓋亩冬,就沒有必要給變量賦初值
反例:
List<UserDO> userList = new ArrayList<>();if (isAll) {? ? userList = userDAO.queryAll();} else {? ? userList = userDAO.queryActive();}
正例:
List<UserDO> userList;if (isAll) {? ? userList = userDAO.queryAll();} else {? ? userList = userDAO.queryActive();}
1.5.盡量使用函數(shù)內(nèi)的基本類型臨時(shí)變量
在函數(shù)內(nèi),基本類型的參數(shù)和臨時(shí)變量都保存在棧(Stack)中茉盏,訪問速度較快鉴未;對(duì)象類型的參數(shù)和臨時(shí)變量的引用都保存在棧(Stack)中,內(nèi)容都保存在堆(Heap)中鸠姨,訪問速度較慢。在類中淹真,任何類型的成員變量都保存在堆(Heap)中讶迁,訪問速度較慢。
反例:
public final class Accumulator {? ? private double result = 0.0D;? ? public void addAll(@NonNull double[] values) {? ? ? ? for(double value : values) {? ? ? ? ? ? result += value;? ? ? ? }? ? }? ? ...}
正例:
public final class Accumulator {? ? private double result = 0.0D;? ? public void addAll(@NonNull double[] values) {? ? ? ? double sum = 0.0D;? ? ? ? for(double value : values) {? ? ? ? ? ? sum += value;? ? ? ? }? ? ? ? result += sum;? ? }? ? ...}
1.6.盡量不要在循環(huán)體外定義變量
在老版JDK中核蘸,建議“盡量不要在循環(huán)體內(nèi)定義變量”巍糯,但是在新版的JDK中已經(jīng)做了優(yōu)化啸驯。通過對(duì)編譯后的字節(jié)碼分析舰蟆,變量定義在循環(huán)體外和循環(huán)體內(nèi)沒有本質(zhì)的區(qū)別漩蟆,運(yùn)行效率基本上是一樣的。
反而苍日,根據(jù)“ 局部變量作用域最小化 ”原則宅楞,變量定義在循環(huán)體內(nèi)更科學(xué)更便于維護(hù)针姿,避免了延長(zhǎng)大對(duì)象生命周期導(dǎo)致延緩回收問題 。
反例:
UserVO userVO;List<UserDO> userDOList = ...;List<UserVO> userVOList = new ArrayList<>(userDOList.size());for (UserDO userDO : userDOList) {? ? userVO = new UserVO();? ? userVO.setId(userDO.getId());? ? ...? ? userVOList.add(userVO);}
正例:
List<UserDO> userDOList = ...;List<UserVO> userVOList = new ArrayList<>(userDOList.size());for (UserDO userDO : userDOList) {? ? UserVO userVO = new UserVO();? ? userVO.setId(userDO.getId());? ? ...? ? userVOList.add(userVO);}
1.7.不可變的靜態(tài)常量厌衙,盡量使用非線程安全類
不可變的靜態(tài)常量距淫,雖然需要支持多線程訪問,也可以使用非線程安全類婶希。
反例:
public static final Map<String, Class> CLASS_MAP;static {? ? Map<String, Class> classMap = new ConcurrentHashMap<>(16);? ? classMap.put("VARCHAR", java.lang.String.class);? ? ...? ? CLASS_MAP = Collections.unmodifiableMap(classMap);}
正例:
public static final Map<String, Class> CLASS_MAP;static {? ? Map<String, Class> classMap = new HashMap<>(16);? ? classMap.put("VARCHAR", java.lang.String.class);? ? ...? ? CLASS_MAP = Collections.unmodifiableMap(classMap);}
1.8.不可變的成員變量榕暇,盡量使用非線程安全類
不可變的成員變量,雖然需要支持多線程訪問喻杈,也可以使用非線程安全類彤枢。
反例:
@Servicepublic class StrategyFactory implements InitializingBean {? ? @Autowired? ? private List<Strategy> strategyList;? ? private Map<String, Strategy> strategyMap;? ? @Override? ? public void afterPropertiesSet() {? ? ? ? if (CollectionUtils.isNotEmpty(strategyList)) {? ? ? ? ? ? int size = (int) Math.ceil(strategyList.size() * 4.0 / 3);? ? ? ? ? ? Map<String, Strategy> map = new ConcurrentHashMap<>(size);? ? ? ? ? ? for (Strategy strategy : strategyList) {? ? ? ? ? ? ? ? map.put(strategy.getType(), strategy);? ? ? ? ? ? }? ? ? ? ? ? strategyMap = Collections.unmodifiableMap(map);? ? ? ? }? ? }? ? ...}
正例:
@Servicepublic class StrategyFactory implements InitializingBean {? ? @Autowired? ? private List<Strategy> strategyList;? ? private Map<String, Strategy> strategyMap;? ? @Override? ? public void afterPropertiesSet() {? ? ? ? if (CollectionUtils.isNotEmpty(strategyList)) {? ? ? ? ? ? int size = (int) Math.ceil(strategyList.size() * 4.0 / 3);? ? ? ? ? ? Map<String, Strategy> map = new HashMap<>(size);? ? ? ? ? ? for (Strategy strategy : strategyList) {? ? ? ? ? ? ? ? map.put(strategy.getType(), strategy);? ? ? ? ? ? }? ? ? ? ? ? strategyMap = Collections.unmodifiableMap(map);? ? ? ? }? ? }? ? ...
2.對(duì)象&類
2.1.禁止使用JSON轉(zhuǎn)化對(duì)象
JSON提供把對(duì)象轉(zhuǎn)化為JSON字符串、把JSON字符串轉(zhuǎn)為對(duì)象的功能筒饰,于是被某些人用來轉(zhuǎn)化對(duì)象堂污。這種對(duì)象轉(zhuǎn)化方式,雖然在功能上沒有問題龄砰,但是在性能上卻存在問題盟猖。
反例:
List<UserDO> userDOList = ...;List<UserVO> userVOList = JSON.parseArray(JSON.toJSONString(userDOList), UserVO.class);
正例:
List<UserDO> userDOList = ...;List<UserVO> userVOList = new ArrayList<>(userDOList.size());for (UserDO userDO : userDOList) {? ? UserVO userVO = new UserVO();? ? userVO.setId(userDO.getId());? ? ...? ? userVOList.add(userVO);}
2.2.盡量不使用反射賦值對(duì)象
用反射賦值對(duì)象,主要優(yōu)點(diǎn)是節(jié)省了代碼量换棚,主要缺點(diǎn)卻是性能有所下降式镐。
反例:
List<UserDO> userDOList = ...;List<UserVO> userVOList = new ArrayList<>(userDOList.size());for (UserDO userDO : userDOList) {? ? UserVO userVO = new UserVO();? ? BeanUtils.copyProperties(userDO, userVO);? ? userVOList.add(userVO);}
正例:
List<UserDO> userDOList = ...;List<UserVO> userVOList = new ArrayList<>(userDOList.size());for (UserDO userDO : userDOList) {? ? UserVO userVO = new UserVO();? ? userVO.setId(userDO.getId());? ? ...? ? userVOList.add(userVO);}
2.3.采用Lambda表達(dá)式替換內(nèi)部匿名類
對(duì)于大多數(shù)剛接觸JDK8的同學(xué)來說,都會(huì)認(rèn)為L(zhǎng)ambda表達(dá)式就是匿名內(nèi)部類的語法糖固蚤。實(shí)際上娘汞, Lambda表達(dá)式在大多數(shù)虛擬機(jī)中采用invokeDynamic指令實(shí)現(xiàn),相對(duì)于匿名內(nèi)部類在效率上會(huì)更高一些夕玩。
反例:
List<User> userList = ...;Collections.sort(userList, new Comparator<User>() {? ? @Override? ? public int compare(User user1, User user2) {? ? ? ? Long userId1 = user1.getId();? ? ? ? Long userId2 = user2.getId();? ? ? ? ...? ? ? ? return userId1.compareTo(userId2);? ? }});
正例:
List<User> userList = ...;Collections.sort(userList, (user1, user2) -> {? ? Long userId1 = user1.getId();? ? Long userId2 = user2.getId();? ? ...? ? return userId1.compareTo(userId2);});
2.4.盡量避免定義不必要的子類
多一個(gè)類就需要多一份類加載你弦,所以盡量避免定義不必要的子類。
反例:
public static final Map<String, Class> CLASS_MAP =? ? Collections.unmodifiableMap(new HashMap<String, Class>(16) {? ? private static final long serialVersionUID = 1L;? ? {? ? ? ? put("VARCHAR", java.lang.String.class);? ? }});
正例:
public static final Map<String, Class> CLASS_MAP;static {? ? Map<String, Class> classMap = new HashMap<>(16);? ? classMap.put("VARCHAR", java.lang.String.class);? ? ...? ? CLASS_MAP = Collections.unmodifiableMap(classMap);}
2.5.盡量指定類的final修飾符
為類指定final修飾符燎孟,可以讓該類不可以被繼承禽作。如果指定了一個(gè)類為final,則該類所有的方法都是final的揩页,Java編譯器會(huì)尋找機(jī)會(huì)內(nèi)聯(lián)所有的final方法旷偿。內(nèi)聯(lián)對(duì)于提升Java運(yùn)行效率作用重大,具體可參見Java運(yùn)行期優(yōu)化,能夠使性能平均提高50%萍程。
反例:
public class DateHelper {? ? ...}
正例:
public final class DateHelper {? ? ...}
注意:使用Spring的AOP特性時(shí)幢妄,需要對(duì)Bean進(jìn)行動(dòng)態(tài)代理,如果Bean類添加了final修飾茫负,會(huì)導(dǎo)致異常蕉鸳。
3.方法
3.1.把跟類成員變量無關(guān)的方法聲明成靜態(tài)方法
靜態(tài)方法的好處就是不用生成類的實(shí)例就可以直接調(diào)用。靜態(tài)方法不再屬于某個(gè)對(duì)象忍法,而是屬于它所在的類潮尝。只需要通過其類名就可以訪問,不需要再消耗資源去反復(fù)創(chuàng)建對(duì)象缔赠。即便在類內(nèi)部的私有方法衍锚,如果沒有使用到類成員變量,也應(yīng)該聲明為靜態(tài)方法嗤堰。
反例:
public int getMonth(Date date) {? Calendar calendar = Calendar.getInstance();? calendar.setTime(date);? return calendar.get(Calendar.MONTH) + 1;}
正例:
public static int getMonth(Date date) {? Calendar calendar = Calendar.getInstance();? calendar.setTime(date);? return calendar.get(Calendar.MONTH) + 1;}
3.2.盡量使用基本數(shù)據(jù)類型作為方法參數(shù)類型戴质,避免不必要的裝箱、拆箱和空指針判斷
反例:
public static double sum(Double value1, Double value2) {? ? double double1 = Objects.isNull(value1) ? 0.0D : value1;? ? double double2 = Objects.isNull(value2) ? 0.0D : value2;? ? return double1 + double2;}double result = sum(1.0D, 2.0D);
正例:
public static double sum(double value1, double value2) {? ? return value1 + value2;}double result = sum(1.0D, 2.0D);
3.3.盡量使用基本數(shù)據(jù)類型作為方法返回值類型踢匣,避免不必要的裝箱告匠、拆箱和空指針判斷
在JDK類庫的方法中,很多方法返回值都采用了基本數(shù)據(jù)類型离唬,首先是為了避免不必要的裝箱和拆箱后专,其次是為了避免返回值的空指針判斷。比如:Collection.isEmpty()和Map.size()输莺。
反例:
public static Boolean isValid(UserDO user) {? ? if (Objects.isNull(user)) {? ? ? ? return false;? ? }? return Boolean.TRUE.equals(user.getIsValid());}// 調(diào)用代碼UserDO user = ...;Boolean isValid = isValid(user);if (Objects.nonNull(isValid) && isValid.booleanValue()) {? ? ...}
正例:
public static boolean isValid(UserDO user) {? ? if (Objects.isNull(user)) {? ? ? ? return false;? ? }? return Boolean.TRUE.equals(user.getIsValid());}// 調(diào)用代碼UserDO user = ...;if (isValid(user)) {? ? ...}
3.4.協(xié)議方法參數(shù)值非空戚哎,避免不必要的空指針判斷
協(xié)議編程,可以@NonNull和@Nullable標(biāo)注參數(shù)嫂用,是否遵循全憑調(diào)用者自覺型凳。
反例:
public static boolean isValid(UserDO user) {? ? if (Objects.isNull(user)) {? ? ? ? return false;? ? }? return Boolean.TRUE.equals(user.getIsValid());}
正例:
public static boolean isValid(@NonNull UserDO user) {? return Boolean.TRUE.equals(user.getIsValid());}
3.5.協(xié)議方法返回值非空,避免不必要的空指針判斷
協(xié)議編程嘱函,可以@NonNull和@Nullable標(biāo)注參數(shù)甘畅,是否遵循全憑實(shí)現(xiàn)者自覺。
反例:
// 定義接口public interface OrderService {? ? public List<OrderVO> queryUserOrder(Long userId);}// 調(diào)用代碼List<OrderVO> orderList = orderService.queryUserOrder(userId);if (CollectionUtils.isNotEmpty(orderList)) {? ? for (OrderVO order : orderList) {? ? ? ? ...? ? }}
正例:
// 定義接口public interface OrderService {? ? @NonNull? ? public List<OrderVO> queryUserOrder(Long userId);}// 調(diào)用代碼List<OrderVO> orderList = orderService.queryUserOrder(userId);for (OrderVO order : orderList) {? ? ...}
3.6.被調(diào)用方法已支持判空處理往弓,調(diào)用方法無需再進(jìn)行判空處理
反例:
UserDO user = null;if (StringUtils.isNotBlank(value)) {? ? user = JSON.parseObject(value, UserDO.class);}
正例:
UserDO user =JSON.parseObject(value, UserDO.class);
3.7.盡量避免不必要的函數(shù)封裝
方法調(diào)用會(huì)引起入棧和出棧疏唾,導(dǎo)致消耗更多的CPU和內(nèi)存,應(yīng)當(dāng)盡量避免不必要的函數(shù)封裝函似。當(dāng)然槐脏,為了使代碼更簡(jiǎn)潔、更清晰缴淋、更易維護(hù)准给,增加一定的方法調(diào)用所帶來的性能損耗是值得的泄朴。
反例:
// 函數(shù)封裝public static boolean isVip(Boolean isVip) {? ? return Boolean.TRUE.equals(isVip);}// 使用代碼boolean isVip = isVip(user.getVip());
正例:
booleanisVip =Boolean.TRUE.equals(user.getVip());
3.8.盡量指定方法的final修飾符
方法指定final修飾符重抖,可以讓方法不可以被重寫露氮,Java編譯器會(huì)尋找機(jī)會(huì)內(nèi)聯(lián)所有的final方法。內(nèi)聯(lián)對(duì)于提升Java運(yùn)行效率作用重大钟沛,具體可參見Java運(yùn)行期優(yōu)化畔规,能夠使性能平均提高50%。
注意:所有的private方法會(huì)隱式地被指定final修飾符恨统,所以無須再為其指定final修飾符叁扫。
反例:
public class Rectangle {? ? ...? ? public double area() {? ? ? ? ...? ? }}
正例:
public class Rectangle {? ? ...? ? public final double area() {? ? ? ? ...? ? }}
注意:使用Spring的AOP特性時(shí),需要對(duì)Bean進(jìn)行動(dòng)態(tài)代理畜埋,如果方法添加了final修飾莫绣,將不會(huì)被代理。
4.表達(dá)式
4.1.盡量減少方法的重復(fù)調(diào)用
反例:
List<UserDO> userList = ...;for (int i = 0; i < userList.size(); i++) {? ? ...}
正例:
List<UserDO> userList = ...;int userLength = userList.size();for (int i = 0; i < userLength; i++) {? ? ...}
4.2.盡量避免不必要的方法調(diào)用
反例:
List<UserDO> userList = userDAO.queryActive();if (isAll) {? ? userList = userDAO.queryAll();}
正例:
List userList;if(isAll) {? ? userList = userDAO.queryAll();}else{? ? userList = userDAO.queryActive();}
4.3.盡量使用移位來代替正整數(shù)乘除
用移位操作可以極大地提高性能悠鞍。對(duì)于乘除2^n(n為正整數(shù))的正整數(shù)計(jì)算对室,可以用移位操作來代替。
反例:
int num1 = a * 4;int num2 = a / 4;
正例:
int num1 = a << 2;int num2 = a >> 2;
4.4.提取公共表達(dá)式咖祭,避免重復(fù)計(jì)算
提取公共表達(dá)式掩宜,只計(jì)算一次值,然后重復(fù)利用值么翰。
反例:
double distance = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
正例:
doubledx = x2 - x1;doubledy = y2 - y1;doubledistance = Math.sqrt(dx * dx + dy * dy);或doubledistance = Math.sqrt(Math.pow(x2 - x1,2) + Math.pow(y2 - y1,2));
4.5.盡量不在條件表達(dá)式中用!取反
使用!取反會(huì)多一次計(jì)算牺汤,如果沒有必要?jiǎng)t優(yōu)化掉。
反例:
if(!(a >=10)) {...// 條件處理1}else{...// 條件處理2}
正例:
if(a <10) {...// 條件處理1}else{...// 條件處理2}
4.6.對(duì)于多常量選擇分支浩嫌,盡量使用switch語句而不是if-else語句
if-else語句檐迟,每個(gè)if條件語句都要加裝計(jì)算,直到if條件語句為true為止码耐。switch語句進(jìn)行了跳轉(zhuǎn)優(yōu)化追迟,Java中采用tableswitch或lookupswitch指令實(shí)現(xiàn),對(duì)于多常量選擇分支處理效率更高伐坏。經(jīng)過試驗(yàn)證明:在每個(gè)分支出現(xiàn)概率相同的情況下怔匣,低于5個(gè)分支時(shí)if-else語句效率更高,高于5個(gè)分支時(shí)switch語句效率更高桦沉。
反例:
if (i == 1) {? ? ...; // 分支1} else if (i == 2) {? ? ...; // 分支2} else if (i == ...) {? ? ...; // 分支n} else {? ? ...; // 分支n+1}
正例:
switch (i) {? ? case 1 :? ? ? ? ... // 分支1? ? ? ? break;? ? case 2 :? ? ? ? ... // 分支2? ? ? ? break;? ? case ... :? ? ? ? ... // 分支n? ? ? ? break;? ? default :? ? ? ? ... // 分支n+1? ? ? ? break;}
備注:如果業(yè)務(wù)復(fù)雜每瞒,可以采用Map實(shí)現(xiàn)策略模式。
5.字符串
5.1.盡量不要使用正則表達(dá)式匹配
正則表達(dá)式匹配效率較低纯露,盡量使用字符串匹配操作剿骨。
反例:
String source = "a::1,b::2,c::3,d::4";String target = source.replaceAll("::", "=");Stringp[] targets = source.spit("::");
正例:
String source = "a::1,b::2,c::3,d::4";String target = source.replace("::", "=");Stringp[] targets = StringUtils.split(source, "::");
5.2.盡量使用字符替換字符串
字符串的長(zhǎng)度不確定,而字符的長(zhǎng)度固定為1埠褪,查找和匹配的效率自然提高了浓利。
反例:
String source = "a:1,b:2,c:3,d:4";int index = source.indexOf(":");String target = source.replace(":", "=");
正例:
String source = "a:1,b:2,c:3,d:4";int index = source.indexOf(':');String target = source.replace(':', '=');
5.3.盡量使用StringBuilder進(jìn)行字符串拼接
String是final類挤庇,內(nèi)容不可修改,所以每次字符串拼接都會(huì)生成一個(gè)新對(duì)象贷掖。StringBuilder在初始化時(shí)申請(qǐng)了一塊內(nèi)存嫡秕,以后的字符串拼接都在這塊內(nèi)存中執(zhí)行,不會(huì)申請(qǐng)新內(nèi)存和生成新對(duì)象苹威。
反例:
String s = "";for (int i = 0; i < 10; i++) {? ? if (i != 0) {? ? ? ? s += ',';? ? }? ? s += i;}
正例:
StringBuilder sb =newStringBuilder(128);for(inti =0; i <10; i++) {if(i !=0) {sb.append(',');? ? }sb.append(i);}
5.4.不要使用""+轉(zhuǎn)化字符串
使用""+進(jìn)行字符串轉(zhuǎn)化昆咽,使用方便但是效率低,建議使用String.valueOf.
反例:
int i = 12345;String s = "" + i;
正例:
int i =12345;Strings =String.valueOf(i);
6.數(shù)組
6.1.不要使用循環(huán)拷貝數(shù)組牙甫,盡量使用System.arraycopy拷貝數(shù)組
推薦使用System.arraycopy拷貝數(shù)組掷酗,也可以使用Arrays.copyOf拷貝數(shù)組。
反例:
int[] sources = new int[] {1, 2, 3, 4, 5};int[] targets = new int[sources.length];for (int i = 0; i < targets.length; i++) {? ? targets[i] = sources[i];}
正例:
int[] sources = new int[] {1, 2, 3, 4, 5};int[] targets = new int[sources.length];System.arraycopy(sources, 0, targets, 0, targets.length);
6.2.集合轉(zhuǎn)化為類型T數(shù)組時(shí)窟哺,盡量傳入空數(shù)組T[0]
將集合轉(zhuǎn)換為數(shù)組有2種形式:toArray(new T[n])和toArray(new T[0])泻轰。在舊的Java版本中,建議使用toArray(new T[n])且轨,因?yàn)閯?chuàng)建數(shù)組時(shí)所需的反射調(diào)用非常慢浮声。在OpenJDK6后,反射調(diào)用是內(nèi)在的殖告,使得性能得以提高阿蝶,toArray(new T[0])比toArray(new T[n])效率更高。此外黄绩,toArray(new T[n])比toArray(new T[0])多獲取一次列表大小羡洁,如果計(jì)算列表大小耗時(shí)過長(zhǎng),也會(huì)導(dǎo)致toArray(new T[n])效率降低爽丹。
反例:
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5, ...);Integer[] integers = integerList.toArray(new Integer[integerList.size()]);
正例:
List integerList = Arrays.asList(1,2,3,4,5, ...);Integer[] integers = integerList.toArray(newInteger[0]);// 勿用new Integer[]{}
建議:集合應(yīng)該提供一個(gè)toArray(Class<T> clazz)方法筑煮,避免無用的空數(shù)組初始化(new T[0])。
6.3.集合轉(zhuǎn)化為Object數(shù)組時(shí)粤蝎,盡量使用toArray()方法
轉(zhuǎn)化Object數(shù)組時(shí)真仲,沒有必要使用toArray[new Object[0]],可以直接使用toArray()初澎。避免了類型的判斷秸应,也避免了空數(shù)組的申請(qǐng),所以效率會(huì)更高碑宴。
反例:
List<Object> objectList = Arrays.asList(1, "2", 3, "4", 5, ...);Object[] objects = objectList.toArray(new Object[0]);
正例:
List objectList = Arrays.asList(1,"2",3,"4",5, ...);Object[] objects = objectList.toArray();
7.集合
7.1.初始化集合時(shí)软啼,盡量指定集合大小
Java集合初始化時(shí)都會(huì)指定一個(gè)默認(rèn)大小,當(dāng)默認(rèn)大小不再滿足數(shù)據(jù)需求時(shí)就會(huì)擴(kuò)容延柠,每次擴(kuò)容的時(shí)間復(fù)雜度有可能是O(n)祸挪。所以,盡量指定預(yù)知的集合大小贞间,就能避免或減少集合的擴(kuò)容次數(shù)贿条。
反例:
List<UserDO> userDOList = ...;Set<Long> userSet = new HashSet<>();Map<Long, UserDO> userMap = new HashMap<>();List<UserVO> userList = new ArrayList<>();for (UserDO userDO : userDOList) {? ? userSet.add(userDO.getId());? ? userMap.put(userDO.getId(), userDO);? ? userList.add(transUser(userDO));}
正例:
List<UserDO> userDOList = ...;int userSize = userDOList.size();Set<Long> userSet = new HashSet<>(userSize);Map<Long, UserDO> userMap = new HashMap<>((int) Math.ceil(userSize * 4.0 / 3));List<UserVO> userList = new ArrayList<>(userSize);for (UserDO userDO : userDOList) {? ? userSet.add(userDO.getId());? ? userMap.put(userDO.getId(), userDO);? ? userList.add(transUser(userDO));}
7.2.不要使用循環(huán)拷貝集合雹仿,盡量使用JDK提供的方法拷貝集合
JDK提供的方法可以一步指定集合的容量,避免多次擴(kuò)容浪費(fèi)時(shí)間和空間整以。同時(shí)胧辽,這些方法的底層也是調(diào)用System.arraycopy方法實(shí)現(xiàn),進(jìn)行數(shù)據(jù)的批量拷貝效率更高悄蕾。
反例:
List<UserDO> user1List = ...;List<UserDO> user2List = ...;List<UserDO> userList = new ArrayList<>(user1List.size() + user2List.size());for (UserDO user1 : user1List) {? ? userList.add(user1);}for (UserDO user2 : user2List) {? ? userList.add(user2);}
正例:
Listuser1List = ...;Listuser2List = ...;ListuserList = new ArrayList<>(user1List.size() + user2List.size());userList.addAll(user1List);userList.addAll(user2List);
7.3.盡量使用Arrays.asList轉(zhuǎn)化數(shù)組為列表
原理與"不要使用循環(huán)拷貝集合票顾,盡量使用JDK提供的方法拷貝集合"類似础浮。
反例:
List<String> typeList = new ArrayList<>(8);typeList.add("Short");typeList.add("Integer");typeList.add("Long");String[] names = ...;List<String> nameList = ...;for (String name : names) {? ? nameList.add(name);}
正例:
List typeList = Arrays.asList("Short","Integer","Long");String[] names = ...;List nameList = ...;nameList.addAll(Arrays.asList(names));
7.4.直接迭代需要使用的集合
直接迭代需要使用的集合帆调,無需通過其它操作獲取數(shù)據(jù)。
反例:
Map<Long, UserDO> userMap = ...;for (Long userId : userMap.keySet()) {? ? UserDO user = userMap.get(userId);? ? ...}
正例:
Map userMap = ...;for(Map.Entry userEntry : userMap.entrySet()) {LonguserId = userEntry.getKey();? ? UserDO user = userEntry.getValue();? ? ...}
7.5.不要使用size方法檢測(cè)空豆同,必須使用isEmpty方法檢測(cè)空
使用size方法來檢測(cè)空邏輯上沒有問題番刊,但使用isEmpty方法使得代碼更易讀,并且可以獲得更好的性能影锈。任何isEmpty方法實(shí)現(xiàn)的時(shí)間復(fù)雜度都是O(1)芹务,但是某些size方法實(shí)現(xiàn)的時(shí)間復(fù)雜度有可能是O(n)。
反例:
List<UserDO> userList = ...;if (userList.size() == 0) {? ? ...}Map<Long, UserDO> userMap = ...;if (userMap.size() == 0) {? ? ...}
正例:
List<UserDO> userList = ...;if (userList.isEmpty()) {? ? ...}Map<Long, UserDO> userMap = ...;if (userMap.isEmpty()) {? ? ...}
7.6.非隨機(jī)訪問的List鸭廷,盡量使用迭代代替隨機(jī)訪問
對(duì)于列表枣抱,可分為隨機(jī)訪問和非隨機(jī)訪問兩類,可以用是否實(shí)現(xiàn)RandomAccess接口判斷辆床。隨機(jī)訪問列表佳晶,直接通過get獲取數(shù)據(jù)不影響效率。而非隨機(jī)訪問列表讼载,通過get獲取數(shù)據(jù)效率極低轿秧。
反例:
LinkedList<UserDO> userDOList = ...;int size = userDOList.size();for (int i = 0; i < size; i++) {? ? UserDO userDO = userDOList.get(i);? ? ...}
正例:
LinkedList<UserDO> userDOList = ...;for (UserDO userDO : userDOList) {? ? ...}
其實(shí),不管列表支不支持隨機(jī)訪問咨堤,都應(yīng)該使用迭代進(jìn)行遍歷菇篡。
7.7.盡量使用HashSet判斷值存在
在Java集合類庫中,List的contains方法普遍時(shí)間復(fù)雜度是O(n)一喘,而HashSet的時(shí)間復(fù)雜度為O(1)驱还。如果需要頻繁調(diào)用contains方法查找數(shù)據(jù),可以先將List轉(zhuǎn)換成HashSet凸克。
反例:
List<Long> adminIdList = ...;List<UserDO> userDOList = ...;List<UserVO> userVOList = new ArrayList<>(userDOList.size());for (UserDO userDO : userDOList) {? ? if (adminIdList.contains(userDO.getId())) {? ? ? ? userVOList.add(transUser(userDO));? ? }}
正例:
Set<Long> adminIdSet = ...;List userDOList = ...;List userVOList =newArrayList<>(userDOList.size());for(UserDO userDO : userDOList) {if(adminIdSet.contains(userDO.getId())) {? ? ? ? userVOList.add(transUser(userDO));? ? }}
7.8.避免先判斷存在再進(jìn)行獲取
如果需要先判斷存在再進(jìn)行獲取议蟆,可以直接獲取并判斷空,從而避免了二次查找操作触徐。
反例:
public static UserVO transUser(UserDO user, Map<Long, RoleDO> roleMap) {? ? UserVO userVO = new UserVO();? ? userVO.setId(user.getId());? ? ...? ? if (roleMap.contains(user.getRoleId())) {? ? ? ? RoleDO role = roleMap.get(user.getRoleId());? ? ? ? userVO.setRole(transRole(role));? ? }}
正例:
publicstaticUserVOtransUser(UserDO user, Map<Long, RoleDO> roleMap){UserVO userVO =newUserVO();? ? userVO.setId(user.getId());? ? ...RoleDO role = roleMap.get(user.getRoleId());if(Objects.nonNull(role)) {? ? ? ? userVO.setRole(transRole(role));? ? }}
8.異常
8.1.直接捕獲對(duì)應(yīng)的異常
直接捕獲對(duì)應(yīng)的異常咪鲜,避免用instanceof判斷,效率更高代碼更簡(jiǎn)潔撞鹉。
反例:
try {? ? saveData();} catch (Exception e) {? ? if (e instanceof IOException) {? ? ? ? log.error("保存數(shù)據(jù)IO異常", e);? ? } else {? ? ? ? log.error("保存數(shù)據(jù)其它異常", e);? ? }}
正例:
try {? ? saveData();} catch (IOException e) {? ? log.error("保存數(shù)據(jù)IO異常", e);} catch (Exception e) {? ? log.error("保存數(shù)據(jù)其它異常", e);}
8.2.盡量避免在循環(huán)中捕獲異常
當(dāng)循環(huán)體拋出異常后疟丙,無需循環(huán)繼續(xù)執(zhí)行時(shí)颖侄,沒有必要在循環(huán)體中捕獲異常。因?yàn)橄斫迹^多的捕獲異常會(huì)降低程序執(zhí)行效率览祖。
反例:
public Double sum(List<String> valueList) {? ? double sum = 0.0D;? ? for (String value : valueList) {? ? ? ? try {? ? ? ? ? ? sum += Double.parseDouble(value);? ? ? ? } catch (NumberFormatException e) {? ? ? ? ? ? return null;? ? ? ? }? ? }? ? return sum;}
正例:
publicDoublesum(List<String> valueList){doublesum =0.0D;try{for(Stringvalue: valueList) {sum += Double.parseDouble(value);? ? ? ? }}catch(NumberFormatException e) {returnnull;? ? }returnsum;}
8.3.禁止使用異常控制業(yè)務(wù)流程
相對(duì)于條件表達(dá)式炊琉,異常的處理效率更低展蒂。
反例:
publicstaticbooleanisValid(UserDO user){try{returnBoolean.TRUE.equals(user.getIsValid());}catch(NullPointerException e) {returnfalse;? ? }}
正例:
publicstaticbooleanisValid(UserDO user){if(Objects.isNull(user)) {returnfalse;? ? }returnBoolean.TRUE.equals(user.getIsValid());}
9.緩沖區(qū)
9.1.初始化時(shí)盡量指定緩沖區(qū)大小
初始化時(shí),指定緩沖區(qū)的預(yù)期容量大小苔咪,避免多次擴(kuò)容浪費(fèi)時(shí)間和空間锰悼。
反例:
StringBuffer buffer =newStringBuffer();StringBuilder builder =newStringBuilder();
正例:
StringBuffer buffer =newStringBuffer(1024);StringBuilder builder =newStringBuilder(1024);
9.2.盡量重復(fù)使用同一緩沖區(qū)
針對(duì)緩沖區(qū),Java虛擬機(jī)需要花時(shí)間生成對(duì)象团赏,還要花時(shí)間進(jìn)行垃圾回收處理箕般。所以,盡量重復(fù)利用緩沖區(qū)舔清。
反例:
StringBuilder builder1 = new StringBuilder(128);builder1.append("update t_user set name = '").append(userName).append("' where id = ").append(userId);statement.executeUpdate(builder1.toString());StringBuilder builder2 = new StringBuilder(128);builder2.append("select id, name from t_user where id = ").append(userId);ResultSet resultSet = statement.executeQuery(builder2.toString());...
正例:
StringBuilder builder = new StringBuilder(128);builder.append("updatet_usersetname='").append(userName).append("'whereid=").append(userId);statement.executeUpdate(builder.toString());builder.setLength(0);builder.append("selectid,namefromt_userwhereid=").append(userId);ResultSet resultSet = statement.executeQuery(builder.toString());...
其中丝里,使用setLength方法讓緩沖區(qū)重新從0開始。
9.3.盡量設(shè)計(jì)使用同一緩沖區(qū)
為了提高程序運(yùn)行效率体谒,在設(shè)計(jì)上盡量使用同一緩沖區(qū)杯聚。
反例:
// 轉(zhuǎn)化XML(UserDO)public static String toXml(UserDO user) {? ? StringBuilder builder = new StringBuilder(128);? ? builder.append("<UserDO>");? ? builder.append(toXml(user.getId()));? ? builder.append(toXml(user.getName()));? ? builder.append(toXml(user.getRole()));? ? builder.append("</UserDO>");? ? return builder.toString();}// 轉(zhuǎn)化XML(Long)public static String toXml(Long value) {? ? StringBuilder builder = new StringBuilder(128);? ? builder.append("<Long>");? ? builder.append(value);? ? builder.append("</Long>");? ? return builder.toString();}...// 使用代碼UserDO user = ...;String xml = toXml(user);
正例:
// 轉(zhuǎn)化XML(UserDO)public static void toXml(StringBuilder builder, UserDO user) {? ? builder.append("<UserDO>");? ? toXml(builder, user.getId());? ? toXml(builder, user.getName());? ? toXml(builder, user.getRole());? ? builder.append("</UserDO>");}// 轉(zhuǎn)化XML(Long)public static void toXml(StringBuilder builder, Long value) {? ? builder.append("<Long>");? ? builder.append(value);? ? builder.append("</Long>");}...// 使用代碼UserDO user = ...;StringBuilder builder = new StringBuilder(1024);toXml(builder, user);String xml = builder.toString();
去掉每個(gè)轉(zhuǎn)化方法中的緩沖區(qū)申請(qǐng),申請(qǐng)一個(gè)緩沖區(qū)給每個(gè)轉(zhuǎn)化方法使用抒痒。從時(shí)間上來說幌绍,節(jié)約了大量緩沖區(qū)的申請(qǐng)釋放時(shí)間;從空間上來說评汰,節(jié)約了大量緩沖區(qū)的臨時(shí)存儲(chǔ)空間纷捞。
9.4.盡量使用緩沖流減少IO操作
使用緩沖流BufferedReader、BufferedWriter被去、BufferedInputStream主儡、BufferedOutputStream等,可以大幅較少IO次數(shù)并提升IO速度惨缆。
反例:
try (FileInputStream input = new FileInputStream("a");? ? FileOutputStream output = new FileOutputStream("b")) {? ? int size = 0;? ? byte[] temp = new byte[1024];? ? while ((size = input.read(temp)) != -1) {? ? ? ? output.write(temp, 0, size);? ? }} catch (IOException e) {? ? log.error("復(fù)制文件異常", e);}
正例:
try (BufferedInputStream input = new BufferedInputStream(new FileInputStream("a"));? ? BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream("b"))) {? ? int size = 0;? ? byte[] temp = new byte[1024];? ? while ((size = input.read(temp)) != -1) {? ? ? ? output.write(temp, 0, size);? ? }} catch (IOException e) {? ? log.error("復(fù)制文件異常", e);}
其中糜值,可以根據(jù)實(shí)際情況手動(dòng)指定緩沖流的大小,把緩沖流的緩沖作用發(fā)揮到最大坯墨。
10.線程
10.1.在單線程中寂汇,盡量使用非線程安全類
使用非線程安全類,避免了不必要的同步開銷捣染。
反例:
StringBuffer buffer = new StringBuffer(128);buffer.append("select * from ").append(T_USER).append(" where id = ?");
正例:
StringBuilder buffer =newStringBuilder(128);buffer.append("select * from ").append(T_USER).append(" where id = ?");
10.2.在多線程中骄瓣,盡量使用線程安全類
使用線程安全類,比自己實(shí)現(xiàn)的同步代碼更簡(jiǎn)潔更高效耍攘。
反例:
private volatile int counter = 0;public void access(Long userId) {? ? synchronized (this) {? ? ? ? counter++;? ? }? ? ...}
正例:
privatefinalAtomicInteger counter =newAtomicInteger(0);publicvoidaccess(Long userId){? ? counter.incrementAndGet();? ? ...}
10.3.盡量減少同步代碼塊范圍
在一個(gè)方法中榕栏,可能只有一小部分的邏輯是需要同步控制的畔勤,如果同步控制了整個(gè)方法會(huì)影響執(zhí)行效率。所以扒磁,盡量減少同步代碼塊的范圍庆揪,只對(duì)需要進(jìn)行同步的代碼進(jìn)行同步。
反例:
private volatile int counter = 0;public synchronized void access(Long userId) {? counter++;? ? ... // 非同步操作}
正例:
private volatile int counter = 0;public void access(Long userId) {? ? synchronized (this) {? ? ? ? counter++;? ? }? ? ... // 非同步操作}
10.4.盡量合并為同一同步代碼塊
同步代碼塊是有性能開銷的妨托,如果確定可以合并為同一同步代碼塊缸榛,就應(yīng)該盡量合并為同一同步代碼塊。
反例:
// 處理單一訂單public synchronized handleOrder(OrderDO order) {? ? ...}// 處理所有訂單public void handleOrder(List<OrderDO> orderList) {? ? for (OrderDO order : orderList) {? ? ? ? handleOrder(order);? ? }}
正例:
// 處理單一訂單public handleOrder(OrderDO order) {? ? ...}// 處理所有訂單public synchronized void handleOrder(List<OrderDO> orderList) {? ? for (OrderDO order : orderList) {? ? ? ? handleOrder(order);? ? }}
10.5.盡量使用線程池減少線程開銷
多線程中兩個(gè)必要的開銷:線程的創(chuàng)建和上下文切換兰伤。采用線程池内颗,可以盡量地避免這些開銷。
反例:
publicvoidexecuteTask(Runnable runnable){newThread(runnable).start();}
正例:
privatestaticfinalExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(10);publicvoidexecuteTask(Runnable runnable){? ? executorService.execute(runnable);}
后記
作為一名長(zhǎng)期奮戰(zhàn)在業(yè)務(wù)一線的"IT民工"医清,沒有機(jī)會(huì)去研究什么高深莫測(cè)的"理論"起暮,只能專注于眼前看得見摸得著的"技術(shù)",致力于做到"干一行会烙、愛一行、專一行筒捺、精一行"柏腻。