在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編譯器在編譯期,會自動把全部相同的字符串看成一個對象放入常量池,天然s1
和s2
的引用就是相同的。app
因此,這種==
比較返回true
純屬巧合。換一種寫法,==
比較就會失敗
結論:兩個字符串比較,必須老是使用equals()
方法。優化
要忽略大小寫比較,使用equalsIgnoreCase()
方法。ui
String
類還提供了多種方法來搜索子串、提取子串。經常使用的方法有:this
// 是否包含子串: "Hello".contains("ll"); // true
注意到contains()
方法的參數是CharSequence
而不是String
,由於CharSequence
是String
的父類。編碼
搜索子串的更多的例子:線程
"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
String
和char[]
類型能夠互相轉換,方法是:
char[] cs = "Hello".toCharArray(); // String -> char[] String s = new String(cs); // char[] -> String
在早期的計算機系統中,爲了給字符編碼,美國國家標準學會制定了一套英文字母、數字和經常使用符號的編碼,它佔用一個字節,編碼範圍從0
到127
,最高位始終爲0
,稱爲ASCII
編碼。例如,字符'A'
的編碼是0x41
,字符'1'
的編碼是0x31
。
若是要把漢字也歸入計算機編碼,很顯然一個字節是不夠的。GB2312
標準使用兩個字節表示一個漢字,其中第一個字節的最高位始終爲1
,以便和ASCII
編碼區分開。例如,漢字'中'
的GB2312
編碼是0xd6d0
。
相似的,日文有Shift_JIS
編碼,韓文有EUC-KR
編碼,這些編碼由於標準不統一,同時使用,就會產生衝突。
爲了統一全球全部語言的編碼,全球統一碼聯盟發佈了Unicode
編碼,它把世界上主要語言都歸入同一個編碼,這樣,中文、日文、韓文和其餘語言就不會衝突。
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
也是安全的,可是同步會帶來執行速度的降低。
StringBuilder
和StringBuffer
接口徹底相同,如今徹底沒有必要使用StringBuffer
。
相似用分隔符拼接數組的需求很常見,因此Java標準庫還提供了一個StringJoiner
來幹這個事:
String[] names = {"Bob", "Alice", "Grace"}; var sj = new StringJoiner(", "); for (String name : names) { sj.add(name); } System.out.println(sj.toString());
咱們已經知道,Java的數據類型分兩種:
byte
,short
,int
,long
,boolean
,float
,double
,char
class
和interface
類型引用類型能夠賦值爲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
類,咱們就能夠把int
和Integer
互相轉換:
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());
由於int
和Integer
能夠互相轉換:
int i = 100; Integer n = Integer.valueOf(i); int x = n.intValue();
因此,Java編譯器能夠幫助咱們自動在int
和Integer
之間轉型:
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