contains方法底层逻辑依集合类型而异:ArrayList遍历调用equals();HashSet先算hashCode()定位桶再equals();TreeSet用compareTo()或Comparator二分查找;自定义类须同步重写equals和hashCode且逻辑自洽。

contains 方法不是简单地“比一下就完事”,它的底层逻辑取决于集合类型,核心是“先定位,再确认”——但不同集合的“定位策略”完全不同。
ArrayList:老老实实一个个比
它没有索引结构,只能从头到尾遍历:
- 如果传入的是 null,就用
==判断集合里哪个元素是null - 如果传入的是非 null 对象,就调用该对象的
equals()方法,逐个和集合中每个元素比较 - 哪怕两个对象内容完全一样,但没重写
equals(),默认用的是Object.equals()(也就是==),结果一定是 false
HashSet:先算桶号,再桶里细查
它靠哈希表加速,分两步走:
- 先调用目标对象的
hashCode(),算出它“应该在哪个桶里” - 只去那个桶里,用
equals()逐个比——不是全集合遍历 - 如果
hashCode()写错了(比如用了可变字段、没和equals()保持一致),对象可能存进去了,但查的时候算错桶号,根本不会触发equals(),导致“明明有却查不到”
TreeSet:不看哈希,只看大小关系
它基于红黑树,不依赖 hashCode(),但要求可排序:
- 用
compareTo()或构造时传入的Comparator来比较大小 - 查找过程类似二分搜索,时间复杂度是
O(log n) - 如果元素是 null,而比较器又没处理
null,直接抛NullPointerException
自定义类必须同步重写 equals 和 hashCode
这是最容易出错的地方:
- 只重写
equals()不够——HashSet根本不会走到这一步 - 只重写
hashCode()也不行——equals()逻辑不对,照样匹配失败 - 二者必须逻辑自洽:如果 equals 返回 true,hashCode 必须相等;但 hashCode 相等,equals 不一定为 true
- 推荐用 IDE 自动生成,但要检查字段是否全覆盖、是否含可变状态(比如对象内部 list 改了,但 hashCode 没更新)

















