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

本篇文章是《零基礎學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學習資料見【編程攻略】

Tags: