天天看點

《Effective Java》閱讀筆記1 考慮使用靜态工廠方法替代構造方法2.優勢3.缺點4.補充知識點5.參考文獻

​記得研一剛進實驗室的時候,看見師兄手裡拿着一本書《Effective Java》,心理想着,這微信大佬(師兄已經被微信錄取)看的書,肯定很有用,是以自己也買來一本喵喵。結果可想而知,看不懂。。。。現在自己再次拿起來看,發現有點意思了。是以把它分享出來。

書擷取方式---->>>1.序

對于一個類,要擷取它的一個執行個體,通常的做法是提供一個公用的構造函數,然而還有另一種方法,我們稱之為靜态工廠方法,實質上也就是一個簡單的靜态方法,它傳回一個類的執行個體。其實,靜态工廠方法擷取對象執行個體,我們并不陌生,來看兩個例子。

構造方法建立對象

在Java中,建立對象常用的方法是通過公有構造方法建立;

舉個例子:如下,是Boolean類的一個構造方法,以及通過該構造方法建立一個Boolean對象;

public Boolean(String s) {
        this(toBoolean(s));
  }
  
  Boolean bTrue = new Boolean("true");
           

靜态工廠方法建立對象

其實,建立對象還有另外一種方法,通過公有靜态工廠方法來建立對象,不過這種方法往往容易被程式員忽略;

舉個例子,如下是Boolean類的valueOf方法,以及通過該靜态工廠方法傳回的Boolean執行個體,注意,這裡并沒有建立Boolean執行個體對象,而是傳回事先建立好的Boolean對象;

public static Boolean valueOf(String s) {
    return toBoolean(s) ? TRUE : FALSE;
}

Boolean bTrue = Boolean.valueOf("true");
           

2.優勢

2.1.靜态工廠方法與構造器不同的第一大優勢在于,它們有名稱 (增強了代碼的可讀性)

//使用構造器方法擷取到一個素數
BigInteger prime = new BigInteger(int, int ,Random);

//使用靜态工廠方法
BigInteger prime = BigInteger.probablePrime(int, Random);
           

詳細示例

假設我們需要寫一個産生随即數的類RandomIntGenerator,該類有兩個成員屬性:最小值min和最大值max,

假設我們的需求是需要建立三種類型的RandomIntGenerator對象,

1、大于min,小于max;

2、大于min 小于Integer.MAX_VALUE;

3、大于Integer.MIN_VALUE 小于max

如果我們不使用靜态工廠方法,代碼一般如下設計:

class RandomIntGenerator
{
    /**
     * 最小值
     */
    private int min = Integer.MIN_VALUE;
    /**
     * 最大值
     */
    private int max = Integer.MAX_VALUE;

    /**
     * 大于min 小于max
     * @param min
     * @param max
     */
    public RandomIntGenerator(int min, int max)
    {
        this.min = min;
        this.max = max;
    }
    
    /**
     * 大于min 小于Integer.MAX_VALUE
     */
    public RandomIntGenerator(int min)
    {
        this.min = min;
    }

//    報錯:Duplicate method RandomIntGenerator(int) in type RandomIntGenerator
//    /**
//     * 大于Integer.MIN_VALUE 小于max
//     */
//    public RandomIntGenerator(int max)
//    {
//        this.max = max;
//    }
}
           

觀察以上代碼,我們發現,以上代碼不僅可讀性差(new RandomIntGenerator(1, 10)與new RandomIntGenerator(10),不查文檔,不看注釋很難知道其建立的對象的具體含義),而且在設計最後一個構造方法的時候,還報錯,因為已經存在一個參數一緻的工作方法了,提示重複定義;

那麼假設我們使用靜态工廠方法會怎樣呢,如下所示:

class RandomIntGenerator
{
    /**
     * 最小值
     */
    private int min = Integer.MIN_VALUE;
    /**
     * 最大值
     */
    private int max = Integer.MAX_VALUE;

    /**
     * 大于min 小于max
     * @param min
     * @param max
     */
    public RandomIntGenerator(int min, int max)
    {
        this.min = min;
        this.max = max;
    }
    /**
     * 大于min 小于max
     * @param min
     * @param max
     */
    public static RandomIntGenerator between(int min, int max)
    {
        return new RandomIntGenerator(min, max);
    }
    /**
     * 大于min 小于Integer.MAX_VALUE
     */
    public static RandomIntGenerator biggerThan(int min)
    {
        return new RandomIntGenerator(min, Integer.MAX_VALUE);
    }

    /**
     * 大于Integer.MIN_VALUE 小于max
     */
    public static RandomIntGenerator smallerThan(int max)
    {
        return new RandomIntGenerator(Integer.MIN_VALUE, max);
    }
}
           

成功滿足需求:建立三種類型的RandomIntGenerator對象,而且建立對象的時候,代碼可讀性比使用構造方法強;

2.2.不必在每次調用它們的時候都建立一個新對象(提升性能)

通過構造方法建立對象的時候,每次都要new一個新對象。而通過靜态方法,可以選擇每次采用單例模式來複用對象,這對于一個性能要求高的系統,可以顯著提高運作速度。

詳細解析:2.3.靜态工廠方法可以傳回原傳回類型的任何子類型對象(增大程式靈活性,減少API數量)

這樣使我們在選擇傳回對象的類時就有了更大的靈活性。

class Father {
    private Father() {
    }

    public static Father newInstance(String type) {
        if (type.equals("ChildA")) { // 根據類型判斷傳回那個子類對象
            return new ChildA();
        } else {
            return new ChildB();
        }
    }

    public void getName() { 
        System.out.println("My name is father");
    }

    private static class ChildA extends Father {
        public void getName() { 
            System.out.println("My name is child A");
        }
    }

    private static class ChildB extends Father {
        public void getName() {
            System.out.println("My name is child B");
        }
    }
}

public class Test {
    public static void main(String[] args) {
        Father c1 = Father.newInstance("ChildA");
        c1.getName();
        Father c2 = Father.newInstance("ChildB");
        c2.getName();
    }
}
           

2.4.可以使代碼變得更加簡潔(具體類型取決于傳入的參數)

2.4.1 示例1

package tmp;

class MyMap<K,V> {
    /**
     *
     */
    public MyMap()
    {
    }

    public static <K,V> MyMap<K,V> newInstance(){
        return new MyMap<K, V>();
    }
}

public class Main
{
    public static void main(String[] args)
    {
        MyMap<String, String> map1 = new MyMap<String, String>();

        //更加簡潔,不需要重複指明類型參數,可以自行推導出來
        MyMap<String, String> map2 = MyMap.newInstance();
    }
}
           

但實際上,現在Java最新的版本已經構造函數已經不需要補充參數了。

HashMap<String,List<String>> map = new HashMap<>();
           

2.4.2 示例2

//傳統寫法Map<String, String> map =new HashMa<String,String>();

//用guava寫法Map<String, String> map = Maps.newHashMap(); 
           

3.缺點

3.1.類如果不含有公有的類或者受保護的構造器,就不能被子類化

如下類,不能被其它類繼承;

class MyMap<K,V> {
    /**
     *
     */
    private MyMap()
    {
    }

    public static <K,V> MyMap<K,V> newInstance(){
        return new MyMap<K, V>();

    }
}
           

3.2.它們與其他的靜态方法實際上沒什麼差別,是以我們約定了一些靜态工廠方法的常用名稱

  • 1.valueOf —— from 和 to 更為詳細的替代 方式,例如:
BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
           
  • 2.of —— 聚合方法,接受多個參數并傳回該類型的執行個體,并把他們合并在一起,例如:
Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
           
  • 3.instance 或 getinstance —— 傳回一個由其參數 (如果有的話) 描述的執行個體,但不能說它具有相同的值,例如:
StackWalker luke = StackWalker.getInstance(options);
           
  • 4.create 或 newInstance —— 與 instance 或 getInstance 類似,除此之外該方法保證每次調用傳回一個新的執行個體,例如:
Object newArray = Array.newInstance(classObject, arrayLen);
           
  • 5.getType —— 與 getInstance 類似,但是在工廠方法處于不同的類中的時候使用。getType 中的 Type 是工廠方法傳回的對象類型,例如:
FileStore fs = Files.getFileStore(path);
           
  • 6.newType —— 與 newInstance 類似,但是在工廠方法處于不同的類中的時候使用。newType中的 Type 是工廠方法傳回的對象類型,例如:
BufferedReader br = Files.newBufferedReader(path);
           

4.補充知識點

4.1.guava将很多集合類做了靜态工廠方法封裝

//用guava的工具類 
List<String> list = Lists.newArrayList(); 
Set<String> set = Sets.newHashSet();
Map<String, String> map = Maps.newHashMap(); 

//而不是下面這種方式
Map<String, String> map =new HashMa<String,String>();
           

4.2.什麼是immutable(不可變)對象

  • 在多線程操作下,是線程安全的
  • 所有不可變集合會比可變集合更有效的利用資源
  • 中途不可改變
ImmutableList<String> immutableList = ImmutableList.of("1","2","3","4");
           

這聲明了一個不可變的List集合,List中有資料1,2,3,4。類中的 操作集合的方法(譬如add, set, sort, replace等)都被聲明過期,并且抛出異常。 而沒用guava之前是需要聲明并且加各種包裹集合才能實作這個功能

// add 方法  @Deprecated @Override
  public final void add(int index, E element) {
    throw new UnsupportedOperationException();
  }
           

當我們需要一個map中包含key為String類型,value為List類型的時候

//以前我們是這樣寫的
Map<String,List<Integer>> map = new HashMap<String,List<Integer>>();
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
map.put("aa", list);
System.out.println(map.get("aa"));//[1, 2]

//現在這麼寫
Multimap<String,Integer> map = ArrayListMultimap.create();		
map.put("aa", 1);
map.put("aa", 2);
System.out.println(map.get("aa"));  //[1, 2]
           

4.3.注意區分靜态工廠方法和工廠方法模式

注意,這裡的靜态工廠方法與設計模式裡的工廠方法模式不是一個概念:

靜态工廠方法 通常指的是某個類裡的靜态方法,通過調用該靜态方法可以得到屬于該類的一個執行個體;

工廠方法模式 是一種設計模式,指的是讓具體的工廠對象負責生産具體的産品對象,這裡涉及多種工廠(類),多種對象(類),如記憶體工廠生産記憶體對象,CPU工廠生産CPU對象;

不過,如果要說相似的話,靜态工廠方法跟簡單工廠模式倒有那麼點像,不過差別也挺大,簡單工廠模式裡的靜态工廠方法會建立各種不同的對象(不同類的執行個體),而靜态工廠方法一般隻建立屬于該類的一個執行個體(包括子類);

總之,靜态工廠方法和公共構造方法都有它們的用途,并且了解它們的相對優點是值得的。通常,靜态工廠更可取,是以避免在沒有考慮靜态工廠的情況下直接選擇使用公共構造方法。

5.參考文獻

https://zhuanlan.zhihu.com/p/90837015

https://www.cnblogs.com/chenpi/p/5981084.html

《Effective Java》閱讀筆記1 考慮使用靜态工廠方法替代構造方法2.優勢3.缺點4.補充知識點5.參考文獻

本公衆号分享自己從程式員小白到經曆春招秋招斬獲10幾個offer的面試筆試經驗,其中包括【Java】、【作業系統】、【計算機網絡】、【設計模式】、【資料結構與算法】、【大廠面經】、【資料庫】期待你加入!!!

1.計算機網絡----三次握手四次揮手

2.夢想成真-----項目自我介紹

3.你們要的設計模式來了

4.一字一句教你面試“個人簡介”

5.你們要的免費書來了

繼續閱讀