零基礎學Java第四節(字串相關類)

程式設計攻略發表於2022-05-17

本篇文章是《零基礎學Java》專欄的第四篇文章,文章採用通俗易懂的文字、圖示及程式碼實戰,從零基礎開始帶大家走上高薪之路!

String

本文章首發於公眾號【程式設計攻略】

在Java中,我們經常使用字串,所有的字串值的型別均為String,它不屬於基本型別,它的全名為java.lang.String,我們有必要在這裡學習掌握一些它的基本使用方法。

  • 字串常量:在Java中所有的字串常量均是以雙引號括起來的,比如: "abc"等。因為它的型別是String型別,自然,每個字串常量均為String的物件,也自然可以呼叫String中的public(所謂public就是全部開放給程式設計師使用的方法、屬性或者類)方法。比如:"abc".indexof('b'),它的意義就是在"abc"中,'a'第一次出現的位置(從0開始),它的結果就是1。

  • 字串比較:

    • ==!=在比較引用型別的資料時是比較引用是不是相同,而不是比較物件中的內容。比如:"abc" == "abc"結果是什麼?答案是true,這意味著什麼?意味著==左右的這兩個常量其實是同一個物件,並不是兩個不同的具有相同字元組合的物件。所以,在你的程式中,不管你寫多少個"abc",這些"abc"都是同一個字串。

    • 那麼如何比較兩個字串的內容是否一樣呢?我們使用String中equals()方法,比如:比較"abc""abd"的內容,我們可以這麼寫:

      "abc".equals("abd") 或者 "abd".equals("abc")
      
  • 字串變數的初始化:所謂初始化就是獲得第一個值。因為String類的構造方法有好幾個,所以字串變數的初始化也會有相應的幾個形式,我們這裡只瞭解常用的方式,其他方式,大家自己檢視JDK說明書進行了解。

    • 第一種形式:

        String s = "asdf";
      

      我們在學習Java的時候,一定要知道原理,不要只知其一不知其二。這條語句的含義是什麼呢?我們知道字串常量也是物件,所以它的意義就是把"asdf"的引用放入s這個變數中,那麼 s == "asdf" 的結果呢?自然也是true

    • 第二種形式:

        String s = new String("asdf");
      

      大家可以看到這種形式是標準的生成類物件的形式,那麼這條語句執行以後,s == "asdf" 的結果呢?此時,就不再是true,而是false,它說明s引用的"asdf"和作為引數傳遞給String構造方法的"asdf"是兩個不同的字串了。這條語句的含義就是以"asdf"為模板,再建立一個內容為asdf的字串物件。但是,s.equals("asdf")的值呢?因為這兩個字串的字元序列是一致的,所以,結果為true

  • 常用方法表

方法首部 方法功能 可能丟擲的異常
public char charAt(int index) 返回指定索引處的 char 值。索引範圍為從 0 到 length() - 1。序列的第一個 char 值位於索引 0 處,第二個位於索引 1 處,依此類推,這類似於陣列索引。 index的值如果不在字串長度範圍內,會產生IndexOutOfBoundsException,該異常可以不進行捕獲。
public boolean contains(CharSequence s) 當且僅當此字串包含指定的s字元序列時,返回 true。 NullPointerException - 如果 s 為 null,該異常可以不進行捕獲
public boolean equals(Object anObject) 將此字串與指定的物件比較。當且僅當該引數不為 null,並且是與此物件表示相同字元序列的 String 物件時,結果才為 true。
public boolean equalsIgnoreCase(String anotherString) 將此 String 與另一個 String 比較,不考慮大小寫。如果兩個字串的長度相同,並且其中的相應字元都相等(忽略大小寫),則認為這兩個字串是相等的。
public int compareTo(String anotherString) 如果引數字串等於此字串,則返回值 0;如果此字串按字典順序小於字串引數,則返回一個小於 0 的值;如果此字串按字典順序大於字串引數,則返回一個大於 0 的值。
public int compareToIgnoreCase(String str) 按字典順序比較兩個字串,不考慮大小寫。根據指定 String 大於、等於還是小於此 String(不考慮大小寫),分別返回一個負整數、0 或一個正整數。
public int indexOf(int ch) 在此物件表示的字元序列中第一次出現該字元的索引;如果未出現該字元,則返回 -1。
public int indexOf(int ch,int fromIndex) 從指定的索引(含)開始搜尋,返回在此字串中第一次出現指定字元處的索引,否則返回 -1。
public int indexOf(String str) 如果字串引數作為一個子字串在此物件中出現,則返回第一個這種子字串的第一個字元的索引;如果它不作為一個子字串出現,則返回 -1。
public int indexOf(String str,int fromIndex) 從指定的索引開始,返回指定子字串在此字串中第一次出現處的索引,否則返回 -1。
public boolean isEmpty() 如果 length() 為 0,則返回 true;否則返回 false。
public int lastIndexOf(int ch) 在此字元序列中最後一次出現該字元的索引;如果未出現該字元,則返回 -1。
public int lastIndexOf(int ch,int fromIndex) 從指定的索引處開始進行反向搜尋,返回指定字元在此字串中最後一次出現處的索引。如果在該點之前未出現該字元,則返回 -1。
public int lastIndexOf(String str) 如果字串引數作為一個子字串在此物件中出現一次或多次,則返回最後一個這種子字串的第一個字元。如果它不作為一個子字串出現,則返回 -1。
public int lastIndexOf(String str,int fromIndex) 返回指定子字串在此字串中最後一次出現處的索引,從指定的索引開始反向搜尋。如果在該點之前未出現該字串,則返回 -1。
public int length() 返回此字串的長度。
public String replace(char oldChar,char newChar) 返回一個新的字串,它是通過用 newChar 替換此字串中出現的所有 oldChar 得到的。
public String replace(CharSequence target,CharSequence replacement) 使用指定的replacement替換此字串所有匹配target的子字串。該替換從字串的開頭朝末尾執行,例如,用 "b" 替換字串 "aaa" 中的 "aa" 將生成 "ba" 而不是 "ab"。 NullPointerException - 如果 target 或 replacement 為 null。。該異常不必捕獲。
public String substring(int beginIndex) 返回一個新的字串,它是此字串的一個子字串。該子字串從指定索引處(含)的字元開始,直到此字串末尾。示例: "unhappy".substring(2) 返回 "happy" IndexOutOfBoundsException - 如果 beginIndex 為負或大於此 String 物件的長度。該異常不必捕獲。
public String substring(int beginIndex,int endIndex) 返回一個新字串,它是此字串的一個子字串。該子字串從指定的 beginIndex 處開始,直到索引 endIndex - 1 處的字元。因此,該子字串的長度為 endIndex-beginIndex。示例: "hamburger".substring(4, 8) returns "urge" IndexOutOfBoundsException - 如果 beginIndex 為負,或 endIndex 大於此 String 物件的長度,或 beginIndex 大於 endIndex。該異常不必捕獲。
public String toLowerCase() 使用預設語言環境的規則將此 String 中的所有字元都轉換為小寫。
public String toUpperCase() 使用預設語言環境的規則將此 String 中的所有字元都轉換為大寫。
public String trim() 返回字串的副本,忽略前導空白和尾部空白。
public static String valueOf(boolean b) 如果引數為 true,則返回一個等於 "true" 的字串;否則,返回一個等於 "false" 的字串。
public static String valueOf(char c) 返回 char 引數的字串表示形式。例如:String.valueOf('a')的值為"a"
public static String valueOf(char[] data) 一個新分配的字串,它表示包含在字元陣列引數中的相同字元序列。
public static String valueOf(char[] data,int offset,int count) 返回由data陣列中從offset開始的count個陣列元素組成字串 IndexOutOfBoundsException - 如果 offset 為負,count 為負,或者 offset+count 大於 data.length。該異常可以不捕獲。
public static String valueOf(double d) 返回 double 引數的字串表示形式。同單引數的 Double.toString 方法返回的結果一致。
public static String valueOf(float f) 返回 float 引數的字串表示形式。 同單引數的 Float.toString 方法返回的結果一致。
public static String valueOf(int i) 返回 int 引數的字串表示形式。 同單引數的 Integer.toString 方法返回的結果一致。
public static String valueOf(long l) 返回 long 引數的字串表示形式。 同單引數的 Long.toString 方法返回的結果一致。
public static String valueOf(Object obj) 如果引數為 null,則字串等於 "null";否則,返回 obj.toString() 的值。
  • 補充解釋
    • 從上面的方法列表,我們看到有些方法名字相同,但是引數不同的情況,這種情況為方法的過載(overload),比如valueOf方法。所謂過載,是在同一個類中,同名但參數列不同的方法的多次定義,這些過載的方法在被呼叫時,Java會根據實參的不同而決定呼叫不同的過載方法。Java是根據什麼區分不同的引數的呢?是根據對應位置引數的型別來區分的。
    • 有些方法的前面帶有static這個修飾詞,那麼這種方法,我們稱之為靜態方法,這種方法是通過類名直接呼叫,而不必通過物件來呼叫。例如上表中valueOf這個方法,呼叫的時候,形如:String.valueOf(123)
    • 前表中,我們看到所有的方法前面都有個public,類對外提供服務的方法,都是通過public這個修飾詞進行標識的。我們在定義類的方法時,不是所有的方法都是public的,有些方法只供類(或包內、或子類可用)內部使用,這就好比大家在超市結賬的時候,只需要把貨物和錢款交給收銀員就行了,至於收銀員在隨後如何盤存,都是超市內部的機制,我們無需關注一樣。
    • 有些方法,如:charAt可能會丟擲異常,但是這些異常在程式中又不必捕獲,也不必宣告,這是什麼情況?我們可以看看這些異常都繼承自哪個類:java.lang.RuntimeException 。我們這裡說:凡繼承自這個類的異常子類在你的程式中可以不進行捕獲,也不進行宣告,但是一旦發生這種型別的異常,你的程式會被毫不猶豫的中斷,由系統對該異常進行處理,而系統處理的很簡單,只是列印出出錯棧資訊,然後中斷掉你的程式。所以,如果從你的程式的健壯性考慮,我們最好還是進行捕獲並進行處理。

StringBuffer和StringBuilder

String物件一旦建立,它的內容是不能改變的,大家可能說String的replace方法不是在替換子字串嗎?我們要明確,replace得到的是一個新的字串,對原字串沒有任何影響。有時候,我們需要在原有字串的基礎上操作,這個時候就需要使用StringBuffer或者StringBuilder了。

StringBuffer用於多執行緒環境,StringBuilder用於單執行緒環境。這兩個類中提供public方法是一致的。在這兩個類上的主要操作是 appendinsert 方法,這兩個方法以各種型別過載,以接受任意型別的資料。每個方法都能有效地將給定的資料轉換成字串,然後將該字串的字元新增或插入到字串生成器中。append 方法始終將這些字元新增到生成器的末端;而 insert 方法則在指定的點新增字元。其他還有一些方法,大家可以參考JDk說明書。

我們在生成這兩種類的物件時,如果不帶引數,比如:StringBuffer sb = new StringBuffer(),它會構造一個其中不帶字元的字串緩衝區,初始容量為 16 個字元。 但是如果使用的是帶引數構造方法,比如:StringBuffer sb = new StringBuffer("abc");它會構造一個字串緩衝區,並將其內容初始化為 abc 。該字串的初始容量為 16 加上字串引數的長度,既是19。也可以在建立物件的時候,通過傳遞一個數值來設定字串緩衝區的大小,比如:StringBuffer sb = new StringBuffer(20);,這裡20就是字串緩衝區的大小。

陣列概念

一個陣列就是一組資料的序列,該序列中每個元素的型別相同,可以是基本型別,也可以是引用型別。如果是基本型別,每個陣列元素所在的記憶體空間中存放的是基本型別的數值;如果是引用型別,每個陣列元素所在的記憶體空間中存放的是引用。如圖:

  • 陣列的定義形式(兩種):

    • int[] a1; 這種形式表明a1這個變數是陣列變數,它的陣列元素型別為int型別
    • int a1[]; 這種形式表明a1[]陣列元素的型別為int型別,a1是陣列變數

    不管哪種形式,我們在定義的時候都不能像C語言一樣指定陣列的大小,我們通過下面的這個例子,來進一步說明它們之間的區別:

    int[] a, b, c; 這裡我們可以知道a、b、c這三個變數均為陣列變數
    int a[], b, c; 這裡我們知道只有a是陣列變數,而b、c均為一般變數,而非陣列變數
    
  • 陣列變數的意義:陣列變數是引用型別的變數,這意味著,陣列變數中存放的是陣列的引用,而非陣列本身,陣列的儲存空間是在初始化的時候在堆(所謂堆,大家可以理解做一個大倉庫)中分配的,這一點同C語言有很大區別,這也成為Java陣列的一個優勢,陣列的大小可以在執行的時候確定,而不必在定義的時候就確定下來。

  • 陣列的初始化:陣列的初始化像其他型別的變數一樣,既可以在定義的同時初始化,也可以在定義以後,在第一次使用的使用初始化。初始化的形式用兩種:

    • int a[] = new int[10]; 這種形式,在堆中分配一段能夠放下10個int型別資料的儲存空間,並將其引用放在a這個陣列變數中;
    • int a [] = { 1, 2, 3, 4, 5 }; 這種形式其實是把陣列{ 1, 2, 3, 4, 5 }的引用放入了a中,而且這種形式只能在定義陣列的同時進行。
      • 如果陣列元素為引用型別,有兩種使用大括號的對陣列初始化的形式:
      public class ArrayInit {
      	public static void main(String[] args) {
      		Integer[] a = {
      				new Integer(1),
      				new Integer(2),
      				new Integer(3),
      		};
      		Integer[] b = new Integer[] {
      				new Integer(1),
      				new Integer(2),
      				new Integer(3),
      		};
      	}
      }
      
  • 陣列元素的引用:陣列元素的引用也是通過下標進行的,下標可以是一個int型別的表示式,但是值的範圍必須在0陣列大小-1這個範圍內。陣列元素的型別既是定義陣列時所指定的型別。

多維陣列

二維以上的陣列就看作多維陣列,陣列在Java中的實現是採用鏈式儲存實現的,如圖:

多維陣列的定義和初始化原則同一維是一樣的,如下:

  • 第一種形式,
    int[][] a1 = {
    	{ 1, 2, 3},
    	{ 4, 5, 6},
    	{ 7, 8, 9}
    };  //每個向量用大括號括起來。
    
  • 使用new定義a2的大小:
    int[][][] a2 = new int[2][2][4];
    

由於在Java中採用鏈式儲存陣列,陣列中向量的大小不必相同,比如:

int[][] a1 = {
		{ 1, 2},
		{ 3, 4, 5, 6},
		{ 7, 8, 9}
	};

甚至還可以如下例:

int b[][]; //定義一個二維陣列
b = new int[ 2 ][ ]; // b引用一個具有兩個子陣列的陣列 
b[ 0 ] = new int[ 5 ]; // b[0]引用一個具有5個元素的陣列 
b[ 1 ] = new int[ 3 ]; // b[1]引用一個具有3個元素的陣列 

陣列作為方法的引數

方法的引數可以是陣列,在使用陣列引數時需要注意以下事項:

  • 在形參表中,陣列名後的方括號不能省略,方括號個數和陣列的維數相等
  • 實參表中,陣列名後不需括號
  • 引數是陣列時,形參和實參傳遞的是引用

示例:

class A{
	void f(int va[]){
		for(int i = 0; i < va.length; i++)//va.length為va這個陣列的大小
			va[i]++;
	}

	public static void main(String args[]){

		int[] aa = new int[10];
		A ta = new A();

		for(int i = 0; i < aa.length; i++) 
			aa[i] = i;

		System.out.println("執行f()之前");
		for(int i = 0; i < aa.length; i++) 
			System.out.print(aa[i] + " ");

		//把aa作為實參傳遞給f方法
		ta.f(aa); //f這個方法的呼叫必須使用物件,因為它是一個非靜態方法
 
		System.out.println("\n執行f()之後");
		for(int i = 0; i < aa.length; i++) 
			System.out.print(aa[i] + " ");
	}
}

陣列的複製

把一個陣列中的內容複製到另一個陣列不能使用賦值語句a = b,這種形式使得a引用和b相同的陣列。如果需要複製陣列,我們可以使用System類中的 arraycopy方法,它的方法首部如下:

public static void arraycopy(Object src,
                             int srcPos,
                             Object dest,
                             int destPos,
                             int length)

從指定源陣列src中複製一個陣列,從指定位置srcPos開始,srcPossrcPos+length-1 之間的length個陣列元素,到目標陣列dest的指定位置destPos開始,destPosdestPos+length-1 位置。

如果引數 srcdest 引用相同的陣列物件,則複製的執行過程就好像首先將 srcPossrcPos+length-1 位置的元素複製到一個有 length 個元素的臨時陣列,然後再將此臨時陣列的內容複製到目標陣列的 destPosdestPos+length-1 位置一樣。

以下三種情況會丟擲異常:

  • 如果 src或者destnull,則丟擲 NullPointerException 異常。

  • 只要下列任何情況為真,則丟擲 ArrayStoreException 異常並且不會修改目標陣列:

    • src 引數不是陣列物件。
    • dest 引數不是陣列物件。
    • srcdest 引用的陣列元素的型別是不一致的基本型別。
    • srcdest引數引用的陣列的元素為一個為基本型別,另一個為引用型別
  • 如果源陣列中 srcPossrcPos+length-1 位置上的實際元素通過分配轉換並不能全部轉換成目標陣列的元素型別,則丟擲 ArrayStoreException 異常。在這種情況下,假設複製過程已經進行到k個(k < length)這麼多,此時丟擲異常,從 srcPossrcPos+k-1 位置上的源陣列元素已經被複制到目標陣列中的 destPosdestPos+k-1 位置,而目標陣列中的其他位置不會被修改。

  • 只要下列任何情況為真,則丟擲 IndexOutOfBoundsException 異常,並且不會修改目標陣列:

    • srcPosdestPoslength 引數為負。
    • srcPos+length 大於 src.length,即源陣列的長度。
    • destPos+length 大於 dest.length,即目標陣列的長度

String與字元陣列

在Java中字元陣列不能當作字串來看待,但是我們可以使用字元陣列作為模板來建立字串,如下:

char data[] = {'a', 'b', 'c'}; //這裡data不能當作字串
String str = new String(data); //str引用的既是字串 "abc"

對陣列的操作

對陣列遍歷

所謂遍歷(Traversal),是指按照某種方式,依次對某種資料結構中的每個元素做一次且僅做一次的訪問。對陣列進行遍歷通常可以使用迴圈語句,這裡我們再介紹一個專門針對遍歷的foreach語句,它的語法格式如下:

//這裡type為被遍歷結構中元素的型別名,x為結構中的元素,collection為被遍歷的結構物件
for(type x : collection){ 
	...//迴圈體
}

如下例:

int[] a = new int[10];
//這裡為一般的for迴圈
for(int i = 0; i < a.length; i++) a[i] = i;
//這裡為foreach語句
for(int x : a){//foreach語句中無法使用下標
	System.out.print(x + " ");
}

對陣列的排序

對陣列的排序,我們當然可以自己寫出各種標準的排序演算法,這裡介紹一個工具類java.util.Arrays(注意是複數)。此類包含用來運算元組(比如排序和搜尋)的各種方法。除非特別註明,否則如果該類中的方法的陣列引數引用值為 null,則會丟擲 NullPointerException

升序排序

該類中有一系列對陣列進行排序的方法,方法名為sort,它的一系列過載實現,可以針對各種陣列元素型別的陣列進行升序排序。典型的,我們看下面的方法首部:

public static void sort(int[] a)

該方法對傳入的 int 型陣列a按數字升序進行排序。該排序演算法是一個經過調優的快速排序演算法。

我們也可以只對陣列中的某一部分進行排序,方法首部如下:

public static void sort(int[] a,
                        int fromIndex,
                        int toIndex)

該方法對傳入的 int 型陣列a中從fromIndextoIndex-1的元素按數字升序進行排序。同樣,它也是一個經過調優的快速排序演算法。
該方法可能會丟擲下面的異常:

  • IllegalArgumentException - 如果 fromIndex > toIndex
  • ArrayIndexOutOfBoundsException - 如果 fromIndex < 0toIndex > a.length

上面的兩個方法,經過過載,第一個引數可以是其他各種型別,包括基本型別和引用型別。

大家可能注意到了,上述的sort只能進行升序的排序,如果是其他複雜的排序方式,則就不適用了。

帶有 Comparator的排序

JDK為我們提供了強大的排序支援,因為涉及到一些我們尚未接觸的知識,這裡我先只做瞭解。

public static <T> void sort(T[] a, Comparator<? super T> c)
與
public static <T> void sort(T[] a,
                            int fromIndex,
                            int toIndex,
                            Comparator<? super T> c)

這兩個的區別在於第一個對整個陣列進行排序,第二個可以選擇排序範圍。

陣列元素的查詢

對陣列中元素進行查詢,我們最簡單但是效率可能最低下的方法就是對陣列進行遍歷。同樣工具類java.util.Arrays也為我們提供了可以直接使用的查詢方法binarySearch,該方法也有一系列的過載。使用該方法的前提,該陣列必須是通過sort進行過排序的。它的方法首部如下:

public static int binarySearch(int[] a, int key)
或者
public static int binarySearch(int[] a,
                               int fromIndex,
                               int toIndex,
                               int key)

這兩個的區別在於第一個對整個陣列進行排序,第二個可以選擇排序範圍。經過過載,第一個引數可以是其他各種型別,包括基本型別和引用型別。

方法中a為被查詢陣列,key是需要在此陣列中查詢的鍵值,fromIndex為起始位置,toIndex-1為終止位置。

如果key值包含在陣列中,則返回它的索引值;否則返回 (-(插入點) - 1)。插入點 被定義為將鍵插入陣列的那一點:即第一個大於此鍵的元素索引,如果陣列中的所有元素都小於指定的鍵,則為 a.length或者toIndex,這保證了當且僅當此鍵被找到時,返回的值將 >= 0,否則為負值。

同樣,該方法也有二個帶有Comparator的方法過載,這裡不再贅述。

關於工具類java.util.Arrays中的其他方法,大家可以檢視JDK說明書。

問題

用篩法求1000以內的素數,並按每行10個輸出出來。

最後

本文章來自公眾號【程式設計攻略】,更多Java學習資料見【程式設計攻略】

相關文章