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\"));
}
}
来源:https://www.cnblogs.com/liyuelian/p/16596482.html
本站部分图文来源于网络,如有侵权请联系删除。