物件陣列有哪些問題?普通的物件陣列最大的問題在于,陣列中的元素個數是固定的,不能動態的擴充大小,所以最早的時候可以通過鏈表實作一個動態物件陣列,但是這樣做畢竟太復雜了,所以在Java中為了方便用戶操作各個資料結構,所以引入了類集的概念,有時候就可以把類集稱為Java對資料結構的實作,
類集這個概念是從JDK1.2(Java2)之后才正式引入的,最早也提供了很多的操作類,但是并沒有完整的提出類集的概念,
類集中最大的幾個操作介面:Collection、Map、Iterator,這三個介面為以后要使用的最重點的介面,

Collection介面(重點)
Collection介面是在整個Java類集中保存單值的最大操作父介面,里面每次操作的時候都只能保存一個物件的資料,此介面定義在java.util包中
此介面定義如下:
public interface Collection<E> extends Iterable<E>
此介面使用了泛型技術,在JDK1.5滯后為了使類集操作的更加安全,所以引入了泛型
此介面的常用方法如下所示,
| No. | 方法名稱 | 型別 | 描述 |
|---|---|---|---|
| 1 | public boolean add(E e) | 普通 | 向集合中插入一個元素 |
| 2 | public boolean addAll(Collection<? extends E> c) | 普通 | 向集合中插入一組元素 |
| 3 | public void clear() | 普通 | 清空集合中的元素 |
| 4 | public boolean contains(Object o) | 普通 | 查找一個元素是否存在 |
| 5 | public boolean containsAll(Collection<?> c) | 普通 | 查找一組元素是否存在 |
| 6 | public boolean isEmpty() | 普通 | 判斷集合是否為空 |
| 7 | public Iterator<E> iterator() | 普通 | 為Iterator介面實體化 |
| 8 | public boolean remove(Object o) | 普通 | 從集合中洗掉一個物件 |
| 9 | public boolean removeAll(Collection<?> c) | 普通 | 從集合中洗掉一組物件 |
| 10 | public boolean retainAll(Collection<?> c) | 普通 | 判斷是否沒有指定的集合 |
| 11 | public int size() | 普通 | 回傳集合中元素的個數 |
| 12 | public Obejct[] toArray() | 普通 | 以物件陣列的形式回傳集合中的全部內容 |
| 13 | public <T> T[] toArray(T[] a) | 普通 | 指定操作的泛型型別,并把內容回傳 |
| 14 | public boolean equals(Object o) | 普通 | 從Object類中重寫而來 |
| 15 | public int hasCode() | 普通 | 從Object類中重寫而來 |
本介面中一共定義了15個方法,那么此介面的全部子類或子介面就將全部繼承以上介面中的方法,
但是,我們在實際開發中不會直接使用Collection介面,而使用其操作的子介面:List、Set
之所以有這樣的明文規定,也是在JDK1.2之后才有的,一開始在EJB中的最早模型中全部都是使用Collection操作的,所以很早之前開發代碼都是以Collection為準,但是后來為了更加清楚的區分,集合中是否允許有重復元素,所以SUN公司在其開源專案——PetShop(寵物商店)中就開始推廣List和Set的使用,
List介面(重點)
在整個集合中,List是Collection的子介面,里面所有的內容都是允許重復的
List子介面的定義:
public interface List<E> extends Collection<E>
此介面上依然使用了泛型技術,此介面對于Collection介面來講有如下的擴充方法:
| No. | 方法名稱 | 型別 | 描述 |
|---|---|---|---|
| 1 | public void add(int index,E element) | 普通 | 在指定位置處添加元素 |
| 2 | public boolean addAll(int index,Collection<? extends E> c) | 普通 | 在指定位置處增加一組元素 |
| 3 | public E get(int index) | 普通 | 根據索引位置取出元素 |
| 4 | public int indexOf(Obejct o) | 普通 | 根據物件查找指定的位置,找不到回傳-1 |
| 5 | public int lastIndexOf(Object o) | 普通 | 從后面向前查找位置,找不到回傳-1 |
| 6 | public ListIterator<E> listIterator() | 普通 | 回傳ListIterator介面的實體 |
| 7 | public ListIterator<E> listIterator(int index) | 普通 | 回傳從指定位置的 ListIterator 介面的實體 |
| 8 | public E remove(int index) | 普通 | 洗掉指定位置的內容 |
| 9 | public E set(int index,E element) | 普通 | 修改指定位置的內容 |
| 10 | public List<E> subList(int fromIndex,int toIndex) | 普通 | 回傳范圍內的子集合 |
在List介面中有以上10個方法是對已有的Collection介面進行得擴充,
所以,證明,List介面擁有比Collecton介面更多的操作方法,
了解List介面之后,那么該如何使用該介面呢?需要找到此介面的實作類,常用的實作類有下面這幾個:
ArrayList、Vector、LinkedList;其中我們使用頻率最高的,就是ArrayList了,其次是Vector,最后是LinkedList啦,
ArrayList類
也就是“動態陣列”,可以自動擴容,
ArrayList類是List的子類,底層實作是陣列(查詢速度快, 增刪元素慢),
此類的定義如下:
public class ArrayList<E> extends AbstractList<E> implements List<E>,RandomAccess,Cloneable,Serializable
此類繼承了 AbstractList 類,AbstractList 是 List 介面的子類,AbstractList 是個抽象類,且實作了 RandomAccess 介面,因此支持隨機訪問,這是理所當然的,因為 ArrayList 是基于陣列實作的,
例子:增加及取得元素
import java.util.ArrayList;
public class Demo1 {
public static void main(String[] args) {
ArrayList<String> data = new ArrayList<>();//實體化ArrayList物件
data.add("Hello");//添加內容,此方法是從Collection介面繼承而來
data.add(0,"Lamp");//添加內容,此方法是List介面單獨定義的
data.add("World");
System.out.println(data);//列印data物件呼叫toString()方法
}
}
輸出
[Lamp, Hello, World]
以上的操作向集合中增加了三個元素,其中在指定位置增加的操作是List介面單獨定義的,隨后進行輸出的時候,實際上呼叫的是toString()方法完成輸出的,
我們可以發現,此時的物件陣列并沒有長度的限制,長度可以任意長,只要記憶體夠大,
例子:洗掉、回圈列印操作
- 使用remove()方法洗掉若干個元素,并且使用回圈的方式輸出,
- 根據指定位置取得內容的方法,只有List介面才有定義,其他任何介面都沒有任何定義
import java.util.ArrayList;
public class Demo1 {
public static void main(String[] args) {
ArrayList<String> data = new ArrayList<>();//實體化ArrayList物件
data.add("Hello");//添加內容,此方法是從Collection介面繼承而來
data.add(0,"Lamp");//添加內容,此方法是List介面單獨定義的
data.add("World");
data.remove(1);//根據索引洗掉元素,此方法是List介面單獨定義的,
data.remove("World");//洗掉指定物件,
for(int i =0;i<data.size();i++){ //size()方法是從Collection介面繼承而來
System.out.print(data.get(i)+" "); //get方法示List介面單獨定義的
}
}
}
輸出
Lamp
這里只需要對于洗掉的元素的操作有個基本了解,后續再深入理解,
Vector類
與ArrayList一樣,Vector本身也屬于List介面的子類,此類的定義如下:
public class Vector<E> extends AbstractList<E> implements List<E>,RandomAccess,Cloneable,Serializable
此類與ArrayList一樣,都是AbstractList的子類,所以,此時的操作只要是List介面的子類就都按照List進行操作
import java.util.List;
import java.util.Vector;
public class Demo1 {
public static void main(String[] args) {
List<String> all = new Vector<>();//多型,父類List參考指向子類Vector物件
all.add("hello");
all.add(0,"LAMP");
all.add("world");
all.remove(1);
all.remove("world");
System.out.println("集合中的內容是:");
for(int i = 0;i<all.size();i++){
System.out.print(all.get(i)+" ");
}
}
}
輸出
LAMP
以上的操作結果與使用ArrayList本身沒有任何區別,因為操作的時候是以介面為操作的標準,
但是Vector屬于java元老級的操作類,是最早的提供了動態物件陣列的操作類,在JDK1.0的時候就已經推出了此類的使用,只是后來在JDK1.2之后引入了java類集合框架,但是為了照顧很多已經習慣于使用Vector的用戶,所以在JDK1.2之后將Vector進行了升級,讓其多實作了一個List介面,這樣才將這個類繼續保留了下來,
Vector 類和 ArrayList 類的區別(筆試重點)
| No. | 區別點 | ArrayList | Vector |
| 1 | 時間 | 是新的類,是在JDK1.2之后推出的 | 是舊的類,是在JDK1.0的時候就定義了的 |
| 2 | 性能 | 性能較高,是采用了異步處理 | 性能較低,是采用了同步處理 |
| 3 | 輸出 | 支持 Iterator、ListIterator 輸出 | 除了支持 Iterator、ListIterator 輸出,還支持 Enumeration 輸出 |
Iterator(絕對重點)
俗稱迭代器,基本操作原理:是不斷判斷是否有下一個元素,如果有,則直接輸出,
此介面定義如下:
public interface Iterator<E>
要想使用此介面,則必須使用Collection介面,在Collection介面中規定了一個iterator()方法,可以用于Iterator介面進行實體化操作,
此介面定義了如下三個方法:
| No. | 方法名稱 | 型別 | 描述 |
| 1 | boolean hasNext() | 普通 | 判斷是否有下個元素 |
| 2 | E next() | 普通 | 取出內容 |
| 3 | void remove() | 普通 | 洗掉當前內容 |
通過Collection介面為其進行實體化之后,一定要記住,Iterator中的操作指標是在第一個元素之上的,當呼叫next()
方法的時候,選取當前指標指向的值并向下移動,使用hasNext()可以檢查序列中是否還有元素,

例:觀察Iterator輸出
import java.util.*;
public class Demo1 {
public static void main(String[] args) {
Collection<String> l = new ArrayList<>();//創建一個動態陣列物件
l.add("A");
l.add("B");
l.add("C");
l.add("D");
l.add("E");
Iterator<String> iter = l.iterator();//創建一個迭代器物件
while(iter.hasNext()){ //判斷是否有下一個元素
String str = iter.next(); //取出當前元素
System.out.println(str);
}
}
}
輸出
A
B
C
D
E
以上的操作是Iterator介面使用最多的形式,也是一個標準的輸出形式,
但是在使用Iterator輸出的時候有一點必須注意,在進行迭代輸出的時候如果想要洗掉當前元素,則只能使用Iterator介面中的remove()方法,而不能使用集合中的remove()方法,否則將出現未知的錯誤,
import java.util.*;
public class Demo1 {
public static void main(String[] args) {
Collection<String> l = new ArrayList<>();
l.add("A");
l.add("B");
l.add("C");
l.add("D");
l.add("E");
Iterator<String> iter = l.iterator();
while(iter.hasNext()){
String str = iter.next();
if(str == "C"){
l.remove(str);//這是錯誤的,呼叫了集合中的洗掉方法
}
else{
System.out.println(str + "、");
}
}
}
}
程式出現了例外,因為原本的要輸出的集合的內容被破壞了,
import java.util.*;
public class Demo1 {
public static void main(String[] args) {
Collection<String> l = new ArrayList<>();
l.add("A");
l.add("B");
l.add("C");
l.add("D");
l.add("E");
Iterator<String> iter = l.iterator();
while(iter.hasNext()){
String str = iter.next();
if(str == "C"){
iter.remove();
}
else{
System.out.println(str);
}
}
}
}
輸出
A
B
D
E
這才是正確的方式
但是,從實際開發的角度來看,元素的洗掉操作出現的幾率是很小的,基本上可以忽略,即:集合中很少有洗掉元素的操作,
Iterator介面本身可以完成輸出的功能,但是此介面只能進行由前向后的單向輸出,如果要想進行雙向輸出,則必須使用其子介面——ListIterator,
ListIterator(理解)
ListIterator是可以進行雙向輸出的迭代介面,此介面定義如下:
public interface ListIterator<E> extends Iterator<E>
此介面是Iterator的子介面,此介面中定義了以下的操作方法:
| No. | 方法名稱 | 型別 | 描述 |
| 1 | void add(E e) | 普通 | 增加元素 |
| 2 | boolean hasPrevious() | 普通 | 判斷是否有前一個元素 |
| 3 | E previous() | 普通 | 取出前一個元素 |
| 4 | void set(E e) | 普通 | 修改元素的內容 |
| 5 | int previousIndex() | 普通 | 前一個索引的位置 |
| 6 | int nextIndex() | 普通 | 后一個索引的位置 |
但是如果想要使用ListIterator介面,則必須依靠List介面進行實體化,
List介面中定義了以下方法:ListIterator<E> listIterator()
例:使用ListIterator輸出:
import java.util.*;
public class Demo1 {
public static void main(String[] args) {
List<Integer> l = new ArrayList<>(); //創建動態陣列物件,并指定存入整形資料
l.add(1); //添加資料
l.add(2);
l.add(3);
l.add(4);
l.add(5);
ListIterator<Integer> iter =l.listIterator();創建迭代器
System.out.println("從前向后輸出");
while(iter.hasNext()){ //判斷下一位有無元素
Integer a = iter.next();
System.out.print(a + "、");
}
//根據迭代器模型圖,此時指標以及移動到存放元素“5”之后的那個位置,
System.out.println();
System.out.println("從后向前輸出");
while(iter.hasPrevious()){ //判斷上一位有無元素
Integer b = iter.previous();
System.out.print(b + "、");
}
}
}
輸出
從前向后輸出
1、2、3、4、5、
從后向前輸出
5、4、3、2、1、
但是,此處有一點需要注意:如果想要進行由后向前的輸出,則必須要先將指標移到最后一個資料之后的那個位置,
此介面一般使用較少,
增強版for回圈——forEach
用于迭代陣列或集合(Collection以下的)這種資料結構,
語法:
for(資料型別 變數名:陣列或集合名稱){}
示例
import java.util.*;
public class Demo1 {
public static void main(String[] args) {
List<String> l = new ArrayList<>();
l.add("床前明月光");
l.add("疑似地上霜");
l.add("舉頭望明月");
l.add("低頭思故鄉");
for(String s:l) {
System.out.println(s);
}
}
}
輸出
床前明月光
疑似地上霜
舉頭望明月
低頭思故鄉
Set介面(重點)
Set介面也是Collection的子介面,與List介面最大的不同在于,Set介面里面的內容是不允許重復的,
Set介面并沒有對Collection介面進行擴充,基本上還是與Collection介面保持一致,因為此介面沒有List介面中定義的get(int index)方法,所以無法使用回圈進行輸出,
那么在此介面中有兩個常用的子類:HashSet、TreeSet
散列存放:HashSet(重點)
既然Set介面并沒有擴充任何的Collection介面中的內容,所以實用的方法全部都是Collection介面定義而來的,
HashSet屬于散列的存放類集,里面的內容是無序存放的,
此類實作Set介面,由哈希表(實際上是HashMap實體)支持,
范例:觀察HashSet
import java.util.*;
public class Demo1 {
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
set.add("鋤禾日當午");
set.add("汗滴禾下土");
set.add("誰知盤中餐");
set.add("粒粒皆辛苦");
set.add("粒粒皆辛苦");
//轉為陣列進行輸出
String[] str = set.toArray(new String[] {});
for(int i = 0;i< str.length;i++){
System.out.println(str[i]+" ");
}
//直接輸出
System.out.println(set);
//foreach輸出
for (String s:set) {
System.out.println(s);
}
}
}
輸出
汗滴禾下土
誰知盤中餐
鋤禾日當午
粒粒皆辛苦
[汗滴禾下土, 誰知盤中餐, 鋤禾日當午, 粒粒皆辛苦]
汗滴禾下土
誰知盤中餐
鋤禾日當午
粒粒皆辛苦
實體化的HashSet物件,本身屬于無序存放,而且是不能有重復內容的,所以在輸出的時候,重復存入的內容,只輸出一次,而且也并不是按我們存入的順序輸出的,具體是怎么輸出的,以后會有講解,
排序的子類:TreeSet(重點)
內部原理是二叉樹,與HashSet不同的是,TreeSet本身屬于排序的子類,此類的定義如下
public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>,Cloneable,Serializable
下面通過代碼來觀察其是如何進行排序的
例:TreeSet排序觀察
import java.util.*;
public class Demo1 {
public static void main(String[] args) {
TreeSet<String> set = new TreeSet<>();
set.add("A");
set.add("C");
set.add("D");
set.add("B");
for (String s:set) {
System.out.println(s);
}
}
}
輸出
A
B
C
D
雖然在增加元素的時候屬于無序操作,但是增加之后卻可以為用戶進行排序功能的實作,它不是根據你輸入的順序進行排序的,而是根據阿斯科碼的大小進行排序的,
排序的說明(重點)
既然Set介面的TreeSet類本身是允許排序,那么現在自定義一個類是否可以進行物件的排序呢?
例:定義Preson類
import java.util.*;
public class Demo1 {
public static void main(String[] args) {
TreeSet<Preson> set = new TreeSet<>();
Preson p1 = new Preson("張三",11);
Preson p2 = new Preson("李四",12);
Preson p3 = new Preson("王五",10);
set.add(p1);
set.add(p2);
set.add(p3);
for (Preson p:set) {
System.out.println(p);
}
}
public static class Preson{
private String name;
private int age;
public Preson(String name, int age) {
this.name = name;
this.age = age;
}
public Preson() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Preson{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
}
執行以上的操作代碼之后,發現出現了如下的錯誤提示:
Exception in thread "main" java.lang.ClassCastException: class com.practice.demo3.Demo1$Preson cannot be cast to class java.lang.Comparable (com.practice.demo3.Demo1$Preson is in unnamed module of loader 'app'; java.lang.Comparable is in module java.base of loader 'bootstrap')
at java.base/java.util.TreeMap.compare(TreeMap.java:1291)
at java.base/java.util.TreeMap.put(TreeMap.java:536)
at java.base/java.util.TreeSet.add(TreeSet.java:255)
at com.practice.demo3.Demo1.main(Demo1.java:10)
此時的提示是:Preson類不能向Comparable介面轉型的問題
所以,證明:如果現在要是想進行排序的話,則必須在Person類中實作Comparable介面,
import java.util.*;
public class Demo1 {
public static void main(String[] args) {
TreeSet<Preson> set = new TreeSet<>();
Preson p1 = new Preson("張三",11);
Preson p2 = new Preson("李四",12);
Preson p3 = new Preson("王五",10);
Preson p4 = new Preson("錢六",10);
set.add(p1);
set.add(p2);
set.add(p3);
set.add(p4);
for (Preson p:set) {
System.out.println(p);
}
}
public static class Preson implements Comparable<Preson>{
private String name;
private int age;
//實作Comparable介面的compareTo抽象方法
//this大回傳正整數,this小回傳負整數,相等回傳0
@Override
public int compareTo(Preson o) {
if(this.age > o.age){
return 1;
}else if(this.age < o.age){
return -1;
}else{
return 0;
}
}
public Preson(String name, int age) {
this.name = name;
this.age = age;
}
public Preson() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Preson{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
}
輸出
Preson{name='王五', age=10}
Preson{name='張三', age=11}
Preson{name='李四', age=12}
此次代碼運行成功,如上所示,compareTo方法中規定了按年齡大小從小到大排序(回傳值小于0排在前,大于0排在后),又發現錢六沒有了,因為錢六的年齡和王五的年齡是一樣的,在呼叫comapreTo的時候,回傳值是0,則會被程式認為是同一個物件,此時必須修改Preson類,如果年齡相等的話,按字串進行排序,
public int compareTo(Preson o) {
if(this.age > o.age){
return 1;
}else if(this.age < o.age){
return -1;
}else{
return this.name.compareTo(o.name);//String類也重寫了compareTo方法,呼叫String類的compareTo方法進行比較
}
}
此時再運行程式,發現錢六出現了,如果加入的是同一個人的資訊的話,會被認為是重復元素,無法繼續加入,
Preson{name='王五', age=10}
Preson{name='錢六', age=10}
Preson{name='張三', age=11}
Preson{name='李四', age=12}
關于重復元素的說明(重點)
之前使用Comparable完成的對于重復元素的判斷,那么Set介面定義的時候本身就是不允許重復元素的,那么證明如果現在真的是有重復元素的話,使用HashSet也同樣可以進行區分,
import java.util.*;
public class Demo1 {
public static void main(String[] args) {
HashSet<Preson> set = new HashSet<>();
Preson p1 = new Preson("張三",18);
Preson p2 = new Preson("李四",12);
Preson p3 = new Preson("王五",10);
Preson p4 = new Preson("錢六",10);
Preson p5 = new Preson("錢六",10);
set.add(p1);
set.add(p2);
set.add(p3);
set.add(p4);
set.add(p5);
for (Preson p:set) {
System.out.println(p);
}
}
public static class Preson{
private String name;
private int age;
public Preson(String name, int age) {
this.name = name;
this.age = age;
}
public Preson() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Preson{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
}
輸出
Preson{name='張三', age=18}
Preson{name='錢六', age=10}
Preson{name='錢六', age=10}
Preson{name='王五', age=10}
Preson{name='李四', age=12}
此時發現,并沒有去掉所謂的重復元素,也就是說之前的操作并不是真正意義上的重復元素判斷,而是通過Comparable介面間接完成的,
如果想要判斷兩個物件是否相等,則必須使用Object類的equals()方法,
從最正規的來講,如果要想判斷兩個物件是否相等,則有兩種方法可以完成:
- 第一種判斷兩個物件的編碼是否一致,這個方法需要通過 hashCode()完成,即:每個物件有唯一的編碼
- 還需要進一步驗證物件中的每個屬性是否相等,需要通過 equals()完成,
此時需要重寫Object中的hashCode方法,此方法表示一個唯一的編碼,一般是通過公示計算出來的,
import java.util.*;
public class Demo1 {
public static void main(String[] args) {
HashSet<Preson> set = new HashSet<>();
Preson p1 = new Preson("張三",18);
Preson p2 = new Preson("李四",12);
Preson p3 = new Preson("王五",10);
Preson p4 = new Preson("錢六",10);
Preson p5 = new Preson("錢六",10);
set.add(p1);
set.add(p2);
set.add(p3);
set.add(p4);
set.add(p5);
for (Preson p:set) {
System.out.println(p);
}
}
public static class Preson{
private String name;
private int age;
public Preson(String name, int age) {
this.name = name;
this.age = age;
}
public Preson() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Preson preson = (Preson) o;
return age == preson.age && Objects.equals(name, preson.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Preson{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
}
此時運行程式發現,已經沒有重復元素了,所以想要去掉重復元素的話,需要依靠 hashCode()和 equals()方法共同完成,
小結
關于TreeSet的排序實作,如果是集合中物件是自定義的或者說其他系統定義的類沒有實作Comparable介面,則不能實作TreeSet的排序,會報錯,換句話說要添加到TreeSet集合中的物件的型別必須實作了Comparable介面,
不過TreeSet的集合因為借用了Comparable介面,同時可以去除重復值,而HashSet雖然是Set類的子介面,但是對于沒有重寫沒有復寫 Object 的 equals 和 hashCode 方法的物件,加入了 HashSet 集合中也是不能去掉重復值的,
Map介面(重點)
以上的Collection中,每次操作的都是一個物件,如果現在假設要操作一對物件,則就必須使用Map了,類似于以下一種情況
- 張三 123456
- 李四 234567
那么保存以上的資訊的時候使用Collection就不那么方便,所以要使用Map介面,里面的所有內容都按照key->value的形式保存,也稱為二元偶物件,Map集合的鍵Key不可重復,
此介面定義如下:
public interface Map<E,V>
此介面與Collection介面沒有任何關系,是第二大的集合操作介面,此介面常用方法如下:
| No. | 方法名稱 | 型別 | 描述 |
|---|---|---|---|
| 1 | void clear() | 普通 | 清空Map集合中的內容 |
| 2 | boolean containsKey(Obejct key) | 普通 | 判斷集合中是否存在指定的key |
| 3 | boolean containsValue(Objec tvalue) | 普通 | 判斷集合中是否存在指定的 value |
| 4 | Set<Map.Entry<K,V>> entrySet() | 普通 | 將Map介面變為Set集合 |
| 5 | V get(Object key) | 普通 | 根據key找到其對應的value |
| 6 | boolean isEmpty() | 普通 | 判斷是否為空 |
| 7 | Set<K> keySet() | 普通 | 將全部的key變為Set集合 |
| 8 | Collection<V> values() | 普通 | 將全部的value變為Collection集合 |
| 9 | V put(K key,V value) | 普通 | 向集合中添加元素 |
| 10 | void putAll(Map<? extends K,? extends V> m) | 普通 | 增加一組集合 |
| 11 | V remove(Object key) | 普通 | 根據key洗掉內容 |
Map本身是一個介面,所以一般會使用以下的幾個子類:HashMap、TreeMap、Hashtable
HashMap(重點)
HashMap是Map的子類,此類的定義如下:
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>,Cloneable,Serializable
此類繼承了AbstractMap類,同時可以被克隆,可以被序列化下來,
例:向HashMap內添加,洗掉,輸出內容
import java.util.*;
public class Demo1 {
public static void main(String[] args) {
HashMap<String,String> date = new HashMap<>();
date.put("key1","鋤禾日當午");
date.put("key2","汗滴禾下土");
date.put("key3","誰知盤中餐");
Set<String> set = date.keySet(); //將key值存入一個Set集合
for (String s:set) { //根據key獲得value并回圈輸出value
System.out.print(s+"->");
System.out.println(date.get(s));
}
date.remove("key1"); //洗掉key1
Collection<String> values = date.values(); //將value存入一個Collection集合
for (String s:values) { //回圈輸出value
System.out.println(s);
}
}
}
輸出
key1->鋤禾日當午
key2->汗滴禾下土
key3->誰知盤中餐
汗滴禾下土
誰知盤中餐
以上是Map介面在開發中最基本的操作程序,根據指定的key找到value,如果沒有找到,回傳null,找到了就回傳具體的內人,然后通過foreach輸出,foreach的原理就是利用迭代器Iterator,HashMap本身屬于無序存放的,
Hashtable(重點)
Hashtable是一個最早的key->value的操作類,本身是在JDK1.0的時候推出的,其基本操作與HashMap是類似的,
import java.util.*;
public class Demo1 {
public static void main(String[] args) {
Hashtable<String,String> date = new Hashtable<>();
date.put("key1","鋤禾日當午");
date.put("key2","汗滴禾下土");
date.put("key3","誰知盤中餐");
Set<String> set = date.keySet(); //將key值存入一個Set集合
for (String s:set) { //根據key獲得value并回圈輸出value
System.out.print(s+"->");
System.out.println(date.get(s));
}
date.remove("key1"); //洗掉key1
Collection<String> values = date.values(); //將value存入一個Collection集合
for (String s:values) { //回圈輸出value
System.out.println(s);
}
}
}
輸出
key3->誰知盤中餐
key2->汗滴禾下土
key1->鋤禾日當午
誰知盤中餐
汗滴禾下土
我們會發現它的輸出順序和HashMap是有所不同的,那是因為它們內部計算方式的不同,但是操作方式上幾乎沒有什么不同,因為本身就是以Map為操作標準的,但是Hashtable中是不能向集合中插入null值的,
HashMap 與 Hashtable 的區別(重點)
在整個集合中除了 ArrayList 和 Vector 的區別之外,另外一個最重要的區別就是 HashMap 與 Hashtable 的區別,
| No. | 區別點 | HashMap | Hashtable |
|---|---|---|---|
| 1 | 推出時間 | JDK1.2之后推出的,新的操作類 | JDK1.0時推出的,舊的操作類 |
| 2 | 性能 | 執行緒不安全的異步處理,性能較高 | 執行緒安全的同步處理,性能較低 |
| 3 | null | 允許設定為null | 不允許設定,否則將出現空指標例外 |
Map集合存盤自定義物件問題
import java.util.*;
public class Demo1 {
public static void main(String[] args) {
HashMap<Book,String > date = new HashMap<>();
Book book1 = new Book("金蘋果","介紹金蘋果");
Book book2 = new Book("銀蘋果","介紹銀蘋果");
date.put(book1,"我們人生的第一本書");
date.put(book2,"我們人生的第二本書");
book1.setName("銅蘋果");
/*book1的name值發生改變,那么該物件的哈希值就發生了改變
改變了之后,再根據這個改變了的key去找value的時候是找不到的,會回傳null,
那么假設這個哈希表已經存到加載因子決定的臨界位置
那么更改name值之后哈希表會發生散列,擴容并重新存盤資料
散列后哈希表會根據新的book1的新哈希值將key和value存放到新的位置
注意這個時候舊的book1存盤的value依然存在,位置也沒變,只是沒有key指向它了*/
System.out.println(date.get(book1));
/*這個時候呼叫get,是找不到資料的
因為get是呼叫key去找對應的value
此時key指定的位置發生了改變,已經不再指向之前的位置了
而是新的位置,但是新的位置,并沒有存入value,所以找到的是null*/
Book book3 = new Book("金蘋果","介紹金蘋果");
/*再看這一步,新建一個物件
它的哈希值和之前的book1一樣,我們會心想那這樣,應該能找到最初的book1對應的value了吧*/
System.out.println(date.get(book3));
/*輸出仍為null,首先,哈希值一樣了沒錯,
它也找到了"我們人生的第一本書"這個值,但是這個時候哈希表還會進行equals比對
金蘋果和銅蘋果不同,所以equals比對失敗,依然找不到資料*/
}
static class Book{
private String name;
private String info;
public Book(String name, String info) {
this.name = name;
this.info = info;
}
public Book() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Book book = (Book) o;
return Objects.equals(name, book.name) && Objects.equals(info, book.info);
}
@Override
public int hashCode() {
return Objects.hash(name, info);
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", info='" + info + '\'' +
'}';
}
}
}
輸出
null
null
我們會發現,輸出全是null,找不到資料了,我們不要隨便更改key的物件的值,特別是key是自定義物件的時候,因為這樣很容易造成哈希值錯亂,而導致找不到資料,那么如果這個自定義物件它就是會變,那怎么辦?解決辦法很簡單,不要將這個物件存盤到key的位置就可以了,
此文僅作為本人的學習筆記,如有錯誤,還請個位指正,非常感謝!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/262546.html
標籤:java
下一篇:Java面向物件(知識點整理)
