寫不好規(guī)范Java代碼怎么去大廠
話不多說(shuō)直接上干貨,你我共勉。
1. 構(gòu)造器參數(shù)太多怎么辦
解決辦法 :引入Builder模式 場(chǎng)景:當(dāng)構(gòu)造器有5個(gè)或者以上的構(gòu)造參數(shù)時(shí)或者目前參數(shù)不多但是以后會(huì)不斷增多的時(shí)候。demo 如下:
public class Computer {
protected String mBoard;
protected String mDisplay;
protected String mOs;
private Computer(Builder builder) {
this.mOs = builder.mOs;
this.mBoard = builder.mBoard;
this.mDisplay = builder.mDisplay;
}
@Override
public String toString() {
return "Computer{" +
"mBoard='" + mBoard + '\'' +
", mDisplay='" + mDisplay + '\'' +
", mOs='" + mOs + '\'' +
'}';
}
static class Builder {
protected String mBoard;
protected String mDisplay;
protected String mOs;
public Builder setmOs(String mOs) {
this.mOs = mOs;
return this;
}
public Builder setmBoard(String mBoard) {
this.mBoard = mBoard;
return this;
}
public Builder setmDisplay(String mDisplay) {
this.mDisplay = mDisplay;
return this;
}
public Computer build() {
return new Computer(this);
}
}
}
調(diào)用的時(shí)候如下:
public static void main(String[] args) {
Computer macbook = new Computer.Builder()
.setmBoard("board")
.setmDisplay("sowhat")
.setmOs("Mac")
.build();
System.out.println(macbook);
}
2. 不需要實(shí)例化的類構(gòu)造器要私有化
經(jīng)常用到的Utils類,比如Java自帶的java.util.Arrays 這樣的類,工具類都盡量不要實(shí)例化。
public class Arrays {
private static final int MIN_ARRAY_SORT_GRAN = 8192;
private static final int INSERTIONSORT_THRESHOLD = 7;
private Arrays() {
}
}
3. 不要?jiǎng)?chuàng)建不必要對(duì)象
能用基本類型到時(shí)候盡量要用基本類型。比如我們做數(shù)字運(yùn)算,如果定義稱了個(gè)Long 類型,會(huì)涉及到==自動(dòng)裝箱==。大大耗時(shí)。 對(duì)于一些程序中共用的參數(shù)盡量設(shè)置為static類型變量。 對(duì)于一些耗時(shí)性較大的對(duì)象比如數(shù)據(jù)庫(kù)連接,盡量創(chuàng)建數(shù)據(jù)庫(kù)連接池。 對(duì)于一些占據(jù)內(nèi)存較大的對(duì)象也盡量少創(chuàng)建。因?yàn)樵趀den區(qū)來(lái)回倒騰,它累??!。
public class Sum {
public static void main(String[] args) {
long start = System.currentTimeMillis();
Long sum = 0L; //對(duì)象
for(long i=0;i<Integer.MAX_VALUE;i++) {
sum = sum+i;
//new 20多億的Long的實(shí)例
}
System.out.println("spend time:"+(System.currentTimeMillis()-start)+"ms");
}
}
耗時(shí):spend time:7042ms
public class Sum {
public static void main(String[] args) {
long start = System.currentTimeMillis();
long sum = 0L; //基本類型
for(long i=0;i<Integer.MAX_VALUE;i++) {
sum = sum+i;
}
System.out.println("spend time:"+(System.currentTimeMillis()-start)+"ms");
}
}
耗時(shí):spend time:820ms
4. 避免使用終結(jié)方法
finalze方法:
finalize()是Object的protected方法,子類可以覆蓋該方法以實(shí)現(xiàn)==資源清理==工作,GC在回收對(duì)象之前調(diào)用該方法。 finalize()與C++中的析構(gòu)函數(shù)不是對(duì)應(yīng)的,但Java中的finalize的調(diào)用具有==不確定性==。 finalize方法在垃圾回收器準(zhǔn)備垃圾回收前被調(diào)用,但是==不一定==會(huì)被調(diào)用 4.finalize()其實(shí)是用來(lái)釋放==不是==通過(guò)java的new關(guān)鍵字分配的內(nèi)存,比如說(shuō)通過(guò)本地方法調(diào)用了c程序,該c程序malloc分配了內(nèi)存,那么垃圾回收器就不能通過(guò)java語(yǔ)言來(lái)釋放內(nèi)存,只能在finalize方法內(nèi)通過(guò)本地方法調(diào)用c程序進(jìn)行釋放內(nèi)存。
一個(gè)對(duì)象要被回收要經(jīng)過(guò)一次標(biāo)記可達(dá)法已經(jīng)兩次check才算死亡??偨Y(jié)來(lái)說(shuō):finalize()并不是必須要執(zhí)行的,它只能執(zhí)行一次或者0次。如果在finalize中建立對(duì)象關(guān)聯(lián),則當(dāng)前對(duì)象可以復(fù)活一次
System.gc 用 System.gc()的時(shí)候,其實(shí)并不會(huì)馬上進(jìn)行垃圾回收,甚至不一定會(huì)執(zhí)行垃圾回收。查看System.gc()的源碼可以看到只有當(dāng)justRanFinalization=true的時(shí)候系統(tǒng)才會(huì)真正GC。如果真要回收查看源碼知道System.gc()要跟System.runFinalization()一起搭配使用才好。
5. 類跟成員的可訪問(wèn)性最小化
這個(gè)說(shuō)白了就是設(shè)計(jì)模式中的迪米特法則。定義:要求一個(gè)對(duì)象應(yīng)該對(duì)其他對(duì)象有最少的了解,所以迪米特法則又叫做最少知識(shí)原則(Least Knowledge Principle, LKP)。
意義:迪米特法則的意義在于降低類之間的耦合。由于每個(gè)對(duì)象盡量減少對(duì)其他對(duì)象的了解,因此,很容易使得系統(tǒng)的功能模塊功能獨(dú)立,相互之間不存在(或很少有)依賴關(guān)系,日常最常見的比如成員變量私有化。
6. 使可變性最小化
盡可能的使用final 來(lái)修飾一些變量,這樣的化線程操作就不需要考慮同步問(wèn)題,同時(shí)也對(duì)于一個(gè)變量不同setX方法也 OK。
7. 優(yōu)先使用復(fù)合勝過(guò)繼承
繼承是實(shí)現(xiàn)代碼重用的有力手段,但是使用不當(dāng)會(huì)導(dǎo)致軟件變得脆弱。在包的內(nèi)部使用繼承是非常安全的,子類和超類的實(shí)現(xiàn)都處在同一個(gè)程序員的控制之下。對(duì)于專門為了繼承而設(shè)計(jì)、并且具有很好的文檔說(shuō)明的類來(lái)說(shuō),使用繼承也是非常安全的。然而們對(duì)于進(jìn)行跨越包邊界的繼承,則要非常小心?!袄^承”在這里特指一個(gè)類擴(kuò)展另一個(gè)類。需要我們對(duì)父類十分對(duì)了解才可繼承,只有當(dāng)子類和超類之間==確實(shí)存在==父子關(guān)系時(shí),才可以考慮使用繼承。否則都應(yīng)該用復(fù)合,包裝類不僅比子類更加健壯,而且功能也更加強(qiáng)大。Effective Java
8.接口優(yōu)于抽象類
簡(jiǎn)而言之,Java只允許單繼承但是允許實(shí)現(xiàn)多個(gè)接口。通過(guò)接口擴(kuò)充方法很簡(jiǎn)單,這樣也復(fù)合設(shè)計(jì)模式中的開閉原則。接口可以簡(jiǎn)單的理解比抽象類還要抽象的一層,是我們對(duì)外提供的接口。同時(shí)這里提一下==骨架抽象類==。
骨架類存在的意義是實(shí)現(xiàn)一個(gè)接口中設(shè)計(jì)者認(rèn)為比較公用的方法,然后在具體類實(shí)現(xiàn)的時(shí)候,具體類繼承自骨架類同時(shí)實(shí)現(xiàn)接口類的若干方法。
HashSet源碼
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
看Set源碼
public interface Set<E> extends Collection<E> {
}
AbstractSet源碼
public abstract class AbstractSet<E> extends
AbstractCollection<E> implements Set<E> //
AbstractSet抽象類有若干函數(shù)已經(jīng)實(shí)現(xiàn)了。
HashSet實(shí)現(xiàn)了Set<E>的接口,同時(shí)它還繼承自AbstractSet<E>這個(gè)骨架類(類中有一些公用方法)
9. 可變參數(shù)謹(jǐn)慎使用
JDK5增加了可變參數(shù)方法(variable arity method),可變參數(shù)方法接受0個(gè)或多個(gè)指定類型的參數(shù)。可變參數(shù)機(jī)制:先創(chuàng)建一個(gè)數(shù)組,數(shù)組大小為調(diào)用位置所傳遞的參數(shù)數(shù)量,然后將參數(shù)值傳遞到數(shù)組中,最后將數(shù)組傳遞給方法。弊端如下:
1.如果所傳參數(shù)為null,方法里有對(duì)參數(shù)的引用(比如 args[0])時(shí),那么就會(huì)在運(yùn)行時(shí)失?。ň幾g時(shí)卻檢測(cè)不出錯(cuò)誤);2.在對(duì)性能有要求時(shí),我們要慎重考慮是否使用可變參數(shù)。因?yàn)?,可變參?shù)方法的調(diào)用都會(huì)引起array的內(nèi)存分配和初始化,這會(huì)給性能帶來(lái)?yè)p耗。3. 當(dāng)可變參數(shù)的使用發(fā)生變動(dòng)時(shí)(比如:以前用可變參數(shù)方法,現(xiàn)在用普通方法),所有引用參數(shù)列表的類的.class都要重新生成,因?yàn)榭勺儏?shù)的array的分配和初始化是在編譯期間完成的。
Effective Java給我們的建議是:假設(shè)調(diào)用可變參數(shù)的方法中,有95%只是調(diào)用參數(shù)個(gè)數(shù)小于4,那么就可以將個(gè)數(shù)小于4的方法,用普通方法定義;剩余的5%,調(diào)用可變參數(shù)方法。
public void foo() {}
public void foo(int a1) {}
public void foo(int a1, int a2) {}
public void foo(int a1, int a2, int a3) {}
public void foo(int a1, int a2, int a3, int... rest) {}
10. 盡量不要返回NULL,盡量返回零數(shù)組或集合
函數(shù)中如果返回NULL,那么代碼還要去重新的判斷返回值。JDK都主動(dòng)給我們提供好了Collections.EMPTY_LIST。當(dāng)然如果是對(duì)象就只能是NULL了。阿里巴巴開發(fā)手冊(cè)也是這樣建議的哦。
1.返回值為null并不會(huì)有什么問(wèn)題,但是在“解引用”(dereference)時(shí),調(diào)用者沒(méi)有對(duì)null進(jìn)行判斷就會(huì)出現(xiàn)NullPointerException。2. 在返回值為數(shù)組或者集合時(shí),盡量返回長(zhǎng)度為零的數(shù)組或者集合,而不是null,這樣在調(diào)用時(shí)就能簡(jiǎn)化代碼,減少不必要的麻煩,并且不必?fù)?dān)心NullPointer異常(除非這對(duì)性能會(huì)造成很大的影響)。
11.優(yōu)先使用標(biāo)準(zhǔn)異常
總結(jié)來(lái)說(shuō)好處就是:追求代碼的重用考慮,在裝載類的性能上面考慮。具體細(xì)節(jié)如下:
它使你對(duì)官方API更加易于學(xué)習(xí)和使用,因?yàn)樗c程序員已經(jīng)熟悉的習(xí)慣用法是一致的。 對(duì)于用到這些API的程序而言,他們的可讀性會(huì)更好,因?yàn)樗麄儾粫?huì)出現(xiàn)很多程序員不熟悉的異常。 異常類越少,意味著內(nèi)存印跡就越小,裝載這些類的時(shí)間開銷也越少。
12.盡量使用枚舉替換int
枚舉的本質(zhì)就是一個(gè)類的具體實(shí)例。枚舉比普通業(yè)務(wù)類型int類型的區(qū)別跟好處,以及策略枚舉的使用,這些以前寫給直接看==>花樣玩枚舉
13. 局部變量作用域最小化
從系統(tǒng)GC的角度考慮(一個(gè)變量的周期越短整個(gè)gc過(guò)程越快)。2.從棧楨中的局部變量表的可重用性來(lái)看,作用域越小系統(tǒng)的棧楨空間利用了越大。 可以增強(qiáng)代碼的可讀性和可維護(hù)性,并降低出錯(cuò)的可能性。
應(yīng)該:
在第一次使用某個(gè)局部變量的地方進(jìn)行聲明。 如果你還沒(méi)有足夠的前置信息來(lái)對(duì)一個(gè)變量進(jìn)行有意義的初始化,就應(yīng)該推遲這個(gè)聲明,直到可以初始化為止。 盡量將方法小而集中。方法的功能盡量單一。
14. 對(duì)于精度技術(shù)不用float或double
Java在允許float或者double類之間計(jì)算的時(shí)候會(huì)有誤差。
double a = 0.2;
double b = 0.1;
double c = a+ b;
System.out.println(c);
-----
0.30000000000000004
要用:BigDecimal 或者FloatDecimal。具體類用法自行百度。
15.字符串操作少用String
這點(diǎn)操作是大家都知道的,String定義為private final byte[] value;是不可變的。StringBuilder 和 StringBuffer都繼承于:AbstractStringBuilder他們的底層使用的是沒(méi)有用final修飾的字符數(shù)組:char[]

1.如果要操作少量的數(shù)據(jù)用 String;
2. 多線程操作字符串緩沖區(qū)下操作大量數(shù)據(jù) StringBuffer;
3. 單線程操作字符串緩沖區(qū)下操作大量數(shù)據(jù) StringBuilder。
16.對(duì)資源的close建議分開操作
比如說(shuō)我們有這樣的一個(gè)close方法,
try{
a.close()
b.close()
}catch(Exception e){
...
}
上面這樣寫一旦a關(guān)閉的時(shí)候出錯(cuò)了,b的關(guān)閉也會(huì)出錯(cuò)。盡量分開來(lái)關(guān)閉。
try{
a.close()
}catch(Exception e){
...
}
try{
b.close()
}catch(Exception e){
...
}
17. 數(shù)據(jù)類型轉(zhuǎn)換
基本數(shù)據(jù)類型轉(zhuǎn)換為String的時(shí)候注意性能優(yōu)化,比如Integer類型數(shù)據(jù)轉(zhuǎn)換為String一般有三種方法
Integer.toString方法,首先推薦。速度最好 String.valueOf() ,該方法底層調(diào)用的是Integer.toString方法,速度中等。 i + "" 這種方法,底層是用StringBuilder實(shí)現(xiàn),先用append方法拼接,再用toString方法獲取字符串。速度最慢。
18. 不用的對(duì)象記得置NULL
我們不用一個(gè)空間對(duì)象后而沒(méi)有將其置NULL,JDK底層代碼對(duì)用不到的對(duì)象都會(huì)立馬置空,如果不這樣容易造成內(nèi)存泄露,比如我自己實(shí)現(xiàn)了一個(gè)棧
public class Stack {
public Object[] elements;
private int size = 0; //指示器,用來(lái)指示當(dāng)前棧頂?shù)奈恢?/span>
private static final int Cap = 16;
public Stack() {
elements = new Object[Cap];
}
//入棧
public void push(Object e){
elements[size] = e;
size++;
}
//出棧
public Object pop(){
size = size-1;
Object object = elements[size];
elements[size] = null; // 這里很重要 JDK底層都是這樣實(shí)現(xiàn)的,不用了,即使置NULL
return object;
}
}
19. if判斷常量在前
if(i==1)跟if(1==i)看起來(lái)沒(méi)有差別,但是從容錯(cuò)性上面來(lái)考慮,如果手誤寫成了if(i=1)就是賦值語(yǔ)句了,而if(1=i)則不會(huì)出現(xiàn)這樣的錯(cuò)誤。因此if判斷建議常量在前,變量在后。
20. 字符串變量比較的時(shí)候
str.equal("sowhat")跟“sowhat”.equal(str)功能上看是一樣的,但是從代碼的健壯性來(lái)看推薦后者,因?yàn)槟銦o(wú)法確實(shí)str 一定是非空,可以避免空指針異常。
21. 同步方法跟同步方法塊
盡量使用同步方法塊而不是同步方法,這點(diǎn)在多線程模塊中的synchronized鎖 方法塊文章中已經(jīng)講得很清楚了,除非我們能確定一整個(gè)方法都是需要進(jìn)行同步的,否則盡量使用同步代碼塊,避免對(duì)那些不需要進(jìn)行同步的代碼也進(jìn)行了同步,影響了代碼執(zhí)行效率。
22.方法要盡可能小
一個(gè)方法要盡量實(shí)現(xiàn)單一指責(zé),方法編譯后字節(jié)碼越小越可能會(huì)引發(fā)JIT的方法內(nèi)聯(lián),
public void SetAge(int age){
this.age = age
}
-------JIT 優(yōu)化后直接就是 如下,避免類方法調(diào)用。
this.age = age
23.一定記得寫注釋
代碼寫的再好也要記得寫類跟方法的大概注釋,不然接收你工作的人絕對(duì)分分鐘化身祖安玩家!
參考
阿里Java編碼規(guī)范
Builder模式
finalize跟gc
深入理解String
推薦閱讀:
Nginx的這個(gè)默認(rèn)配置,差點(diǎn)讓我的職場(chǎng)生涯折戟沉沙
