我的GitHub | 我的博客 | 我的微信 | 我的郵箱 |
---|---|---|---|
baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
目錄
Java開發手冊
P3C
是世界知名的反潛機,專門對付水下潛水艇,項目組使用此名稱寓意是掃描出所有潛在的代碼隱患
。
規則
Forty-nine rules are realized based on PMD, Four rules are implemented within IDE plugins (IDEA and Eclipse) as follows:
- [Mandatory] Using a deprecated class or method is prohibited 禁止.
- [Mandatory] An overridden method from an interface or abstract class must be marked with
@Override
annotation. - [Mandatory] A static field or method should be directly referred
by its class name
instead ofits corresponding object name
. - [Mandatory] The usage of hashCode and equals should follow:
- Override hashCode if equals is overridden.
- 如果重寫了equals,必須重寫hashCode
- 如果equals為true,則hashCode一定相同
- 既然equals為false,hashCode也有可能相同
- 如果hashCode不相同,那么equals一定為false
- These two methods must be overridden for Set since they are used to ensure that no duplicate 重復 object will be inserted in Set.
- 因為Set存儲的是不重復的對象,依據 hashCode 和 equals 進行判斷,所以Set存儲的對象必須重寫這兩個方法。
- These two methods must be overridden if self-defined 自定義 object is used as
the key of Map
. Note: String can be used asthe key of Map
since these two methods have been rewritten.- 如果自定義對象做為Map的鍵,那么必須重寫 hashCode 和 equals
- Override hashCode if equals is overridden.
Mandatory:必須
IDEA 插件
目前插件實現了開發手冊中的的53
條規則,大部分基於PMD
實現,其中有4
條規則基於IDEA
實現,並且基於Inspection
實現了實時檢測
功能。部分規則實現了Quick Fix
功能。
目前插件檢測有兩種模式:實時檢測、手動觸發。
實時檢測
實時檢測功能會在開發過程中對當前文件
進行檢測,並以高亮的形式提示出來,同時也可以支持Quick Fix,該功能默認開啟,可以通過配置關閉。
- 檢測結果高亮提示,並且鼠標放上去會彈出提示信息。
- Alt+Enter鍵可呼出
Intention
菜單,不同的規則會提示不同信息的Quick Fix
按鈕 - 如果我們不希望對代碼提示違規信息,可以通過
Inspection
的設置關閉實時檢測功能。- 通過菜單【關閉/打開實時檢測功能】關閉/打開
所有規則
的實時檢測功能 - 通過
File -> Settings -> Editor -> Inspections -> Ali-Check
對每一個規則進行自由設置,比如可以關閉某條規則的實時檢測功能或者修改提示級別。
- 通過菜單【關閉/打開實時檢測功能】關閉/打開
PS:我覺得里面最惡心的一條規則是:不能在行尾添加注釋!
提示內容:方法內部單行注釋,在被注釋語句上方另起一行,使用//注釋。方法內部多行注釋使用/* */注釋。注意與代碼對齊。
代碼掃描
可以通過右鍵菜單、Toolbar按鈕、快捷鍵三種方式手動觸發代碼檢測。同時結果面板中可以對部分實現了QuickFix
功能的規則進行快速修復。
觸發方式
- 在當前編輯的文件中點擊右鍵,可以在彈出的菜單中觸發對該文件的檢測。
- 在左側的Project目錄樹種點擊右鍵,可以觸發對整個工程或者選擇的某個目錄、文件進行檢測。
- 也可以通過Toolbar中的按鈕來觸發檢測,目前Toolbar的按鈕觸發的檢測范圍與您IDE當時的焦點有關,如當前編輯的文件或者是Project目錄樹選中的項。
- 使用快捷鍵觸發彈出窗口,會讓你選擇檢測范圍。
掃描結果
- 檢測結果直接使用
IDEA Run Inspection By Name
功能的結果界面,插件的檢測結果分級為Blocker、Critical、Major
,默認按等級分組。Blocker
(崩潰):阻礙開發或測試工作的問題Critical
(嚴重):系統主要功能部分喪失、數據庫保存調用錯誤、用戶數據丟失Major
(一般):功能沒有完全實現但是不影響使用,功能菜單存在缺陷但不會影響系統穩定性
- 默認情況需要
雙擊
具體違規項才能打開對應的源文件,開啟Autoscroll To Source
選項后單擊
面板中的文件名、或者是具體的違規項的時候就可自動打開對應的源文件。 - 對於實現
Quick Fix
的規則,在結果面板中可以直接一鍵修復。
其他說明
版本要求:
- 最低支持IDEA版本為14.1(buildNumber 141.0)
- 最低支持JDK版本為1.7
設置入口:
- 頂部工具欄圖標
- 主菜單 Tools -> 阿里編碼規約
- 右鍵菜單(在最下面)
- 搜索Action【阿里】
代碼提交時檢測
- 在提交代碼框勾選
Alibaba Code Guidelines
選項 - 如果有違反手冊的地方會提示是否繼續提交,選擇取消后會自動對修改的代碼進行掃描
中文亂碼解決方法
- 將字體設置成中文字體,如微軟雅黑(microsoft yahei light)
- 主菜單 Tools -> 阿里編碼規約 -> 切換語言
P3C-PMD
插件詳細信息可以在以下路徑查閱:File -> Settings -> Editor -> Inspections -> Ali-Check
檢測內容簡要說明
Ali-Check
全部檢測內容:
- 【】ArrayList的
subList
結果不可強轉成ArrayList,否則會拋出ClassCastException
異常。 - iBATIS自帶的queryForList(String statementName,int start,int size)不推薦使用
- 【】long或者Long初始賦值時,必須使用大寫的
L
,不能是小寫的l
,小寫容易跟數字1混淆,造成誤解。 - 【-】Map/Set的
key
為自定義對象時,必須重寫 hashCode 和 equals。 - 【】Object的
equals
方法容易拋空指針異常,應使用常量或確定有值的對象
來調用 equals。 - 【-】POJO類中的任何布爾類型的變量,都
不要加is
,否則部分框架解析會引起序列化錯誤 - 【-】POJO類必須寫
toString()
方法。如果
繼承了另一個POJO類,注意在前面加一下super.toString()
。 - 【-】SimpleDateFormat 是線程不安全的類,一般不要定義為static變量,如果定義為static,必須加鎖,或者使用DateUtils工具類。
- 如果是JDK8的應用,可以使用Instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFormat
- 官方給出的解釋:simple beautiful strong immutable thread-safe
- 【】不允許任何魔法值(即未經定義的常量)直接出現在代碼中。
- 【】不能使用過時的類或方法。
- 接口提供方既然明確是過時接口,那么有義務同時提供新的接口
- 作為調用方來說,有義務去考證過時方法的新實現是什么
- 【-】不能在finally塊中使用return,finally塊中的return返回后方法結束執行,
不會再執行try塊中的return語句
。 - 【】不要在
foreach
循環里進行元素的remove/add操作,remove元素請使用Iterator
方式。 - 【】中括號是數組類型的一部分,數組定義如下:
String[] args
- 事務場景中,拋出異常被catch后,如果需要回滾,一定要手動回滾事務。
- 【--】使用
CountDownLatch
進行異步轉同步操作,每個線程退出前必須調用countDown()
方法,線程執行代碼注意catch異常,確保countDown()
方法可以執行,避免主線程無法執行至await方法,直到超時才返回結果。- 注意,子線程拋出異常堆棧,不能在主線程try-catch到
- 【】使用工具類
Arrays.asList()
把數組轉換成集合時,不能使用其修改集合相關的方法,它的add/remove/clear方法會拋出UnsupportedOperationException
異常。 - 【-】使用集合轉數組的方法,必須使用集合的
toArray(T[] array)
,傳入的是類型完全一樣的數組,大小就是list.size()
- 反例:
Integer[] a = (Integer[]) list.toArray();
- 正例:
Integer[] b = (Integer[]) list.toArray(new Integer[list.size()]);
- 反例:
- 【-】基本數據類型與包裝數據類型的使用標准:所有的
POJO類屬性
必須使用包裝數據類型;RPC方法的返回值和參數
必須使用包裝數據類型;所有的局部變量
推薦使用基本數據類型。- POJO類屬性沒有
初值
是提醒使用者在需要使用時,必須自己顯式地進行賦值,任何NPE問題,或者入庫檢查,都由使用者來保證。
- POJO類屬性沒有
- 【-】創建
線程或線程池
時請指定有意義的線程名稱
,方便出錯時回溯。創建線程池的時候請使用帶ThreadFactory
的構造函數,並且提供自定義ThreadFactory
實現或者使用第三方實現。 - 【】
包名
統一使用小寫,點分隔符之間有且僅有一個自然語義的英語單詞。包名統一使用單數形式,但是類名
如果有復數含義,類名可以使用復數形式 - 【】單個方法的總行數不超過
80
行。除注釋之外
的方法簽名、結束右大括號、方法內代碼、空行、回車及任何不可見字符的總行數不超過80行。 - 【】及時清理不再使用的代碼段或配置信息。對於垃圾代碼或過時配置,堅決清理干凈,避免程序過度臃腫,代碼冗余。
- 后台輸送給頁面的變量必須加感嘆號,
${var}
——中間加感嘆號!。如果var=null
或者不存在,那么${var}
會直接顯示在頁面上。 - 【】在if/else/for/while/do語句中必須使用大括號,
即使只有一行代碼
,避免使用下面的形式:if (condition) statements;
- 【-】在
subList
場景中,高度注意對原列表的修改,會導致子列表的遍歷、增加、刪除均產生ConcurrentModificationException
異常。 - 【】在一個switch塊內,每個case要么通過
break/return
等來終止,要么注釋說明程序將繼續執行到哪一個case為止;在一個switch塊內,都必須包含一個default
語句並且放在最后,即使它什么代碼也沒有。 - 【】在使用正則表達式時,利用好其
預編譯
功能,可以有效加快正則匹配速度。- 不要在方法體內定義:
Pattern pattern = Pattern.compile(規則);
,應該定義為類屬性
- 不要在方法體內定義:
- 【-】在使用阻塞等待
獲取鎖
的方式中,必須在try代碼塊之外
,並且在lock方法與try代碼塊之間
沒有任何可能拋出異常的方法調用,避免加鎖成功后,在finally中無法解鎖。- 說明一:如果在
lock方法與try代碼塊之間
的方法調用拋出異常,那么無法解鎖(因為沒有try就不會執行finally),造成其它線程無法成功獲取鎖。 - 說明二:如果
lock方法在try代碼塊之內
,可能由於其它方法拋出異常,導致在finally代碼塊中,執行unlock
時對未加鎖的對象解鎖,拋出IllegalMonitorStateException異常。 - 說明三:如果
lock方法在try代碼塊之內
,由於Lock對象的lock方法實現
中可能拋出unchecked異常,產生與說明二相同的問題。
- 說明一:如果在
- 【-】多線程並行處理定時任務時,
Timer
運行多個TimeTask時,只要其中之一沒有捕獲拋出的異常,其它任務便會自動終止運行,使用ScheduledExecutorService
則沒有這個問題。 - 【-】定義DO/DTO/VO等POJO類時,不要加任何
屬性默認值
。 - 【】對於Service和DAO類,基於SOA的理念,暴露出來的服務一定是
接口
,內部的實現類用Impl
的后綴與接口區別 - 【】常量命名應該全部大寫,單詞間用
下划線
隔開,力求語義表達完整清楚,不要嫌名字長
- 【】異常類命名使用
Exception
結尾 - 【】循環體內,字符串的聯接方式,使用
StringBuilder
的append
方法進行擴展。- 反編譯出的字節碼文件顯示每次循環都會new出一個StringBuilder對象,然后進行append操作,最后通過toString方法返回String對象,造成內存資源浪費。
- 【-】必須回收自定義的
ThreadLocal
變量,尤其在線程池場景下,線程經常會被復用,如果不清理自定義的 ThreadLocal 變量,可能會影響后續業務邏輯和造成內存泄露等問題。 - 【】所有的
包裝類
對象之間值的比較
,全部使用equals
方法比較。- 對於Integer在
[-128,128)
之間的賦值,Integer對象是在IntegerCache.cache
產生,會復用已有對象,這個區間內的Integer值可以直接使用==
進行判斷 - 但是
[-128,128)
這個區間之外的所有數據,都會在堆
上產生,並不會復用已有對象,這是一個大坑,推薦使用equals
方法進行判斷。
- 對於Integer在
- 【】所有的抽象方法(包括接口中的方法)必須要用
javadoc
注釋,除了返回值、參數、異常說明
外,還必須指出該方法做什么事情,實現什么功能。 - 【】所有的
枚舉類型
字段必須要有注釋,說明每個數據項的用途。 - 【】所有的類都必須添加
創建者
信息。在設置模板時,注意IDEA的@author
為${USER}
,而eclipse為${user}
,大小寫有區別,而日期的設置統一為yyyy/MM/dd
的格式。 - 【】所有的覆寫方法,必須加
@Override
注解。加@Override
可以准確判斷是否覆蓋成功。另外,如果在抽象類中對方法簽名進行修改,其實現類會馬上編譯報錯。 - 【】所有編程相關的命名均不能以下划線或美元符號開始
- 【】抽象類命名使用
Abstract
或Base
開頭 - 【】方法內部單行注釋,在被注釋語句上方
另起一行
,使用//
注釋。方法內部多行注釋使用/* */
注釋。注意與代碼對齊。 - 【】方法名、參數名、成員變量、局部變量都統一使用lowerCamelCase,必須遵從駝峰形式
- 【】日期格式化字符串
[%s]
使用錯誤,應注意使用小寫y
表示當天所在的年,大寫Y
代表week in which year。- 日期格式化時,
yyyy
表示當天所在的年,而大寫的YYYY
代表是week in which year(JDK7之后引入的概念),意思是當天所在的周屬於的年份,一周從周日開始,周六結束,只要本周跨年,返回的YYYY
就是下一年。
- 日期格式化時,
- 【】注意
Math.random()
這個方法返回是double類型,注意取值的范圍[0,1)
,如果想獲取整數類型的隨機數,不要將x放大10的若干倍然后取整,直接使用Random對象的nextInt
或者nextLong
方法。 - 【】測試類命名以它要測試的類的名稱開始,以Test結尾
- 【-】
浮點數
之間的等值判斷,基本數據類型不能用==
來比較,包裝數據類型不能用equals
來判斷。- 浮點數采用
尾數+階碼
的編碼方式,類似於科學計數法的有效數字+指數
的表示方式,二進制無法精確表示大部分的十進制小數
- 浮點數采用
- 【-】禁止使用構造方法
BigDecimal(double)
的方式把double值轉化為BigDecimal對象- 反編譯出的字節碼文件顯示每次循環都會new出一個StringBuilder對象,然后進行append操作,最后通過toString方法返回String對象,造成內存資源浪費。
- 反例:
BigDecimal bad = new BigDecimal(0.1);
- 正例:
BigDecimal good = new BigDecimal("0.1");
或BigDecimal good = BigDecimal.valueOf(0.1);
- 【】類、類屬性、類方法的注釋必須使用
javadoc
規范,使用/**內容*/
格式,不得使用//xxx
方式和/*xxx*/
方式。 - 【】類名使用UpperCamelCase風格,必須遵從駝峰形式,但以下情形例外:(領域模型的相關命名)DO / BO / DTO / VO / DAO
- 【-】線程池不允許使用
Executors
去創建,而是通過ThreadPoolExecutor
的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。FixedThreadPool和SingleThreadPool
:由於允許的請求隊列
長度為Integer.MAX_VALUE,可能會堆積
大量的請求,從而導致OOM。CachedThreadPool
:由於允許的創建線程數量
為Integer.MAX_VALUE,可能會創建
大量的線程,從而導致OOM。
- 【】線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程。
- 使用線程池的好處是減少在
創建和銷毀
線程上所花的時間以及系統資源
的開銷,解決資源不足的問題。 - 如果不使用線程池,有可能造成系統創建大量同類線程而導致消耗完內存或者
過度切換
的問題。
- 使用線程池的好處是減少在
- 【】獲取當前毫秒數:
System.currentTimeMillis();
而不是new Date().getTime();
。如果想獲取更加精確的納秒級時間值,用System.nanoTime
。 - 【】返回類型為基本數據類型,return包裝數據類型的對象時,(
return null
時)自動拆箱有可能產生NPE - 【】避免Random實例被多線程使用,雖然共享該實例是線程安全的,但會因競爭同一
seed
導致的性能下降。說明:Random實例包括java.util.Random
的實例或者Math.random()
的方式。 - 避免用Apache Beanutils進行屬性的copy,因為性能較差。可以使用其他方案比如Spring BeanUtils, Cglib BeanCopier。
- 【】避免通過一個類的對象引用訪問此類的
靜態變量或靜態方法
,無謂增加編譯器解析成本,直接用類名來訪問即可。 - 【】避免采用
取反
邏輯運算符。取反邏輯不利於快速理解,並且取反邏輯寫法必然存在對應的正向邏輯寫法。 - 【】除常用方法(如getXxx/isXxx)等外,不要在條件判斷中執行復雜的語句,將復雜邏輯判斷的結果賦值給一個有意義的布爾
變量
,以提高可讀性。 - 【】集合初始化時,指定集合初始值大小。如果暫時無法確定集合大小,那么指定默認值
16
即可。
對一些規則的解釋
SimpleDateFormat
public class Test {
private static final String FORMAT = "yyyy-MM-dd HH:mm:ss";
private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static final ThreadLocal<DateFormat> DATE_FORMATTER = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
public static void main(String[] args) {
System.out.println(new SimpleDateFormat(FORMAT).format(new Date()));
synchronized (SIMPLE_DATE_FORMAT) {
System.out.println(SIMPLE_DATE_FORMAT.format(new Date()));
}
System.out.println(DATE_FORMATTER.get().format(new Date()));
}
}
CountDownLatch
public void operate(CountDownLatch countDownLatch) {
try {
System.out.println("business logic");
} catch (RuntimeException e) {
// do something
} finally {
countDownLatch.countDown();
}
}
ThreadFactory
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
ExecutorService singleThreadPool = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
singleThreadPool.execute(() -> System.out.println(Thread.currentThread().getName()));
singleThreadPool.shutdown();
subList
List<String> originList = new ArrayList<>();
originList.add("22");
List<String> subList = originList.subList(0, 1);
originList.add("23");
System.out.println(subList.size()); //java.util.ConcurrentModificationException
lock 方法獲取鎖
正例
Lock lock = new XxxLock();
// ...
lock.lock(); // 如果在lock方法與try代碼塊之間的方法調用拋出異常,那么無法解鎖,造成其它線程無法成功獲取鎖
try {
//doSomething
//doOthers
} finally {
lock.unlock();
}
反例
Lock lock = new XxxLock();
// ...
try {
// If an exception is thrown here, the finally block is executed directly
//doSomething
lock.lock(); //如果lock方法在try之內,可能由於其它方法拋出異常,導致在finally中執行unlock時拋出異常
//doOthers
} finally {
// The finally block executes regardless of whether the lock is successful or not
lock.unlock();
}
ThreadLocal
public class Test {
public static void main(String[] args) {
UserHolder.set(new User());
User user = UserHolder.get();
UserHolder.remove();
}
}
class UserHolder {
private static final ThreadLocal<User> userThreadLocal = new ThreadLocal<>();
public static void set(User user) {
userThreadLocal.set(user);
}
public static User get() {
return userThreadLocal.get();
}
public static void remove() {
userThreadLocal.remove();
}
}
class User {
}
浮點數之間的等值判斷
方法一:指定一個誤差范圍
,兩個浮點數的差值在此范圍之內,則認為是相等的
float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
float diff = 1e-6f;
if (Math.abs(a - b) < diff) {
System.out.println("true");
}
方法二:使用BigDecimal
來定義值,再進行浮點數的運算操作
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");
BigDecimal x = a.subtract(b);
BigDecimal y = b.subtract(c);
if (x.equals(y)) {
System.out.println("true");
}
線程池
//org.apache.commons.lang3.concurrent.BasicThreadFactory
BasicThreadFactory factory = new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build();
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, factory);
TimeUnit unit = TimeUnit.MILLISECONDS;
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(1024);
ThreadFactory factory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
//Common Thread Pool
ExecutorService pool = new ThreadPoolExecutor(5, 200, 0L, unit, workQueue, factory, handler);
pool.execute(() -> System.out.println(Thread.currentThread().getName()));
pool.shutdown();//gracefully shutdown
Random
class RandomInThread extends Thread {
//private final Random random = new Random();
private final Random random = ThreadLocalRandom.current();
@Override
public void run() {
System.out.println(random.nextLong());
}
}
2020-05-28