目錄
- 一、編程規約
- 1.區分JavaBean、POJO、Entity
- 2. 創建枚舉類
- 3. equals判斷
- 4. 包裝類比較
- 5. float、double不能直接做等值判斷
- 6. 不能使用BigDecimal(double)
- 7. foreach循環中不可remove/add
- 8. 使用entrySet()遍歷Map集合
- 9. 注意Map中不能存null的情況
- 10. SimpleDateFormat 線程不安全
- 11. switch括號中不能爲null
- 12. 沒有說明的註釋代碼可以直接刪掉
- 13. 正則表達式Pattern不要方法內定義
- 14. 避免使用BeanUtils.copy()
- 15. Meth.random()技巧
- 二、日誌規範
- 三、Mysql數據庫規範
一、編程規約
1.區分JavaBean、POJO、Entity
原文:(P8)
領域模型命名規約如下:
- 數據對象: xxxDO,xx爲數據表名。
- 數據傳輸對象:xxxDTO,xxx爲業務領域相關的名稱。
- 展示對象: xxxVO,xxx一般爲網頁命名。
- POJO是DO/DTO/BO/VO的統稱,禁止命名成xxxPOJO。
1.1 JavaBean
JavaBean: 符合一定規範編寫的Java類。
- 所有屬性私有化;
- 有一個公共的午餐構造器;
- 有getter/setter方法;
- 可序列化,實現Serializable接口。
1.2 POJ
POJO: 就是普通的JavaBean,爲了避免和EJB混淆所創造的簡稱,包括DO/DTO/BO/VO。
- DO: 數據對象,對應一個表的所有字段;
- DTO: 數據傳輸對象,對應一個表的所有字段;
- BO: 業務對象,封裝了一定的業務邏輯,內部可能包含多個對象,常位於業務層;
- VO: 表現對象,表現層對象,主要用於前端界面的展示,類似ModuleAndView。
1.3 Entity
Entity: 實體類,對應表中的部分數據,類似DTO。
2. 創建枚舉類
原文:(p10)
如果變量值僅在一個範圍內變化,則用enum類型來定義。
2.1 Enum(value)
/**
* 當枚舉類只有一個屬性value的時候
*/
public enum SeasonEnum {
// 枚舉值
SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4);
// 屬性
private Integer value;
// 構造函數
SeasonEnum(Integer value) {
this.value = value;
}
public Integer value() {
return value;
}
}
2.2 Enum(value, name)
/**
* 當枚舉類有兩個屬性value、name的時候
*/
public enum SeasonEnum {
// 枚舉值
SPRING(1, "spring"),
SUMMER(2, "summer"),
AUTUMN(3, "autumn"),
WINTER(4, "winter");
// 屬性
private Integer value;
private String name;
// 靜態map容器,用於存放value和SeasonEnum的對應關係
private static Map<Integer, SeasonEnum> valueMap = new HashMap();
// 初始化
static {
Arrays.asStream(SeasonEnum.values()).forEach(e -> valueMap.put(e.value, e));
}
// 構造函數
SeasonEnum(Integer value, String name) {
this.value = value;
this.name = name;
}
// 獲取枚舉類
public SeasonEnum of(Integer value) {
return valueMap.get(value);
}
// 獲取value
public Integer value() {
return value;
}
// 獲取name
public Integer name() {
return name;
}
}
3. equals判斷
原文:(p18)
推薦使用java.util.Objects.equals(Object obj, Object obj)(JDK7引入的工具類)。
4. 包裝類比較
原文:(p18)
所有整形包裝類對象之間值的比較,全部使用equals方法。
說明:
對於Integer var = ? 在 -128~127範圍內的賦值,Integer對象是在IntegerCache.cache中產生的,會複用已有對象,這個區間內的Integer值可以直接使用 == 進行判斷,但是這個區間之外的所有數據,都會在堆上產生,並不會服用已有對象,這是一個大坑,推薦使用equals方法進行判斷。
5. float、double不能直接做等值判斷
原文:(p19)
浮點數之間的等職判斷,基本數據類型不能用==進行比較,包裝數據類型不能用equals方法進行判斷。
舉例:
public static void main(String[] args) {
float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
if (a == b) {
// 預期進入此代碼塊,但是a == b的結果爲false
}
Float x = Float.valueOf(a);
Float y = Float.valueOf(b);
if (x.equals(y)) {
// 預期進入此代碼塊,但是x.equals(y)的結果爲false
}
// 結果全部爲false
System.out.println("result1: " + ((0.05 + 0.01) == 0.06));
System.out.println("result2: " + ((1.0 - 0.42) == 0.58));
System.out.println("result3: " + ((4.015 * 100) == 401.5));
System.out.println("result4: " + ((123.3 / 100) == 1.233));
}
解決方案:
數字類型操作儘量使用BigDecimal類型。
6. 不能使用BigDecimal(double)
原文:(p21)
禁止使用構造方法BigDecimal(double)的方式把double值轉化爲BigDecimal對象。
舉例:
public static void main(String[] args) {
System.out.println(new BigDecimal(0.13));
// 輸出結果爲:0.13000000000000000444089209850062616169452667236328125
}
解決方案:
採用如下兩種方式:
- new BigDecimal(Double.toString(0.13))
- BigDecimal.valueOf(0.13)
7. foreach循環中不可remove/add
原文:(p31)
不要再foreach循環裏進行元素的 remove/add 操作。remove元素請使用Iterator方式,如果併發操作,需要對Iterator對象加鎖。
舉例:
public static void main(String[] args) {
List<String> list = new ArrayList();
list.add("1");
list.add("2");
for (String item : list) {
if ("1".equals(item)) {
// 會拋出ConcurrentModificationException
list.remove(item);
}
}
}
解決方案:
public static void main(String[] args) {
List<String> list = new ArrayList();
list.add("1");
list.add("2");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
if ("1".equals(iterator.next())) {
iterator.remove();
}
}
}
8. 使用entrySet()遍歷Map集合
原文:(p33)
使用entrySet()遍歷Map類集合K/V,而不是用keySet()方式遍歷。
說明:
keySet 其實遍歷了2次,一次是轉爲Iterator對象,另一次是從hashMap中取出key所對應的value。
如果是JDK8,使用Map.forEach方法。
舉例:
public static void main(String[] args) {
Map<String, String> map = new HashMap();
map.put("name", "小明");
map.put("age", "18");
// entrySet()
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println("(" + entry.getKey() + ", " + entry.getValue() + ")");
}
// JDK 8
map.forEach((key, value) -> System.out.println("(" + key + ", " + value + ")"));
}
9. 注意Map中不能存null的情況
原文:(p34)
高度注意Map類集合K/V能不能存儲null值的情況。
集合類 Key Value Super 說明 Hashtable 不允許爲null 不允許爲null Dictionary 線程安全 ConcurrentHashMap 不允許爲null 不允許爲null AbstractMap 鎖分段技術
(JDK8:CAS)TreeMap 不允許爲null 允許爲null AbstractMap 線程不安全 HashMap 允許爲null 允許爲null AbstractMap 線程不安全
10. SimpleDateFormat 線程不安全
原文:(p37)
SimpleDateFormat 是線程不安全的類,一般不要定義爲static變量,如果定義爲static,必須加鎖,或者使用DateUtils工具類。
舉例:
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialVlue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
說明:
如果是JDK8 的應用,可以進行如下替換使用:
- Date -> Instant
- Calendar -> LocalDateTime
- SimpleDateFormat -> DateTimeFormatter
11. switch括號中不能爲null
原文:(p43)
當switch括號內的變量類型爲String並且此變量爲外部參數時,必須先進行null判斷,否則會拋NPE。
舉例:
private static void method(String s) {
if (Objects.nonNull(s)) {
// 爲null會拋NullPointerException
switch (s) {
case "a":
System.out.println(111);
break;
default:
System.out.println(222);
}
}
}
12. 沒有說明的註釋代碼可以直接刪掉
原文:(p50)
謹慎註釋掉代碼,要在上方詳細說明,若不是簡單地註釋掉。如果無用,則刪除。
說明:
代碼被註釋掉有兩種可能性。
- 後續會恢復此段代碼邏輯;
- 永久不用。
前者如果沒有備註信息,難以知曉註釋動機。後者可解刪掉即可。
13. 正則表達式Pattern不要方法內定義
原文:(p52)
在使用正則表達式時,利用好其預編譯功能,可以有效加快正則表達式匹配速度。
說明:
不要在方法體內定義Pattern:
Pattern pattern = Pattern.compile("規則");
14. 避免使用BeanUtils.copy()
原文:(p52)
避免用Apache BeanUtils進行屬性的copy。
說明:
Apache BeanUtils 性能較差,可以使用其他方案比如是Spring BeanUtils、Cglib BeanCopier,注意均是淺克隆。
15. Meth.random()技巧
原文:(p53)
注意 Math.random() 這個方法返回的是 double類型,取值的範圍是 0≤x<1(能夠取到零值,注意除零異常),如果想獲取整數類型的隨機數,不要將x放大10的若干倍然後取整,直接使用Random對象的nextInt()或者nextLong()方法。
舉例:
public static void main(String[] args) {
// 隨機生成[0, 10)之間的整數
System.out.println(new Random().nextInt(10));
// 方法調用返回下一個從這個僞隨機數生成器的序列中均勻分佈的long值。
// 18~20位
System.out.println(Long.toString(l).length() + ", " + l);
}
二、日誌規範
1. 不要直接使用日誌系統
原文:(p62)
應用中不可直接使用日誌系統(Log4j、Logback)中的API,而應依賴使用日誌框架 SLF4J 中的API。使用門面模式的日誌架構,有利於維護和各個類的日誌處理方式統一。
舉例:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Test.calss);
2. 不要直接System.out.println()、e.printStackTrace()
原文:(p64)
在生產環境(線上)中禁止直接使用 System.out 或 System.err 輸出日誌,或使用 e.printStackTrace()打印異常堆棧。
說明:
每次Jboss(一個基於Java的應用服務器程序)重啓時標準日誌輸出文件與標準錯誤輸出文件才滾動,如果大量輸出送往這兩個文件,容易造成文件大小超過操作系統大小限制。
3. 輸出日誌使用佔位符
原文:(p63)
在日誌輸出時,字符串變量之間的拼接使用佔位符的方式。
說明:
因爲String字符串的拼接會使用StringBuilder的append()方法,有一定的幸能損耗。使用佔位符僅是替換動作,可以有效提升性能。
舉例:
logger.debug("Processing trade with id: {} and symbol: {}", id, symbole);
4. 異常日誌輸出規範
原文:(p64)
異常信息應該包括兩類:案發現場信息和異常堆棧信息。如果不處理,那麼通過關鍵字 throws 往上拋出。
舉例:
logger.error("{}_{}", 各類參數或對象.toString(), e.getMessage(), e);
三、Mysql數據庫規範
原文:(p79)
表名不適用名詞複數。
索引命名。
- 主鍵索引 -> pk_字段名
- 唯一索引 -> uk_字段名
- 普通索引 -> idx_字段名
小數類型爲decimal,禁止使用float和double。
如果存儲的字符串長度幾乎相等,應該使用char定長字符串類型。
varchar是可變長字符串,不預先分配存儲空間,長度不要超過5000個字符。如果存儲長度大於此值,則應定義字段類型爲text,獨立出來一張表,用主鍵來對應,避免影響其他字段的索引效率。
表必備三個字段:id,create_time,update_time。
表的命名最好遵循”業務名稱_表的作用“原則。
當表單行數超過500萬行或者單表容量超過 2GB 時,才推薦進行分庫分表。
(如果預計三年後的數據量無法達到這個級別,請不要在創建表時就分庫分表)超過三個表禁止 join。需要join的字段,數據類型必須絕對一致;當多表關聯查詢時,保證被關聯的字段需要有索引。