本文是Java基礎課程的第五課。主要介紹Java中的字符串,包括字符串基本的聲明、使用,字符串在JVM中的內存分配,字符串常用的API,字符串與基本數據類型的轉換等內容
文章目錄
一、認識字符串
字符(char
)類型是Java的基本數據類型之一,用來存儲單個字符。在開發過程中,往往多個字符一起才能表達一個有意義的數據。Java提供了字符串(String
)類型,用來處理一連串的字符。字符串便是由若干字符組成的序列。
1、字符串的聲明和初始化
Java中,聲明和初始化一個字符串的語法格式如下:
String 變量名 = "初始值";
// 或
String 變量名 = new String("初始值");
下面是一個示例:
public class Test {
public static void main(String[] args) {
String str1 = "Hello world";
String str2 = new String("Hello World");
}
}
說明:
- 一旦創建了
String
對象,那它的值就無法改變了。 - 事實上,
String
類型用一個char
類型的數組來存儲,處理字符串的。
2、字符串的連接
多個字符串連接在一起,也可以將字符串與基本類型變量連接。最常用的連接字符串的方法是使用+
操作符。
下面是一個示例:
public class Test {
public static void main(String[] args) {
String str1 = "Hello";
String str2 = "World";
String str3 = str1 + str2;
String str4 = "Hello" + "World";
int num1 = 123;
String str5 = num1 + str1;
String str6 = str1 + num1;
System.out.println("str1 = " + str1);
System.out.println("str2 = " + str2);
System.out.println("str3 = " + str3);
System.out.println("str4 = " + str4);
System.out.println("str5 = " + str5);
System.out.println("str6 = " + str6);
}
}
說明:
- 字符串與基本數據類型的變量使用
+
操作符進行連接時,會先將基本類型轉換爲字符串類型,然後進行連接操作。
3、字符串的比較
在程序中,比較兩個數據是否相等是一種很常見的操作,基本數據類型的變量之間使用==
操作符便可以判斷變量中保存的數據是否相等。而字符串之間的比較有所不同。
下面是一個示例:
public class Test {
public static void main(String[] args) {
String str1 = "Hello";
String str2 = "Hello";
String str3 = new String("Hello");
System.out.println("(str1 == str2) = " + (str1 == str2));
System.out.println("(str1 == str3) = " + (str1 == str3));
System.out.println("str1.equals(str3) = " + str1.equals(str3));
}
}
說明:
- 用
==
操作符比較str1
和str2
,結果爲true
,事實上,==
操作符比較的是變量中存儲的內容(數據或內存地址編號)。字符串是引用類型,str1 == str2
的結果爲true
,意味着str1
和str2
兩個變量中存儲着同樣的內存地址編號,即引用了同一塊內存(這一點在下面會詳細介紹)。 - 用
==
操作符比較str1
和str2
,結果爲false
,意味着str1
和str3
兩個變量分別存儲了不同的內存地址編號。 String
類型提供了equals(Object anObject)
方法,可以比較字符串的字面值是否相同,str1.equals(str3)
的返回結果爲true
。因此,如果要比較字符串的字面值是否相同,需要使用equals(Object anObject)
方法。
4、內存中的字符串
觀察下面的示例:
public class Test {
public static void main(String[] args) {
// 基本數據類型
int num1 = 1;
int num2 = 1;
System.out.println("(num1 == num2) = " + (num1 == num2));
// 數組,引用數據類型
char[] char1 = {'H'};
char[] char2 = {'H'};
System.out.println("(char1 == char2) = " + (char1 == char2));
// String,引用數據類型
String str1 = "Hello";
String str2 = "Hello";
System.out.println("(str1 == str2) = " + (str1 == str2));
}
}
num1
和num2
兩個基本數據類型的變量進行==
運算,這兩個變臉中存儲的都是基本數據類型的值,即1
,因此返回結果是true
;char1
和char2
兩個數組進行==
運算,因爲它們是引用類型,分別在堆內存中申請了內存空間,所以char1
和char2
兩個變量中存儲的是不同的內存地址編號,故而返回結果爲false
。同爲引用類型的String
型的變量,str1
和str2
使用==
操作符比較時,返回的結果是true
,即str1
和str2
引用的內存地址相同,這是什麼原因呢?
事實上,這是Java有意爲之,由於字符串這一類型的數據是程序中出現頻率最高的數據類型,並且字符串內容重複的概率也非常的高,如果全部分開申請內存的話,將會非常耗費內存,Java爲了節省內存考慮,設計了字符串池這一內存區域(字符串池在JDK 7
以前設計在方法區中,而在JDK 8
以後設計在堆內存中),下面用圖示說明上例中兩個變量str1
和str2
中存儲的內存地址編號爲何相同:
說明:
- 執行代碼
String str1 = "Hello"
時,首先在棧中main
方法對應棧幀的局部變量表中,爲變量str1
分配內存,查找字符串池中是否有一塊內存中已經存儲了Hello
這一字符串字面量,沒有的話,字符串池中分配內存並存儲字符串字面量"Hello"
,賦值操作會將字符串池中已經分配好的內存空間的首地址存儲到變量str1
在局部變量表的對應位置。 - 執行代碼
String str2 = "Hello"
時,棧中main
方法對應棧幀的局部變量表中,爲變量str2
分配內存,查找字符串池中是否有一塊內存中已經存儲了Hello
這一字符串字面量,有的話,引用該內存地址。 - 執行代碼
System.out.println("(str1 == str2) = " + (str1 == str2))
,由於變量str1
和str2
引用的內存地址相同,故返回結果爲true
。
進一步觀察下面的另一個例子:
public class Test {
public static void main(String[] args) {
String str1 = "Hello";
String str2 = "Hello";
String str3 = new String("Hello");
System.out.println("(str1 == str2) = " + (str1 == str2));
System.out.println("(str1 == str3) = " + (str1 == str3));
}
}
前面的例子已經進行了分析,變量str1
和str2
引用了字符串池中相同的內存地址,str1 == str2
的結果爲true
。運行str1 == str3
的結果爲false
,這又是什麼原因呢?
原因就在於使用了new
運算符,下面用圖示來說明:
說明:
- 執行代碼
new String("Hello")
時,會判斷字符串池中是否已經存儲了Hello
這一字符串字面量,沒有的話,字符串池中會分配內存並存儲Hello
這一字符串字面量,同時,堆內存中也會分配內存並存儲Hello
這一字符串字面量,最終,返回堆內存中分配好的內存空間的首地址。因此,變量str1
和str3
中存儲的是不同的內存地址編號,str1 == str3
的結果爲false
。 - 使用
new
運算符時,總會在堆內存中分配內存空間。
再來觀察一個例子:
public class Test {
public static void main(String[] args) {
String str1 = "Hello";
String str2 = "HelloWorld";
String str3 = "Hello" + "World";
String str4 = str1 + "World";
System.out.println("(str2 == str3) = " + (str2 == str3));
System.out.println("(str2 == str4) = " + (str2 == str4));
}
}
str2 == str3
的結果爲true
,即變量str2
和str3
引用的內存地址相同;而str2 == str4
的結果爲false
,即變量str2
和str4
引用的內存地址不同,這又是怎麼回事呢?
str2 == str3
的結果爲true
,原因在於字符串字面量的連接,在編譯階段就已經完成了,String str3 = "Hello" + "World"
和String str3 = "HelloWorld"
,對於JVM來說並沒有什麼區別;而str2 == str4
的結果爲false
,是由於字符串變量參與了字符串的連接,這一過程發生在程序運行時,會在堆內存中申請內存空間。下面用圖示來說明:
說明:
- 執行代碼
String str2 = "HelloWorld"
時,字符串池中分配內存並存儲HelloWorld
這一字符串字面量,變量str2
引用該內存地址。 - 執行代碼
String str3 = "Hello" + "World"
時,會在字符串池中查找HelloWorld
這一字符串字面量,有則直接返回內存地址,故變量str3
和str2
引用相同的內存地址。 - 執行代碼
String str4 = str1 + "World"
時,會將字符串變量str1
中的字符串Hello
和字符串字面量World
進行連接,並在堆內存中分配內存空間進行存儲,最終變量str4
引用該堆內存中的內存地址。
最後來觀察一個例子:
public class Test {
public static void main(String[] args) {
// 引用數據類型的變量相互賦值
int[] nums1, nums2;
nums1 = new int[1];
nums1[0] = 3;
nums2 = nums1;
nums2[0] = 4;
System.out.println("nums1[0] = " + nums1[0]);
// 字符串類型的變量相互賦值
String str1, str2;
str1 = "Hello";
str2 = str1;
str2 = "World";
System.out.println("str1 = " + str1);
}
}
打印nums1[0]
的結果爲4
,作爲引用類型的變量,將數組nums2
賦值給數組nums1
時,實際拷貝的是內存地址編號,變量nums1
和nums2
引用了同一內存地址,改變數組nums2
中元素的值,其實就是改變數組nums1
中元素的值。打印str1
的結果爲Hello
,同樣作爲引用類型,在執行str2 = str1
後,字符串變量str1
和str2
也引用同一內存地址,可是改變變量str2
的值,變量str1
卻沒有發生變化,這又是爲什麼呢?
事實上,在程序運行str2 = "World"
之後,字符串變量str1
和str2
也並不再引用同一內存地址了,仍然用圖示來說明:
說明:
- 執行代碼
str1 = "Hello"
時,首先查找字符串池中是否有一塊內存中已經存儲了Hello
這一字符串字面量,沒有的話,字符串池中分配內存並存儲字符串字面量"Hello"
,賦值操作會將字符串池中已經分配好的內存空間的首地址存儲到變量str1
在局部變量表的對應位置。 - 執行代碼
str2 = str1
時,變量str2
和str1
引用字符串池中同一內存地址。 - 執行代碼
str2 = "World"
時,首先查找字符串池中是否有一塊內存中已經存儲了World
這一字符串字面量,沒有的話,字符串池中分配內存並存儲字符串字面量"World"
,賦值操作會將字符串池中已經分配好的內存空間的首地址存儲到變量str2
在局部變量表的對應位置。注意,這一過程中變量str1
並沒有受到影響。
二、字符串常用API
在開發工程中,經常需要對字符串進行各種操作,熟練掌握字符串的各種操作,對編碼過程很有幫助。
1、什麼是API
API(Application Programming Interface),即應用程序編程接口,對這一概念的定義大多數都晦澀難懂。這裏通俗的理解這一概念:
- A(Application),即應用,按不同的場合,可以是一個提供特定功能的軟件、一個提供某些數據的網絡服務、一個作爲程序組件的類庫、甚至是一個硬件設備,等等。
- P(Programming),即編程。
- I(Interface),即接口,是不同系統或實體交接並通過它彼此作用的媒介(或者理解爲方式、渠道、手段)。
所以,API就是在編程過程中,實體或系統之間進行信息交換的媒介。
更直白的理解,當某一應用需要與其他應用進行信息交換時,需要通過某一媒介,告知對方應用一些信息,同時獲取本應用所需要的信息,同時,該媒介應當隱藏這些應用的其他細節,這一媒介便是API,與這一媒介具有同樣性質的事物,都可以稱之爲API。
開發人員在使用API時,無需關心API後面隱藏的實現細節,僅需要關心API的名稱、輸入什麼數據、獲得什麼數據這三點就足夠了。
2、字符串常用API
Java中的字符串提供給開發人員許多非常有用的API,通過它們,可以很輕鬆的獲得字符串的一些常用信息,或者對字符串進行一些加工處理。
Java中字符串常用的API有:
方法 | 返回值類型 | 方法說明 |
---|---|---|
equals(Object anObject) |
boolean |
將此字符串與指定的對象比較 |
equalsIgnoreCase(String anotherString) |
boolean |
忽略大小寫比較兩個字符串的字面值是否相等 |
length() |
int |
獲取字符串的長度 |
charAt(int index) |
char |
獲取指定索引處的 char 值 |
indexOf(String str) |
int |
獲取指定子字符串在原字符串中第一次出現處的索引 |
lastIndexOf(String str) |
int |
獲取指定子字符串在原字符串中最右邊出現處的索引 |
startsWith(String prefix) |
boolean |
測試原字符串是否以指定的前綴開始 |
endsWith(String suffix) |
boolean |
測試原字符串是否以指定的後綴結束 |
toLowerCase() |
String |
獲取原字符串對應的小寫字符串 |
toUpperCase() |
String |
獲取原字符串對應的大寫字符串 |
substring(int beginIndex) |
String |
截取原字符串,從傳入參數beginIndex 爲下標的位置開始截取到末尾 |
substring(int beginIndex, int endIndex) |
String |
截取原字符串,從參數beginIndex 爲下標的位置開始截取到參數endIndex 爲下標的位置 |
trim() |
String |
去掉原字符串首尾的空格 |
split(String regex) |
String[] |
將原字符串按照傳入參數regex 分割爲字符串數組 |
replace(String regex, String replacement) |
String |
將原字符串中指定的內容替換成另外的內容 |
3、案例
3.1 案例1
下面是一個示例:
public class Test {
public static void main(String[] args) {
// 驗證用戶名長度,長度在6至20之間爲合法,否則爲不合法
Scanner input = new Scanner(System.in);
System.out.println("請輸入用戶名");
String name = input.next();
if (name.length() >= 6 && name.length() <= 20) {
System.out.println("用戶名長度合法");
} else {
System.out.println("用戶名長度不合法");
}
}
}
說明:
- 該案例演示驗證用戶名的長度,需要從鍵盤上輸入用戶名,對用戶名進行驗證,合法的用戶名長度在6到20之間。如果在這區間,輸出用戶名長度合法,否則輸出用戶名長度不合法。
- 使用字符串的
length()
方法可以獲取字符串的長度。
3.2 案例2
下面是一個示例:
public class Test {
public static void main(String[] args) {
/*
從鍵盤上輸入email,對email進行驗證,合法的email的條件是
1. 必須包含“@”和“.”
2. “@”必須在“.”的前面且不能連着
3. “@”不在開頭和結尾,並且只能出現一次
*/
Scanner input = new Scanner(System.in);
System.out.println("請輸入Email");
String email = input.next();
int atIndex = email.indexOf("@");
int dotIndex = email.indexOf(".");
if (atIndex == -1 || dotIndex == -1) { //必須包含“@”和“.”
System.out.println("Email非法,不存在@或.");
} else if (atIndex >= dotIndex - 1) { //@必須在.的前面且不能連着
System.out.println("Email非法,@必須在.的前面且不能連着");
} else if (email.startsWith("@") || email.endsWith("@")) { //“@”不在開頭和結尾
System.out.println("Email非法,“@”不能出現在開頭和結尾");
} else if(email.lastIndexOf("@") != atIndex){ // “@”只能出現一次
System.out.println("Email非法,要求@只能出現一次");
}else {
System.out.println("Email合法");
}
}
}
說明:
- 該案例演示電子郵箱地址校驗,校驗規則爲必須包含“
@
”和“.
”,“@
”必須在“.
”的前面且不能連着,“@
”不在開頭和結尾,並且只能出現一次。 - 使用字符串的
indexOf(String str)
方法可以獲取指定子字符串在原字符串中第一次出現處的索引,沒有找到則返回-1
。 - 使用字符串的
startsWith(String str)
方法可以判斷原字符串是否以指定的前綴開始。 - 使用字符串的
endsWith(String str)
方法可以判斷原字符串是否以指定的後綴結束。 - 使用字符串的
lastIndexOf(String str)
方法可以獲取指定子字符串在原字符串中最後一次出現處的索引,沒有找到則返回-1
。
3.3 案例3
下面是一個示例:
public class Test {
public static void main(String[] args) {
// 將新聞標題中的“爪窪”換成“Java”
String title = "爪窪技術發展這些年";
System.out.println("替換前的標題:" + title);
title = title.replace("爪窪", "Java");
System.out.println("替換後的標題:" + title);
}
}
說明:
- 該案例演示將新聞標題中的“
爪窪
”換成“Java
”。 - 使用字符串的
replace(String regex, String replacement)
方法可以將原字符串中指定的內容替換成另外的內容。
3.4 案例4
下面是一個示例:
public class Test {
public static void main(String[] args) {
// 截取路徑中的文件名稱
String path = "C:\\HTML\\front\\assets\\img\\pc\\logo.png";
int startIndex = path.lastIndexOf("\\");
int endIndex = path.lastIndexOf(".");
String fileName = path.substring(startIndex + 1, endIndex);
System.out.println(path + "路徑中的文件是:" + fileName);
}
}
說明:
- 該案例演示截取路徑中的文件名稱。
- 使用字符串的
substring(int beginIndex, int endIndex)
方法可以獲取指定索引處的char
值。
3.5 案例5
下面是一個示例:
public class Test {
public static void main(String[] args) {
/*
檢測輸入的單詞是否是迴文單詞
迴文單詞即單詞倒過來與原單詞一樣,比如“level”、“pop”、“noon”等
*/
Scanner input = new Scanner(System.in);
System.out.println("請輸入需要檢測的單詞:");
String word = input.next();
// 檢測迴文單詞的邏輯
boolean isPalindromeWord = true;
for (int i = 0; i < word.length() / 2; i++) {
if (word.charAt(i) != word.charAt(word.length() - 1 - i)) {
isPalindromeWord = false;
}
}
System.out.println(word + (isPalindromeWord ? "是" : "不是") + "迴文單詞");
}
}
說明:
- 該案例演示迴文單詞檢測。
- 使用字符串的
charAt(int index)
方法可以從指定的開始位置和結束位置截取字符串。
3.6 案例6
下面是一個示例:
public class Test {
public static void main(String[] args) {
// 字符串格式化
String str1 = String.format("見過,%s及%s", "晁天王", "衆位頭領");
System.out.println(str1);
String str2 = String.format("字母a的大寫是:%c", 'A');
System.out.println(str2);
String str3 = String.format("3 > 7的結果是:%b", 3 > 7);
System.out.println(str3);
String str4 = String.format("100的一半是:%d", 100 / 2);
System.out.println(str4);
//使用printf代替format方法來格式化字符串
System.out.printf("50元的書打8.5折扣是:%f 元", 50 * 0.85);
}
}
說明:
- 輸出結果爲:
見過,晁天王及衆位頭領 字母a的大寫是:A 3 > 7的結果是:false 100的一半是:50 50元的書打8.5折扣是:42.500000 元
String
類的format(String format, Object... args)
方法用於創建格式化的字符串。該方法無需String
類型的變量,直接使用類名String
即可調用。該方法第一個參數是被格式化的字符串,第二個參數是替換格式符的字符串,第二個參數中的…
表示方法可變參數,即參數的個數根據格式符個數來確定。字符串格式化就是使用第二個可變參數中的值按照順序替換第一個參數中的格式符。format(String format, Object... args)
方法的格式符定義如下:格式符 說明 示例 %s
字符串類型 "李逵"
%c
字符類型 'm'
%b
布爾類型 true
%d
整數類型(十進制) 100
%x
整數類型(十六進制) FF
%o
整數類型(八進制) 77
%f
浮點類型 99.99
- 字符串的格式化避免了使用
+
來連接字符串,使得字符串的構建更方便。 System.out.printf(String format, Object ... args)
具有相同的功能,但是只能用於一次輸出。
三、字符串與基本數據類型的轉換
在實際開發中,經常會遇到字符串類型與基本類型的轉換操作。
1、基本數據類型轉換爲String類型
基本類型的數據轉換成字符串類型有通常有兩種方法:
- 通過字符串連接將基本類型轉換成
String
類型。 - 通過
String
類型提供的valueOf(基本類型數據 變量名)
方法將基本類型轉換成字符串類型。
下面是一個示例:
public class Test {
public static void main(String[] args) {
// 通過連接字符串將基本類型轉換爲String類型
int num1 = 10;
double num2 = 3.14;
boolean boolean1 = true;
String sNum1 = "" + num1;
String sNum2 = "" + num2;
String sBoolean1 = "" + boolean1;
// 通過valueOf(基本類型數據 變量名)方法將基本類型轉換爲String類型
int num3 = 10;
double num4 = 3.14;
boolean boolean2 = true;
String sNum3 = String.valueOf(num3);
String sNum4 = String.valueOf(num4);
String sBoolean2 = String.valueOf(boolean2);
}
}
說明:
- 使用
+
運算符將基本類型與字符串類型連接時,Java首先自動將基本類型轉換成字符串類型,然後連接在一起.。 - 使用
String
類的valueOf(基本類型數據 變量名)
方法可以將基本類型轉換成字符串類型,該方法無需String
類型的變量,直接使用類名String
即可調用。
2、String類型轉換爲基本數據類型
將字符串類型轉換爲基本類型,需要使用基本類型的包裝類。Java爲每一種基本類型都提供了對應的包裝類,包裝類提供了一些常用的操作,其中就包括將字符串類型轉換成基本類型。基本類型的包裝類及其轉換方法如下表:
基本類型 | 包裝類 | 方法 | 方法說明 |
---|---|---|---|
byte |
Byte |
parseByte(String s) |
將字符串轉換爲byte 類型 |
short |
Short |
parseShort(String s) |
將字符串轉換爲short 類型 |
int |
Integer |
parseInt(String s) |
將字符串轉換爲int 類型 |
long |
Long |
parseLong(String s) |
將字符串轉換爲long 類型 |
float |
Float |
parseFloat(String s) |
將字符串轉換爲float 類型 |
double |
Double |
parseDouble(String s) |
將字符串轉換爲double 類型 |
boolean |
Boolean |
parseBoolean(String s) |
將字符串轉換爲boolean 類型 |
下面是一個示例:
public class Test {
public static void main(String[] args) {
String str1 = "1", str2 = "true", str3 = "3.14";
byte num1 = Byte.parseByte(str1);
short num2 = Short.parseShort(str1);
int num3 = Integer.parseInt(str1);
long num4 = Long.parseLong(str1);
float num5 = Float.parseFloat(str3);
double num6 = Double.parseDouble(str3);
boolean boolean1 = Boolean.parseBoolean(str2);
}
}
說明:
- 本例中的轉換方法均無需對應類型的變量,直接使用類名即可調用。
- 字符串與
char
類型的轉換可以通過字符串的charAt(int index)
方法完成。如下例:public class Test { public static void main(String[] args) { String str = "Hello World"; char char1 = str.charAt(6); System.out.println("Hello World中第6位下標是字符" + char1); // “Hello World中第6位下標是字符W” } }