注解
什么是注解(Annotation)?注解是放在Java源碼的類、方法、字段、參數前的一種特殊“注釋”
// this is a component:
@Resource("hello")
public class Hello {@Injectint n;@PostConstructpublic void hello(@Param String name) {System.out.println(name);}@Overridepublic String toString() {return "Hello";}
}
注釋會被編譯器直接忽略,注解則可以被編譯器打包進入class文件,因此,注解是一種用作標注的“元數據”。
注解的作用
從JVM的角度看,注解本身對代碼邏輯沒有任何影響,如何使用注解完全由工具決定。
Java的注解可以分為三類:
- 由編譯器使用的注解,
例如:@Override:讓編譯器檢查該方法是否正確地實現了覆寫;
@SuppressWarnings:告訴編譯器忽略此處代碼產生的警告
這類注解不會被編譯進class文件中。
- 第二類是由工具處理.class文件使用的注解,比如有些工具會在加載class的時候,對class做動態修改,實現一些特殊的功能。一般只被底層庫使用,不必自己處理。
- 第三類是在程序運行期能夠讀取的注解,它們在加載后一直存在于JVM中,這也是最常用的注解。例如,一個配置了@PostConstruct的方法會在調用構造方法后自動被調用(這是Java代碼讀取該注解實現的功能,JVM并不會識別該注解)
定義一個注解時,還可以定義配置參數。配置參數可以包括:
所有基本類型; String; 枚舉類型; 基本類型、String、Class以及枚舉的數組。
public class Hello {@Check(min=0, max=100, value=55)public int n;@Check(value=99)public int p;@Check(99) // @Check(value=99)public int x;@Checkpublic int y;
}
@Check就是一個注解。第一個@Check(min=0, max=100, value=55)明確定義了三個參數,第二個@Check(value=99)只定義了一個value參數,它實際上和@Check(99)是完全一樣的。最后一個@Check表示所有參數都使用默認值。
定義注解
java 用@interface定義注解,然后添加參數、默認值:
public @interface Report {int type() default 0;String level() default "info";String value() default "";
}
接著有一些可以用于注解的注解,成為元注解。如
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Report {int type() default 0;String level() default "info";String value() default "";
}
@Target定義該注解能注解什么東西
類或接口:ElementType.TYPE;
字段:ElementType.FIELD;
方法:ElementType.METHOD;
構造方法:ElementType.CONSTRUCTOR;
方法參數:ElementType.PARAMETER。
@Retention定義該注解的生命周期,如上就是定義在runtime的時候運行,因為我們自己寫的注解一般在runtime的時候執行。
僅編譯期:RetentionPolicy.SOURCE;
僅class文件:RetentionPolicy.CLASS;
運行期:RetentionPolicy.RUNTIME。
其次還有@Repeatable定義該注解是否可以重復使用在同一個地方,@Inherited定義是否可以繼承父類的注解。
處理注解
Java的注解本身對代碼邏輯沒有任何影響。根據@Retention的配置:
- SOURCE類型的注解在編譯期就被丟掉了;
- CLASS類型的注解僅保存在class文件中,它們不會被加載進JVM;
- RUNTIME類型的注解會被加載進JVM,并且在運行期可以被程序讀取。
使用注解
// 定義一個注解
@Retention(RetentionPolicy.RUNTIME) // runtime時運行,會打入jvm
@Target(ElementType.FIELD) // 作用于字段
@interface Range {int min() default 0;int max() default 255;
}class Person1 {@Range(min = 1, max = 20)public String name;@Range(max = 10)public String city;
}
定義了注解,本身對程序邏輯沒有任何影響。我們必須自己編寫代碼來使用注解。
void check(Person person) throws IllegalArgumentException, ReflectiveOperationException {// 遍歷所有Field:for (Field field : person.getClass().getFields()) {// 獲取Field定義的@Range:Range range = field.getAnnotation(Range.class);// 如果@Range存在:if (range != null) {// 獲取Field的值:Object value = field.get(person);// 如果值是String:if (value instanceof String s) {// 判斷值是否滿足@Range的min/max:if (s.length() < range.min() || s.length() > range.max()) {throw new IllegalArgumentException("Invalid field: " + field.getName());}}}}
}
集合
什么是集合(Collection)?集合就是“由若干個確定的元素所構成的整體”。
在Java中,如果一個Java對象可以在內部持有若干其他Java對象(注意是對象,而不是基礎類型,比如Integer可以,但是int不行),并對外提供訪問接口,我們把這種Java對象稱為集合。很顯然,Java的數組可以看作是一種集合:
String[] ss = new String[10]; // 可以持有10個String對象
ss[0] = "Hello"; // 可以放入String對象
String first = ss[0]; // 可以獲取String對象
數組的限制,長度固定,數據只能通過索引獲取。
我們需要各種不同類型的集合類來處理不同的數據,例如:
- 可變大小的順序鏈表;
- 保證無重復元素的集合;
Collection
Java標準庫自帶的java.util包提供了集合類:Collection,它是除Map外所有其他集合類的根接口
ava的java.util包主要提供了以下三種類型的集合:
- List 一種有序列表的集合,例如,按索引排列的Student的List,如ArrayList, LinkedList
- Set 一種保證沒有重復元素的集合,例如,所有無重復名稱的Student的Set
- Map 一種通過鍵值(key-value)查找的映射表集合,例如,根據Student的name查找對應Student的Map
Java集合設計的特點
- 實現了 接口和 實現類 相分離,如有序表的接口是List,具體的實現類有ArrayList,LinkedList等
- 支持泛型 List list = new ArrayList<>(); // 只能放入String類型
- 訪問集合都是通過迭代器(Iterator)的形式訪問的,它最明顯的好處在于無需知道集合內部元素是按什么方式存儲的。(類似于js的實現Symbol.iterator,實現對象的迭代,外部無需知道他是怎么存儲的。)
List
List是最基礎的一種集合:它是一種有序列,數組和List非常相似。但是數組進行增刪操作會非常麻煩。反觀List
當我們需要增刪操作的時候,可以使用ArrayList,
如 一個ArrayList擁有5個元素,實際數組大小為6(即有一個空位)
- 當添加一個元素并指定索引如2到ArrayList時,ArrayList自動移動需要移動的元素,把2及后面的元素移動。
- 然后,往內部指定索引的數組位置添加一個元素,然后把size加1
- 此時繼續添加元素的時候,數組已滿,沒有空閑位置的時候,ArrayList先創建一個更大的新數組,然后把舊數組的所有元素復制到新數組,緊接著用新數組取代舊數組。
可見,ArrayList把添加和刪除的操作封裝起來,讓我們操作List類似于操作數組,卻不用關心內部元素如何移動。
List < T >主要的接口方法
在末尾添加一個元素:boolean add(E e)
在指定索引添加一個元素:boolean add(int index, E e)
刪除指定索引的元素:E remove(int index)
刪除某個元素:boolean remove(Object e)
獲取指定索引的元素:E get(int index)
獲取鏈表大小(包含元素的個數):int size()
上述是通過數組實現的List,實際上,使用鏈表也能實現。
在LinkedList中,它的內部每個元素都指向下一個元素:
┌───┬───┐ ┌───┬───┐ ┌───┬───┐ ┌───┬───┐
HEAD ──>│ A │ ●─┼──>│ B │ ●─┼──>│ C │ ●─┼──>│ D │ │└───┴───┘ └───┴───┘ └───┴───┘ └───┴───┘
我們來比較一下ArrayList和LinkedList:
123 | ArrayList | LinkedList |
---|---|---|
獲取指定元素 | 速度很快 | 需要從頭開始查找元素 |
添加元素到末尾 | 速度很快 | 速度很快 |
在指定位置添加/刪除 | 需要移動元素 | 不需要移動元素 |
內存占用 | 少 | 較大 |
使用
List<String> list = new ArrayList<>();list.add("apple");list.add("pear");List<String> list1 = new LinkedList<>();list1.add("test1");list1.add("test2");// Java 9 List<Integer> list2 = List.of(1, 2, 5);System.out.println(list);System.out.println(list1);
遍歷List,使用for循環
for (int i = 0; i < list.size(); i++) {String s = list.get(i);System.out.println(s);}
這種方法并不推薦
一是代碼復雜,二是因為get(int)方法只有ArrayList的實現是高效的,換成LinkedList后,索引越大,訪問速度越慢。
要始終堅持使用迭代器Iterator來訪問List。Iterator本身也是一個對象,但它是由List的實例調用iterator()方法的時候創建的。
for (Iterator<String> it = list.iterator();it.hasNext();) {String s = it.next();System.out.println(s);}
調用iterator獲取一個iterator對象,跟js的迭代器很類似。it.hasNext()判斷是否還有下一個元素,it.next()返回下一個元素
以上是一種固定寫法,第二個參數需要搭配it.hasNext()返回一個boolen判斷是否繼續循環。
Java的for each循環本身就可以幫我們使用Iterator遍歷
for (String it1 : list1) {System.out.println(it1);}
可以直接遍歷獲取值。
實際上,只要實現了Iterable接口的集合類都可以直接用for each循環來遍歷,Java編譯器本身并不知道如何遍歷集合對象,但它會自動把for each循環變成Iterator的調用
,原因就在于Iterable接口定義了一個Iterator<E> iterator()方法,強迫集合類必須返回一個Iterator實例。
有點像js中,只要實現了Symbol.iterator方法的對象,就可以使用for of進行遍歷。因為Symbol.iteraotr方法返回了一個迭代器對象。
List和Array互相轉換
- 把List變為Array有三種方法,第一種是調用toArray()方法直接返回一個Object[]數組,不常用,因為類型可能會丟失
- 第二種方式是給toArray(T[])傳入一個類型相同的Array,List內部自動把元素復制到傳入的Array中。這里的T和List< E>不一樣,所以toArray可以傳其他類型的數組,但不兼容會報錯。
List<String> list1 = new LinkedList<>();list1.add("test1");list1.add("test2");String[] test2 = list1.toArray(new String[2]);Integer[] test3 = list1.toArray(new Integer[2]); //會報錯
傳入的數組如果不夠大,那么List內部會創建一個新的數組,大小一樣。
如果傳入的數組過大,那么多余的元素全部填充null。
Array to List
// JAVA 9
Integer[] array = { 1, 2, 3 };
List<Integer> list = List.of(array);// JAVA 9 之前List<String> list3 = Arrays.asList(test2);
注意,這種返回的List不一定是ArrayList或者LinkedList,因為返回的List是只讀的,在操作會報錯。’
編寫equals方法
List提供了contains方法,來判斷當前list是否包含哪些內容。提供了indexOf(obj)方法來判斷obj在當前list的位置
List<String> list3 = new ArrayList<>();list3.add("S");list3.add("C");System.out.println(list3.contains(new String("S"))); // trueSystem.out.println(list3.indexOf("S")); //0
這里contains我們傳入了一個全新的String對象,但是結果還是為true,這是因為contains的實現是
public class ArrayList {Object[] elementData;public boolean contains(Object o) {for (int i = 0; i < elementData.length; i++) {if (o.equals(elementData[i])) {return true;}}return false;}
}
拿傳入的對象,調用其equals方法,所以,要向正確的使用contains和indexOf,傳入的對象就必須正確的實現equals方法。而String和Integer等對象,java已經幫我們實現了equals方法。
如何編寫equals方法
- 先確定實例“相等”的邏輯,即哪些字段相等,就認為實例相等;
- 用instanceof判斷傳入的待比較的Object是不是當前類型,如果是,繼續比較,否則,返回false;
- 對引用類型用Objects.equals()比較,對基本類型直接用==比較。
class Person {private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}public boolean equals(Object o) {if (o instanceof Person) {Person p = (Person) o;boolean nameEquals = false;if (this.name == null && p.name == null) {nameEquals = true;}if (this.name != null) {nameEquals = this.name.equals(p.name);}return nameEquals && this.age == p.age;}return false;}// 使用Objects.equals()比較兩個引用類型是否相等的目的是省去了判斷null的麻煩。兩個引用類型都是null時它們也是相等的。public boolean equals1(Object o) {if (o instanceof Person) {Person p = (Person) o;return Objects.equals(this.name, p.name) && this.age == p.age;}return false;}
}
Map
有個需求,在存放Students 集合 List中查找一個名為小明的,查看他的成績。
這時候我們需要遍歷List,然后判斷到name微小明的,就取他的成績。這樣效率很低。
而Map這種key value的形式剛好可以滿足。
Map<String, Person> map = new HashMap<>();map.put("lin", new Person("lin", 20));Person Target = map.get("lin");
和List類似,Map也是一個接口,最常用的實現類是HashMap。如果只是想查詢某個key是否存在,可以調用boolean containsKey(K key)方法。
其他與js的Map類似。
遍歷
Person Target = map.get("lin");for (String key : map.keySet()) {System.out.println(key);}for (Map.Entry<String, Person> entry : map.entrySet()) {String key = entry.getKey();Person value = entry.getValue();System.out.println(key);System.out.println(value);}
map.keySet()獲取key,map.entrySet()獲取key-value
- Map不保證順序
編寫equals 和 hashCode
Map之所以能很快通過key獲取value,是因為內部通過空間換時間的處理。
Map內部用很大的數組存放數據,然后通過特殊的計算,比如f函數 f(key) = index,傳入同一個key,index總是相同的,然后通過index存放value,取數據的時候也是通過f(key) = index計算出index,然后通過數組[index]去獲取數據。但這樣有個問題,如果傳入的兩個key是不同的對象呢。
Map<String, String> map2 = new HashMap<>();map2.put("2", "123");System.out.println(map2.get(new String("2"))); // 123
傳入不同的String還是能拿到正確的值,因為跟List一樣,key值得比對也是通過equals方法。
其次,通過key獲取index是通過hashCode方法獲取的,
正確使用Map必須保證:
- 作為key的對象必須正確覆寫equals()方法,相等的兩個key實例調用equals()必須返回true;
- 作為key的對象還必須正確覆寫hashCode()方法,且hashCode()方法要嚴格遵循以下規范:
- 如果兩個對象相等,則兩個對象的hashCode()必須相等;
- 如果兩個對象不相等,則兩個對象的hashCode()盡量不要相等
public class Person {String firstName;String lastName;int age;@Overrideint hashCode() {int h = 0;h = 31 * h + firstName.hashCode();h = 31 * h + lastName.hashCode();h = 31 * h + age;return h;}
}
// 借助Objects.hash
int hashCode() {return Objects.hash(firstName, lastName, age);
}
equals()用到的用于比較的每一個字段,都必須在hashCode()中用于計算;
equals()中沒有使用到的字段,絕不可放在hashCode()中計算。
這里注意,兩個對象的hashCode盡量不要想等,也就是有可能相等。
我們把不同的key具有相同的hashCode()的情況稱之為哈希沖突。在沖突的時候,一種最簡單的解決辦法是用List存儲hashCode()相同的key-value。顯然,如果沖突的概率越大,這個List就越長,Map的get()方法效率就越低,這就是為什么要盡量滿足條件二。
hashCode()方法編寫得越好,HashMap工作的效率就越高。
計算hash code,相同hash code用一個List存放。
EnumMap
HsahMap通過空間換時間,計算index從數組直接拿數據。
如果key是Enum的話,可以用EnumMap ,在內部以一個非常緊湊的數組存儲value,并且根據enum類型的key直接定位到內部數組的索引,并不需要計算hashCode(),不但效率最高,而且沒有額外的空間浪費。
public class Main {public static void main(String[] args) {Map<DayOfWeek, String> map = new EnumMap<>(DayOfWeek.class);map.put(DayOfWeek.MONDAY, "星期一");map.put(DayOfWeek.TUESDAY, "星期二");map.put(DayOfWeek.WEDNESDAY, "星期三");map.put(DayOfWeek.THURSDAY, "星期四");map.put(DayOfWeek.FRIDAY, "星期五");map.put(DayOfWeek.SATURDAY, "星期六");map.put(DayOfWeek.SUNDAY, "星期日");System.out.println(map);System.out.println(map.get(DayOfWeek.MONDAY));}
}
TreeMap
HashMap無序,還有一種Map,它在內部會對Key進行排序,這種Map就是SortedMap。注意到SortedMap是接口,它的實現類是TreeMap。
┌───┐│Map│└───┘▲┌────┴─────┐│ │
┌───────┐ ┌─────────┐
│HashMap│ │SortedMap│
└───────┘ └─────────┘▲│┌─────────┐│ TreeMap │└─────────┘
SortedMap保證遍歷時以Key的順序來進行排序。例如,放入的Key是"apple"、“pear”、“orange”,遍歷的順序一定是"apple"、“orange”、“pear”,因為String默認按字母排序
public static void main(String[] args) {Map<String, Integer> map = new TreeMap<>();map.put("orange", 1);map.put("apple", 2);map.put("pear", 3);for (String key : map.keySet()) {System.out.println(key);}// apple, orange, pear}
TreeMap要求實現Comparable方法
沒有的話需要在使用TreeMap的時候指定排序方法,
public class Main {public static void main(String[] args) {Map<Person, Integer> map = new TreeMap<>(new Comparator<Person>() {public int compare(Person p1, Person p2) {return p1.name.compareTo(p2.name);}});map.put(new Person("Tom"), 1);map.put(new Person("Bob"), 2);map.put(new Person("Lily"), 3);for (Person key : map.keySet()) {System.out.println(key);}// {Person: Bob}, {Person: Lily}, {Person: Tom}System.out.println(map.get(new Person("Bob"))); // 2}
}
Comparator接口要求實現一個比較方法,它負責比較傳入的兩個元素a和b,如果a<b,則返回負數,通常是-1,如果a==b,則返回0,如果a>b,則返回正數,通常是1。TreeMap內部根據比較結果對Key進行排序
- SortedMap在遍歷時嚴格按照Key的順序遍歷,最常用的實現類是TreeMap;
- 作為SortedMap的Key必須實現Comparable接口,或者傳入Comparator;
- 要嚴格按照compare()規范實現比較邏輯,否則,TreeMap將不能正常工作。
Set
不重復的集合,且不需要value,類似于js的Set
Set<String> set = new HashSet<>();System.out.println(set.add("abc")); // trueSystem.out.println(set.add("xyz")); // trueSystem.out.println(set.add("xyz")); // false,添加失敗,因為元素已存在System.out.println(set.contains("xyz")); // true,元素存在System.out.println(set.contains("XYZ")); // false,元素不存在System.out.println(set.remove("hello")); // false,刪除失敗,因為元素不存在System.out.println(set.size()); // 2,一共兩個元素
set.add, set.contains, set.remove, set.resize
Set實際上相當于只存儲key、不存儲value的Map。我們經常用Set用于去除重復元素。
因為放入Set的元素和Map的key類似,都要正確實現equals()和hashCode()方法,否則該元素無法正確地放入Set
HashSet其實是封裝HashMap
public class HashSet<E> implements Set<E> {// 持有一個HashMap:private HashMap<E, Object> map = new HashMap<>();// 放入HashMap的value:private static final Object PRESENT = new Object();public boolean add(E e) {return map.put(e, PRESENT) == null;}public boolean contains(Object o) {return map.containsKey(o);}public boolean remove(Object o) {return map.remove(o) == PRESENT;}
}
HasHSet是無序的,而SortedSet是有序的,但他只是個接口,實現的類是TreeSet
public class Main {public static void main(String[] args) {Set<String> set = new TreeSet<>();set.add("apple");set.add("banana");set.add("pear");set.add("orange");for (String s : set) {System.out.println(s);}}
}
使用TreeSet和使用TreeMap的要求一樣,添加的元素必須正確實現Comparable接口,如果沒有實現Comparable接口,那么創建TreeSet時必須傳入一個Comparator對象。
Queue
隊列,先進先出
它和List的區別在于,List可以在任意位置添加和刪除元素,而Queue只有兩個操作:
把元素添加到隊列末尾;
從隊列頭部取出元素。
這些操作有兩個方法,一個會拋出錯誤,一個返回false/null。
// 這是一個List:
List<String> list = new LinkedList<>();
// 這是一個Queue:
Queue<String> queue = new LinkedList<>();
LinkedList(鏈表)類實現了Queue接口和List接口
PriorityQueue
上述的Queue是先進先出,但是如果有優先級的處理,就像react中優先級高的渲染,就出現了優先級隊列PriorityQueue,
PriorityQueue和Queue的區別在于,它的出隊順序與元素的優先級有關,對PriorityQueue調用remove()或poll()方法,返回的總是優先級最高的元素。(react-sceduler的實現類似)
public class Main {public static void main(String[] args) {Queue<String> q = new PriorityQueue<>();// 添加3個元素到隊列:q.offer("apple");q.offer("pear");q.offer("banana");// 按照元素順序System.out.println(q.poll()); // appleSystem.out.println(q.poll()); // bananaSystem.out.println(q.poll()); // pearSystem.out.println(q.poll()); // null,因為隊列為空}
}
放入PriorityQueue的元素,必須實現Comparable接口,PriorityQueue會根據元素的排序順序決定出隊的優先級。
也可以在創建PriorityQeue的時候提供一個comparator對象來判斷兩個元素的順序,跟TreeMap類似。
public class Main {public static void main(String[] args) {Queue<User> q = new PriorityQueue<>(new UserComparator());// 添加3個元素到隊列:q.offer(new User("Bob", "A1"));q.offer(new User("Alice", "A2"));q.offer(new User("Boss", "V1"));System.out.println(q.poll()); // Boss/V1System.out.println(q.poll()); // Bob/A1System.out.println(q.poll()); // Alice/A2System.out.println(q.poll()); // null,因為隊列為空}
}class UserComparator implements Comparator<User> {public int compare(User u1, User u2) {if (u1.number.charAt(0) == u2.number.charAt(0)) {// 如果兩人的號都是A開頭或者都是V開頭,比較號的大小:return u1.number.compareTo(u2.number);}if (u1.number.charAt(0) == 'V') {// u1的號碼是V開頭,優先級高:return -1;} else {return 1;}}
}
Deque雙端隊列
,允許兩頭都進,兩頭都出,這種隊列叫雙端隊列
Java集合提供了接口Deque來實現一個雙端隊列,它的功能是:
既可以添加到隊尾,也可以添加到隊首;
既可以從隊首獲取,又可以從隊尾獲取。
Deque是一個接口,它的實現類有ArrayDeque
和LinkedList
。
Stack 先進后出
將十進制轉為16進制
public static void main(String[] args) {String hex = toHex(12500);System.out.println(hex);if (hex.equalsIgnoreCase("30D4")) {System.out.println("測試通過");} else {System.out.println("測試失敗");}}static String toHex(int n) {Deque<String> dq = new LinkedList<>();int num = n % 16;int num1 = n / 16;while (num1 != 0) {dq.addFirst(Integer.toHexString(num));num = num1 % 16;num1 = num1 / 16;}dq.addFirst(Integer.toHexString(num));String result = "";String s = dq.pollFirst();System.out.println(s);while (s != null) {result += s;s = dq.pollFirst();}return result;}
循環求余數壓入棧中,再循環取出。
Iterator
Java的集合類都可以使用for each循環,List、Set和Queue會迭代每個元素,Map會迭代每個key。因為他們都實現了Iterator對象
Java編譯器并不知道如何遍歷List。上述代碼能夠編譯通過,只是因為編譯器把for each循環通過Iterator改寫為了普通的for循環:
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {String s = it.next();System.out.println(s);
}
for (String s : list) {System.out.println(s);
}
類似于js的Symbol.Iterator迭代器,只要實現了Symbol.iterator,就可以用for of循環。
實現一個集合,需要實現他的Iterator方法,并且返回一個Iterator對象
class ReverseList<T> implements Iterable<T> {private List<T> list = new ArrayList<>();public void add(T t) {list.add(t);}@Overridepublic Iterator<T> iterator() {return new ReverseIterator(list.size());}class ReverseIterator implements Iterator<T> {int index;ReverseIterator(int index) {this.index = index;}@Overridepublic boolean hasNext() {return index > 0;}@Overridepublic T next() {index--;return ReverseList.this.list.get(index);}}
}
可以看到迭代器主要實現hasNext方法和next方法。js的Symbol.Iterator則是實現{done: ture, value: ‘’}來判斷是否已經遍歷結束。
Collections
Collections
是JDK提供的工具類,同樣位于java.util包中。它提供了一系列靜態方法,能更方便地操作各種集合。
// 空集合List<String> list2 = Collections.emptyList(); // 返回不可變List// 單元素集合List<String> list3 = Collections.singletonList("apple"); // 返回不可變ListList<String> list4 = Arrays.asList("1230", "456", "132"); // 返回不可變List// 排序Collections.sort(list4); // 排序回修改List元素的位置,所以需要傳入可變ListSystem.out.println(list4);Collections.shuffle(list4); // 打亂List的順序System.out.println(list4);// 變為不可變集合 假設List4是可變ListList<String> list5 = Collections.unmodifiableList(list4);// list5.add("orange"); // UnsupportedOperationException!list4.add("2222");list4 = null; // 轉變后立馬扔掉老的引用System.out.println(list5); // 老的List修改影響不可變List
小結
List
- Java集合使用統一的Iterator遍歷
- 有序集合 List -> ArrayList(通過數組實現,) LinkedList(通過鏈表實現),可以用for each遍歷。存放List的對象需要實現equals方法,便于調用List的contains和indexOf方法。
Map
-
key-value 映射表 -> Map , Map集合無序,可以通過for each遍歷keySet(),也可以通過for each遍歷entrySet(),直接獲取key-value。最常用的Map就是HashMap
-
HashMap通過數組的形式存放,調用key.hashCode()獲取索引,通過key.equals()判斷是否相等,所以HashMap存放到對象需要實現hashCode和equals方法,而且equals方法返回true,則hashCode返回的索引一定相等,如果equals返回false,那么hashCode返回的值盡兩不要相等。
- 但是萬一相等了呢,HashMap的數組存放的是List<Entry<string, Object>>,當不同的key的HashCode返回的索引相等的時候,會拿到一個List,再通過key.equlas去獲取到對應的值。
-
如果Map的key是enum類型,推薦使用EnumMap,既保證速度,也不浪費空間。使用EnumMap的時候,根據面向抽象編程的原則,應持有Map接口。
-
HashMap是無序的,但是Map下面還有一個SortedMap接口,TreeMap類實現了這個接口,他是有序的。
- TreeMap怎么排序呢,通過存放的每個對象實現一個comparable接口,或者在定義TreeMap的時候,指定一個自定義排序算法
-
Map<Person, Integer> map = new TreeMap<>(new Comparator<Person>() {public int compare(Person p1, Person p2) {return p1.name.compareTo(p2.name);}});
- Comparator接口要求實現一個比較方法,它負責比較傳入的兩個元素a和b,如果a<b,則返回負數,通常是-1,如果a==b,則返回0,如果a>b,則返回正數,通常是1。TreeMap內部根據比較結果對Key進行排序。
Set
- 只需要存儲不重復的key,并不需要存儲映射的value,那么就可以使用Set。
- 最常用的Set是HashSet,他是HashMap的封裝
- 此外HashSet是無序的,但同時,SrotedSet是有序的,他是一個接口,TreeSet實現了這個接口
- 跟TreeMap一樣,TreeSet也要求一個compator接口,來判斷兩個對象的順序。
Queue 通過LinkedList類實現該接口
- 隊列先進先出,通過add()/offer()方法將元素添加到隊尾
- 通過remove/poll獲取隊首元素并挪去。
- 通過element()/peek()獲取隊首元素但不去掉。
- 前面的add/remove/eleemtn執行出錯會報錯,而offer/poll/peek只返回對應的false/null
- PriorityQueue優先級隊列,優先級隊列需要實現Comparable接口,priorityQueue根據元素的優先順序決定誰先出隊。
- PriorityQueue默認按元素比較的順序排序(必須實現Comparable接口),也可以通過Comparator自定義排序算法(元素就不必實現Comparable接口)。
Deque 雙頭隊列 也是LinkedList實現
- Deque其實也是繼承于Queue的
- addLast/offerLast addFirst/offerFirst 從隊頭/隊尾插入
- removeFirst/pollFirst removeLast/pollLast 從對頭/隊尾取出并刪除
- getFirst/peekFirst getLast/peekLast 從對頭/隊尾取出但不刪除
Stack 棧,先進后廚
- 利用Deque實現棧即可
- 只調用對應的offerFirst/peekFirst/getFirst三個方法。
- Stack作用很大,比如計算機調用方法通過執行棧維護等等
Iterator
Iterator是一種抽象的數據訪問模型。使用Iterator模式進行迭代的好處有:
- 對任何集合都采用同一種訪問模型;
- 調用者對集合內部結構一無所知;
- 集合類返回的Iterator對象知道如何迭代。
- Java提供了標準的迭代器模型,即集合類實現java.util.Iterable接口,返回java.util.Iterator實例。
Collections
Collections類提供了一組工具方法來方便使用集合類:
- Collections.emptyList()創建空集合 返回不可變集合
- Collections.singletonList(“test”)創建單個集合 返回不可變集合
- Collections.unmodifiableList(可變集合),將可變集合變成不可變集合
- Collections.sort()排序集合, Collections.shuffle() 將集合的順序重新打亂(洗牌)