Java集合05 11.HashSet課堂練習 11.1課堂練習1 定義一個Employee類,該類包括:private成員屬性name,age 要求: 創建3個Employee對象放入HashSet中 當name和age的值相同時,認為是相同員工,不能添加到HashSet集合中 思路:不同對象的哈 ...
Java集合05
11.HashSet課堂練習
11.1課堂練習1
定義一個Employee類,該類包括:private成員屬性name,age
要求:
- 創建3個Employee對象放入HashSet中
- 當name和age的值相同時,認為是相同員工,不能添加到HashSet集合中
思路:不同對象的哈希值一般會不一樣,導致在添加對象時可能會在table數組的不同位置添加,因此想要比較對象的屬性值,就要重寫hashCode方法,使具有相同屬性的對象具有一樣的hash值,這樣才能在插入時比較對象的值;但不同的對象也可能具有相同的hash值,所以要重寫equals方法來比較對象屬性值
如下圖:在add()方法最終調用的putVal()方法中可以看出,如果插入的新元素的哈希值相同 且 值也相同 就不加入
例子:
package li.collections.set.hashset;
import java.util.HashSet;
import java.util.Objects;
@SuppressWarnings("all")
public class HashSetPractice {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add(new Employee("jack", 18));
hashSet.add(new Employee("smith", 18));
hashSet.add(new Employee("jack", 18));
System.out.println(hashSet);//[Employee{name='smith', age=18.0}, Employee{name='jack', age=18.0}]
}
}
class Employee {
private String name;
private double age;
public Employee(String name, double age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getAge() {
return age;
}
public void setAge(double age) {
this.age = age;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return Double.compare(employee.age, age) == 0 && Objects.equals(name, employee.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
快捷鍵:alt+insert選擇equals()and hashCode()快速重寫
如果name和age的值相同,在使用equals時,返回true
如果name和age的值相同,在計算hashCode的時候返回相同的hash值
11.2課堂練習2
定義一個Employee類,該類包含:private成員屬性name,sal,birthday,其中birthday為MyDate類型,屬性包括year,month,day
要求:
- 創建3個Employee對象放入到HashSet中
- 當name和birthday的值相同時,熱衛視相同員工,不能添加到HashSet集合中
思路:和練習1思路一致,不同的是MyDate類也要重寫equals()和hashCode()方法
練習:
package li.collections.set.hashset;
import java.util.HashSet;
import java.util.Objects;
@SuppressWarnings("all")
public class HashSetPractice2 {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add(new Employee("jack",8000,new MyDate(1997,12,23)));
hashSet.add(new Employee("jack",8000,new MyDate(1997,12,23)));
hashSet.add(new Employee("jack",8000,new MyDate(1997,12,23)));
hashSet.add(new Employee("jack",8000,new MyDate(1997,12,23)));
System.out.println(hashSet);//[Employee{name='jack', sal=8000.0, birthday=MyDate{year=1997, month=12, day=23}}]
}
}
class Employee {
private String name;
private double sal;
private MyDate birthday;
public Employee(String name, double sal, MyDate birthday) {
this.name = name;
this.sal = sal;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
public MyDate getBirthday() {
return birthday;
}
public void setBirthday(MyDate birthday) {
this.birthday = birthday;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return Double.compare(employee.sal, sal) == 0 && Objects.equals(name, employee.name) && Objects.equals(birthday, employee.birthday);
}
@Override
public int hashCode() {
return Objects.hash(name, sal, birthday);
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", sal=" + sal +
", birthday=" + birthday +
'}';
}
}
class MyDate {
private int year;
private int month;
private int day;
public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyDate myDate = (MyDate) o;
return year == myDate.year && month == myDate.month && day == myDate.day;
}
@Override
public int hashCode() {
return Objects.hash(year, month, day);
}
@Override
public String toString() {
return "MyDate{" +
"year=" + year +
", month=" + month +
", day=" + day +
'}';
}
}
12.LinkedHashSet
12.1LinkedHashSet底層
- LinkedHashSet是HashSet的子類
- LinkedHashSet底層是一個LinkedHashMap(LinkedHashMap是HashMap的子類),底層維護了一個 數組+雙向鏈表
- LinkedHashSet根據元素的hashCode值來決定元素的存儲位置,同時使用鏈表維護元素的次序,這是元素看起來是以插入順序保存的
- LinkedHashSet不允許重覆元素
說明:
1)在LinkedHashSet中維護了一個hash表和雙向鏈表(LinkedHashSet中有head和tail)
2)每一個節點都有前後指針(before和after屬性),形成雙向鏈表
3)在添加一個元素時,先求hash值,再求索引。確定該元素在table的位置然後將添加的元素加入到雙向鏈表(如果已經村存在,就不添加,原理和HashSet一樣)
tail.next = newElement;//將新添加的節點連接至尾節點的後面
newElement.pre = tail;//將尾節點設為新結點的前驅結點
tail = newElement;//將新節點設為尾節點
4)這樣,遍歷LinkedHashSet也能確保插入順序和遍歷順序一致
例子1:LinkedHashSet底層分析
package li.collections.set.hashset;
import java.util.LinkedHashSet;
import java.util.Set;
@SuppressWarnings("all")
public class LinkedHashSetSource {
public static void main(String[] args) {
Set set = new LinkedHashSet();
set.add(new String("AA"));
set.add(456);
set.add(456);
set.add(new Customer("劉", 1001));
set.add(123);
set.add("jack");
System.out.println(set);
}
}
class Customer {
private String name;
private int number;
public Customer(String name, int number) {
this.name = name;
this.number = number;
}
@Override
public String toString() {
return "Customer{" +
"name='" + name + '\'' +
", number=" + number +
'}';
}
}
如下圖,LinkedHashSet不允許重覆值,且插入順序和取出順序一致
在Set set = new LinkedHashSet();
出打上斷點調試:
如下圖:可以看到,LinkedHashSet底層是一個LinkedHashMap
如下圖所示:點擊map展開,Step Over之後可以看到第一次添加數據時,直接將數組table擴容到16(數組下標從零開始),存放的節點類型是LinkedHashMap$Entry
數組是HashMap$Node[ ]
存放的元素/數據是LinkedHashMap$Entry類型(LinkedHashMap的內部類Entry繼承了HashMap的內部類Node)
如下圖:繼續添加數據,可以看到在索引為8 的位置添加了新的數據456,點開索引為0 的節點,可以看到這時原來節點的after指向了新的節點,並且新結點的before指向了原來的節點,形成了雙向鏈表
如下圖所示:在add()底層仍然是和HashSet調用了相同的方法,詳情見10.3HashSet源碼詳解
以此類推,形成一條雙向鏈表:
12.1.LinkedHashSet練習
有一個Car類,存在兩個私有屬性name和price
要求:如果兩個Car對象的name和price一樣,就認為是相同元素,不能添加
練習:
package li.collections.set.hashset;
import java.util.LinkedHashSet;
import java.util.Objects;
@SuppressWarnings("all")
public class LinkedHashSetPractice {
public static void main(String[] args) {
LinkedHashSet linkedHashSet = new LinkedHashSet();
linkedHashSet.add(new Car("奧拓",100_000));
linkedHashSet.add(new Car("保時捷",990_000));
linkedHashSet.add(new Car("法拉利",3100_000));
linkedHashSet.add(new Car("特斯拉",100_000));
linkedHashSet.add(new Car("特斯拉",100_000));
for (Object o:linkedHashSet) {
System.out.println(o);
}
}
}
class Car{
private String name;
private double price;
public Car(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Car car = (Car) o;
return Double.compare(car.price, price) == 0 && Objects.equals(name, car.name);
}
@Override
public int hashCode() {
return Objects.hash(name, price);
}
}
在沒有重寫hashCode()和equals()之前不同的對象實例的hash值一般是不一樣的,因此可以插入name和price相同的對象數據。重寫之後可以看到相同屬性的對象無法插入了。具體解題思路和11HashSet的課堂練習一致。
13.Map介面
13.1Map介面特點
Map介面實現類的特點:( JDK8的Map介面特點 )
- Map和Collection併列存在。Map用於保存具有映射關係的數據:key-value(雙列元素)
- Map中的key和value可以是任何引用類的數據,會封裝到HashMap$Node對象中
- Map中的key不允許重覆(key值不允許重覆,重覆的話就會用新的值替換/覆蓋舊的值e = p--->詳細原因和HashSet一樣,詳見10.1-10.3)
- Map中的value允許重覆(hash值取決於key)
- Map中的key可以為null,value也可以為null(註意key的null最多只能有一個,value的null可以有多個)
- 常用String類作為Map的key
- key和value之間存在單向一對一關係,即通過指定的key總能找到對應的value
例子:map的簡單使用
package li.map;
import java.util.HashMap;
import java.util.Map;
public class MapIntroduce {
@SuppressWarnings("all")
public static void main(String[] args) {
Map map = new HashMap();
map.put("no1","北京");//k-v
map.put("no4","深圳");//k-v
map.put("no4","長沙");//no4=長沙-->key值不允許重覆,重覆的話就會用新的值替換/覆蓋舊的值 e=p
map.put("no5","北京");//value可以重覆,hash值取決於key
map.put(null,null);
map.put(null,"abc");//key不能重覆,因此這裡的值會將上一行的值覆蓋 null=abc
map.put("abc",null);//value可以重覆
map.put(new Object(),123);
//無序的:原因是底層是按計算的hash值來存放
//{null=abc, no1=北京, no4=長沙, abc=null, no5=北京, java.lang.Object@1b6d3586=123}
System.out.println(map);
//通過指定的key總能找到對應的value
System.out.println(map.get(null));//abc
System.out.println(map.get("no1"));
}
}