java基礎第17天



常見數據結構


1 常見數據結構有哪些
 棧、隊列、數組、鏈表


2 棧(Last In First Out)
  後進先出,最上面的元素我們叫棧頂元素!
  出棧(彈棧),就是把棧頂元素從棧中移除,並返回。
  壓棧,就是把一個新元素放到棧頂位置。
  相當於:大缸
  


3 隊列(FIFO)
  先進先出!
  相當於:沒底的管道!


4 數組
  數組索引元素的速度天下無敵!
  arr[10] – 快速最快!
  如果我們需要向數組插入和刪除元素,那麼就需要copy數組。速度慢。


5 鏈表
  鏈表元素在內存中不是鄰居,鏈表頭元素知道下一個元素的地址,下一個元素又知道下下元素 的地址,最後一個元素,即鏈表尾,它的下一個元素是null。
  鏈表的索引速度很慢,但插入和刪除元素快速很快。


List實現類


1 常用List實現類
  ArrayList
  LinkedList
  Vector
  Stack


2 ArrayList存儲String數據
創建ArrayList
創建字符串對象(多個)
把字符串對象添加到ArrayList
使用迭代器遍歷集合對象
使用get()方法遍歷集合對象
嘗試向集合中添加int類型


3 ArrayList存儲自定義類型(Person)數據
創建ArrayList
創建Person類對象(多個)
把Person對象添加到ArrayList對象中
使用迭代器遍歷集合對象
使用get()方法遍歷集合對象


4 LinkedList特有方法
void addFirst(Object)
void addLast(Object)
Object getFirst()
Object getLast()
Object removeFirst()
Object removeLast()
void push(Object o):壓棧,就是把元素添加到0下標位置,與addFirst一樣。
Object pop():彈棧,就是把0下標位置的元素返回並移除。


5 LinkedList存儲String類型數據
  參考ArrayList存儲String類型數據


6 LinkedList存儲自定義(Person)類型數據




7 LinkedList特有方法測試
創建LinkedList對象
創建String對象(多個)
把String對象添加到LinkedList對象中
測試向集合頭部添加元素:addFirst(Object)
測試向集合尾部添加元素:addLast(Object)
測試獲取集合頭部元素:getFirst()
測試獲取集合尾部元素:getLast()
測試移除集合頭部元素:removeFirst()
測試移除集合尾部元素:removeLast()


8 Vector特有方法
  底層用的是數組,與ArrayList相同!
  Vector中的方法都是同步的,即Vector是線程安全的。
  ArrayList是線程這安全的。
void addElement(Object):等同與add()
Object elementAt(int):等同與get()
Object removeElement(Object):等同與remove()
Enumeration elements():返回枚舉器,它是老版本的迭代器!


9 Vector存儲String數據




10 Vector存儲自定義(Person)類型數據




11 Vector特有方法測試




12 遍歷Vector對象
Iterator遍歷
Enumeration遍歷


13 去除ArrayList中重複元素(String)
  編寫toSingletonList()方法
  參數:List,待處理的集合(包含重複元素的集合)
  返回:List,處理後的集合(不包含重複元素的集合)
創建ArrayList對象
創建String對象(多個,包含重複對象)
把String對象添加到ArrayList對象中
調用toSingletonList()方法
創建一個新集合對象
遍歷老集合對象
查看新集合對象中是否包含元素
包含就不添加,不包含就添加到新集合中
返回新集合。


14 去除ArrayList中重複元素(自定義)
發現沒有去除重複元素
測試String類的equals()方法
測試Person類的equals()方法
contains()方法依賴元素的equals()方法
爲Person添加equals()方法


使用LinkedList編寫棧和隊列


編寫MyStack
要求MyStack類的方法:
boolean empty()
Object pop()
void push(Object o)
乾坤大挪移!
  使用一個LinkedList對象做屬性,使用它來存儲數據。本類所有方法都針對這個屬性進行操作。也就是把LinkedList封裝起來,外界對Stack施加的操作都轉移到對本類屬性LinkedList之上。


2 測試MyStack
創建MyStack類的對象
創建String對象(多個)
調用push()方法把String對象壓棧(多個)
使用循環(條件使用empty())調用pop()方法出棧


3 編寫MyQueue
  要求MyQueue類的方法:
boolean empty()
void add(Object o)
Object poll()
與編寫MyStack類一樣,也是內部使用LinkedList爲屬性,所有方法都針對屬性進行操作。


4 測試MyQueue
創建MyQueue類的對象
創建String對象(多個)
調用push()方法把String對象添加到隊列中(多個)
使用循環(條件使用empty())調用poll()方法出列。


泛型
  java中泛型應用最多的地方是集合類。
1 定義數組和集合的比較
  在定義數組時都需要指定元素的類型,例如:
  String[] arr = new String[10];
  對於arr而言,只能爲元素指定String類型的值,而其他類型不行。
  -------------------
  我們以前定義的集合對象都是可以存儲任意類型(Object)的對象的。就像是在使用Object類 型的數組一樣。但這有一個缺點,就是存入後再取出的元素需要強轉。這也是可能發生 ClassCastExcetpion的地方。
  -------------------
  大多數情況下,我們不會對同一個集合對象添加多種類型的元素,所以我們最希望可以像使用數組一樣,在定義集合對象時指定可以裝載的元素類型。


4 什麼是泛型類
  具有一個或多個類型參數的類就是泛型類。
  java中所有集合類都是泛型類。
  在定義泛型類對象時,可以指定裝載的元素類型。就像定義數組時可以指定元素類型一樣。


5 泛型的好處
  將運行期遇到的問題轉移到了編譯期。
  壞處也不少!!!
  Java沒有類模板的概念,爲了添加這一概述,就出現了泛型。這也是爲了兼容性遷移的第一步吧。泛型被稱爲四不像!這可能需要一個很慢長的遷移的過程,也許將來會完善的!相對C++而言,Java的泛型可能是敗筆了。


6 使用泛型類
  泛型類中使用的類型參數都被實際參數賦值。
  使用泛型集合類


7 泛型類需要給類型參數賦值
  類型參數是變量,你要給類型參數賦值。當然這個值必須是個類型。
  定義泛型類一定要給類型參數賦值,不然就等同於賦值爲Object。
  在泛型類中使用的類型參數都會被賦的值替代。例如定義的屬性或方法中使用的泛型參數都被替換了。
  因爲在定義泛型類時,不知道用戶指定的類型值是什麼,所以類中使用類型變量的屬性和方法都不能確定,那麼也就不能去調用類型不確定對象的方法(但可以調用Object類具有的方法)。


8 繼承泛型類時的兩種賦值方式
  給出類型常量
  給出類型變量


9 定義泛型接口




10 實現泛型接口兩種賦值方式
  給出類型常量
  給出類型變量


泛型方法


1 泛型方法
  java中不只可以定義泛型類,還可以定義泛型方法。
  泛型方法被調用時需要指定泛型參數的值,這一點與創建泛型類的對象一樣。
  泛型方法可以定義在泛型類中,也可以定義在非泛型類中。泛型方法可以是static的,也可以不是static的。這與泛型和static沒有半毛錢關係!


2 定義泛型方法
  定義泛型方法的格式爲:
  修飾符 <類型參數> 返回值 方法名(參數列表)
  例如:
  public class MyClass {
    public <T> void fun(T t) {
          System.out.println(t.getClass().getName());
    }
  }
  通常泛型方法的參數都會使用類型變量。上例中的參數就使用類型變量T!在使用者調用這個方法時會給T賦值,那麼參數t的類型也就確定了。


3 調用泛型方法
  通常泛型方法的參數都會使用類型變量,所以一般不需要顯示爲泛型方法指定類型參數的值。而是通過調用時傳遞的實際參數的類型隱示給類型參數賦值的。例如:
  MyClass mc = new MyClass();
  mc.fun(“hello”);
  因爲調用fun()方法時實參的類型爲String,那麼這就隱示爲fun()方法的類型參數賦值爲String了。
  當然也可以顯示指定泛型方法的類型參數,這需要在點前面指定類型值。例如:
  mc.<String>(“hello”);


泛型邊界


1 泛型邊界限定的類型值的範圍
  通常我們看到的泛型類都沒有邊界限定,也就是說可以給泛型類的類型變量賦任意類型的值(當然基本類型是不可以的)。
  java允許給類型參數指定邊界,這樣在指定類型參數的值時就必須在邊界之內。


2 帶有邊界的泛型類
  帶胡邊界的泛型類需要使用extends關鍵字爲類型變量指定邊界,格式如下:
  class 類名<變量名 extends 邊界類型> {…}
  例如:
  class MyClass<T extends Person> {…}


3 創建帶有邊界的泛型類對象
  MyClass mc = new MyClass();//在沒有指定泛型參數的值時,類型變量的值就是邊界類型了。也就是Person類型了。
  因爲MyClass類指定了邊界Person,那麼只能爲類型變量賦值爲Person或Person的子類了。
  MyClass<Student> mc =new MyClass<Student>();


4 多個邊界類型
  還可以爲類型參數指定多個邊界類型,格式爲:
  class 類名<類型變量名 extends 邊界類型1 & 邊界類型2…> {…}
  當爲類型變量指定多個邊界類型時,那麼最多隻能有一個是類類型,其他的都必須是接口類型。這個道理就不用說了吧。


通配符


1 錯誤理解泛型
  現在有Person和Student類,其中Student是Person的子類。這說明:
  Person p = new Student();
  是正確的。
  
  下面代碼編譯也是通過的,但運行時會拋出ArrayStoreException。
  
Person[] ps = new Student[10];
ps[0] = new Student();
ps[1] = new Employee();
  
  在泛型中java就不在允許這麼方式了:
ArrayList<Person> list = new ArrayList<Student>();
  上面代碼是編譯不通過的,也就是說Person雖然是Student的父類,但ArrayList<Person>不是ArrayList<Student>的父類。
  這也就是說,不能使用ArrayList<Student>類型的對象來調用參數爲ArrayList<Person>類型的方法了。這真是太可惜了!
  如果現在有方法用來打印封裝Person的集合對象,即參數類型爲List<Person>。但我們不能讓這個方法打印List<Student>,這不是很可惜麼?
public static void fun(List<Person> list) {
for(Person p : list) {
System.out.println(p);
}
}




2 子類型通配符
  爲了處理上面的問題,java提供了通配符。嘗試把上面的fun()方法參數類型修改爲List<? extends Person>類型:
public static void fun(List<? extends Person> list) {
for(Person p : list) {
System.out.println(p);
}
}


  我們可以把List<Student>、List<Employee>、List<Person>類型的對象賦值給List<? extends Person>類型的引用。
  ?並不是變量,而是確定的值。它表示一種確定下來的值,但我們不知道它是什麼類型罷了。也就是說List<? extends Person>的元素類型已經開始不明確了,我們只知道元素類型是Person或Person的某種子類型,但具體是哪一個類型。這也說明我們不能向它添加東西了。
  
List<? extends Person> list1 = new ArrayList<Student>();//ok
List<? extends Person> list2 = new ArrayList<Employee>();//ok
list1.add(new Student());//error
list1.add(new Employee());//error
list2.add(new Student());//error
list2.add(new Employee());//error


  上面代碼中,對list1、list2添加什麼元素都是錯誤的。讓我們分析一下,對編譯器而言,它不知道list1、list2指向的是實體是什麼類型。如果允許了list1.add(new Student()),那麼說明也要允許list1.add(new Employee())。因爲對list1這個引用而言,它是List<? extends Person>類型,元素類型表示一種不知道的但卻是確定的類型,但只知道它是Person的子類型。如果可以添加Student,那爲什麼不能添加Employee呢?所以編譯器的態度是添加什麼東西都不行!
  也就是說,當給類型參數E賦值爲<? extends 類型>時,那麼對於參數爲E的方法就不能再使用了,因爲傳遞什麼類型的參數都是錯誤的。


3 父類型通配符
  還可以給類型參數賦值爲<? super 類型>,這就是父類型通配符。
  如果你需要一個方法,給參數List添加元素。
  
public static void fun(List<Person> list, Person p) {
list.add(p);
}
  
  你不能使用List<Student>來調用上面的方法,這個道理我們應該已經明白了。但你可能會說可以把方法參數類型修改爲List<? extends Person>類型,這樣就可以把List<Person>類型的值傳遞給參數了。但是如果參數類型爲List<? extends Person>類型,那麼就不能調用add()方法了!!!
  這時可以把參數類型指定爲List<? super Student>類型,你可以把List<Student>、List<Preson>,甚至List<Object>類型的對象賦值給它。
  List<? extends Person>表示元素類型爲不知道的確定類型,只知道元素類型爲Student的父類型,但不知道是哪個父類型!也許是Person,也許是Object類型。無論是哪一種父類型,說明我們可以向集合對象添加Student對象!因爲把Student類型對象添加到List<Person>或List<Object>中都是可以的!
  但要注意,這時你就不能再使用get()方法了,因爲元素的類型不知道是什麼,只知道是Student類型的父類型,哪一種就不知道了,這說明你不能用Peson引用指向get()方法的返回值。
  但可以使用Object引用來指向get()方法的返回值,因爲Object是所有類的父類!


4 無界通配符
  無界通配符:List<?>!
  你可以使用List<?>的引用指向List<? extends Person>、List<? super Stuent>、List<Person>、List<Student>等等,即沒有指定泛型的List可以指向什麼,它就可以引向什麼。
List<?> list1 = new ArrayList();
List<?> list2 = new ArrayList<Object>();
List<? extends Person> pList = new ArrayList<Person>();
List<?> list3 = pList;
List<? super Student> sList = new ArrayList<Student>();
List<?> list4 = sList;


  呵呵~,你不能使用List<?>引用調用add()方法,也只能使用Object引用來指向get()方法的返回值了。
  其實大多數情況下List<?>就是一種裝飾!基本與List一樣,沒有什麼區別了!但如果你使用List會有警告,而使用List<?>時就沒有警告了!因爲傻瓜編譯器看到了你使用List<?>說明你在使用泛型的集合類,它會很開心!


5 通配符總結
  可以使用List<? extends Person>指向List<Person>、List<Student>、List<Employee>。只要元素類型爲Person,或其子類就是可以的。
  可以使用List<? super Student>指向List<Student>、List<Person>、List<Object>。只要元素類型爲Student或其Student父類就是可以的。
  小心,你不能在new時使用通配符:
  new ArrayList<? extends Person>(),這是不可以的!
  但可以在定義引用時使用通配符:
  ArrayList<? extends Person> list;


四不像的泛型
  泛型只是編譯器狀態!!!
  所有泛型類在JVM那裏都是普通類!也就是說ArrayList<Person>和ArrayList<Student>在JVM那裏都是ArrayList而已!也就是說元素的類型還是Object類型,一切的一切都是編譯器的要求!
  其實對泛型還有很多限制!這裏就不在多說了!





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章