day22--Java集合05
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的课堂练习一致。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战