天天看點

【Java基礎】Java反射機制、正規表達式、比較器、GC垃圾回收、StringBuffer類、對象克隆、日期處理

《第一行代碼:Java》第10章、Java常用類庫 讀書筆記

文章目錄

    • 第10章、Java常用類庫
      • 10.1 StringBuffer類
      • 10.2 Runtime類
        • Runtime類方法
        • 記憶體劃分與GC垃圾回收
      • 10.3 System類
      • 10.4 對象克隆
      • 10.5 數字操作類
        • Math類
        • Random類
        • 大數操作類
      • 10.6 日期處理類
        • Date類
        • 日期格式化:SimpleDateFormat
        • Calendar類
      • 10.7 比較器
        • Arrays類
        • 比較器:Comparable
        • 資料結構——BinaryTree(二叉樹)
        • 挽救的比較器:Comparator
      • 10.8 正規表達式
        • 正則标記
        • String類對正則的支援
      • 10.9 反射機制
        • 認識反射
        • Class類對象執行個體化
        • 反射執行個體化對象
        • 利用反射實作工廠設計模式
        • 反射調用構造
        • 反射調用方法
        • 反射調用成員
        • 反射機制總結
      • 本章小結

第10章、Java常用類庫

10.1 StringBuffer類

在Java中,字元串使用String類進行表示,但String類表示的字元串一旦聲明就無法改變。是以String字元串不适合用于需要頻繁修改的字元串操作上,是以對于這種情況Java中提供了StringBuffer類字元串友善使用者修改。

  • String與StringBuffer定義的差別:
    • String類的定義:
      public final class String extends Object implements Serializable, Comparable<String>, CharSequence
                 
    • StringBuffer類定義:
      public final class StringBuffer extends Object implements Serializable, CharSequence
                 
    通過兩個類的定義結構可以發現,String類與StringBuffer類都是CharSequence接口的子類,也就是說String類與StringBuffer類對象都可以向上轉型為CharSequence接口執行個體化。
  • 取得CharSequence接口執行個體化對象:
    package com.yootk.demo;
    public class TestDemo {
    	public static void main(String[] args) throws Exception {
    		CharSequence seq1 = "String類字元串"; 							// 向上轉型
            CharSequence seq2 = new StringBuffer("StringBuffer類字元串");	// 向上轉型
    		System.out.println(seq1); 				// String類覆寫的toString()
            System.out.println(seq2); 				// StringBuffer類覆寫的toString()
    	}
    }
               
  • 雖然String與StringBuffer類都屬于CharSequence接口的子類,但這兩個類的對象不能直接轉換。
  • String轉StringBuffer類對象:
    • 方式一、利用StringBuffer類的構造方法:public StringBuffer(String str)
    • 方式二、利用StringBuffer類中的append()方法:public StringBuffer append(String str)
      StringBuffer buf = new StringBuffer();
      buf.append("java").append("學習");
                 
  • StringBuffer轉String類對象:
    • 方式一、利用String類的構造方法:public String(StringBuffer buffer)
      StringBuffer buf = new StringBuffer("java學習");
      String str = new String(buf)
                 
    • 方式二、利用StringBuffer類的toString()方法:public String toString()
      StringBuffer buf = new StringBuffer("java學習");
      String str = buf.toString();
                 
  • String類對象與StringBuffer類對象内容的比較:

    在String類中提供了一個與StringBuffer比較的方法:public boolean contentEquals(StringBuffer sb)

    package com.yootk.demo;
    public class TestDemo {
    	public static void main(String[] args) throws Exception {
    		StringBuffer buf = new StringBuffer("yootk");
    		System.out.println("yootk".contentEquals(buf));	
    	}
    }
    // 程式執行結果:	true
               
  • StringBuffer類的常用操作方法:
    public StringBuffer append(資料類型變量)					// 資料最佳操作,可接收8種基本資料類型和String、StringBuffer類字元串
    public StringBuffer reverse()							// 字元串反轉操作
    public StringBuffer insert(int offset, 資料類型 變量名)	// 在指定位置追加内容,可接收8種基本資料類型和String、StringBuffer類字元串
    public StringBuffer delete(int Start, int end)			// 删除指定索引範圍的内容
               
    package com.yootk.demo;
    public class TestDemo {
    	public static void main(String[] args) throws Exception {
    		StringBuffer buf = new StringBuffer();
            buf.append("www..com").insert(4, "baidu").reverse().delete(4, 8);
    		System.out.println(buf);
        }
    }
    // 程式執行結果:	moc..www
               
    由于上面所有方法傳回值都是StringBuffer類對象,所有可以使用代碼鍊的方式調用方法,例如:對象.append(“xxx”).insert()…
  • StringBuilder類:

    從JDK1.5開始,Java中增加了一個新類:StringBuilder類,其定義結構如下:

    public final class StringBuilder extends Object implements Serializable, CharSequence
               
    通過定義結構發現,StringBuilder類與Stringbuffer類是完全相同的。而你去檢視JDK源碼後會發現,這兩個類中的方法功能是一樣的,唯一的差別是StringBuffer類中的所有方法都用關鍵字“synchronized”進行同步定義,而StringBuilder類中沒有進行同步定義,是以StringBuilder類的方法都是異步的。

10.2 Runtime類

Runtime類方法

在每一個JVM程序中,都會存在一個運作狀态的操作類的對象,而這個對象所屬類型就是Runtime類。利用Runtime類可以啟動新的程序或進行運作環境的操作,如:取得記憶體空間大小和釋放垃圾空間。

  • Runtime類使用了單例設計模式(見5.7單例設計模式),是以無法通過構造方法獲得其執行個體化對象,是以Runtime中提供了一個static方法getRuntime()用來取得Runtime類的執行個體化對象。
  • Runtime類中的常用方法:
    public static Runtime getRuntime()						// 普通,取得Runtime類執行個體化對象
    public long maxMemory()									// 普通,傳回最大可用記憶體大小,機關Byte(位元組)
    public long totalMemory()								// 普通,傳回所有可用記憶體大小,機關Byte(位元組)
    public long freeMemory()								// 普通,傳回空餘記憶體大小,機關Byte(位元組)
    public void gc()										// 普通,執行垃圾回收操作
    public Process exec(String command) throws IOException	// 普通,建立新的程序
               
    package com.yootk.demo;
    public class TestDemo {
    	public static void main(String[] args) throws Exception {
            Runtime run = Runtime.getRuntime(); 					// 取得Runtime類的執行個體化對象
            String str = "";
            for (int x = 0; x < 2000; x++) {
                str += x; 							                // 産生大量垃圾
            }
            System.out.println("【垃圾處理前最大可用記憶體量】MAX = " + run.maxMemory());
            System.out.println("【垃圾處理前所有可用記憶體量】TOTAL = " + run.totalMemory());
            System.out.println("【垃圾處理前所有空閑記憶體量】FREE = " + run.freeMemory());
            run.gc(); 							// 釋放垃圾空間
            System.out.println("【垃圾處理後最大可用記憶體量】MAX = " + run.maxMemory());
            System.out.println("【垃圾處理後所有可用記憶體量】TOTAL = " + run.totalMemory());
            System.out.println("【垃圾處理後所有空閑記憶體量】FREE = " + run.freeMemory());
    	}
    }
    /*
    程式執行結果:
        【垃圾處理前最大可用記憶體量】MAX = 3799515136
        【垃圾處理前所有可用記憶體量】TOTAL = 257425408
        【垃圾處理前所有空閑記憶體量】FREE = 200972864
        【垃圾處理後最大可用記憶體量】MAX = 3799515136
        【垃圾處理後所有可用記憶體量】TOTAL = 257425408
        【垃圾處理後所有空閑記憶體量】FREE = 255381872
    */
               
    本程式使用for循環産生了大量垃圾空間,然後調用gc方法進行垃圾回收,時可用的空閑空間大大增加。

記憶體劃分與GC垃圾回收

  • Java記憶體劃分:
    【Java基礎】Java反射機制、正規表達式、比較器、GC垃圾回收、StringBuffer類、對象克隆、日期處理
    每一塊記憶體空間都會存在一個記憶體伸縮區,當記憶體不足時就會動态開辟。
  • 垃圾回收過程:
    • 雖然垃圾收集隻需通過一個gc()方法完成,但垃圾回收與Java的記憶體劃分也是有關系的。
      • 因為垃圾回收主要是對年輕代(Young Generation)與舊生代(Old Generation)的記憶體進行回收。
      • 年輕代記憶體空間用于存放新産生的對象,而經過若幹次回收還沒有被回收掉的對象向舊生代記憶體空間移動。
      • 對年輕代進行垃圾回收稱為MinorGC(從垃圾收集),對舊生代垃圾回收稱為MajorGC(主垃圾收集),并且兩塊記憶體回收互不幹涉。
      • 在JVM中的對象回收機制會使用分代回收(Generational Collection)的政策,用較高的頻率對年輕代對象進行掃描和回收,而對舊生代對象則用較低的頻率進行回收,這樣就不需要在每次執行GC時将記憶體中的所有對象都檢查一遍。
    • 對于GC的執行可以用文字描述為:當JVM剩餘記憶體空間不足時就會自動除法GC或直接調用Runtime類中的gc()方法手工回收(手動gc()是進行完全垃圾回收)。
      • Eden記憶體空間不足就要進行從回收(Minor Collection);
      • 舊生代空間不足時就進行主回收(Major Collection);
      • 永久代空間不足就進行完全垃圾回收(Full Collection)。
  • 在清楚JVM的記憶體配置設定後,就可以進一步了解對象建立流程,如圖10-3所示:
    【Java基礎】Java反射機制、正規表達式、比較器、GC垃圾回收、StringBuffer類、對象克隆、日期處理
    • 當使用關鍵字new建立一個新對象時,JVM 會将新對象儲存在Eden 區,但是此時需要判斷Eden 區是否有空餘空間,如果有,則直接将新對象儲存在Eden區内,如果沒有,則會執行“Minor GC”(年輕代 GC)。
    • 在執行完“Minor GC”後會清除掉不活躍的對象,進而釋放Eden區的記憶體空間,随後會對 Eden空間進行再次判斷。如果此時剩餘空間可以直接容納新對象,則會直接為新對象申請記憶體空間;如果此時 Eden區的空間依然不足,則會将部分活躍對象儲存在 Survivor區。
    • 由于Survivor區也有對象會存儲在内,是以在儲存Eden 區發送來的對象前首先需要判斷其空間是否充足,如果Survivor有足夠的空餘空間,則直接儲存 Eden 區晉升來的對象,那麼此時Eden區将得到空間釋放,随後可以在Eden 區為新的對象申請記憶體空間的開辟;如果Survivor 區空間不足,就需要将Survivor區的部分活躍對象儲存到Tenured區。
    • Tenured區如果有足夠的記憶體空間,則會将Survivor區發送來的對象進行儲存,如果此時Tenured 區的記憶體空間也已經滿了,則将執行“Full GC”(完全GC 或稱為“Major GC”,包括年輕代和老年代,相當于使用 “Runtime.getRuntime().gc()” 處理,以釋放老年代中儲存的不活躍對象。如果在釋放後有足夠的記憶體空間,則會儲存Survivor 發送來的對象,進而Survivor 将儲存 Eden 發送來的對象,這樣就可以在Eden區内有足夠的記憶體儲存新的對象。
    • 如果此時老年代的記憶體區也已經被占滿,則會抛出 “OutOfMemoryError” (OOM錯誤),程式将中斷運作。

10.3 System類

  • System類的常用方法:
    public void arraycopy(Object stc, int srcPos, Object dest, int destPos, intlength)	// 數組複制
    public static long currentTimeMillis()												// 取得目前日期時間
    public static void gc()																// 垃圾收集
               
  • System類中的垃圾回收方法gc()并不是新操作,而是間接調用Runtime類中的gc()方法,不是方法重寫。是以調用System.gc()和Runtime.gc()效果設計一樣的。
  • 對象的回收:我們可以發現Java中并沒有像C++中一樣擁有析構函數,所有當我們向回收一個對象時,則對象所在的類可以通過finalize()方法實作,該方法會在對象被回收時自動調用。此方法由Object類定義:
    protected void finalize() throws Throwable
               
  • 對象回收操作:
    package com.yootk.demo;
    class Human {
    	public Human() {
    		System.out.println("調用構造方法");
    	}
    	@Override
        protected void finalize() throws Throwable {		// 覆寫Object類方法
    		System.out.println("對象被回收");
    		throw new Exception("此處即使抛出異常對象也不會産生任何影響!");
    	}
    }
    public class TestDemo {
    	public static void main(String[] args) {
    		Human mem = new Human(); 						// 執行個體化新的對象
    		mem = null; 									// 産生垃圾
    		System.gc(); 									// 手工處理垃圾收集
    	}
    }
    /*
    程式執行結果:
        調用構造方法
        對象被回收
    */
               
    當對象被回收時會自動調用finalize()方法,這樣就可以對一些對象回收前進行收尾操作。并且此方法即使産生任何異常或錯誤也不會影響程式的正常執行。
  • final、finally和finalize的差別:
    • final:表示終結器,用于定義不能被繼承的父類,不能被覆寫的方法、變量。
    • finally:異常處理的出口
    • finalize:Object類定義的一個方法,用于執行對象回收的收尾操作,在對象被回收時自動調用。

10.4 對象克隆

  • 對象克隆就是對象的複制操作,在Object類中存在一個clone()方法用于對象的克隆:

    此方法是實作克隆的唯一方法,但此方法是使用protected聲明的,這樣不同包的類産生的對象就無法調用Object類中的clone()方法,是以需要子類來覆寫clone()方法(但調用的任然是父類中的clone方法),才能正常完成克隆操作。

  • Cloneable接口:隻有實作了Cloneable接口的類的對象才能進行克隆,負責會抛出CloneNotSupportedException異常。這個接口屬于辨別接口,用于表示一種能力。
  • 實作克隆操作:
    package com.yootk.demo;
    class Book implements Cloneable { 					// 實作Cloneable接口,表示此類的對象可以被克隆
    	private String title;
    	private double price;
        public Book() {}
    	public Book(String title, double price) {
    		this.title = title;
    		this.price = price;
    	}
    	public void setTitle(String title) {
    		this.title = title;
    	}
    	@Override
    	public String toString() {
    		return "書名:" + this.title + ",價格:" + this.price;
    	}
    	// 由于此類需要對象克隆操作,是以才需要進行方法的覆寫
    	@Override
    	public Object clone() throws CloneNotSupportedException {
    		return super.clone(); 							// 調用父類的克隆方法
    	}
    }
    public class TestDemo {
    	public static void main(String[] args) throws Exception {
            Book bookA = new Book("Java開發", 79.8);			// 執行個體化新對象
            Book bookB = (Book) bookA.clone();				 // 克隆對象,開辟新的堆記憶體空間
            Book bookC = bookA;								 // 使用“ = ”屬于引用指派
            bookB.setTitle("JSP開發");						// 修改克隆對象屬性,不影響其他對象
            System.out.println(bookA);
            bookC.setTitle("C++開發");						// 修改引用對象屬性,會影響被引用對象
            System.out.println(bookA);
    	}
    }
    /*
    程式執行結果:
    	書名:Java開發,價格:79.8
    	書名:C++開發,價格:79.8
    */
               
    克隆出的新對象與被克隆的對象屬性相同,但并不是同一個對象,兩個對象占據着不同的堆空間,彼此間不會互相印象。而使用 “ = ” 指派,屬于引用傳遞,屬于同一個對象,不同的名字而已。

10.5 數字操作類

Java中為了友善進行數學計算,專門提供了 java.lang.Math 類,但Math類所能完成的操作有限。是以在 java.math 包中專門提供了負責大數字的操作。

Math類

Math類就是一個專門進行數學計算的操作類,在Math類中提供的所有方法都是static型的方法,是以可以直接使用類名調用:Math.方法()

Random類

java.util.Random是一個專門負責産生随機數的操作類,此類的常用方法:

public Random()						// 構造,建立一個新的Random執行個體
public Random(long seed)			// 構造,建立一個新的Random執行個體并設定一個種子數
public int nextInt(int bound)		// 普通,産生一個不大于指定邊界的随機整數
           

大數操作類

如果有兩個非常大的數要進行數學操作(這時數字已經超過double的範圍),那麼隻能由字元串來表示,然後再取出進行數學運算,但這種方法難度較高。是以Java中提供了兩個大數字操作類:java.math.BigInteger和java.math.BigDecimal,而這兩個類都屬于Number的子類。

  • 大整形操作類:BIgInteger
    【Java基礎】Java反射機制、正規表達式、比較器、GC垃圾回收、StringBuffer類、對象克隆、日期處理
  • 大小數操作類:BigDecimal(除了擁有與BigInteger同樣的基本計算方式,還擁有以下操作)
    【Java基礎】Java反射機制、正規表達式、比較器、GC垃圾回收、StringBuffer類、對象克隆、日期處理

10.6 日期處理類

Date類

  • 在Java中,如果要表示日期型,需要使用java.ueil.Date類,該類中的主要常用方法:
    public Date()					// 構造,執行個體化Date類對象,預設值為目前時間
    public Date(long date)			// 将long型的日期時間資料變成Date類對象,
    public long getTime()			// 将目前的日期時間變成long型
               
  • Date與long的互相轉換:
    package com.yootk.demo;
    import java.util.Date;
    public class TestDemo {
        public static void main(String[] args) throws Exception {
            Date date = new Date();
            System.out.println(date); 		// 輸出對象資訊
            long cur = date.getTime();		// 将日期時間變成long型
            System.out.println(cur);
    	}
    }
    /*
    程式執行結果:
    	Sun Apr 04 10:44:15 CST 2021
    	1617504255560
    */
               

日期格式化:SimpleDateFormat

雖然使用java.util.Date類可以明确取得目前日期時間,但最終資料的顯示格式不友善使用者閱讀。如果要對顯示的日期時間進行格式轉換,可以通過java.text.SimpleDateFormat類完成。

  • java.text.SimpleDateFormat類常用方法:
    public SimpleDateFormat(String pattern)					// 構造,傳入日期時間标記執行個體化對象
    public final String format(Date date)					// 将日期格式化為字元串資料
    public Date parse(String source) throws ParseException	// 将字元串格式化為日期資料
               
  • 日期時間标記:年(yyyy)、月(MM)、日(dd)、時(HH)、分(mm)、秒(ss)、毫秒(SSS)
  • 将日期格式化顯示:
    package com.yootk.demo;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    public class TestDemo {
        public static void main(String[] args) throws Exception {
            Date date = new Date();					// 執行個體化Date類對象
            // 執行個體化SimpleDateFormat類對象,同時定義好要轉換的目标字元串格式
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
            String str = sdf.format(date); 			// 将Date型變為String型
            System.out.println(str);
        }
    }
    // 程式執行結果:	2021-04-04 11:01:42.339
               
  • SimpleDateFormat可以自動處理錯誤的日期時間數,比如13月,會自動加一年,然後算成1月。
    package com.yootk.demo;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    public class TestDemo {
        public static void main(String[] args) throws Exception {
            String str = "2005-15-57 77:95:22.111" ;	// 字元串由日期時間組成
            // 執行個體化SimpleDateFormat類對象,同時定義好要轉換的目标字元串格式
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS") ;
            Date date = sdf.parse(str) ;				// 将字元串變為日期型資料
            System.out.println(date);
        }
    }
    // 程式執行結果:	Sat Apr 29 06:35:22 CST 2006
               
  • 關于資料類型轉換:
    • Date與String類之間的轉換依靠SimpleDateFormat類
    • String與8種基本資料類型的轉換依靠包裝類與String.valueOf()方法
    • longyuDate轉換依靠Date類提供的構造以及getTime()方法

Calendar類

Calendar類可以取得單獨的日期時間的時分秒日月年。

  • Calendar類中定義的常量與方法:
    【Java基礎】Java反射機制、正規表達式、比較器、GC垃圾回收、StringBuffer類、對象克隆、日期處理
  • Calendar類是一個抽象類,是以不能直接執行個體化對象,其獲得其執行個體化有兩種方式:
    • 使用其子類GregorianCalendar類進行執行個體化
    • 使用Calendar類中的getInstance()方法獲得本類的執行個體化對象。
  • 取得目前的日期時間:
    package com.yootk.demo;
    import java.util.Calendar;
    public class TestDemo {
        public static void main(String[] args) throws Exception {
            Calendar cal = Calendar.getInstance(); 					// 取得本類對象
            StringBuffer buf = new StringBuffer();					// 儲存日期時間資料
            buf.append(cal.get(Calendar.YEAR)).append("-");			// 取得年資料
            buf.append(cal.get(Calendar.MONTH) + 1).append("-");	// 取得月資料,從0開始
            buf.append(cal.get(Calendar.DAY_OF_MONTH)).append(" ");	// 取得天資料
            buf.append(cal.get(Calendar.HOUR_OF_DAY)).append(":");	// 取得小時資料
            buf.append(cal.get(Calendar.MINUTE)).append(":");		// 取得分鐘資料
            buf.append(cal.get(Calendar.SECOND));					// 取得秒資料
            System.out.println(buf);
        }
    }
    // 程式執行結果:	2021-4-4 11:17:49
               
    注意Calendar類取得的月份是從0開始的,是以月份需要+1

10.7 比較器

在Java中存在對象數組概念,可以利用比較器實作對象數組的比較操作。

Arrays類

在Java中,java.util.Arrays類中定義了所有的與數組有關的操作,如:二分查找、相等判斷、數組填充等

  • Arrays類的常用方法
    public static boolean equals(int[] a, int[] b)		// 普通,判斷兩個數組是否相等
    public static void fill(int[] a, int val)			// 将指定内容填充到數組中
    public static void sort(int[] a)					// 數組排序
    public static int binarySearch(int[] a, int key)	// 使用二分查找法對排序後的數組進行檢索
    public static String toString(int[] a)				// 輸出數組資訊
               
    以上方法被重載多次,是以不隻适用于int型數組,還适用于其他資料類型的數組,除了8個基本資料類型數組,還适用于對象數組,不過該對象所屬的類需要實作某些接口。
  • 二分查找法(binarySearch()):又被稱為伴着查找法,在金鄉資料查找時速度比較快。但要想使用二分查找法,則要求數組必須是有序的。其原理如圖10-4所示:
    【Java基礎】Java反射機制、正規表達式、比較器、GC垃圾回收、StringBuffer類、對象克隆、日期處理
  • 二分查找法(binarySearch())的基本思路是将n個元素的數組分成大緻相等的兩個部分。假設需要查找的資料類容為x,則取""數組[長度/2]"與x進行比較:
    • x = 數組[長度/2],則表示存在x,算法終止;
    • x < 數組[長度/2],則隻在數組左半部分繼續搜尋x;
    • x > 數組[長度/2],則隻在數組右半部分繼續搜尋x;
  • 實作二分查找:
    package com.yootk.demo;
    import java.util.Arrays;
    public class TestDemo {
        public static void main(String[] args) throws Exception {
            int data[] = new int[] { 1, 5, 6, 2, 3, 4, 9, 8, 7, 10 };
            java.util.Arrays.sort(data);						// 數組必須排序才可以查找
            System.out.println(Arrays.binarySearch(data, 9));	// 二分查找法
        }
    }	
               

比較器:Comparable

數組分為普通數組和對象數組,普通數組排序可以直接調用Arrays.sort()進行,但對象數組由于其每個元素其實存放的是位址資料,是以不能依據其大小關系來排序,但Arrays類中依然重載了一個sort()方法:public static void sort(Object[] a),此方法可以直接實作對象數組的排序。但使用此方法的前提是,對象所在的類一定要實作Comparable接口,否則就會出現ClassCastException異常。

  • Comparable接口定義如下
    public interface Comparable<T> {
        public int comparaTo(T o);
    }
               
    在Comparable接口中制定一了一個comparaTo()方法,傳回值為int型,而使用者覆寫此方法時隻需傳回3種結果:1(>)、0(=)、-1(<)
  • 實作對象數組排序
    package com.yootk.demo;
    import java.util.Arrays;
    class Book implements Comparable<Book> {		// 實作比較器
        private String title ;
        private double price ;
        public Book(String title,double price) {
            this.title = title ;
            this.price = price ;
        }
        @Override
        public String toString() {
            return "書名:" + this.title + ",價格:" + this.price + "\n" ;
        }
        @Override
        public int compareTo(Book o) {				// Arrays.sort()會自動調用此方法比較
            if (this.price > o.price) {
               return 1 ; 
            } else if (this.price < o.price) {
               return -1 ;
            } else {
               return 0; 
            }
        }
    }
    public class TestDemo {
        public static void main(String[] args) throws Exception {
            Book books [] = new Book [] {
                     new Book("Java開發實戰經典",79.8) ,
                     new Book("JavaWEB開發實戰經典",69.8) ,
                     new Book("Oracle開發實戰經典",99.8) ,
                     new Book("Android開發實戰經典",89.8) 
            } ;
            Arrays.sort(books);						// 對象數組排序
            System.out.println(Arrays.toString(books));
        }
    }
               
  • 注意:對象數組要想使用Arrays.toString()方法,則需要在對象所屬的類中覆寫Object.toString()方法,否則直接使用Arrays.toString()方法傳回的是對象資訊。

資料結構——BinaryTree(二叉樹)

樹是一種比連結清單更加複雜的概念應用,其本質也屬于動态數組對象,但是與連結清單不同的是,樹的最大特征是可以針對資料進行排序。

  • 樹的操作原理:選擇第一個資料作為根節點,而後比根節點小的放在根節點的左子樹(左節點),比根節點大的資料放在右子樹(右節點),取得資料時按照中序周遊的方式取出(左->中->右)。但是如果要想實作這樣的排列,則需要有一個資料的包裝類Node,而且在此類中除了要儲存資料外,還需要儲存對應的左子樹以及右子樹節點對象,如圖10-5所示:
    【Java基礎】Java反射機制、正規表達式、比較器、GC垃圾回收、StringBuffer類、對象克隆、日期處理
  • 其實常用的資料結構已經被Java完整地實作好了,在本書第13章類集架構中會有詳細的講解,此處隻是一個原理的簡單分析。
  • 實作二叉樹:
    • 定義出需要使用的資料類型,并且該類一定要實作Comparable接口,并覆寫compareTo()方法
      class Book implements Comparable<Book> {		// 實作比較器
          private String title ;
          private double price ;
          public Book(String title,double price) {
              this.title = title ;
              this.price = price ;
          }
          @Override
          public String toString() {					// 覆寫Object類的toString()方法
              return "書名:" + this.title + ",價格:" + this.price + "\n" ;
          }
          @Override
          public int compareTo(Book o) {				// 覆寫Comparable接口的compareTo()方法
              if (this.price > o.price) {
                 return 1 ; 
              } else if (this.price < o.price) {
                 return -1 ;
              } else {
                 return 0; 
              }
          }
      }
                 
    • 定義二叉樹,所有的資料結果都通過Node類的對象包裝,同時為了排序需要,儲存資料可以直接用Comparable接口類型,所有的類對象都必須強制轉換為Comparable接口。
      @SuppressWarnings("rawtypes")
      class BinaryTree {
          private class Node {                                    // 定義内部類Node
              private final Comparable data;                      // 排序依據就是Comparable
              private Node left;                                  // 儲存左節點
              private Node right;                                 // 儲存右節點
              public Node(Comparable data) {                      // 定義Node構造方法
                  this.data = data;
              }
              @SuppressWarnings("unchecked")
              public void addNode(Node newNode) {
                  if (this.data.compareTo(newNode.data) > 0) {    // 比this節點小放左邊
                      if (this.left == null) {                    // 左節點為null
                          this.left = newNode;                    // 儲存到左節點
                      } else {
                          this.left.addNode(newNode);             // 接續遞歸判斷
                      }
                  } else {                                        // 比this節點大或等于的放右邊
                      if (this.right == null) {                   // 右節點為null
                          this.right = newNode;                   // 儲存到右節點
                      } else {
                          this.right.addNode(newNode);            // 繼續遞歸判斷
                      }
                  }
              }
              public void toArrayNode() {                         // 将節點轉換為對象數組
                  if (this.left != null) {                        // 表示有左節點
                      this.left.toArrayNode();                    // 繼續遞歸判斷
                  }
                  // 先判斷左節點,再取出中間節點,再取得右節點。(中序周遊)
                  BinaryTree.this.retData[BinaryTree.this.foot++] = this.data;    
                  if (this.right != null) {                       // 表示有右節點
                      this.right.toArrayNode();                   // 繼續遞歸判斷
                  }
              }
          }
          //*******************以上為内部類************************//
          private Node root;                                      // 儲存根節點
          private int count;                                      // 儲存節點數
          private Object[] retData;                               // 儲存傳回的對象數組
          private int foot;                                       // 操作角标
      
          public void add(Object obj) {                           // 進行資料追加
              Comparable com = (Comparable) obj;                  // 向下轉型為Comparable
              Node newNode = new Node(com);                       // 建立節點
              if (this.root == null) {
                  this.root = newNode;
              } else {
                  this.root.addNode(newNode);
              }
              this.count++;
          }
          public Object[] toArray() {                             // 将樹轉為數組形式傳回
              if (this.root == null) {
                  return null;
              }
              this.foot = 0;
              this.retData = new Object[this.count];
              this.root.toArrayNode();
              return this.retData;
          }
      }
                 
    • 測試程式:
      public class TestDemo {
          public static void main(String[] args) throws Exception {
              BinaryTree bt = new BinaryTree();                       // 儲存二叉樹
              bt.add(new Book("Java開發實戰經典",79.8));      // 新增節點
              bt.add(new Book("JavaWEB開發實戰經典",69.8));
              bt.add(new Book("Oracle開發實戰經典",99.8));
              bt.add(new Book("Android開發實戰經典",89.8));
              Object[] obj = bt.toArray();                            // 将資料轉為對象數組儲存
              System.out.println(Arrays.toString(obj));               // 列印資料
          }
      }
      /*
      程式執行結果:
      	[書名:JavaWEB開發實戰經典,價格:69.8, 
      	 書名:Java開發實戰經典,價格:79.8, 
      	 書名:Android開發實戰經典,價格:89.8, 
      	 書名:Oracle開發實戰經典,價格:99.8]
      */
                 
    本程式實作了一個最基礎的二叉樹資料結構,整個程式的實作關鍵在于Node節點的比較判斷。

挽救的比較器:Comparator

想使用Comparable比較器,就意味着得在對象的類定義時就必須考慮好排序的需求。但如果一個類在定義時沒有實作Comparable接口,但又想實作對象數組的排序怎麼辦?為此,在Java中又提供了一個比較器:Comparator接口(挽救的比較器)。

  • Comparator接口定義:
    @FunctionalInterface
    public interface Comparator<T> {
        public int compate(T o1, T o2);
        public boolean equlas(Object obj);
    }
               
    請注意:Comparator接口使用了@FunctionalInterface注解進行聲明,說明其為一個函數式接口,前面我們學函數式接口時說過:函數式接口隻能有一個抽象方法,但這裡為什麼可以定義兩個?其實是因為equlas()方法是Object類裡的,不算在Comparator接口中。
  • 如果想要用Comparator接口實作對象數組的排序操作,需要使用這個數組排序方法:
  • 使用Comparator接口實作對象數組的排序:
    package com.yootk.demo;
    class Book { 
        private String title ;
        private double price ;
        public Book() {}
        public Book(String title,double price) {
            this.title = title ;
            this.price = price ;
        }
        @Override
        public String toString() {
            return "書名:" + this.title + ",價格:" + this.price + "\n" ;
        }
        public double getPrice() {
            return price;
        }
    }
    class BookComparator implements java.util.Comparator<Book> {
        @Override
        public int compare(Book o1, Book o2) {
            if (o1.getPrice() > o2.getPrice()) {
               return 1 ; 
            } else if (o1.getPrice() < o2.getPrice()) {
               return -1 ;
            } else {
               return 0; 
            }
        }
    }
    public class TestDemo {
        public static void main(String[] args) throws Exception {
            Book[] books = new Book[] {
                new Book("Java開發實戰經典",79.8);
                new Book("JavaWEB開發實戰經典",69.8);
                new Book("Oracle開發實戰經典",99.8);
                new Book("Android開發實戰經典",89.8);
            };
            java.util.Arrays.sort(books, new BookComparator());
            System.out.println(java.util.Arrays.toString());
        }
    }
               
  • 由于Comparator接口為函數式接口,是以使用者也可以使用Lambda表達式來實作比較規則定義
    public class TestDemo {
        public static void main(String[] args) throws Exception {
            Book[] books = new Book[] {
                new Book("Java開發實戰經典",79.8);
                new Book("JavaWEB開發實戰經典",69.8);
                new Book("Oracle開發實戰經典",99.8);
                new Book("Android開發實戰經典",89.8);
            };
            java.util.Arrays.sort(books, (o1, o2) -> {				// Lambda表達式
                if (o1.getPrice() > o2.getPrice()) {
                    return 1 ;
                } else if (o1.getPrice() < o2.getPrice()) {
                    return -1 ;
                } else {
                    return 0;
                }
            });
            System.out.println(java.util.Arrays.toString());
        }
    }
               
  • Comparable和Comparator的差別:
    • java.lang.Comparable:是一個需要在類定義時就實作好的接口,實作該接口時覆寫public int compareTo()方法
    • java.util.Comparator:是一個挽救比較器,無需再類定義時實作,實作時應覆寫public int compare()方法。

10.8 正規表達式

正規表達式(Regular Expression,在代碼中常簡寫為regex、regexp或RE)是從JDK1.4引入到Java中的。正規表達式在本質上是一種字元串操作的文法規則,利用此文法規可以更加靈活地實作字元串的比對、拆分、替換等操作。

正則标記

  • 所有的正規表達式支援的類都定義在java.util.regex包裡面,包中定義如下主要的類:
    • Pattern類:主要定義要使用的表達式對象;
    • Matcher類:用于進行正則标記與指定類容的比對操作;
  • 所有可以使用的正則标記都在java.util.regex.Pattern類的說明文檔中定義,常用的标記有6種:
  • 單個字元:
    • 字元:表示由一位字元組成;
    • \\:表示轉義字元 “\”;
    • \t:表示一個“\t”符号(Tab鍵);
    • \n:比對換行(\n)符号;
  • 字元集:
    • [abc]:表示可能是字元a、字元b、字元c中的任意一位;
    • [^abc]:表示不是字元a、b. c中的任意一位;
    • [a-z]:表示所有小寫字母
    • [a-zA-Z]:表示任意的一位字母,不區分大小寫;
    • [0-9]:表示任意的一-位數字;
  • 簡化的字元集表達式:
    • .:表示任意的一位字元;

      \d:等價于“[0-9]”, 屬于簡化寫法;

      \D:等價于“[^0-9]”, 屬于簡化寫法;

      \s:表示任意的空白字元,例如:“\t” “\n";

      \S:表示任意的非空白字元;

      \w:等價于“[a-zA-Z_ 0-9]”, 表示由任意的字母、數字、_組成;

      \W:等價于“[^a-zA-Z _0-9]”, 表示不是由任意的字母、數字、 _組成;

  • 邊界比對

    ^:正則的開始;

    $:正則的結束;

  • 數量表達
    • 正則?:表示此正則可以出現0次或1次;
    • 正則+:表示此正則可以出現1次或1次以上;
    • 正則*:表示此正則可以出現0次、1次或多次;
    • 正則{n}:表示此正則正好出現n次;
    • 正則{n,}:表示此正則出現n次以上(包含n次);
    • 正則{n,m};表示此正則出現n~ m次;
  • 邏輯運算
    • 正則1正則2:正則1判斷完成後繼續判斷正則2;
    • 正則1 |正則2:正則1或者是正則2有一-組滿足即可;
    • (正則):将多個正則作為一-組,可以為這一-組單獨設定出現的次數。

String類對正則的支援

雖然Java本身提供的正則支援類都在java.util.regex包中,但從實際使用來看,很少會利用這個包中的正則操作。大部分的情況下都會考慮使用java.lang.String類中提供的方法來直接簡化正則的操作。

  • String類中與正則有關的5個操作方法:
    public boolean matches(String regex)							// 普通,正則驗證,使用指定的字元串判斷其是否符合給出的正規表達式regex結構
    public String replaceAll(String regex, String replacement)		// 普通,将滿足正規表達式regex的内容全部替換為新内容replacement
    public String replaceFirst(String regex, String replacement)	// 普通,将滿足正規表達式regex的首個内容替換為新内容replacement
    public String[] split(String regex)								// 普通,按照指定的正規表達式regex進行字元串的全部拆分
    public String[] split(String regex, int limit)					// 普通,按照指定的正規表達式regex将字元串拆分為limit部分
               
  • 實作字元串替換:
    package com.yootk.demo;
    public class TestDemo {
        public static void main(String[] args) throws Exception {
            String str = "hello*)(*()yootk(*#mldn*";
            String regex = "[^a-z]"; 								// 此處編寫正規表達式
            System.out.println(str.replaceAll(regex, ""));			// 字元串替換
        }
    }
    // 程式執行結果:	helloyootkmldn
               
    正規表達式regex = “[^a-z]”,表示比對所有非小寫字母的字元,再執行 str.replaceAll(regex, “”) ,會把字元串str中所有的非小寫字母的字元全部用”“替代(删除)。
  • 字元串拆分:
    package com.yootk.demo;
    public class TestDemo {
        public static void main(String[] args) throws Exception {
            String str = "yootk9mldnyo8798o5555tk";
            String regex = "\\d+"; 							// \d:[0-9]  ,  +:一次及以上
            String result[] = str.split(regex);				// 以數字進行拆分
            for (int x = 0; x < result.length; x++) {
                System.out.print(result[x] + "  ");
            }
        }
    }
    // 程式執行結果:	yootk  mldnyo  o  tk  
               
    注意:這裡的“\\d+”為什麼有兩個“\”呢?因為“\”表示轉義符,兩個“\”表示不需要轉義。
  • 判斷是否為IP位址(IPV4)
    package com.yootk.demo;
    public class TestDemo {
        public static void main(String[] args) throws Exception {
            String str = "192.168.1.1";
            String regex = "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}";
            System.out.println(str.matches(regex));
        }
    }
               
    IPV4位址是由4組數字資料和3個“.”組成,是以需要4組"\\d{1, 3}"來判斷數字,和3組“\\.”來判斷“.”。
  • 為什麼“.”符号需要使用“\\.”來判斷?

    因為“.”本身就代表一個正則标記,是以需要在“.”前面使用轉義符“\”,而轉義符在正則标記中使用“\\”來表示。是以使用“\\.”來表示“.”。

  • 簡化正規表達式:
    String regex = "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}";			// 簡化為:
    String regex = "(\\d{1,3}\\.){3}\\d{1,3}";
               
  • 判斷是否為Email,要求:使用者名要求由字母、數字、“_”、“.”組成,必須以字母開頭,結尾必須為字母或數字,長度為2-30最後域名隻能為.com、.cn、.net、.com.cn、.net.cn、.edu、.goc、.org其中之一。完整Email組成正則分析如圖10-8所示。
    【Java基礎】Java反射機制、正規表達式、比較器、GC垃圾回收、StringBuffer類、對象克隆、日期處理
    package com.yootk.demo;
    public class TestDemo {
        public static void main(String[] args) {
            String str = "[email protected]";
            String regex = "[a-zA-Z][a-zA-z0-9_\\.]{0,28}[a-zA-Z]@\\w+\\.(com|cn|net|com\\.cn|net\\.cn|edu|goc|org)";
            System.out.println(str.matches(regex));
        }
    }
               

10.9 反射機制

反射是Java中最為重要的特性,幾乎所有的開發架構以及應用技術中都是基于反射技術的應用。

認識反射

在正常的操作過程中,一定是先确定使用的類,再利用關鍵字new産生執行個體化對象後使用。但是也可以通過Object類中的getClass()方法獲得對象所在的類的資訊。

  • java.lang.Object.getClass()方法定義:
  • 反射初步操作:
    package com.yootk.demo;
    import java.util.Date; 								// 導入所需要的類
    public class TestDemo {
        public static void main(String[] args) throws Exception {
            Date date = new Date(); 					// 産生執行個體化對象
            System.out.println(date.getClass());		// 直接反射輸出
        }
    }
    // 程式執行結果:	class java.util.Date
               

Class類對象執行個體化

當我們使用getClass()方法時,傳回值類型為java.lang.Class,這是反射操作的源頭類。而Class類有3種執行個體化的方式:

  • 方式一:調用Object類中的getClass()方法,但這種方法必須要有Object對象才能使用。
  • 方式二:使用 “ 類名.class ” 獲得Class類執行個體化對象,這種方法不需要對象隻需要類名就可以使用
    package com.yootk.demo;
    public class TestDemo {
        public static void main(String[] args) throws Exception {
            Class<?> cls = java.util.Date.class;	// 通過類名取得Class對象
            System.out.println(cls.getName());		// 輸出對象所在類的名稱
        }
    }
    // 程式執行結果:	java.util.Date
               
    Class類中定義有 “ public String getName() ”方法獲得類的完整名稱的String字元串形式
  • 方式三:調用Class類提供的forName()方法:
    public static Class<?> forName(String className) throws ClassNotFoundException
               
    package com.yootk.demo;
    public class TestDemo {
        public static void main(String[] args) throws Exception {
            Class<?> cls = Class.forName("java.util.Date");		// 通過String字元串類型的類的完整名稱,取得Class對象
            System.out.println(cls.getName());					// 輸出對象所在類的名稱
        }
    }
    // 程式執行結果:	java.util.Date
               
  • 幾乎所有開發架構都會用到以上的3種執行個體化Class類對象的方式。

反射執行個體化對象

掌握了Class類對象執行個體化的三種方式,就可以利用Class類來進行類的反射控制了。

  • Class類中提供有以下10個常用方法:
    public static Class<?> forName(String className) throws ClassNotFoundException	// 通過字元串設定的類名稱執行個體化Class類對象
    public Class<?>[] getInterface()												// 取得類實作的所有接口
    public String getName()															// 取得反射操作類的全名
    public String getSimpleName()													// 取得反射操作類名,不包括包名
    public Package getPackage()														// 取得反射操作類所在的包
    public Class<? super T> getSuperclass()											// 取得反射操作類的父類
    public boolean isEnum()															// 判斷反射操作類是否為枚舉
    public boolean isInterface()													// 判斷反射操作類是否為接口
    public boolean isArray()														// 判斷反射操作類是否為數組
    public T newInstance() throws InstantionException, IllegalAccessException		// 反射執行個體化對象
               
  • 在以上Class類的方法中,最為重要的就是newInstance()方法,通過此方法就可以利用反射實作反射操作類的執行個體化對象。也就是說不用關鍵字new就可以實作執行個體化操作。
  • 使用newInstance()方法執行個體化對象時預設調用無參構造方法,是以想用Class類中的newInstance()方法反射執行個體化對象,則類中一定要提供一個無參構造方法。
  • 利用反射執行個體化對象:
    package com.yootk.demo;
    class Book {
        public Book() {
            System.out.println("********** Book類的無參構造方法 ***********");
        }
        @Override
        public String toString() {
            return "《名師講壇——Java開發實戰經典》";
        }
    }
    public class TestDemo {
        public static void main(String[] args) throws Exception {
            Class<?> cls = Class.forName("com.yootk.demo.Book");		// 設定要操作對象的類名稱
            // 反射執行個體化後的對象傳回的結果都是Object類型,如果有需要可以對其進行向下轉型強制變成子類對象
            Object obj = cls.newInstance(); 							// 相當于使用new調用無參構造
            Book book = (Book) obj;										// 向下轉型
            System.out.println(book);
        }
    }
    /*
    程式執行結果:
    	********** Book類的無參構造方法 ***********
    	《名師講壇——Java開發實戰經典》
    */
               
    使用newInstance()方法反射執行個體化對象的傳回結果都是Object類型,如果有需要可以對其進行向下轉型強制變成子類對象再進行操作。

利用反射實作工廠設計模式

  • 反射機制執行個體化對象的意義:
    • 使用關鍵字new執行個體化需要明确地指出類的構造方法,是以new是造成耦合的最大元兇,而要解決代碼耦合問題,首先就要解決new執行個體化對象的操作。
    • 在"4.7接口"中,我們介紹了“工廠設計模式”,但當時的工廠設計模式雖然解決了一定的代碼耦合問題,即增加新的接口子類可以不用更改用戶端代碼;
    • 但還是存在一個問題:就是增加新的接口子類還是需要修改工廠類。這就是使用關鍵字new而帶來的問題,而現在可以利用反射解決此問題。
  • 利用反射實作工廠設計模式:
    package com.yootk.test;
    interface Fruit {
        public void eat() ;
    }
    class Apple implements Fruit {
        @Override
        public void eat() {
            System.out.println("** 吃蘋果!");
        }
    }
    class Orange implements Fruit {
        @Override
        public void eat() {
            System.out.println("** 吃橘子!");
        }
    }
    class Factory {
        public static Fruit getInstance(String className) {
            Fruit f = null;
            try {														// 反射執行個體化,子類對象可以使用Fruit接收
                f = (Fruit) Class.forName(className).newInstance();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return f;
        }
    }
    public class TestFactory {
        public static void main(String[] args) {	
            Fruit fa = Factory.getInstance("com.yootk.test.Apple") ;  	// 直接傳遞類名稱
            Fruit fb = Factory.getInstance("com.yootk.test.Orange") ;	// 直接傳遞類名稱
            fa.eat();
            fb.eat();
        }
    }
    /*
    程式執行結果:
    	** 吃蘋果!
    	** 吃橘子!
    */
               
    本程式在工廠類中使用了反射機制,這樣隻需要傳遞完整的類名就可以取得執行個體化對象了。在實際開發中,如果将以上工廠設計模式再結合一些配置檔案(如:XML格式的檔案),就可以利用配置檔案來動圖定義項目中所需要的操作類,此時的程式将變得非常靈活。

反射調用構造

利用Class類的newInstance()方法可以實作反射執行個體化對象的操作,但其還是有一定限制,就是類中一定要提供無參構造方法。是以當類中隻提供了有參構造時,就必須通過 java.lang.reflect.ConStructor類來實作對象的反射執行個體化操作。

  • Class類中定義了取得反射操作類的構造方法的操作:
    public Constructor<?>[] getConstructors() throws SecurityException		// 普通,取得全部構造方法
    public Constructor<T> getConstructor(Class<?> ... parameterTypes) throws NoSuchMethodException, SecurityException	//取得指定參數類型的構造方法
               

這兩個方法的傳回值類型為java.lang.reflect.Constructor類

  • java.lang.reflect.Constructor類常用方法:
    public Class<?>[] getExceptionTypes()					// 普通,傳回構造方法上所有抛出異常的類型
    public int getModifiers()								// 取得構造方法的修飾符
    public String getName()									// 取得構造方法的名字
    public int getParameterCount()							// 取得構造方法中參數的個數
    public Class<?> getParameterTypes()						// 取得構造方法中的參數類型
    public T newInstance(Object... initargs) throws InstantionException, IllegalAccessException, IllegalArgumentException, InvocationTargetException								// 調用指定參數的構造執行個體化對象
               
    從上可知,Constructor類中也存在newInstance()方法,并且此方法定義時使用了可變參數形式,這樣先通過Class類找到指定參數類型的構造方法,再利用Constructor類的newInstance()方法傳入執行個體化對象所需的參數就可以實作指定參數的構造方法調用了。
  • 為什麼取得修飾符的方法傳回值為int型?

    因為所有修飾符都是一個數字,修飾符的組成就是一個數字的加法操作。假設: public 使用1表示,final使用16表示,static使用8表示,如果是public final那麼就使用17表示(1 + 16),而如果是public static final 那麼就使用25表示(1 +8+ 16),是以所有的修飾符本質上都是數字的加法操作。

    在java.lang.reflect.Modifer類中明确地定義了各個修飾符對應的常量操作,同時也提供了将數字轉換為指定修飾符的方法:“public static String toString(int mod)"

  • 明确調用類中的有參構造:
    package com.yootk.demo;
    import java.lang.reflect.Constructor;
    class Book {
        private String title ;
        private double price ;
        public Book(String title, double price) {
            this.title = title ;
            this.price = price ;
        }
        @Override
        public String toString() {
            return "圖書名稱:" + this.title + ",價格:" + this.price ;
        }
    }
    public class TestDemo {
        public static void main(String[] args) throws Exception {
            Class<?> cls = Class.forName("com.yootk.demo.Book") ;			// 獲得Class類執行個體化對象
            // 明确地找到Book類中兩個參數的構造,第一個參數類型是String,第二個是double
            Constructor<?> con = cls.getConstructor(String.class,double.class) ;	//使用類名.class來擷取該類的Class對象
            Object obj = con.newInstance("Java開發實戰經典",79.8) ;			// 執行個體化對象,傳遞參數内容
            System.out.println(obj);
        }
    }
               
    首先利用Class.forName()方法獲得Class對象;再利用Class對象調用getConstructor()取得Book類的對應參數的構造(傳回Constructor類對象);再利用Constructor類中的 newInstance()方法傳遞指定資料,就可以調用有參構造進行對象的反射執行個體化。

反射調用方法

以往都是使用“對象.方法()”的形式進行方法調用,而現在可以利用反射機制實作類方法的操作。

  • 使用Class類取得普通方法:
    public Method[] getMethods() throws SecurityException		// 普通,取得類中全部的方法
    public Method getMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException	// 普通,取得類中指定方法名稱與參數類型的方法
               
    上述兩種獲得類普通方法的方法的傳回值都是都是java.lang.reflect.Method類對象。
  • java.lang.reflect.Method類常用方法:
    public int getModifiers()								// 普通,取得方法的修飾符
    public Class<?> getReturnType()							// 普通,取得方法的傳回值類型
    public int getParameterCount()							// 普通,取得方法中定義的參數數量
    public Class<?>[] getParameterTypes()					// 普通,取得方法中定義的所有參數類型
    public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException								  // 普通,反射調用方法并且傳遞執行方法所需要的參數資料
    public Class<?>[] getExceptionTypes()					// 普通,取得方式抛出的異常類型
               
    上面最重要的方法為invoke(),此方法是實作方法反射調用的核心操作。
  • 反射調用普通方法也是需要執行個體化對象的,invoke()方法中,接收的第一個參數就是類的執行個體化對象,但要注意的是,此時的類型使用的是Object,也就是用反射實作的方法調用,不需要具體的對象類型,這一點比直接使用對象調用方法更加靈活。且正好Class類中的newInstance()方法反射的執行個體化對象就是Object類型。
  • 使用反射調用普通方法:
    package com.yootk.demo;
    import java.lang.reflect.Method;
    class Book {
        private String title ;
        public void setTitle(String title) {
            this.title = title;
        }
        public String getTitle() {
            return title;
        }
    }
    public class TestDemo {
        public static void main(String[] args) throws Exception {
            String fieldName = "title" ;							// 要操作的成員名稱
            Class<?> cls = Class.forName("com.yootk.demo.Book") ;	// 取得要操作類的反射對象
            Object obj = cls.newInstance() ;						// 必須執行個體化對象
            // 取得類中的setTitle()方法,由于title需要首字母大寫,是以調用initcap()處理,參數類型為String
            Method setMet = cls.getMethod("set" + initcap(fieldName), String.class) ;
            // 取得類中的getTitle()方法,本方法不接收參數并且沒有傳回值類型聲明
            Method getMet = cls.getMethod("get" + initcap(fieldName)) ;
            setMet.invoke(obj, "Java開發實戰經典") ; 					// 等價于:Book類對象.setTitle("Java開發實戰經典")
            System.out.println(getMet.invoke(obj));					// 等價于:Book類對象.getTitle()
        }
        public static String initcap(String str) {					// 首字母大寫操作
            return str.substring(0, 1).toUpperCase() + str.substring(1) ;
        }
    }
    // 程式執行結果:	Java開發實戰經典
               
    本程式首先利用Class.forName()方法擷取Book類對應的Class類對象;再使用Class類中的newInstance()方法獲得Book類的執行個體化對象,使用Object類接收;再使用Class類中的getMethod()方法獲得Book類中的普通方法,以Method類對象形式儲存;再通過Method類中的invoke()方法實作方法的反射調用。

反射調用成員

除了構造方法、普通方法外,類中最重要的組成就是成員(變量和常量)。

  • 使用Class類取得成員的操作:
    public Field[] getDeclaredFields() throws SecurityException		// 普通,取得本類定義的全部成員
    public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException	// 普通,取得本類指定名稱的成員
    public Fidld[] getFields() throws SecurityException				// 普通,取得本類繼承父類的全部成員
    public Field getField(String name) throws NoSuchFieldException, SecurityException			// 普通,取得本類繼承父類中指定名稱的成員
               
    以上四種方法的傳回值類型都為:java.lang.reflect.Field,此類可以描述類中的成員資訊。
  • java.lang.reflect.Field類的常用方法:
    public Class<?> getType()					// 普通,取得該成員的資料類型
    
    public void set(Object obj, Object value) throws IllegalAccessException, IllegalArgumentException	// 普通,取得指定對象中的成員的内容,相當于直接調用成員
    public Object get(Object obj) throws IllegalAccessException, IllegalArgumentException	// 普通,設定指定對象中的成員内容,相當于直接利用對象調用成員設定内容
               
    實際上在Field類中還定義了許多例如setInt()、setDouble()、getChar()等方法,可以直接設定這些方法或方法的具體類型。
  • 對于被封裝的成員,不能直接使用get和set方法操作,需要先對它進行取消封裝。Field類在定義時繼承了AccessibleObject類,而在AccessibleObject類中定義了一個setAccessible()方法,該方法的作用就是用來取消成員的封裝屬性。java.lang.reflect.AccessibleObject.setAccessible()方法定義如下
    public void setAccessible(boolean flag) throws SecurityException
               
    setAccessible()方法參數flag的值為true時,表示反射的對象在使用該成員時應禁止Java語言通路檢查,即取消封裝。
  • AccessibleObject類與Constructor、Method、Field類的關系:
    【Java基礎】Java反射機制、正規表達式、比較器、GC垃圾回收、StringBuffer類、對象克隆、日期處理
    由上圖可知:Constructor、Method、Field三個類都是AccessibleObject類的子類,也就是說這三個類的對象都可以使用setAccessible()方法取消封裝。
  • 跟利用反射調用方法一樣,反射調用成員同樣需要執行個體化的對象。
  • 利用反射直接操作私有成員:
    package com.yootk.demo;
    import java.lang.reflect.Field;
    class Book {										// 為了示範,是以使用非标準簡單Java類
        private String title ;							// 私有屬性,并沒有定義setter、getter方法
    }
    public class TestDemo {
        public static void main(String[] args) throws Exception {
            Class<?> cls = Class.forName("com.yootk.demo.Book");	// 取得反射對象
            Object obj = cls.newInstance(); 						// 必須給出執行個體化對象
            Field titleField = cls.getDeclaredField("title");		// 取得類中的title屬性
            titleField.setAccessible(true);							// 取消封裝
            titleField.set(obj, "Java開發實戰經典");					// 相當于:Book類對象.title = "資料"
            System.out.println(titleField.get(obj)); 				// 相當于:Book類對象.title
       }
    }
               
  • 雖然可以通過反射直接調用成員,但這樣的操作還是不标準的,盡量還是使用setter和getter方法操作成員。

反射機制總結

Java反射機制的核心是在程式運作時動态加載類并擷取類的詳細資訊,進而操作類或對象的屬性和方法。

  • 反射機制中主要用到了4個類
    • java.lang.Class類
    • java.lang.reflect.ConStructor類
    • java.lang.reflect.Method類
    • java.lang.reflect.Field類
  • Class類對象執行個體化的3種方式:
    • 方式一:調用Object類中的getClass()方法,但這種方法必須要有Object對象才能使用。
    • 方式二:使用 “ 類名.class ” 獲得Class類執行個體化對象,這種方法不需要對象隻需要類名就可以使用
    • 調用Class類提供的forName()方法:
      public static Class<?> forName(String className) throws ClassNotFoundException
                 
  • java.lang.Class類最重要的4類用法:
    • 反射執行個體化對象,傳回值為Object類型:
    • 取得類中的構造方法,傳回值為Constructor類型:
      public Constructor<?>[] getConstructors() throws SecurityException				// 普通,取得全部構造方法
      public Constructor<T> getConstructor(Class<?> ... parameterTypes) throws NoSuchMethodException, SecurityException	//取得指定參數類型的構造方法
                 
    • 取得類中普通方法,傳回值為Method類型:
      public Method[] getMethods() throws SecurityException		// 普通,取得類中全部的方法
      public Method getMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException	// 普通,取得類中指定方法名稱與參數類型的方法
                 
    • 取得類中成員,傳回值為Field類型:
      public Field[] getDeclaredFields() throws SecurityException		// 普通,取得本類定義的全部成員
      public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException	// 普通,取得本類指定名稱的成員
      public Fidld[] getFields() throws SecurityException				// 普通,取得本類繼承父類的全部成員
      public Field getField(String name) throws NoSuchFieldException, SecurityException			// 普通,取得本類繼承父類中指定名稱的成員
                 
  • java.lang.reflect.ConStructor類最重要的方法:
  • java.lang.reflect.Method類最重要的方法:
  • java.lang.reflect.Field類最重要的方法:
    public void set(Object obj, Object value) throws IllegalAccessException, IllegalArgumentException	// 普通,取得指定對象中的成員的内容,相當于直接調用成員
    public Object get(Object obj) throws IllegalAccessException, IllegalArgumentException	// 普通,設定指定對象中的成員内容,相當于直接利用對象調用成員設定内容
               

本章小結

  • 當一個字元串内容需要頻繁修改時,使用StringBuffer可以提升操作性能,因為StringBuffer的内容是可以改變的,而String的内容是不可以改變的。
  • StringBuffer類中提供了大量的字元串操作方法:增加、替換、插入等。
  • Runtime表示運作時,在一個JVM中隻存在一個 Runtime,是以如果要取得Runtime類的對象,可以直接使用 Runtime類中提供的靜态方法:getRuntime()。
  • System類是系統類,可以取得系統的相關資訊,使用System.gc()方法可以強制性地進行垃圾的收集操作,調用此方法實際上就是調用了Runtime類中的gc()方法。
  • 使用 Date 類可以友善地取得時間,但取得的時間格式不符合地域的風格,是以可以使用SimpleDateFormat類進行日期的格式化操作。
  • 處理大數字可以使用:BigInteger 、BigDecimal,當需要精确小數點操作位數時使用BigDecimal類即可。
  • 通過Random類可以取得指定範圍的随機數字。
  • 如果一個類的對象要想被克隆,則此對象所在的類必須實作Cloneable接口。
  • 要想對一組對象進行排序,則必須使用比較器,比較器接口Comparable中定義了一個compareTo()的比較方法,用來設定比較規則。
  • 正規表達式是在開發中最常使用的一種驗證方法,在 JDK 1.4之後,String 類中的replaceAll()、split()、matches()方法都支援正規表達式。
  • Class類是反射機制操作的源頭,Class類的對象有3種執行個體化方式:通過Object類中的getClass()方法;通過“類.class”的形式;通過Class.forName()方法,此種方式最為常用。
  • 可以通過 Class類中的newInstance()方法進行對象的執行個體化操作,但是要求類中必須存在無參構造方法,如果類中沒有無參構造,則必須使用Constructor類完成對象的執行個體化操作。