筆記-java核心類

Java核心類

字符串和編碼

String

在Java中,String是一個引用類型,它自己也是一個class。可是,Java編譯器對String有特殊處理,便可以直接用"..."來表示一個字符串:java

String s1 = "Hello!";

實際上字符串在String內部是經過一個char[]數組表示的,所以,按下面的寫法也是能夠的:正則表達式

String s2 = new String(new char[] {'H', 'e', 'l', 'l', 'o', '!'});

由於String太經常使用了,因此Java提供了"..."這種字符串字面量表示方法。數組

Java字符串的一個重要特色就是字符串_不可變_。這種不可變性是經過內部的private final char[]字段,以及沒有任何修改char[]的方法實現的。安全

字符串比較

當咱們想要比較兩個字符串是否相同時,要特別注意,咱們其實是想比較字符串的內容是否相同。必須使用equals()方法而不能用==
從表面上看,兩個字符串用==equals()比較都爲true,但實際上那只是Java編譯器在編譯期,會自動把全部相同的字符串看成一個對象放入常量池,天然s1s2的引用就是相同的。app

因此,這種==比較返回true純屬巧合。換一種寫法,==比較就會失敗
結論:兩個字符串比較,必須老是使用equals()方法。優化

要忽略大小寫比較,使用equalsIgnoreCase()方法。ui

String類還提供了多種方法來搜索子串、提取子串。經常使用的方法有:this

// 是否包含子串:
"Hello".contains("ll"); // true

注意到contains()方法的參數是CharSequence而不是String,由於CharSequenceString的父類。編碼

搜索子串的更多的例子:線程

"Hello".indexOf("l"); // 2
"Hello".lastIndexOf("l"); // 3
"Hello".startsWith("He"); // true
"Hello".endsWith("lo"); // true

提取子串的例子:

"Hello".substring(2); // "llo"
"Hello".substring(2, 4); "ll"

注意索引號是從0開始的。

去除首尾空白字符

使用trim()方法能夠移除字符串首尾空白字符。空白字符包括空格,\t\r\n

"  \tHello\r\n ".trim(); // "Hello"

注意:trim()並無改變字符串的內容,而是返回了一個新字符串。

另外一個strip()方法也能夠移除字符串首尾空白字符。它和trim()不一樣的是,相似中文的空格字符\u3000也會被移除。
String還提供了isEmpty()isBlank()來判斷字符串是否爲空和空白字符串:

"".isEmpty(); // true,由於字符串長度爲0
"  ".isEmpty(); // false,由於字符串長度不爲0
"  \n".isBlank(); // true,由於只包含空白字符
" Hello ".isBlank(); // false,由於包含非空白字符

替換子串

要在字符串中替換子串,有兩種方法。一種是根據字符或字符串替換:

String s = "hello";
s.replace('l', 'w'); // "hewwo",全部字符'l'被替換爲'w'
s.replace("ll", "~~"); // "he~~o",全部子串"ll"被替換爲"~~"

另外一種是經過正則表達式替換:

String s = "A,,B;C ,D";
s.replaceAll("[\\,\\;\\s]+", ","); // "A,B,C,D"

上面的代碼經過正則表達式,把匹配的子串統一替換爲","。關於正則表達式的用法咱們會在後面詳細講解。

分割字符串

要分割字符串,使用split()方法,而且傳入的也是正則表達式:

String s = "A,B,C,D";
String[] ss = s.split("\\,"); // {"A", "B", "C", "D"}

拼接字符串

拼接字符串使用靜態方法join(),它用指定的字符串鏈接字符串數組:

String[] arr = {"A", "B", "C"};
String s = String.join("***", arr); // "A***B***C"

格式化字符串

字符串提供了formatted()方法和format()靜態方法,能夠傳入其餘參數,替換佔位符,而後生成新的字符串

String s = "Hi %s, your score is %d!";
    System.out.println(s.formatted("Alice", 80));
    System.out.println(String.format("Hi %s, your score is %.2f!", "Bob", 59.5));

類型轉換

要把任意基本類型或引用類型轉換爲字符串,可使用靜態方法valueOf()。這是一個重載方法,編譯器會根據參數自動選擇合適的方法:

String.valueOf(123); // "123"
String.valueOf(45.67); // "45.67"
String.valueOf(true); // "true"
String.valueOf(new Object()); // 相似java.lang.Object@636be97c

要把字符串轉換爲其餘類型,就須要根據狀況。例如,把字符串轉換爲int類型:

int n1 = Integer.parseInt("123"); // 123
int n2 = Integer.parseInt("ff", 16); // 按十六進制轉換,255

把字符串轉換爲boolean類型:

boolean b1 = Boolean.parseBoolean("true"); // true
boolean b2 = Boolean.parseBoolean("FALSE"); // false

要特別注意,Integer有個getInteger(String)方法,它不是將字符串轉換爲int,而是把該字符串對應的系統變量轉換爲Integer

Integer.getInteger("java.version"); // 版本號,11

轉換爲char[]

Stringchar[]類型能夠互相轉換,方法是:

char[] cs = "Hello".toCharArray(); // String -> char[]
String s = new String(cs); // char[] -> String

字符編碼

在早期的計算機系統中,爲了給字符編碼,美國國家標準學會制定了一套英文字母、數字和經常使用符號的編碼,它佔用一個字節,編碼範圍從0127,最高位始終爲0,稱爲ASCII編碼。例如,字符'A'的編碼是0x41,字符'1'的編碼是0x31

若是要把漢字也歸入計算機編碼,很顯然一個字節是不夠的。GB2312標準使用兩個字節表示一個漢字,其中第一個字節的最高位始終爲1,以便和ASCII編碼區分開。例如,漢字'中'GB2312編碼是0xd6d0

相似的,日文有Shift_JIS編碼,韓文有EUC-KR編碼,這些編碼由於標準不統一,同時使用,就會產生衝突。

爲了統一全球全部語言的編碼,全球統一碼聯盟發佈了Unicode編碼,它把世界上主要語言都歸入同一個編碼,這樣,中文、日文、韓文和其餘語言就不會衝突。

StringBuilder

Java編譯器對String作了特殊處理,使得咱們能夠直接用+拼接字符串。

考察下面的循環代碼:

String s = "";
for (int i = 0; i < 1000; i++) {
    s = s + "," + i;
}

雖然能夠直接拼接字符串,可是,在循環中,每次循環都會建立新的字符串對象,而後扔掉舊的字符串。這樣,絕大部分字符串都是臨時對象,不但浪費內存,還會影響GC效率。

爲了能高效拼接字符串,Java標準庫提供了StringBuilder,它是一個可變對象,能夠預分配緩衝區,這樣,往StringBuilder中新增字符時,不會建立新的臨時對象:

StringBuilder sb = new StringBuilder(1024);
for (int i = 0; i < 1000; i++) {
    sb.append(',');
    sb.append(i);
}
String s = sb.toString();

StringBuilder還能夠進行鏈式操做:

var sb = new StringBuilder(1024);
    sb.append("Mr ")
      .append("Bob")
      .append("!")
      .insert(0, "Hello, ");
    System.out.println(sb.toString());

若是咱們查看StringBuilder的源碼,能夠發現,進行鏈式操做的關鍵是,定義的append()方法會返回this,這樣,就能夠不斷調用自身的其餘方法。

仿照StringBuilder,咱們也能夠設計支持鏈式操做的類。例如,一個能夠不斷增長的計數器:

public class Main {
    public static void main(String[] args) {
        Adder adder = new Adder();
        adder.add(3)
             .add(5)
             .inc()
             .add(10);
        System.out.println(adder.value());
    }
}

class Adder {
    private int sum = 0;

    public Adder add(int n) {
        sum += n;
        return this;
    }

    public Adder inc() {
        sum ++;
        return this;
    }

    public int value() {
        return sum;
    }
}

注意:對於普通的字符串+操做,並不須要咱們將其改寫爲StringBuilder,由於Java編譯器在編譯時就自動把多個連續的+操做編碼爲StringConcatFactory的操做。在運行期,StringConcatFactory會自動把字符串鏈接操做優化爲數組複製或者StringBuilder操做。

你可能還據說過StringBuffer,這是Java早期的一個StringBuilder的線程安全版本,它經過同步來保證多個線程操做StringBuffer也是安全的,可是同步會帶來執行速度的降低。

StringBuilderStringBuffer接口徹底相同,如今徹底沒有必要使用StringBuffer

StringJoiner

相似用分隔符拼接數組的需求很常見,因此Java標準庫還提供了一個StringJoiner來幹這個事:

String[] names = {"Bob", "Alice", "Grace"};
    var sj = new StringJoiner(", ");
    for (String name : names) {
        sj.add(name);
    }
    System.out.println(sj.toString());

包裝類型

咱們已經知道,Java的數據類型分兩種:

  • 基本類型:byteshortintlongbooleanfloatdoublechar
  • 引用類型:全部classinterface類型

引用類型能夠賦值爲null,表示空,但基本類型不能賦值爲null

String s = null;
int n = null; // compile error!

那麼,如何把一個基本類型視爲對象(引用類型)?

好比,想要把int基本類型變成一個引用類型,咱們能夠定義一個Integer類,它只包含一個實例字段int,這樣,Integer類就能夠視爲int的包裝類(Wrapper Class):

public class Integer {
    private int value;

    public Integer(int value) {
        this.value = value;
    }

    public int intValue() {
        return this.value;
    }
}

定義好了Integer類,咱們就能夠把intInteger互相轉換:

Integer n = null;
Integer n2 = new Integer(99);
int n3 = n2.intValue();

實際上,由於包裝類型很是有用,Java核心庫爲每種基本類型都提供了對應的包裝類型:

基本類型

對應的引用類型

boolean -- java.lang.Boolean

byte -- java.lang.Byte

int i = 100;
    // 經過new操做符建立Integer實例(不推薦使用,會有編譯警告):
    Integer n1 = new Integer(i);
    // 經過靜態方法valueOf(int)建立Integer實例:
    Integer n2 = Integer.valueOf(i);
    // 經過靜態方法valueOf(String)建立Integer實例:
    Integer n3 = Integer.valueOf("100");
    System.out.println(n3.intValue());

Auto Boxing

由於intInteger能夠互相轉換:

int i = 100;
Integer n = Integer.valueOf(i);
int x = n.intValue();

因此,Java編譯器能夠幫助咱們自動在intInteger之間轉型:

Integer n = 100; // 編譯器自動使用Integer.valueOf(int)
int x = n; // 編譯器自動使用Integer.intValue()

這種直接把int變爲Integer的賦值寫法,稱爲自動裝箱(Auto Boxing),反過來,把Integer變爲int的賦值寫法,稱爲自動拆箱(Auto Unboxing)。

注意:自動裝箱和自動拆箱只發生在編譯階段,目的是爲了少寫代碼。

裝箱和拆箱會影響代碼的執行效率,由於編譯後的class代碼是嚴格區分基本類型和引用類型的。而且,自動拆箱執行時可能會報NullPointerException

不變類

全部的包裝類型都是不變類。咱們查看Integer的源碼可知,它的核心代碼以下:

public final class Integer {
    private final int value;
}

所以,一旦建立了Integer對象,該對象就是不變的。

對兩個Integer實例進行比較要特別注意:絕對不能用==比較,由於Integer是引用類型,必須使用equals()比較

Integer x = 127;
    Integer y = 127;
    Integer m = 99999;
    Integer n = 99999;
    System.out.println("x == y: " + (x==y)); // true
    System.out.println("m == n: " + (m==n)); // false
    System.out.println("x.equals(y): " + x.equals(y)); // true
    System.out.println("m.equals(n): " + m.equals(n)); // true
相關文章
相關標籤/搜索