一、數組 數組也是一種引用類型,其父類是Object,使用“數據類型[]”聲明,如“int[] array”表示聲明瞭一個元素類型為int類型的數組array。 數組初始化語法: // 靜態初始化語法,即定義的時候就初始化好了所有的元素 int[] array1 = {100, 55, 30}; / ...
一、數組
數組也是一種引用類型,其父類是Object,使用“數據類型[]”聲明,如“int[] array”表示聲明瞭一個元素類型為int類型的數組array。
// 靜態初始化語法,即定義的時候就初始化好了所有的元素 int[] array1 = {100, 55, 30}; // 動態初始化語法,初始化時只定義好數組中元素的個數,new int[5]表示創建一個 // 有5個int類型元素的數組,但是並沒有初始化數組中元素的值,只是賦予了預設值,即 // 基本數據類型的預設值和引用類型的預設值null。 int[] array2 = new int[5];
使用數組時,應註意以下幾點:
- 數組因為是引用類型,所以數組對象是存儲在堆記憶體當中的。
- 數組一旦創建,其長度是不可變的。
- 數組中的元素的數據類型是統一的,如int數組則表示此數組中的元素全部都是int類型的。
- 所有數組對象都有length屬性(註意不是length方法),用來獲取數組中元素的個數。
- 數組中在存儲時,數組中的元素的記憶體地址都是連續的。
數組的訪問和賦值:直接通過下標進行訪問和賦值即可,如“array1[0]=22;”。
數組的優點和缺點:
- 優點:根據下標去檢索元素時效率極高,因為數組中的元素在空間地址上是連續的,並且每個元素占用的記憶體空間是相同的,檢索某個元素時只需要根據數組記憶體地址的起始位置就可以算出這個元素的記憶體地址,所以檢索第100個元素和第100萬個元素的地址的時間都是一樣的。
- 缺點:一個是,為了保證數組中每個元素的記憶體地址連續性,所以在數組中間的某個位置刪除或增加元素時,會涉及到元素的向前或者向後位移的操作,此時的效率就會極低。另外一個是,數組不能存儲大數據量,因為很難在記憶體空間上找到一塊特別大的連續的記憶體空間。
數組擴容:Java中數組擴容的原理或者說方法是將小容量的數組使用“System.arraycopy”方法拷貝到大容量的數組當中,然後刪除小容量的數組,Java中數組的擴容效率是較低的,所以在數組的使用當中,應該儘量避免數組的拷貝和擴容。
二維數組:二維數組,包括三位數組等多位數組,其實就是數組中的元素又是一個數組,多少維其實由這個數組的元素“套了多少層”決定的,二維就是數組中的元素是一個一維數組,同理,三位數組中的元素是一個二維數組,然後以此類推即可。
// 靜態初始化語法 int[][] a = { {1, 2, 3}, {4, 5, 6}, {9} }; // 動態初始化語法 int[][] array = new int[3][4];
Arrays工具類:這個工具類最常用的就是sort和binarySearch這兩個方法,但是註意,二分查找方法的前提是數組已經排好序了。
import java.util.Arrays; public class ArrayToolsTest{ public static void main(String[] args){ int[] myArray = {3, 2, 6, 4}; // sort方法可以將一個數組排序,但是註意,sort方法並沒有返回值, // 即不是返回排好序的數組,而是直接排序傳入的數組 Arrays.sort(myArray); // 二分查找演算法的前提是需要數組已經排好序了 // 返回值為元素在數組中的下標,元素在數組中不存在則返回-1 // 但是這個方法多用來判斷數組中有沒有這個元素,因為如果數組中該元素 // 不只一個的話,那麼返回的下標不一定是第一個元素的下標 int indexd = Arrays.binarySearch(myArray, 6); } }
二、異常
1、對異常的理解
異常也是類,每一個異常類都可以創建異常對象。
異常繼承結構:通過幫助文檔中可以看到,java.lang.Object --> java.lang.Throwable,而Throwable下有兩個子類Error和Exception,它們都是可以拋出的,對於這兩個子類分支,Error分支下的子類(包括Error自身)稱之為錯誤,錯誤一旦發生,通常是直接就退出程式了,沒有辦法及時去處理。而Exception分支下的子類(包括Exception自身)稱之為異常,異常是可以在代碼層面提前去處理的,Exception的子類又可以分為兩個分支,一個分支是RuntimeException及其子類,稱為運行時異常,另一個分支就是除RuntimeException外的其它Exception的直接子類,也稱為編譯時異常。
編譯時異常:之所以稱之為編譯時異常,是因為在編譯階段就可以發現並提醒程式員提前處理這種異常,對於這類異常怎麼提前去處理,還是得要實際使用中多練才能有更深的體會。
運行時異常:這類異常在編譯時不會報錯,但是編譯通過之後在運行時又會出錯,所以叫運行時異常,比如對於表達式10/0,除數為0肯定是錯的,但是編譯器並不會識別並提醒,編譯通過之後運行的時候肯定就會報錯了。
異常處理:處理異常的方式有兩種,一種是使用throws關鍵字和throw關鍵字,將異常拋出給上一級,讓上一級去處理(上一級此時必須處理這個異常);另一種是使用“try...catch”語句把異常捕獲,但是註意,捕獲到了這個異常不一定要去處理它,讓它直接“過”也是允許的。
2、throws拋出異常
throws使用示例:
public class ThrowsTest{ public static void main(String[] args){ // 這裡在編譯時會發生錯誤,也就是編譯時異常,之所以有這個異常 // 因為在func定義中有throws關鍵字,表示這個方法在執行過程中可能會發生 // 對應的異常(ClassNotFoundException),所以它的上一級必須去處理 // 這個異常,不處理的話,編譯器就會不通過。 func(); } // 使用throws關鍵字拋出可能發生的異常 public static void func() throws ClassNotFoundException{ System.out.println("my func!!!"); // 使用throw手動拋出一個異常 throw new ClassNotFoundException("未找到類異常!"); } }
關於throws的使用,需要註意:
- throws拋出的異常,通常有兩種處理方式,一種是繼續使用throws關鍵字向上一級拋出同樣的異常,即調用者自身不處理這個異常,讓再上一級去處理。另一種是使用try...catch語句去捕獲拋出的異常。
- Java內置類或者我們自己定義的類如果有使用throws關鍵字,就表示它是編譯時異常,使用的時候必須要去處理它。
- throws拋異常時,既可以拋出具體的異常,也可以拋出它的某個父類異常,最頂級的異常類可以是Exception類,它包含了所有異常。
- 使用throws的時機就是如果這個異常希望調用者來處理,那麼就是用throws拋出它,其他情況應該使用try捕獲的方式。
3、try捕獲異常
try使用示例:
import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; public class ExceptionTest { public static void main(String[] args) { FileInputStream fis = null; try { // 將可能會發生異常的語句放在try的語句塊中 // try語句塊中的代碼一定會去執行,直到發生異常為止 fis = new FileInputStream("Z:\\Study\\temp.md"); System.out.println(10 / 0); } catch (ArithmeticException e) { // 捕獲可能會發生的異常 // 捕獲到異常後,對異常進行處理 // catch語句塊中的代碼只有捕獲到異常之後才會執行 // ArithmeticException e這個語句相當於是聲明一種引用類型的變數,類似於方法的形參,e保存的是異常對象的記憶體地址,並且可以在catch語句塊中去使用這個對象引用e。 System.out.println("發生算術異常!!!"); } catch (FileNotFoundException e) { // 可以使用多個catch語句塊捕獲不同的異常 // 多個catch時會從上到下依次捕獲,執行了一個catch之後,其他的catch就不會執行了 // 並且多個catch語句時,應該遵循從上到下的“從小到大”捕獲,即具體異常在前,它的父類型依次往後 System.out.println("文件不存在!!!"); } finally { // finally塊中的語句無論是否發生異常都會處理,哪怕try塊最後有個return語句 // 執行到return語句時也會先執行finally中的語句,再執行return // 可以用來處理一些無論是否發生異常都要處理的操作,如關閉文件流等 if (fis != null){ try{ fis.close(); } catch(IOException e) { e.printStackTrace(); } } System.out.println("關閉文件等其他操作。。。"); } } }
註意,catch捕獲的異常可以是具體的異常,也可以是具體異常的父類型異常,此時可以理解為多態。
異常對象中的常用方法:
- getMessage():獲取異常的簡單描述信息。
- printStackTrace():列印異常追蹤的堆棧信息。
4、自定義異常
自定義的異常類需要繼承Exception或者RuntimeException,並且需要定義無參和有參兩個構造方法。
public class MyException extends Exception{ public MyException{ } public MyException(String s){ super(s); } }
自定義異常中的方法重寫或者覆蓋時需要註意一個語法問題,重寫的方法拋出的異常不能比父類的方法更大或者說更寬泛,只能更小或者說更具體,比如父類異常方法拋出了IOException異常,那麼異常子類中重寫這個方法時就不能拋出Exception異常,但是可以拋出IOException異常或者FileNotFoundException異常。
三、泛型
泛型在使用尖括弧“<標識符1,標識符2,...>”來表示,標識符代表的是某種類型。
泛型的作用其實是用它定義了一個模板,定義時並沒有寫死數據的類型,當真正使用的時候可以根據需要傳入自己的數據類型。
自定義泛型:
/* 自定一個泛型只需要在類名之後使用<標識符>即可 註意,此處的標識符是隨意定義,就像變數名一樣 */ public class MyGenericTest<T> { public static void main(String[] args) { // 定義的時候,傳入的類型是什麼,那麼創建的對象使用的泛型類型就是什麼類型 MyGenericTest<String> mgt = new MyGenericTest<>(); mgt.func("自定義泛型方法測試!"); } /* 如果想要使用泛型定義的類型,在方法參數中直接使用即可 */ public void func(T t){ System.out.println(t); } }
集合中泛型的應用:
import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class GenericTest { public static void main(String[] args) { // 指定集合中的元素類型為Pet,不能存儲其它類型的元素 // 使用new的時候可以不用再傳入類型了,可以自動推斷,此時的表達式<>也稱為鑽石表達式 // 如果不指定泛型,也是可以的,預設就是Object類型 List<Pet> petList = new ArrayList<>(); Cat c = new Cat(); Dog d = new Dog(); petList.add(c); petList.add(d); // 迭代器的聲明也需要加上泛型的定義 Iterator<Pet> it = petList.iterator(); while (it.hasNext()) { // 原本next方法返回值的類型為Object,使用泛型之後返回的類型直接就是指定 // 的類型,不需要進行類型轉換了。 Pet p = it.next(); p.play(); // 當然,如果要使用具體的子類對象的方法,還是需要轉型之後才能調用 if (p instanceof Cat){ Cat myCat = (Cat)p; myCat.sleep(); } if (p instanceof Dog){ Dog myDog = (Dog)p; myDog.bark(); } } /* 輸出結果: 寵物在玩耍! 貓咪在睡覺! 寵物在玩耍! 狗子在嚎叫! */ } } class Pet { public void play() { System.out.println("寵物在玩耍!"); } } class Cat extends Pet { public void sleep() { System.out.println("貓咪在睡覺!"); } } class Dog extends Pet { public void bark() { System.out.println("狗子在嚎叫!"); } }