《Java編程的邏輯》筆記6: 常用基礎類

Part2 面向對象

第7章 常用基礎類

7.1 包裝類

包裝類有什麼用呢?Java中很多代碼(比如後續文章介紹的集合類)只能操作對象,爲了能操作基本類型,需要使用其對應的包裝類,另外,包裝類提供了很多有用的方法,可以方便對數據的操作。

  • 基本類型和包裝類的相互轉換
    boolean b1 = false;
    Boolean bObj = Boolean.valueOf(b1);
    boolean b2 = bObj.booleanValue();
    
    int i1 = 12345;
    Integer iObj = Integer.valueOf(i1);
    int i2 = iObj.intValue();
    

每種包裝類都有一個靜態方法valueOf(),接受基本類型,返回引用類型,也都有一個實例方法xxxValue()返回對應的基本類型。其他如double,float,char等類型也是類似.

  • 自動拆箱和裝箱

      Integer a = 100;
      int b = a;
    

    自動裝箱/拆箱是Java編譯器提供的能力,背後,它會替換爲調用對應的valueOf()/xxxValue()

    比如說,上面的代碼會被Java編譯器替換爲:

      Integer a = Integer.valueOf(100);
      int b = a.intValue();
    
  • 包裝類構造方法new

      Integer a = new Integer(100);
      Boolean b = new Boolean(true);
    

    那到底應該用靜態的valueOf方法,還是使用new呢?一般建議使用valueOf。new每次都會創建一個新對象

  • 重寫Object類方法

    所有包裝類都重寫了Object類的如下方法:

    boolean equals(Object obj)
    int hashCode()
    String toString()
    
  • 包裝類和String

    1.除了Character外,每個包裝類都有一個靜態的valueOf(String)方法,根據字符串表示返回包裝類對象:

      Boolean b = Boolean.valueOf("true");
      Float f = Float.valueOf("123.45f"); 
    

    2.有一個靜態的parseXXX(String)方法,根據字符串表示返回基本類型值

      boolean b = Boolean.parseBoolean("true");
      double d = Double.parseDouble("123.45");
    
  • 共同父類Number

  • 不可變性

    包裝類都是不可變類,所謂不可變就是,實例對象一旦創建,就沒有辦法修改了。這是通過如下方式強制實現的:

    1.所有包裝類都聲明爲了final,不能被繼承

    2.內部基本類型值是私有的,且聲明爲了final

    3.沒有定義setter方法

  • 剖析Integer與二進制算法

    reverse位翻轉

    reverseBytes字節翻轉

    內部實現原理,理解位運算

  • valueOf的實現

    IntegerCache, 共享常用對象:享元模式

7.2 剖析String

Java中處理字符串的類主要是String和StringBuilder

  • 基本用法

    通過常量定義String變量:

    String name = "ttee";
    

    通過new創建:

    String name = "ttee";
    

    常用函數:String中提供了很多函數,熟悉常用函數的用法對我們日常的使用有很大幫助,具體可以參考JDK文檔

  • 走進String內部

    String內部實現其實是由一個字符數組維護的,String中的絕大多數方法都是在操作這個字符數組。字符數組的定義如下:

    private final char value[];
    

    如String的length()方法的源碼如下:

    public int length() {
        return value.length;
    }
    

    可以看到返回的是value的長度。

    chatAt方法源碼如下:

    public char charAt(int index) {
        if ((index < 0) || (index >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return value[index];
    }
    

    substring()方法源碼如下:

    public String substring(int beginIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        int subLen = value.length - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    }
    

    可以看到substring其實是計算好要截取的長度,然後調用String的構造函數重新創建了一個字符串。

    String有兩個構造方法,可以根據char數組創建String:

    public String(char value[])
    public String(char value[], int offset, int count)
    

    需要說明的是,String會根據參數新創建一個數組,並拷貝內容,而不會直接用參數中的字符數組,其內部實現代碼如下:

    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
    

    copyof方法源碼如下:

    public static char[] copyOf(char[] original, int newLength) {
        char[] copy = new char[newLength];
        System.arraycopy(original, 0, copy, 0,
                          Math.min(original.length, newLength));
        return copy;
    }
    

    this.value即爲String內部維護的實例變量字符數組,可以看到調用copyof方法會返回一個新創建的char[]數組,將其賦值給this.value.

  • 不可變性

    String類有個很重要的特性就是不可變性,即一旦被創建,就不能修改了,通過前面源碼可以看出String類也聲明爲了final,不能被繼承,內部char數組value也是final的,初始化後就不能再變了。

    這就可以解釋爲什麼String類裏面很多修改或查找的方法都是新建一個字符串了,如前面的substring,大都通過Arrays.copyof()方法複製新建。

  • 常量字符串

    Java中的常量字符串就像一個String類型的對象一樣,可以直接調用String的各種方法:

    System.out.println("ttee".length());
    System.out.println("ttee".contains("tt"));
    System.out.println("ttee".indexOf("ee"));
    

    在內存中,它們被存放在字符串常量池中,每個常量只會保存一份,被所有使用者共享。
    我們可以看看下面的代碼例子:

    String name1 = "ttee";
    String name2 = "ttee";
    System.out.println(name1==name2);
    

    答案輸出爲true,因爲name1和name2都指向的是字符串常量池中的同一個對象(即所存儲的地址相同),再看下面的代碼:

    String name1 = new String("ttee");
    String name2 = new String("ttee");
    System.out.println(name1==name2);
    

    答案時false,這時會創建兩個String對象,而name1和name2分別指向這兩個對象,只不過這兩個對象內部所存儲的地址都指向相同的char數組。內存佈局如下:

  • hashcode

    除了value這個實例變量,String內部還會緩存hash實例變量:

    private int hash; // Default to 0
    

    hashcode()方法會計算並返回這個值:

    public int hashCode() {
      int h = hash;
      if (h == 0 && value.length > 0) {
          char val[] = value;
    
          for (int i = 0; i < value.length; i++) {
              h = 31 * h + val[i];
          }
          hash = h;
      }
      return h;
    }
    

    如果緩存的hash不爲0,就直接返回了,否則根據字符數組中的內容計算hash

    爲什麼要用這個計算方法呢?這個式子中,hash值與每個字符的值有關,每個位置乘以不同的值,hash值與每個字符的位置也有關。使用31大概是因爲兩個原因,一方面可以產生更分散的散列,即不同字符串hash值也一般不同,另一方面計算效率比較高

  • 正則表達式

7.3 剖析StringBuilder

  • 基本用法

    StringBuilder sb = new StringBuilder();
    sb.append("tt");
    sb.append("ee");
    System.out.println(sb.toString());
    //out: ttee
    
  • 實現原理

    與String類似,StringBuilder類也封裝了一個字符數組,定義如下:

    char[] value;
    

    與String不同,它不是final的,可以修改。另外,與String不同,字符數組中不一定所有位置都已經被使用,它有一個實例變量,表示數組中已經使用的字符個數,定義如下:

    int count;
    

    append實現:

    public AbstractStringBuilder append(String str) {
        if (str == null) str = "null";
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }
    

    append會直接拷貝字符到內部的字符數組中,如果字符數組長度不夠,會進行指數擴展.

參考:《Java編程的邏輯》 第7章 常用基礎類

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章