福州網(wǎng)站設計大概多少錢大數(shù)據(jù)查詢
目錄
泛型和類型安全的集合
基本概念
添加一組元素
打印集合
List
Iterator(迭代器)
本筆記參考自: 《On Java 中文版》
??????? 在進行程序設計時我們會發(fā)現(xiàn),程序總是會根據(jù)某些在運行時才能知道的條件來創(chuàng)建新的對象。這意味著,我們必須能夠隨時隨地創(chuàng)建任意數(shù)量的對象。
??????? Java提供了幾種方法來持有對象(的引用),其中之一就是數(shù)組。數(shù)組的優(yōu)點在于其的高效,但其本身卻會受到自身大小固定的制約。實際上,Java.util庫提供了一組集合類來解決這一問題,其中包括的基本類List、Set、Queue和Map也被稱為容器類。
??? Java并沒有直接為集合提供關鍵字支持。
泛型和類型安全的集合
??????? 在Java 5之前,編譯器允許向集合中插入不正確的類型。在舉例之前需要先對ArrayList類進行一些必要的說明:
??????? ArrayList是是一種泛型類型,它實現(xiàn)了迭代器(Iterable)、Collection和List等接口。泛型是一種參數(shù)化類型的機制,在這種機制下,形參的數(shù)據(jù)類型也會成為可傳輸?shù)膮?shù)。因為泛型的存在,ArrayList可以存儲任何類型的對象,而不需要在使用前進行類型轉換。(本段參考?林二月er?的博客、訊飛星火提供的信息)
? ? ArrayList使用一個數(shù)值來查找對應對象。從某種意義上,ArrayList將數(shù)值和對象關聯(lián)起來了。
????????下例中出現(xiàn)的ArrayList是一個反例,它沒有使用泛型:
import java.util.ArrayList;class Apple {private static long counter;private final long id = counter++;public long id() {return id;}
}class Orange {
}public class AppleAndOrangesWithoutGenerics {@SuppressWarnings("unchecked") // 參數(shù)unchecked表示只有“unchecked”警告應該被忽略public static void main(String[] args) {ArrayList apples = new ArrayList<>();for (int i = 0; i < 3; i++)apples.add(new Apple());apples.add(new Orange()); // 注意:插入的是一個Orange類型,這本來應該是錯誤的操作for (Object apple : apples) {((Apple) apple).id(); // 需要強制類型轉換,此時Orange只有在運行時才會被檢測出來}}}
??????? 上述程序的錯誤不會在編譯時被發(fā)現(xiàn),這個報錯會出現(xiàn)在運行時:
??????? Apple和Orange唯一的聯(lián)系在于它們都繼承了Object類。但ArrayList持有的也是Object,所以無論是Apple類還是Orange類都能被加入其中(并且不會引發(fā)報錯)。此時會從ArrayList中取出Object類型的引用,若想使用引用還必須強制類型轉換。也正是在運行時,程序嘗試將Orange對象轉型為Apple時才會發(fā)現(xiàn)錯誤。
??????? 若要定義一個持有Apple對象的ArrayList,應該使用:
ArrayList<Apple>
尖括號包圍的是類型參數(shù)(可以有多個),它指定了集合實例中可以保存的類型。
??????? 下面是修改后的例子:
import java.util.ArrayList;public class AppleAndOrangesWithGenerics {public static void main(String[] args) {ArrayList<Apple> apples = new ArrayList<>();for (int i = 0; i < 3; i++)apples.add(new Apple());// 此時,下方的做法就會引發(fā)編譯時錯誤// apples.add(new Orange());for (Apple apple : apples)System.out.println(apple.id());}
}
??????? 程序執(zhí)行的結果如下:
??????? 上述程序中,出現(xiàn)了語句 ArrayList<Apple> apples = new ArrayList<>(); 。這種語句因為<>符號,也被稱為“鉆石語法”。在Java 7之前,這種語法的使用會更加復雜:
ArrayList<Apple> apples = new ArrayList<Apple>(); // 需要在表達式的兩側重復寫出類型聲明
隨著類型的深入,上述這種語法會變得越發(fā)復雜。因此最后被現(xiàn)在這種更為簡便的語法代替了。
??????? 泛型的存在使得我們從List(接口)中獲取對象時,無須進行轉型了。因為List知道它所持有的類型。另外,在泛型的情況下,向上轉型也可以生效:
import java.util.ArrayList;class GrannySmith extends Apple {
}class Gala extends Apple {
}class Fuji extends Apple {
}class Braeburn extends Apple {
}public class GenericeAndUpcasting {public static void main(String[] args) {ArrayList<Apple> apples = new ArrayList<>();apples.add(new GrannySmith());apples.add(new Gala());apples.add(new Fuji());apples.add(new Braeburn());for (Apple apple : apples)System.out.println(apple);}
}
??????? 程序執(zhí)行的結果是:
??????? 因為向上轉型,所有可以向持有Apple對象的集合中,放入Apple的子類型。
? ? 上述輸出結果中,子對象名字后面的是由無符號十六進制表示的哈希碼。
新特性:類型推斷和泛型
??????? “局部變量類型推斷”也可用于簡化涉及泛型的定義:
import java.util.ArrayList;public class GenericTypeInference {void old() {ArrayList<Apple> apples = new ArrayList<>();}void modern() {var apples = new ArrayList<Apple>();}void pitFall() {var apples = new ArrayList<>();apples.add(new Apple());apples.get(0); // 會作為普通的Object類型返回}
}
??????? 在modern()方法中,定義右側使用的是<Apple>,通過這種方式提供信息,使編譯器知道如何進行類型推斷。
??????? 但替代現(xiàn)有的語法時也可能會產生問題,在pitFall()方法中存在著語句
var apples = new ArrayList<>();
盡管這樣也可以正常編譯,但<>實際上會變?yōu)?span style="background-color:#f3f3f4;"><Object>,這不是我們需要的。當我們想要從apples中提取元素時,它們會作為普通的Object類型返回,而不是具體的Apple類型。
基本概念
??????? Java的集合類庫是用來“持有對象”的。從設計上,可以將這個庫分為兩種概念,這兩種概念分別表示庫的兩個基本接口:
- Collection:一個由單獨元素組成的序列,這些元素要符合一條或多條規(guī)則。
- Map:一組鍵值-對象對,使用鍵來查找值。Map也被稱為關聯(lián)數(shù)組(或是字典),它關聯(lián)了對象與其他對象。
??????? 在理想情況下,我們編寫的大部分代碼都是在和這些接口打交道,只有在創(chuàng)建的時候才需要指名所使用的確切類型。例如,按照這種思想創(chuàng)建一個List:
List<Apple> apples = new ArrayList<>();
??????? 和之前不同,ArrayList在這里被向上轉型為了List。這樣,在我們決定修改實現(xiàn)時,只需要修改創(chuàng)建的地方即可:
List<Apple> apples = new LinkedList<>(); // 將實現(xiàn)修改為LinkedList
??????? 因此,通常會創(chuàng)建一個具體類的對象,并將其向上轉型為相應的接口,在其余的代碼中使用該接口。
??? 但這種方法不會總是行得通,因為有些類會有額外的功能。當我們需要使用到這些額外的功能時,就無法將對象向上轉型為更通用的接口了。
---
??????? 序列是一種持有一組對象的方法,而Collection就是序列這一概念的一般化。例如:
import java.util.ArrayList;
import java.util.Collection;public class SimpleCollection {public static void main(String[] args) {Collection<Integer> c = new ArrayList<>();for (int i = 0; i < 10; i++)c.add(i); // 發(fā)生了自動裝箱,向c中添加一個元素for (Integer i : c)System.out.print(i + " ");System.out.println();}
}
??????? 程序執(zhí)行的結果如下:
??????? 這個示例只使用了Collection中定義的方法,所以繼承自該接口的類的任何對象都可以正常工作。不過,ArrayList是最基本的序列類型。
添加一組元素
??????? java.util中的Arrays和Collection類都包含了一些工具方法,用于向一個Collection中添加一組元素:
- Arrays.asList():可以接受一個數(shù)組,或一個使用逗號分隔的元素列表,將其轉換為一個List對象。
- Collection.addAll():接受一個Collection對象、一個數(shù)組或一個用都逗號分隔的列表,將其中的所有元素都添加到這個Collection中。
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;public class AddingGroups {public static void main(String[] args) {Collection<Integer> collection = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));System.out.println("line 10: " + collection);Integer[] moreInts = { 6, 7, 8, 9, 10 };collection.addAll(Arrays.asList(moreInts)); // 傳統(tǒng)的addAll()方法System.out.println("line 14: " + collection);// 下方的這種方式運行速度更快,但無法通過這種方式構建Collection。Collections.addAll(collection, 11, 12, 13, 14, 15);System.out.println("line 18: " + collection);Collections.addAll(collection, moreInts);System.out.println("line 20: " + collection);// 生成一個底層是數(shù)組的列表:List<Integer> list = Arrays.asList(16, 17, 18, 19, 20);System.out.println("line 24: " + list);list.set(1, 99); // 可以進行元素的修改:將下標為1的元素值改為99// list.add(21); //運行時會發(fā)生報錯,因為底層數(shù)組是不能調整大小的System.out.println("line 27: " + list);}
}
??????? 運行結果如下(line后跟的是行號):
??????? Collection.addAll()的運行速度會快很多。因此,可以先構建一個沒有元素的Collection,之后調用Collection.addAll()進行元素輸入。
??????? collection.addAll(Arrays.asList(moreInts)); 表示該方法只能接受另一個Collection對象作為參數(shù),使用限制較大。相比之下,Arrays.asList()或Collection.addAll()方法就更加好用了。
??????? 可以直接將Arrays.asList()的輸出作為一個List進行使用,但這種方式進行的實現(xiàn),其底層是數(shù)組,大小無法改變。
??????? 也可以向Collection中添加子類型:
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;class Snow {}class Powder extends Snow {}class Light extends Powder {}class Heavy extends Powder {}class Crusty extends Snow {}class Slush extends Snow {}public class AsListInferecnce {public static void main(String[] args) {List<Snow> snow1 = Arrays.asList(new Crusty(), new Slush(), new Powder());// snow1.add(new Heavy()); // 發(fā)生異常List<Snow> snow2 = Arrays.asList(new Light(), new Heavy());// snow2.add(new Slush()); // 發(fā)生異常List<Snow> snow3 = new ArrayList<>();Collections.addAll(snow3, new Light(), new Heavy(), new Powder()); // 有一個可變參數(shù)列表snow3.add(new Crusty()); // 可以正常操作// 使用顯式類型參數(shù)說明作為提示List<Snow> snow4 = Arrays.<Snow>asList(new Light(), new Heavy(), new Slush());// snow4.add(new Powder()); // 發(fā)生異常}
}
打印集合
??????? 數(shù)組的打印往往需要借助于Arrays.toString()等方法,但就像之前的例子演示過的那樣,集合類不需要這些:
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;public class PrintingCollections {static Collection fill(Collection<String> collection) {collection.add("鼠");collection.add("貓");collection.add("狗");collection.add("狗");return collection;}static Map fill(Map<String, String> map) {map.put("鼠", "杰瑞");map.put("貓", "湯姆");map.put("狗", "斯派克");map.put("狗", "斯派克");return map;}public static void main(String[] args) {System.out.println(fill(new ArrayList<>()));System.out.println(fill(new LinkedList<>()));System.out.println();System.out.println(fill(new HashSet<>()));System.out.println(fill(new TreeSet<>())); // 按鍵的升序保存對象System.out.println(fill(new LinkedHashSet<>())); // 按添加順序保存對象System.out.println();System.out.println(fill(new HashMap<>()));System.out.println(fill(new TreeMap<>())); // 按鍵的升序進行排序System.out.println(fill(new LinkedHashMap<>())); // 按插入順序保存鍵,同時具有HashMap的查找速度}
}
??????? 程序執(zhí)行的結果是:
??????? 上述例子演示了Java集合類庫的兩種主要類型。區(qū)別在于集合中的每個“槽”(slot)內持有的條目數(shù):
- Collection:每個槽這只保存一個條目。其中,List以指定順序保存一組條目;Set中,同樣的條目只能加入一個;Queue只能一端進,另一端出。
- Map:每個槽內持有兩個對象,即鍵和與之關聯(lián)的值。
??????? 除此之外,上述例子中ArrayList和LinkedList的區(qū)別不僅在于進行一些操作時性能上的差異,而且LinkedList包含的操作比ArrayList多。另一個值得注意的是與Hash相關的類,這種類使用的保存方式會不同于同屬的其他類。
List
??????? List接口繼承了Collection接口,并在此基礎之上添加了一些方法,這些方法支持在List的中間進行元素的插入和移除。
????????List會以特定的順序維護元素,由其衍生出了許多有用的類:
??????? 接下來會介紹其中兩種較為常用的List:
- ArrayList:一種基本的List,擅長隨機訪問元素,但在List中間進行插入和刪除速度較慢。
- LinkedList:具有理想的順序訪問性能,插入和刪除的成本較低。除此之外,隨機訪問性能也更差,但與ArrayList相比有更多的功能。
??????? 下述程序將會用到的Pet類筆者只進行了簡單實現(xiàn),大致如下:
因此代碼未寫入本筆記。?
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Random;public class ListFeatures {public static void main(String[] args) {List<Pet> pets = new ArrayList<>();Collections.addAll(pets, new Rat(), new Manx(),new Cymric(), new Pug(), new Cymric(), new Pug()); // 插入一些數(shù)據(jù)System.out.println("1. 創(chuàng)建List:" + pets);System.out.println();Hamster h = new Hamster();pets.add(h);System.out.println("2. 刪除元素:" + pets);System.out.println("3. 確定對象是否存在:" + pets.contains(h)); // 按對象搜索pets.remove(h); // 按對象刪除System.out.println();Pet tmpp = pets.get(2); // 按下標獲取System.out.println("4. 獲取元素下標:" + pets.indexOf(tmpp));System.out.println();Pet cymric = new Cymric();System.out.println("5. 試圖通過同類對象進行索引:" + pets.indexOf(cymric));System.out.println("6. 試圖通過同類對象進行刪除:" + pets.remove(cymric));System.out.println("7. 對象能夠精確匹配,正常刪除:" + pets.remove(tmpp));System.out.println();pets.add(3, new Mouse());System.out.println("8. 借助索引進行插入:" + pets);List<Pet> sub = pets.subList(1, 4);System.out.println("9. 使用區(qū)間提取List:" + sub);System.out.println("10. 判斷sub是否存在于pets中:" + pets.containsAll(sub));System.out.println();// List<Pet>沒有實現(xiàn)Comparator接口。為方便請見,這里進行了現(xiàn)場實現(xiàn)Collections.sort(sub, new Comparator<Pet>() {@Overridepublic int compare(Pet p1, Pet p2) {return p1.toString().compareTo(p2.toString());}});System.out.println("11. 對sub進行原地排序:" + sub);System.out.println("12. containsAll()不關心順序:" + pets.containsAll(sub));System.out.println();Random random = new Random(12);Collections.shuffle(sub, random);System.out.println("13. 通過隨機數(shù)使sub進行隨機排序:" + sub);System.out.println();List<Pet> copy = new ArrayList<>(pets);sub = Arrays.asList(pets.get(1), pets.get(4));System.out.println("14. 通過asList獲取sub:" + sub);copy.retainAll(sub);System.out.println("15. 求交集,保留在copy和sub中都存在的元素:" + copy);System.out.println();copy = new ArrayList<>(pets);System.out.println("16. copy獲得了一個pets的副本:" + copy);System.out.println();System.out.println("17. 檢測pets是否為空:" + pets.isEmpty());pets.clear();System.out.println("18. pets被clear()清空:" + pets.isEmpty());System.out.println();Object[] o = copy.toArray();System.out.println("19. toArray()可以將任意Collection轉換為一個數(shù)組:" + o[3]);}
}
??????? 程序執(zhí)行的結果是:
??????? 如之前所說的,List可以在創(chuàng)建后添加或移除元素,而且可以自己調整大小。
????????當需要查找某個元素的索引編號,或按照引用從List中刪除某個元素時,都會使用到equal()方法(這個方法是Object的組成部分)。另外,每一個Pet都是一個獨立的對象,這就是為什么輸出5和輸出6無法進行匹配的原因。
??? 應該意識到,List的因為會隨著equal()行為的改變而改變。
??????? 盡管插入和刪除的操作需要較高的成本,但這并不意味著我們要在插入或刪除時將一個ArrayList轉變?yōu)?span style="background-color:#f3f3f4;">LinkedList。這只意味著我們需要注意這個問題,若ArrayList執(zhí)行多次插入后程序變慢,我們應該看看List的實現(xiàn)(可以使用分析器)。這種優(yōu)化較為棘手,若不必擔心可以暫時放到一邊。
Iterator(迭代器)
??????? 不管是哪種集合,都會需要通過某種方式完成元素的存取。比如List中的add()和get()。
??????? 但當我們開始思考更加復雜的代碼時,會發(fā)現(xiàn)這樣一個缺點:要使用集合,編寫程序時就必須考慮集合的確切類型。如果我一開始針對List編寫代碼,進行優(yōu)化時卻希望將集合修改為Set,這時就麻煩了。
??????? 為此,就出現(xiàn)了迭代器(它同時也是一種設計模式)的概念。迭代器是一個對象,可以在序列中移動,并用來選擇序列中的每個對象。并且使用它的程序員不需要知道序列的底層結構。
??? 另外,迭代器是一個輕量級對象,創(chuàng)建成本很低。也因此,迭代器往往會具有一些使用限制。
??????? Java中的Iterator只能向一個方向移動,并且只能實現(xiàn)幾個功能:
- iterator():讓Collection返回一個Iterator。此時,這個迭代器會準備好返回序列中的第一個元素。
- next():獲得序列中的下一個對象。
- hasNext():檢測序列是否存在更多對象。
- remove():刪除迭代器最近返回的元素。
??????? 下列例子中的PetCreator().list()能夠返回一個裝載了隨機Pet對象的ArrayList(筆者是使用了switch語句對這個方法進行了實現(xiàn))。
import java.util.Iterator;
import java.util.List;public class SimpleIteration {public static void main(String[] args) {// PetCreator().list()會返回一個ArrayList,其中包含隨機填充的Pet對象List<Pet> pets = new PetCreator().list(12);Iterator<Pet> it = pets.iterator();while (it.hasNext()) {Pet p = it.next();System.out.print(p.id() + ":" + p + " ");}System.out.println();// 可以使用一種更簡單的方式for (Pet p : pets)System.out.print(p.id() + ":" + p + " ");System.out.println();// 也可以使用迭代器來刪除元素it = pets.iterator();for (int i = 0; i < 6; i++) {it.next();it.remove();}System.out.println(pets);}
}
??????? 程序執(zhí)行的結果如下:
??????? Iterator的存在使得程序員不再需要操心集合中的元素數(shù)量,因為使用hasNext()和next()就能做到不錯的處理。
??? 若只需要對List進行遍歷,for-in語句顯得更加簡潔。
??????? 這種對集合中的每個對象執(zhí)行一個操作的想法十分有用,可以在許多地方發(fā)現(xiàn)。
??????? 上述的例子還無法完全展示Iterator的作用,下面的例子將會創(chuàng)建一個與具體的集合類型無關的display()方法:
??????? 注:為了實現(xiàn)這一例子,自制的Pet類需要繼承并實現(xiàn)Comparable接口。其中需要被實現(xiàn)的compareTo()方法就是為了進行對象排序而定義的。
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeSet;public class CrossCollectionIteration {public static void display(Iterator<Pet> it) {while (it.hasNext()) {Pet p = it.next();System.out.print(p.id() + ":" + p + " ");}System.out.println();}public static void main(String[] args) {List<Pet> pets = new PetCreator().list(8);LinkedList<Pet> petsLL = new LinkedList<>(pets);HashSet<Pet> petsHS = new HashSet<>(pets);TreeSet<Pet> petsTS = new TreeSet<>(pets);display(pets.iterator());display(petsLL.iterator());display(petsHS.iterator());display(petsTS.iterator());}
}
??????? 程序執(zhí)行的結果如下:
??????? 這個例子體現(xiàn)了Iterator真正的作用:能夠將序列的遍歷操作和序列的底層結構分離。也就是說,迭代器統(tǒng)一了對集合的訪問。
??????? Iterable接口描述了“任何可以產生一個迭代器的事物”。
使用這一接口可以將上述例子修改成更加簡潔的版本:
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeSet;public class CrossCollectionIteration2 {public static void display(Iterable<Pet> ip) {Iterator<Pet> it = ip.iterator();while (it.hasNext()) {Pet p = it.next();System.out.print(p.id() + ":" + p + " ");}System.out.println();}public static void main(String[] args) {List<Pet> pets = new PetCreator().list(8);LinkedList<Pet> petsLL = new LinkedList<>(pets);HashSet<Pet> petsHS = new HashSet<>(pets);TreeSet<Pet> petsTS = new TreeSet<>(pets);// 可以直接傳入數(shù)組display(pets);display(petsLL);display(petsHS);display(petsTS);}
}
??????? 程序執(zhí)行的結果與上面的例子無異,不再重復展示。在這里,因為ArrayList已經(jīng)實現(xiàn)了Iterable接口,所以display()方法在調用時更加方便了。
??????? ListIterator是Iterator的一個更強大的子類型,只有List類才會生成。與Iterator不同,ListIterator可以雙向移動。除此之外,它還可以生成相對于迭代器在列表中指向的當前位置的下一個和上一個元素的索引。并且可以使用set()方法替換它所訪問過的最后一個元素。
import java.util.List;
import java.util.ListIterator;public class ListIteration {public static void main(String[] args) {List<Pet> pets = new PetCreator().list(8);ListIterator<Pet> it = pets.listIterator();while (it.hasNext())System.out.print(it.next() + ", " + it.nextIndex() + ", " + it.previousIndex() + "; ");System.out.println();// 也可以進行反向的遍歷while (it.hasPrevious())System.out.print(it.previous().id() + " ");System.out.println();System.out.println(pets);it = pets.listIterator(3);while (it.hasNext()) { // 從下標為3的位置開始替換對象it.next();it.set(new PetCreator().get());}System.out.println(pets);}
}
??????? 程序執(zhí)行的結果是: