详述HashSet add()方法存储自定义类型对象执行过程(内容相同的同一自定义类型的不同对象的添加失败)
之前分析了HashSet中add方法的执行过程,本篇着重分析存储自定义类型的情况从而了解如何使内容相同的同一自定义类型的不同对象添加失败
因为是自定义类型,所以我们先在同一个包中构建一个自定义类和一个测试类
package com.ame; import java.util.HashSet; class Student{ String id; public Student(String id) { this.id=id; } } public class Test { public static void main(String[] args) { HashSet<Student>set=new HashSet<>(); Student stu=new Student("1"); set.add(stu); set.add(new Student("1")); set.add(new Student("1")); System.out.println(set.size()); } }
输出结果
3
重写了equals方法和HashCode方法后(自定义学生类中添加如下代码,添加时注意缩进)
@Override public boolean equals(Object obj) { Student stu=(Student)obj; return id.equals(stu.id); } @Override public int hashCode() { return id.hashCode(); }
输出结果
1
修改了equals方法和HashCode方法之前,根据上一篇博客的分析,相同自定义类的不同对象因为地址不同,可以实现存储;修改了equals方法和HashCode方法之后,相同自定义类的不同对象,内容相同,无法实现存储,equals比较的内容发生了变化。
仍然分析不为空的情况
else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
相同内容无法存储则必然实现了下述代码
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } }//end of else ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null;
也即满足了条件
p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))
上一篇博客中已经提到了hash方法
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
根据判断条件的前后顺序,我们可以看出hashCode()方法先执行,equals()后执行;
因为我们在自定义的Student类中重写了hashCode()方法,所以此处执行的是Student类中的hashCode()方法
@Override public int hashCode() { return id.hashCode(); }
又因为id是String类型,所以此时调用的实际上是String类中的hashCode()【如果没有在自定义类中重写,则执行Object类中的hashCode()】,因为是String类,内容相同则hash相同
比较了hash,然后比较key,key值来自下方代码
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
上一篇博客中我们已经分析过知道,key最终是tab[]数组中被存储进去的内容,因为add的是对象,两个不同的对象key是不一样的,key不相等,分析逻辑或后面的条件
key != null && key.equals(k)
因为Student类重写了equals方法,所以此处执行的是Student类中的equals方法,又因为是id调用,id是String类型,所以此时是String类型的equals方法,String类中的equals比较内容,所以条件满足,
此时
return oldValue;返回旧值,数组tab[]添加失败
@Override public boolean equals(Object obj) { Student stu=(Student)obj; return id.equals(stu.id); }
需要注意的是,重写的equals方法中发生了向下转型
Student stu=(Student)obj;
所以需要使用instanceof关键字,判断对象是否是是Student类创建的,条件之外注意添加返回值
@Override public boolean equals(Object obj) { if (obj instanceof Student) { Student stu = (Student) obj; return id.equals(stu.id); } return false; }
总结
HashSet中add()在添加自定义类型对象时,如果对象含参,此时调用自定义类型中的相应有参构造方法,然后会先后调用到hashCode()方法和equals()方法,如果变量都是是String类型或者都是基本数据类型的包装类创建的,则可以通过重写自定义类型中的hashCode()方法和equals()方法,分别使用对象的参数值调用,实现HashSet创建的集合中无法存储——>内容相同的同一自定义类型的不同对象
- C#中的类SqlCommand对象使用方法ExecuteNonQuery()调用SQLServer存储过程时,存储过程执行成功,数据发生改变,但是返回-1
- Java基础知识强化之集合框架笔记40:Set集合之HashSet存储自定义对象并遍历
- 使用HashSet和TreeSet存储多个商品信息,遍历并输出;其中商品属性:编号,名称,单价,出版社;要求向其中添加多个相同的商品,验证集合中元素的唯一性。 提示:向HashSet中添加自定义
- ***Redis hash是一个string类型的field和value的映射表.它的添加、删除操作都是O(1)(平均)。hash特别适合用于存储对象
- Java调用存储过程(返回:简单类型、自定义对象、列表数组)
- 《java入门第一季》之HashSet存储自定义对象问题以及注意事项
- 使用ArrayList集合,对其添加100个不同的元素: 1.使用add()方法将元素添加到ArrayList集合对象中; 2.调用集合的iterator()方法获得Iterator对象,并调用Ite
- java并发编程--Executor框架(一) 摘要: Eexecutor作为灵活且强大的异步执行框架,其支持多种不同类型的任务执行策略,提供了一种标准的方法将任务的提交过程和执行过程
- 基于C#中的类SqlCommand对象调用SQLServer存储过程时,存储过程执行成功,但是对象方法ExecuteNonQuery()返回-1
- 往TreeMap和TreeSet里添加自定义对象的两种比较方法
- 存储过程里出错:在将 nvarchar 值 'MAX' 转换成数据类型 smallint 时失败, 的终极解决方法
- Java基础知识强化之集合框架笔记41:Set集合之HashSet存储自定义对象并遍历练习
- 使用ArrayList集合,对其添加100个不同的元素: 1.使用add()方法将元素添加到ArrayList集合对象中; 2.调用集合的iterator()方法获得Iterator对象,并调用Ite
- 为表名不同但属性相同的多个表添加字段的mysql存储过程示例
- PHP调用存储过程失败(没有执行到)--mysql_error()--can't return a result set in the given context
- 自定义Cell访问不了自己的set方法,并且对象初始化产生的不是自定义的类型(cell.contact = self.contacts[indexPath.row])
- 《java入门第一季》之HashSet存储自定义对象问题以及注意事项
- Set集合中使用add方法添加对象
- 执行字符串SQL语句--带有参数的存储过程以及 int类型的字符串变量注意事项
- 关于存储过程事务,返回值,变量声明以及执行方法