您好,登錄后才能下訂單哦!
作者:李艷鵬,現(xiàn)任螞蟻金服高級技術(shù)專家,著有《分布式服務架構(gòu):原理、設計與實戰(zhàn)》和《可伸縮服務架構(gòu):框架與中間件》,曾經(jīng)在易寶支付、花旗銀行、甲骨文、新浪微博、路透社等大型IT互聯(lián)網(wǎng)公司擔任技術(shù)負責人和首席架構(gòu)師的工作,現(xiàn)專注于區(qū)塊鏈平臺的研發(fā)與推廣,擅長大規(guī)模高并發(fā)的線上與線下相結(jié)合的第三方支付平臺的架構(gòu)規(guī)劃與實施。
最近,阿里巴巴發(fā)布了《阿里巴巴Java開發(fā)手冊》,總結(jié)了阿里人多年一線實戰(zhàn)中積累的研發(fā)流程規(guī)范,這些流程規(guī)范在一定程度上能夠保證最終的項目交付質(zhì)量,通過限制開發(fā)人員的編程風格、實現(xiàn)方式來避免研發(fā)人員在實踐中容易犯的錯誤,同樣的問題大家使用同樣的模式解決,便于后期維護和擴展,確保最終在大規(guī)模協(xié)作的項目中達成既定目標。
無獨有偶,筆者去年在公司里負責升級和制定研發(fā)流程、設計模板、設計標準、代碼標準等規(guī)范,并在實際工作中進行了應用和推廣,收效頗豐,也總結(jié)了適合支付平臺的技術(shù)規(guī)范,由于阿里巴巴Java開發(fā)手冊本身定位為規(guī)約和規(guī)范,語言簡單、精煉,沒有太多的解讀和示例,有些條款對于一般開發(fā)人員理解起來比較困難,本文借著阿里巴巴發(fā)布的Java開發(fā)手冊,詳細解讀Java平臺下開發(fā)規(guī)范和標準的制定和實施,強調(diào)那些在開發(fā)過程中需要重點關(guān)注的技術(shù)點,特別是解決某類已識別問題的模式和反模式。
《阿里巴巴Java開發(fā)手冊》分為編程規(guī)約、異常日志、MySQL規(guī)約、工程規(guī)約、安全規(guī)約五大部分,本系列文章以這五部分主題為主線,分為五篇文章發(fā)布,本文為系列文章的第一篇-編程規(guī)約,后續(xù)會盡快發(fā)布其余的文章。
【強制】 代碼中的命名均不能以下劃線或美元符號開始,也不能以下劃線或美元符號結(jié)束。
反例: _name / _name / $Object / name / name$ / Object$
白話:
【強制】 代碼中的命名嚴禁使用拼音與英文混合的方式,更不允許直接使用中文的方式。
說明: 正確的英文拼寫和語法可以讓閱讀者易于理解,避免歧義。注意,即使純拼音命名方式 也要避免采用。
反例: DaZhePromotion [打折] / getPingfenByName() [評分] / int 某變量 = 3
正例: alibaba / taobao / youku / hangzhou 等國際通用的名稱,可視同英文。
白話:
中英混合的人種咱不歧視,變量名混合太丑了。
Java編譯器支持Unicode(UTF-8),允許中文命名變量,不過打中文還是沒有英文快。
【強制】類名使用 UpperCamelCase 風格,必須遵從駝峰形式,但以下情形例外:(領(lǐng)域模型 的相關(guān)命名)DO / BO / DTO / VO等。
正例:MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion
反例:macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion
白話:
【強制】方法名、參數(shù)名、成員變量、局部變量都統(tǒng)一使用 lowerCamelCase 風格,必須遵從駝峰形式。
正例: localValue / getHttpMessage() / inputUserId
白話:
約定俗稱的名稱或者縮寫例外。
【強制】常量命名全部大寫,單詞間用下劃線隔開,力求語義表達完整清楚,不要嫌名字長。
正例: MAX_STOCK_COUNT
反例: MAX_COUNT
白話:
【強制】抽象類命名使用 Abstract 或 Base 開頭;異常類命名使用 Exception 結(jié)尾;測試類命名以它要測試的類的名稱開始,以 Test 結(jié)尾。
白話:
【強制】中括號是數(shù)組類型的一部分,數(shù)組定義如下:String[] args;
反例: 使用String args[]的方式來定義。
白話:
這種語法編譯器也認,但是我們畢竟寫Java程序,而不是寫C/C++程序。
【強制】POJO 類中布爾類型的變量,都不要加 is,否則部分框架解析會引起序列化錯誤。
反例: 定義為基本數(shù)據(jù)類型Boolean isSuccess;的屬性,它的方法也是isSuccess(),RPC 框架在反向解析的時候,“以為”對應的屬性名稱是 success,導致屬性獲取不到,進而拋出異常。
白話:
【強制】包名統(tǒng)一使用小寫,點分隔符之間有且僅有一個自然語義的英語單詞。包名統(tǒng)一使用 單數(shù)形式,但是類名如果有復數(shù)含義,類名可以使用復數(shù)形式。
正例: 應用工具類包名為com.alibaba.open.util、類名為MessageUtils(此規(guī)則參考 spring 的框架結(jié)構(gòu))
白話:
【強制】杜絕完全不規(guī)范的縮寫,避免望文不知義。
反例: AbstractClass“縮寫”命名成 AbsClass;condition“縮寫”命名成 condi,此類 隨意縮寫嚴重降低了代碼的可閱讀性。
白話:
不要太摳,不是太長的名字直接寫上就好,編譯器編譯優(yōu)化后變量名將不存在,會編譯成相對于方法堆棧bp指針地址的相對地址,長變量名不會占用更多空間。
【推薦】如果使用到了設計模式,建議在類名中體現(xiàn)出具體模式。
說明: 將設計模式體現(xiàn)在名字中,有利于閱讀者快速理解架構(gòu)設計思想。
正例: public class OrderFactory;
public class LoginProxy;
public class ResourceObserver;
白話:
【推薦】接口類中的方法和屬性不要加任何修飾符號(public 也不要加),保持代碼的簡潔 性,并加上有效的 Javadoc 注釋。盡量不要在接口里定義變量,如果一定要定義變量,肯定是與接口方法相關(guān),并且是整個應用的基礎(chǔ)常量。
正例: 接口方法簽名:void f();
接口基礎(chǔ)常量表示:String COMPANY = "alibaba";
反例: 接口方法定義:public abstract void f();
說明:JDK8 中接口允許有默認實現(xiàn),那么這個 default 方法,是對所有實現(xiàn)類都有價值的默 認實現(xiàn)。
白話:
接口和實現(xiàn)類的命名有兩套規(guī)則:
1)【強制】對于 Service 和 DAO 類,基于 SOA 的理念,暴露出來的服務一定是接口,內(nèi)部的實現(xiàn)類用 Impl 的后綴與接口區(qū)別。
正例: CacheServiceImpl 實現(xiàn) CacheService 接口。
2)【推薦】 如果是形容能力的接口名稱,取對應的形容詞做接口名(通常是–able 的形式)。
正例: AbstractTranslator 實現(xiàn) Translatable。
白話:
【參考】枚舉類名建議帶上 Enum 后綴,枚舉成員名稱需要全大寫,單詞間用下劃線隔開。
說明: 枚舉其實就是特殊的常量類,且構(gòu)造方法被默認強制是私有。
正例: 枚舉名字:DealStatusEnum,成員名稱: SUCCESS / UNKOWN_REASON。
白話:
【參考】各層命名規(guī)約:
A) Service/DAO層方法命名規(guī)約
1) 獲取單個對象的方法用get做前綴。
2) 獲取多個對象的方法用list做前綴。
3) 獲取統(tǒng)計值的方法用count做前綴。
4) 插入的方法用save(推薦)或insert做前綴。
5) 刪除的方法用remove(推薦)或delete做前綴。
6) 修改的方法用update做前綴。
B) 領(lǐng)域模型命名規(guī)約
1) 數(shù)據(jù)對象:xxxDO,xxx即為數(shù)據(jù)表名。
2) 數(shù)據(jù)傳輸對象:xxxDTO,xxx為業(yè)務領(lǐng)域相關(guān)的名稱。
3) 展示對象:xxxVO,xxx一般為網(wǎng)頁名稱。
4) POJO是DO/DTO/BO/VO的統(tǒng)稱,禁止命名成xxxPOJO。
白話:
【強制】不允許出現(xiàn)任何魔法值(即未經(jīng)定義的常量)直接出現(xiàn)在代碼中。
反例: String key = "Id#taobao_"+tradeId; cache.put(key, value);
白話:
【強制】long 或者 Long 初始賦值時,必須使用大寫的 L,不能是小寫的 l,小寫容易跟數(shù)字 1 混淆,造成誤解。
說明: Long a = 2l; 寫的是數(shù)字的21,還是Long型的2?
白話:
【推薦】不要使用一個常量類維護所有常量,應該按常量功能進行歸類,分開維護。如:緩存相關(guān)的常量放在類: CacheConsts 下; 系統(tǒng)配置相關(guān)的常量放在類: ConfigConsts 下。
說明: 大而全的常量類,非得使用查找功能才能定位到修改的常量,不利于理解和維護。
白話:
【推薦】常量的復用層次有五層: 跨應用共享常量、應用內(nèi)共享常量、子工程內(nèi)共享常量、包內(nèi)共享常量、類內(nèi)共享常量。
1) 跨應用共享常量: 放置在二方庫中,通常是client.jar中的constant目錄下。
2) 應用內(nèi)共享常量: 放置在一方庫的modules中的constant目錄下。
反例: 易懂變量也要統(tǒng)一定義成應用內(nèi)共享常量,兩位攻城師在兩個類中分別定義了表示 “是”的變量:
類A中: public static final String YES = "yes";
類B中: public static final String YES = "y"; A.YES.equals(B.YES)
,預期是 true,但實際返回為 false,導致產(chǎn)生線上問題。
3) 子工程內(nèi)部共享常量: 即在當前子工程的constant目錄下。
4) 包內(nèi)共享常量: 即在當前包下單獨的constant目錄下。
5) 類內(nèi)共享常量: 直接在類內(nèi)部private static final定義。
白話:
【推薦】如果變量值僅在一個范圍內(nèi)變化用 Enum 類。如果還帶有名稱之外的延伸屬性,必須 使用 Enum 類,下面正例中的數(shù)字就是延伸信息,表示星期幾。
正例: public Enum { MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6), SUNDAY(7);}
白話:
【強制】大括號的使用約定。如果是大括號內(nèi)為空,則簡潔地寫成{}即可,不需要換行; 如果 是非空代碼塊則:
1) 左大括號前不換行。
2) 左大括號后換行。
3) 右大括號前換行。
4) 右大括號后還有else等代碼則不換行;表示終止右大括號后必須換行。
白話:
【強制】 左括號和后一個字符之間不出現(xiàn)空格; 同樣,右括號和前一個字符之間也不出現(xiàn)空 格。詳見第 5 條下方正例提示。
白話:
【強制】if/for/while/switch/do 等保留字與左右括號之間都必須加空格。
白話:
【強制】任何運算符左右必須加一個空格。
說明: 運算符包括賦值運算符=、邏輯運算符&&、加減乘除符號、三目運算符等。
白話:
【強制】縮進采用 4 個空格,禁止使用 tab 字符。
說明: 如果使用 tab 縮進,必須設置 1 個 tab 為 4 個空格。IDEA 設置 tab 為 4 個空格時,請勿勾選Use tab character; 而在 eclipse 中,必須勾選 insert spaces for tabs。
正例: (涉及1-5點)
public static void main(String[] args) {
// 縮進 4 個空格
String say = "hello";
// 運算符的左右必須有一個空格
int flag = 0;
// 關(guān)鍵詞 if 與括號之間必須有一個空格,括號內(nèi)的 f 與左括號,0 與右括號不需要空格
if (flag == 0) {
System.out.println(say);
}
// 左大括號前加空格且不換行;左大括號后換行
if (flag == 1) {
System.out.println("world");
// 右大括號前換行,右大括號后有 else,不用換行
} else { System.out.println("ok");
// 在右大括號后直接結(jié)束,則必須換行
}
}
白話:
【強制】單行字符數(shù)限制不超過 120 個,超出需要換行,換行時遵循如下原則:
1) 第二行相對第一行縮進 4 個空格,從第三行開始,不再繼續(xù)縮進,參考示例。
2) 運算符與下文一起換行。
3) 方法調(diào)用的點符號與下文一起換行。
4) 在多個參數(shù)超長,逗號后進行換行。
5) 在括號前不要換行,見反例。
正例:
StringBuffer sb = new StringBuffer();
//超過 120 個字符的情況下,換行縮進 4 個空格,并且方法前的點符號一起換行
sb.append("zi").append("xin")...
.append("huang")...
.append("huang")...
.append("huang");
反例:
StringBuffer sb = new StringBuffer();
//超過 120 個字符的情況下,不要在括號前換行
sb.append("zi").append("xin")...append
("huang");
//參數(shù)很多的方法調(diào)用可能超過 120 個字符,不要在逗號前換行
method(args1, args2, args3, ...
, argsX);
白話:
【強制】方法參數(shù)在定義和傳入時,多個參數(shù)逗號后邊必須加空格。
正例: 下例中實參的"a", 后邊必須要有一個空格。
method("a", "b", "c");
白話:
【強制】IDE的text file encoding設置為UTF-8; IDE中文件的換行符使用Unix格式, 不要使用 windows 格式。
白話:
【推薦】沒有必要增加若干空格來使某一行的字符與上一行的相應字符對齊。
正例:
int a = 3;
long b = 4L;
float c = 5F;
StringBuffer sb = new StringBuffer();
說明: 增加 sb 這個變量,如果需要對齊,則給 a、b、c 都要增加幾個空格,在變量比較多的 情況下,是一種累贅的事情。
白話:
【推薦】方法體內(nèi)的執(zhí)行語句組、變量的定義語句組、不同的業(yè)務邏輯之間或者不同的語義
之間插入一個空行。相同業(yè)務邏輯和語義之間不需要插入空行。
說明: 沒有必要插入多行空格進行隔開。
白話:
【強制】避免通過一個類的對象引用訪問此類的靜態(tài)變量或靜態(tài)方法,無謂增加編譯器解析成
本,直接用類名來訪問即可。
白話:
【強制】所有的覆寫方法,必須加@Override 注解。
反例:getObject()與 get0bject()的問題。一個是字母的 O,一個是數(shù)字的 0,加@Override 可以準確判斷是否覆蓋成功。另外,如果在抽象類中對方法簽名進行修改,其實現(xiàn)類會馬上編譯報錯。
白話:
【強制】相同參數(shù)類型,相同業(yè)務含義,才可以使用 Java 的可變參數(shù),避免使用 Object。
說明: 可變參數(shù)必須放置在參數(shù)列表的最后。(提倡同學們盡量不用可變參數(shù)編程)
正例: public User getUsers(String type, Integer... ids)
白話:
用處不大,可以用重載方法或者數(shù)組參數(shù)代替。
【強制】外部正在調(diào)用或者二方庫依賴的接口,不允許修改方法簽名,避免對接口調(diào)用方產(chǎn)生 影響。接口過時必須加@Deprecated 注解,并清晰地說明采用的新接口或者新服務是什么。
白話:
設計時沒有考慮周全,需要改造接口,需要通過增加新接口,遷移后下線老接口的方式實現(xiàn)。
【強制】不能使用過時的類或方法。
說明: java.net.URLDecoder 中的方法 decode(String encodeStr) 這個方法已經(jīng)過時,應該使用雙參數(shù) decode(String source, String encode)。接口提供方既然明確是過時接口,那么有義務同時提供新的接口; 作為調(diào)用方來說,有義務去考證過時方法的新實現(xiàn)是什么。
白話:
【強制】Object 的 equals 方法容易拋空指針異常,應使用常量或確定有值的對象來調(diào)用 equals。
正例: "test".equals(object);
反例: object.equals("test");
說明: 推薦使用java.util.Objects#equals
(JDK7引入的工具類)
白話:
【強制】所有的相同類型的包裝類對象之間值的比較,全部使用 equals 方法比較。
說明: 對于Integer var = ?在-128至127之間的賦值,Integer對象是在 IntegerCache.cache 產(chǎn)生,會復用已有對象,這個區(qū)間內(nèi)的 Integer 值可以直接使用==進行 判斷,但是這個區(qū)間之外的所有數(shù)據(jù),都會在堆上產(chǎn)生,并不會復用已有對象,這是一個大坑, 推薦使用 equals 方法進行判斷。
白話:
關(guān)于基本數(shù)據(jù)類型與包裝數(shù)據(jù)類型的使用標準如下:
1) 【強制】所有的POJO類屬性必須使用包裝數(shù)據(jù)類型。
2) 【強制】RPC方法的返回值和參數(shù)必須使用包裝數(shù)據(jù)類型。
3) 【推薦】所有的局部變量使用基本數(shù)據(jù)類型。
說明: POJO 類屬性沒有初值是提醒使用者在需要使用時,必須自己顯式地進行賦值,任何
NPE 問題,或者入庫檢查,都由使用者來保證。
正例: 數(shù)據(jù)庫的查詢結(jié)果可能是 null,因為自動拆箱,用基本數(shù)據(jù)類型接收有 NPE 風險。
反例: 比如顯示成交總額漲跌情況,即正負 x%,x 為基本數(shù)據(jù)類型,調(diào)用的 RPC 服務,調(diào)用不成功時,返回的是默認值,頁面顯示:0%,這是不合理的,應該顯示成中劃線-。所以包裝數(shù)據(jù)類型的 null 值,能夠表示額外的信息,如:遠程調(diào)用失敗,異常退出。
白話:
【強制】定義 DO/DTO/VO 等 POJO 類時,不要設定任何屬性默認值。
反例: POJO類的gmtCreate默認值為new Date(); 但是這個屬性在數(shù)據(jù)提取時并沒有置入具體值,在更新其它字段時又附帶更新了此字段,導致創(chuàng)建時間被修改成當前時間。
白話:
【強制】序列化類新增屬性時,請不要修改 serialVersionUID 字段,避免反序列失敗; 如 果完全不兼容升級,避免反序列化混亂,那么請修改 serialVersionUID 值。
說明:注意 serialVersionUID 不一致會拋出序列化運行時異常。
白話:
【強制】構(gòu)造方法里面禁止加入任何業(yè)務邏輯,如果有初始化邏輯,請放在 init 方法中。
白話:
【強制】POJO 類必須寫 toString 方法。使用 IDE 的中工具:source> generate toString 時,如果繼承了另一個 POJO 類,注意在前面加一下 super.toString。
說明: 在方法執(zhí)行拋出異常時,可以直接調(diào)用 POJO 的 toString()方法打印其屬性值,便于排 查問題。
白話:
【推薦】使用索引訪問用 String 的 split 方法得到的數(shù)組時,需做最后一個分隔符后有無內(nèi)容的檢查,否則會有拋 IndexOutOfBoundsException 的風險。
說明:
String str = "a,b,c,,";
String[] ary = str.split(","); //預期大于 3,結(jié)果是 3
System.out.println(ary.length);
白話:
編程要留心眼,任何不確定的地方都要判斷、處理,否則掉到坑里了自己爬出來很費勁。
【推薦】當一個類有多個構(gòu)造方法,或者多個同名方法,這些方法應該按順序放置在一起, 便于閱讀。
白話:
【推薦】 類內(nèi)方法定義順序依次是: 公有方法或保護方法 > 私有方法 > getter/setter
方法。
說明: 公有方法是類的調(diào)用者和維護者最關(guān)心的方法,首屏展示最好; 保護方法雖然只是子類 關(guān)心,也可能是“模板設計模式”下的核心方法; 而私有方法外部一般不需要特別關(guān)心,是一個黑盒實現(xiàn); 因為方法信息價值較低,所有 Service 和 DAO 的 getter/setter 方法放在類體最 后。
白話:
【推薦】setter 方法中,參數(shù)名稱與類成員變量名稱一致,this.成員名 = 參數(shù)名。在
getter/setter 方法中,盡量不要增加業(yè)務邏輯,增加排查問題的難度。
反例:
public Integer getData() {
if (true) {
return data + 100;
} else {
return data - 100; }
}
白話:
【推薦】循環(huán)體內(nèi),字符串的連接方式,使用 StringBuilder 的 append 方法進行擴展。
反例:
String str = "start";
for (int I = 0; I < 100; i++) {
str = str + "hello";
}
說明: 反編譯出的字節(jié)碼文件顯示每次循環(huán)都會 new 出一個 StringBuilder 對象,然后進行 append 操作,最后通過 toString 方法返回 String 對象,造成內(nèi)存資源浪費。
白話:
一定使用StringBuilder,不要使用StringBuffer,StringBuffer是線程安全的,太重。
【推薦】下列情況,聲明成 final 會更有提示性:
1) 不需要重新賦值的變量,包括類屬性、局部變量。
2) 對象參數(shù)前加final,表示不允許修改引用的指向。
3) 類方法確定不允許被重寫。
白話:
【推薦】慎用 Object 的 clone 方法來拷貝對象。
說明: 對象的 clone 方法默認是淺拷貝,若想實現(xiàn)深拷貝需要重寫 clone 方法實現(xiàn)屬性對象的拷貝。
白話:
【推薦】類成員與方法訪問控制從嚴:
1) 如果不允許外部直接通過new來創(chuàng)建對象,那么構(gòu)造方法必須是private。
2) 工具類不允許有public或default構(gòu)造方法。
3) 類非static成員變量并且與子類共享,必須是protected。
4) 類非static成員變量并且僅在本類使用,必須是private。
5) 類static成員變量如果僅在本類使用,必須是private。
6) 若是static成員變量,必須考慮是否為final。
7) 類成員方法只供類內(nèi)部調(diào)用,必須是private。
8) 類成員方法只對繼承類公開,那么限制為protected。
說明: 任何類、方法、參數(shù)、變量,嚴控訪問范圍。過寬泛的訪問范圍,不利于模塊解耦。
思考: 如果是一個 private 的方法,想刪除就刪除,可是一個 public 的 Service 方法,或者一個 public 的成員變量,刪除一下,不得手心冒點汗嗎?變量像自己的小孩,盡量在自己的視 線內(nèi),變量作用域太大,如果無限制的到處跑,那么你會擔心的。
白話:
【強制】關(guān)于 hashCode 和 equals 的處理,遵循如下規(guī)則:
1) 只要重寫equals,就必須重寫hashCode。
2) 因為Set存儲的是不重復的對象,依據(jù)hashCode和equals進行判斷,所以Set存儲的對象必須重寫這兩個方法。
3) 如果自定義對象做為Map的鍵,那么必須重寫hashCode和equals。
說明: String 重寫了 hashCode 和 equals 方法,所以我們可以非常愉快地使用 String 對象作為 key 來使用。
白話:
【強制】ArrayList的subList結(jié)果不可強轉(zhuǎn)成ArrayList,否則會拋出ClassCastException
異常: java.util.RandomAccessSubList cannot be cast to java.util.ArrayList ;
說明: subList 返回的是 ArrayList 的內(nèi)部類 SubList,并不是 ArrayList ,而是 ArrayList 的一個視圖,對于SubList子列表的所有操作最終會反映到原列表上。
白話:
【強制】 在 subList 場景中,高度注意對原集合元素個數(shù)的修改,會導致子列表的遍歷、增 加、刪除均產(chǎn)生ConcurrentModificationException 異常。
白話:
public ArrayList(Collection<? extends E> c)
。【強制】使用集合轉(zhuǎn)數(shù)組的方法,必須使用集合的toArray(T[] array),傳入的是類型完全 一樣的數(shù)組,大小就是 list.size()。
說明: 使用 toArray 帶參方法,入?yún)⒎峙涞臄?shù)組空間不夠大時,toArray 方法內(nèi)部將重新分配內(nèi)存空間,并返回新數(shù)組地址; 如果數(shù)組元素大于實際所需,下標為[ list.size() ]的數(shù)組元素將被置為 null,其它數(shù)組元素保持原值,因此最好將方法入?yún)?shù)組大小定義與集合元素個數(shù)一致。
正例:
List<String> list = new ArrayList<String>(2); list.add("guan");
list.add("bao");
String[] array = new String[list.size()];
array = list.toArray(array);
反例: 直接使用 toArray 無參方法存在問題,此方法返回值只能是 Object[]類,若強轉(zhuǎn)其它類型數(shù)組將出現(xiàn) ClassCastException 錯誤。
白話:
【強制】使用工具類 Arrays.asList()把數(shù)組轉(zhuǎn)換成集合時,不能使用其修改集合相關(guān)的方 法,它的 add/remove/clear 方法會拋出 UnsupportedOperationException 異常。
說明: asList 的返回對象是一個 Arrays 內(nèi)部類,并沒有實現(xiàn)集合的修改方法。Arrays.asList 體現(xiàn)的是適配器模式,只是轉(zhuǎn)換接口,后臺的數(shù)據(jù)仍是數(shù)組。
String[] str = new String[] { "a", "b" };
List list = Arrays.asList(str);
第一種情況: list.add("c");
運行時異常。
第二種情況: str[0] = "gujin";
那么list.get(0)也會隨之修改。
白話:
public ArrayList(Collection<? extends E> c)
構(gòu)造器。【強制】泛型通配符<? extends T>來接收返回的數(shù)據(jù),此寫法的泛型集合不能使用add方 法,而<? super T>不能使用get方法,做為接口調(diào)用賦值時易出錯。
說明: 擴展說一下PECS(Producer Extends Consumer Super)原則: 1)頻繁往外讀取內(nèi)容的,適合用上界 Extends。 2)經(jīng)常往里插入的,適合用下界 Super。
白話:
<? extends T>, ? 必須是T或T的子類
集合寫(add): 因為不能確定集合實例化時用的是T或T的子類,所以沒有辦法寫。例如:List<? extends Number> foo = new ArrayList<Number/Integer/Double>()
,你不能add Number,因為也可能是Integer或Double的List, 同理也不能add Integer或Double,即,extends T, 不能集合add。
集合讀(get): 只能讀出T類型的數(shù)據(jù)。
<? super T>, ? 必須是T或T的父類
集合寫(add): 可以add T或T的子類。
集合讀(get): 不能確定從集合里讀出的是哪個類型(可能是T也可能是T的父類,或者Object),所以沒有辦法使用get。例如:List<? super Integer> foo3 = new ArrayList<Integer/Number/Object>();
只能保證get出來是Object。
下面是示例,test1和test2在編譯時都有錯誤提示。
package com.robert.javaspec;
import java.util.LinkedList;
import java.util.List;
/**
* Created by WangMeng on 2017-04-13.
* FIX ME
*/
public class Main {
public static void main(String[] args) {
}
public void test1(){
List<? extends A> childofa=new LinkedList<>();
B b=new B();
A a=new A();
childofa.add(a);
childofa.add(b);
A ta= childofa.get(0);
}
public void test2(){
List<? super B> superOfb = new LinkedList<>();
B b = new B();
A a = new A();
superOfb.add(a);
superOfb.add(b);
A ta = superOfb.get(0);
B tb = superOfb.get(0);
}
}
class A {
@Override
public String toString() {
return "A";
}
}
class B extends A {
@Override
public String toString() {
return "B";
}
}
【強制】不要在 foreach 循環(huán)里進行元素的 remove/add 操作。remove 元素請使用 Iterator 方式,如果并發(fā)操作,需要對 Iterator 對象加鎖。
反例:
List<String> a = new ArrayList<String>();
a.add("1");
a.add("2");
for (String temp : a) {
if ("1".equals(temp)) {
a.remove(temp);
}
}
說明: 以上代碼的執(zhí)行結(jié)果肯定會出乎大家的意料,那么試一下把“1”換成“2”,會是同樣的
結(jié)果嗎?
正例:
Iterator<String> it = a.iterator();
while (it.hasNext()) {
String temp = it.next();
if (刪除元素的條件) {
it.remove();
}
}
白話:
【強制】 在 JDK7 版本及以上,Comparator 要滿足如下三個條件,不然 Arrays.sort, Collections.sort 會報 IllegalArgumentException 異常。
說明:
1) x,y的比較結(jié)果和y,x的比較結(jié)果相反。
2) x>y,y>z,則x>z。
3) x=y,則x,z比較結(jié)果和y,z比較結(jié)果相同。
反例: 下例中沒有處理相等的情況,實際使用中可能會出現(xiàn)異常:
new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getId() > o2.getId() ? 1 : -1;
}
}
白話:
【推薦】集合初始化時,盡量指定集合初始值大小。
說明: ArrayList盡量使用ArrayList(int initialCapacity) 初始化。
白話:
預估數(shù)組大小,能夠提高程序效率,寫代碼的時候腦袋里面要有運行的思想。
【推薦】使用 entrySet 遍歷 Map 類集合 KV,而不是 keySet 方式進行遍歷。
說明: keySet 其實是遍歷了 2 次,一次是轉(zhuǎn)為 Iterator 對象,另一次是從 hashMap 中取出 key 所對應的 value。而 entrySet 只是遍歷了一次就把 key 和 value 都放到了 entry 中,效率更高。如果是 JDK8,使用 Map.foreach 方法。
正例: values()返回的是 V 值集合,是一個 list 集合對象;keySet()返回的是 K 值集合,是一個 Set 集合對象; entrySet()返回的是 K-V 值組合集合。
白話:
【推薦】高度注意 Map 類集合 K/V 能不能存儲 null 值的情況,如下表格:
集合類 | Key | Value | Super | 說明 |
---|---|---|---|---|
Hashtable | 不允許為 null | 不允許為 null | Dictionary | 線程安全 |
ConcurrentHashMap | 不允許為 null | 不允許為 null | AbstractMap | 分段鎖技術(shù) |
TreeMap | 不允許為 null | 允許為 null | AbstractMap | 線程不安全 |
HashMap | 允許為 null | 允許為 null | AbstractMap | 線程不安全 |
反例: 由于 HashMap 的干擾,很多人認為 ConcurrentHashMap 是可以置入 null 值,注意存儲 null 值時會拋出 NPE 異常。
白話:
【參考】合理利用好集合的有序性(sort)和穩(wěn)定性(order),避免集合的無序性(unsort)和 不穩(wěn)定性(unorder)帶來的負面影響。
說明: 有序性是指遍歷的結(jié)果是按某種比較規(guī)則依次排列的。穩(wěn)定性指集合每次遍歷的元素次 序是一定的。如:ArrayList 是 order/unsort;HashMap 是 unorder/unsort;TreeSet 是 order/sort。
白話:
數(shù)值:
HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
map.put(3, 3);
map.put(1, 1);
map.put(2, 2);
map.put(4, 4);
for (Entry<Integer, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey());
}
事實證明,每次輸出也是1、2、3、4,有序并且穩(wěn)定的。
字符串值:
HashMap<String, String> map = new HashMap<String, String>();
map.put("3000", "3");
map.put("1000", "1");
map.put("2000", "2");
map.put("4000", "4");
for (Entry<Integer, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey());
}
事實證明,每次輸出也是4000、1000、2000、3000,無序但是穩(wěn)定的。
與阿里專家咨詢,這里HashMap不穩(wěn)定性是指rehash后輸出順序則會變化。
【參考】利用 Set 元素唯一的特性,可以快速對一個集合進行去重操作,避免使用 List 的 contains 方法進行遍歷、對比、去重操作。
白話:
【強制】獲取單例對象需要保證線程安全,其中的方法也要保證線程安全。
說明: 資源驅(qū)動類、工具類、單例工廠類都需要注意。
白話:
【強制】創(chuàng)建線程或線程池時請指定有意義的線程名稱,方便出錯時回溯。
正例:
public class TimerTaskThread extends Thread {
public TimerTaskThread() {
super.setName("TimerTaskThread");
...
}
}
白話:
【強制】線程資源必須通過線程池提供,不允許在應用中自行顯式創(chuàng)建線程。
說明: 使用線程池的好處是減少在創(chuàng)建和銷毀線程上所花的時間以及系統(tǒng)資源的開銷,解決資 源不足的問題。如果不使用線程池,有可能造成系統(tǒng)創(chuàng)建大量同類線程而導致消耗完內(nèi)存或者 “過度切換”的問題。
白話:
【強制】線程池不允許使用 Executors 去創(chuàng)建,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規(guī)則,規(guī)避資源耗盡的風險。
說明: Executors 返回的線程池對象的弊端如下:
1)FixedThreadPool 和 SingleThreadPool: 允許的請求隊列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。
2)CachedThreadPool 和 ScheduledThreadPool: 允許的創(chuàng)建線程數(shù)量為 Integer.MAX_VALUE,可能會創(chuàng)建大量的線程,從而導致 OOM。
白話:
線程池如果沒有限制最大數(shù)量,線程池撐開的時候,由于內(nèi)存不夠或者系統(tǒng)配置的最大線程數(shù)超出,都會產(chǎn)生oom: unalbe to create native thread。
正例: :注意線程安全,使用 DateUtils。亦推薦如下處理::
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
說明:如果是 JDK8 的應用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar, DateTimeFormatter 代替 Simpledateformatter,官方給出的解釋: simple beautiful strong immutable thread-safe。
***白話:***
- 記住,打死你,我也不會把SimpleDateFormat共享到類中。
【強制】高并發(fā)時,同步調(diào)用應該去考量鎖的性能損耗。能用無鎖數(shù)據(jù)結(jié)構(gòu),就不要用鎖; 能鎖區(qū)塊,就不要鎖整個方法體; 能用對象鎖,就不要用類鎖。
白話:
【強制】對多個資源、數(shù)據(jù)庫表、對象同時加鎖時,需要保持一致的加鎖順序,否則可能會造 成死鎖。
說明: 線程一需要對表 A、B、C 依次全部加鎖后才可以進行更新操作,那么線程二的加鎖順序也必須是 A、B、C,否則可能出現(xiàn)死鎖。
白話:
解決死鎖的方法:按順序鎖資源、超時、優(yōu)先級、死鎖檢測等。
【強制】并發(fā)修改同一記錄時,避免更新丟失,需要加鎖。要么在應用層加鎖,要么在緩存加 鎖,要么在數(shù)據(jù)庫層使用樂觀鎖,使用 version 作為更新依據(jù)。
說明: 如果每次訪問沖突概率小于 20%,推薦使用樂觀鎖,否則使用悲觀鎖。樂觀鎖的重試次 數(shù)不得小于 3 次。
白話:
【強制】多線程并行處理定時任務時,Timer 運行多個 TimerTask 時,只要其中之一沒有捕獲拋出的異常,其它任務便會自動終止運行,使用 ScheduledExecutorService 則沒有這個問題。
白話:
【推薦】使用 CountDownLatch 進行異步轉(zhuǎn)同步操作,每個線程退出前必須調(diào)用 countDown 方法,線程執(zhí)行代碼注意 catch 異常,確保 countDown 方法可以執(zhí)行,避免主線程無法執(zhí)行至 await 方法,直到超時才返回結(jié)果。
說明: 注意,子線程拋出異常堆棧,不能在主線程 try-catch 到。
白話:
【推薦】避免 Random 實例被多線程使用,雖然共享該實例是線程安全的,但會因競爭同一 seed 導致的性能下降。
說明: Random 實例包括 java.util.Random 的實例或者 Math.random()實例。
正例: 在 JDK7 之后,可以直接使用 API ThreadLocalRandom,在 JDK7 之前,可以做到每個線程一個實例。
白話:
【推薦】在并發(fā)場景下,通過雙重檢查鎖(double-checked locking)實現(xiàn)延遲初始化的優(yōu) 化問題隱患(可參考 The "Double-Checked Locking is Broken" Declaration),推薦問 題解決方案中較為簡單一種(適用于 JDK5 及以上版本),將目標屬性聲明為 volatile 型。
反例:
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null)
synchronized(this) {
if (helper == null)
helper = new Helper();
}
return helper;
}
// other functions and members...
}
白話:
【參考】volatile 解決多線程內(nèi)存不可見問題。對于一寫多讀,是可以解決變量同步問題, 但是如果多寫,同樣無法解決線程安全問題。如果是 count++操作,使用如下類實現(xiàn): AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 如果是 JDK8,推薦使用 LongAdder 對象,比 AtomicLong 性能更好(減少樂觀鎖的重試次數(shù))。
白話:
【參考】 HashMap 在容量不夠進行 resize 時由于高并發(fā)可能出現(xiàn)死鏈,導致 CPU 飆升,在開發(fā)過程中注意規(guī)避此風險。
白話:
開發(fā)程序的時候要預估使用量,根據(jù)使用量來設置初始值。
【參考】ThreadLocal 無法解決共享對象的更新問題,ThreadLocal 對象建議使用 static 修飾。這個變量是針對一個線程內(nèi)所有操作共有的,所以設置為靜態(tài)變量,所有此類實例共享此靜態(tài)變量 ,也就是說在類第一次被使用時裝載,只分配一塊存儲空間,所有此類的對象(只 要是這個線程內(nèi)定義的)都可以操控這個變量。
白話:
ThreadLocal實際上是一個從線程ID到變量的Map,每次取得ThreadLocal變量,實際上是先取得當前線程ID,再用當前線程ID取得關(guān)聯(lián)的變量。
【強制】在一個 switch 塊內(nèi),每個 case 要么通過 break/return 等來終止,要么注釋說明程序?qū)⒗^續(xù)執(zhí)行到哪一個 case 為止;在一個 switch 塊內(nèi),都必須包含一個 default 語句并且放在最后,即使它什么代碼也沒有。
白話:
【強制】在 if/else/for/while/do 語句中必須使用大括號,即使只有一行代碼,避免使用 下面的形式:if (condition) statements;
白話:
【推薦】推薦盡量少用 else, if-else 的方式可以改寫成:
if (condition) { ...
return obj;
}
// 接著寫 else 的業(yè)務邏輯代碼;
說明: 如果非得使用if()...else if()...else...方式表達邏輯,【強制】請勿超過3層, 超過請使用狀態(tài)設計模式。
正例: 邏輯上超過 3 層的 if-else 代碼可以使用衛(wèi)語句,或者狀態(tài)模式來實現(xiàn)。
白話:
朋友說超過三層考慮狀態(tài)設計模式也不完全正確,大概可以理解為多層的邏輯嵌套不是好的代碼風格,需要使用對應的重構(gòu)方法做出優(yōu)化,而每種壞味都有對應的優(yōu)化方法和步驟,以及優(yōu)缺點限制條件。
public void handleProcess() {
// 骨架邏輯
validate();
doProcess();
declareResource();
}
public double getPayAmount() {
if (isDead()) return deadPayAmount();
if (isSeparated()) return separatedPayAmount();
if (isRetired()) return retiredPayAmount();
return normalPayAmount();
}
不提倡的寫法:
public double getPayAmount() {
if (isDead())
return deadPayAmount();
else if (isSeparated())
return separatedPayAmount();
else if (isRetired())
return retiredPayAmount();
else
return normalPayAmount();
}
【推薦】除常用方法(如 getXxx/isXxx)等外,不要在條件判斷中執(zhí)行其它復雜的語句,將復 雜邏輯判斷的結(jié)果賦值給一個有意義的布爾變量名,以提高可讀性。
說明: 很多 if 語句內(nèi)的邏輯相當復雜,閱讀者需要分析條件表達式的最終結(jié)果,才能明確什么 樣的條件執(zhí)行什么樣的語句,那么,如果閱讀者分析邏輯表達式錯誤呢?
正例:
//偽代碼如下
boolean existed = (file.open(fileName, "w") != null) && (...) || (...);
if (existed) {
...
}
反例:
if ((file.open(fileName, "w") != null) && (...) || (...)) { ...}
白話:
【推薦】循環(huán)體中的語句要考量性能,以下操作盡量移至循環(huán)體外處理,如定義對象、變量、
獲取數(shù)據(jù)庫連接,進行不必要的 try-catch 操作(這個 try-catch 是否可以移至循環(huán)體外)。
白話:
【推薦】接口入?yún)⒈Wo,這種場景常見的是用于做批量操作的接口。
白話:
【參考】方法中需要進行參數(shù)校驗的場景:
1) 調(diào)用頻次低的方法。
2) 執(zhí)行時間開銷很大的方法,參數(shù)校驗時間幾乎可以忽略不計,但如果因為參數(shù)錯誤導致
中間執(zhí)行回退,或者錯誤,那得不償失。
3) 需要極高穩(wěn)定性和可用性的方法。
4) 對外提供的開放接口,不管是RPC/API/HTTP接口。
5) 敏感權(quán)限入口。
白話:
【參考】方法中不需要參數(shù)校驗的場景:
1) 極有可能被循環(huán)調(diào)用的方法,不建議對參數(shù)進行校驗。但在方法說明里必須注明外部參
數(shù)檢查要求。
2) 底層的方法調(diào)用頻度都比較高,一般不校驗。畢竟是像純凈水過濾的最后一道,參數(shù)錯
誤不太可能到底層才會暴露問題。一般 DAO 層與 Service 層都在同一個應用中,部署在同一 臺服務器中,所以 DAO 的參數(shù)校驗,可以省略。
3) 被聲明成private只會被自己代碼所調(diào)用的方法,如果能夠確定調(diào)用方法的代碼傳入?yún)?shù)已經(jīng)做過檢查或者肯定不會有問題,此時可以不校驗參數(shù)。
白話:
【強制】類、類屬性、類方法的注釋必須使用 Javadoc 規(guī)范,使用/*內(nèi)容/格式,不得使用 //xxx 方式。
說明:在 IDE 編輯窗口中,Javadoc 方式會提示相關(guān)注釋,生成 Javadoc 可以正確輸出相應注 釋; 在 IDE 中,工程調(diào)用方法時,不進入方法即可懸浮提示方法、參數(shù)、返回值的意義,提高閱讀效率。
白話:
【強制】所有的抽象方法(包括接口中的方法)必須要用 Javadoc 注釋、除了返回值、參數(shù)、 異常說明外,還必須指出該方法做什么事情,實現(xiàn)什么功能。
說明: 對子類的實現(xiàn)要求,或者調(diào)用注意事項,請一并說明。
白話:
【強制】所有的類都必須添加創(chuàng)建者信息。
白話:
【強制】方法內(nèi)部單行注釋,在被注釋語句上方另起一行,使用//注釋。方法內(nèi)部多行注釋
使用/ /注釋,注意與代碼對齊。
白話:
【強制】所有的枚舉類型字段必須要有注釋,說明每個數(shù)據(jù)項的用途。
白話:
【推薦】與其“半吊子”英文來注釋,不如用中文注釋把問題說清楚。專有名詞與關(guān)鍵字保持英文原文即可。
反例:“TCP 連接超時”解釋成“傳輸控制協(xié)議連接超時”,理解反而費腦筋。
白話:
【推薦】代碼修改的同時,注釋也要進行相應的修改,尤其是參數(shù)、返回值、異常、核心邏輯 等的修改。
說明: 代碼與注釋更新不同步,就像路網(wǎng)與導航軟件更新不同步一樣,如果導航軟件嚴重滯后, 就失去了導航的意義。
白話:
【參考】注釋掉的代碼盡量要配合說明,而不是簡單的注釋掉。
說明: 代碼被注釋掉有兩種可能性:1)后續(xù)會恢復此段代碼邏輯。2)永久不用。前者如果沒 有備注信息,難以知曉注釋動機。后者建議直接刪掉(代碼倉庫保存了歷史代碼)。
白話:
【參考】對于注釋的要求: 第一、能夠準確反應設計思想和代碼邏輯;第二、能夠描述業(yè)務含義,使別的程序員能夠迅速了解到代碼背后的信息。完全沒有注釋的大段代碼對于閱讀者形同天書,注釋是給自己看的,即使隔很長時間,也能清晰理解當時的思路;注釋也是給繼任者看的,使其能夠快速接替自己的工作。
白話:
【參考】好的命名、代碼結(jié)構(gòu)是自解釋的,注釋力求精簡準確、表達到位。避免出現(xiàn)注釋的
一個極端: 過多過濫的注釋,代碼的邏輯一旦修改,修改注釋是相當大的負擔。
反例:
// put elephant into fridge
put(elephant, fridge);
方法名put,加上兩個有意義的變量名 elephant 和 fridge,已經(jīng)說明了這是在干什么,語
義清晰的代碼不需要額外的注釋。
白話:
【參考】特殊注釋標記,請注明標記人與標記時間。注意及時處理這些標記,通過標記掃描, 經(jīng)常清理此類標記。線上故障有時候就是來源于這些標記處的代碼。
1) 待辦事宜(TODO):( 標記人,標記時間,[預計處理時間]) 表示需要實現(xiàn),但目前還未實現(xiàn)的功能。這實際上是一個 Javadoc 的標簽,目前的 Javadoc
還沒有實現(xiàn),但已經(jīng)被廣泛使用。只能應用于類,接口和方法(因為它是一個 Javadoc 標簽)。
2) 錯誤,不能工作(FIXME):(標記人,標記時間,[預計處理時間])
在注釋中用 FIXME 標記某代碼是錯誤的,而且不能工作,需要及時糾正的情況。
白話:
【強制】在使用正則表達式時,利用好其預編譯功能,可以有效加快正則匹配速度。
說明: 不要在方法體內(nèi)定義:Pattern pattern = Pattern.compile(規(guī)則);
白話:
【強制】velocity 調(diào)用 POJO 類的屬性時,建議直接使用屬性名取值即可,模板引擎會自動按規(guī)范調(diào)用 POJO 的 getXxx(),如果是 boolean 基本數(shù)據(jù)類型變量(boolean 命名不需要加 is 前綴),會自動調(diào)用 isXxx()方法。
說明: 注意如果是 Boolean 包裝類對象,優(yōu)先調(diào)用 getXxx()的方法。
白話:
【強制】后臺輸送給頁面的變量必須加$!{var}——中間的感嘆號。 說明:如果 var=null 或者不存在,那么${var}會直接顯示在頁面上。
白話:
【強制】注意 Math.random() 這個方法返回是 double 類型,注意取值的范圍 0≤x小于1(能夠取到零值,注意除零異常),如果想獲取整數(shù)類型的隨機數(shù),不要將 x 放大 10 的若干倍然后 取整,直接使用 Random 對象的 nextInt 或者 nextLong 方法。
白話:
【強制】獲取當前毫秒數(shù) System.currentTimeMillis(); 而不是 new Date().getTime();
說明: 如果想獲取更加精確的納秒級時間值,用 System.nanoTime()。在 JDK8 中,針對統(tǒng)計時間等場景,推薦使用 Instant 類。
白話:
【推薦】盡量不要在 velocity 模板中加入變量聲明、邏輯運算符,更不要在模板中加入任何復雜的邏輯。
白話:
【推薦】任何數(shù)據(jù)結(jié)構(gòu)的構(gòu)造或初始化,都應指定大小,避免數(shù)據(jù)結(jié)構(gòu)無限增長吃光內(nèi)存。
白話:
【推薦】對于“明確停止使用的代碼和配置”,如方法、變量、類、配置文件、動態(tài)配置屬性
等要堅決從程序中清理出去,避免造成過多垃圾。
白話:
在總結(jié)的過程中,又對阿里的每一條規(guī)范進行了深入的思考,并與阿里人員以及業(yè)內(nèi)Java專家進行了討論,對難于理解的條目進行了說明,并進行了適當?shù)男畔⒀a充,用白話的形式表達出來,能夠感覺到阿里開發(fā)規(guī)范的每一條都是線上大規(guī)模服務化系統(tǒng)中的踩坑指南,這和筆者正在做的事情一樣,線上出了問題會復盤,復盤后找到問題的原因,總結(jié)歸納,并在研發(fā)流程規(guī)范中體現(xiàn)解決方案和避免措施,然后推廣給開發(fā)人員,讓大家在今后的開發(fā)中輕而易舉的避免犯同樣的錯誤,從而避免發(fā)生同樣的生產(chǎn)事故,關(guān)于生產(chǎn)事故,向大家推薦電影《深海浩劫》。
本文為五篇系列文章的第一篇-編程規(guī)約,后續(xù)會陸續(xù)發(fā)布其余的文章,內(nèi)容包括:異常日志、MySQL規(guī)約、工程規(guī)約、安全規(guī)約等。
本文歡迎轉(zhuǎn)載,轉(zhuǎn)載請注明原文鏈接,并附作者個人信息李艷鵬
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。