Object类和集合

1.了解Object类的部分方法

Object大概属于那种“熟悉的陌生类”。虽然我们日常使用的所有类都默认继承它(毕老师说就像上帝一样的存在),但是我们却“看不到”它,平时用到它的几率更是少之又少。由于集合中的几个重要知识点,比如重写hashCode()和equals()方法,比如打印对象时默认调用的toString()方法,又比如我们上一篇提到的反射可能用到的getClass()方法,这些方法都是继承自父类——Object类。所以我们有必要走进它,了解它。

推荐大家打开IDE,自己点进去看一下。没那么神秘。这里除了equals()、hashCode()、toString(),其他方法下面不做展开

看了上面的部分源码分析后会发现,我们最关心的三个方法:equals(),hashCode(),toString()中,hashCode()是最关键也是最难的。

equals()方法最简单,底层就是用"=="比较两个对象的地址值。但我们说过,即使同一个类相同数据的两个对象,比如:

Student s1 = new Student("老黄", 26);
Student s2 = new Student("老黄", 26);

如果Student类不重写从Object继承来的equals()方法,那么s1.equals(s2)结果就是false。因为前后两次new对象,都在堆内存中开辟了空间,地址值肯定不同。这样的比较是没有任何意义的。因为实际编程中,我们倾向于把内容相同、而不是对象地址值相同的对象认定为重复。比如我已经存了一个new Person("周杰伦", 39),下一个new Person("周杰伦", 39)就不能再存进来。

 

2.集合

集合体系
Collection
    |--List
        |--ArrayList
        |--LinkedList
        |--Vector
    |--Set
        |--HashSet
        |--TreeSet
Map
    |--HashMap
    |--TreeMap

Collection:

Map:

ArrayList去重原理

我们说过,ArrayList底层是可以自动扩容的数组。它的add()方法本身不具备判断该元素是否唯一的功能。但我们可以设计一个策略,使得ArrayList也像Set一样的保证元素唯一性。这里提供其中一种方法:

首先准备两个容器,当我们把元素放入newArray时,通过contains()判断newArray(目的地容器)中是否已经有相同元素。如果newArray中已经有同内容的元素,则不放进去。最终newArray里的元素都是唯一的。那么contains()是根据什么判断两个元素相同的呢?这很重要。比如现实生活中,根据判断标准的不同,判断结果也会不同。双胞胎如果只看脸,可能会被判断为同一人,而如果根据身份证判断,就是不同人。所以我们有必要看一下ArrayList中contains()的源码。

Object类的equals()方法

如果String类直接继承Object的equals(),它的判断依据是对象地址值是否相同,而不是内容是否相同,是无法去重的。具体原因在上一篇已经分析过。所以它肯定复写了equals()。我们去看一下String类关于equals()的源码。

通过查看String类的源码,我们发现它确实复写了从Object继承的equals()方法。它对于两个字符串元素是否相同的判断依据是:是否同类型→是否同长度→是否同内容

ArrayList去重时,都会拿String和Student(自定义元素)举例子。结果是String类型的元素去重成功,而Student去重失败。根本原因在于,Student不同于String,它是我们自己写的。如果我们没有通过重写equals()方法来明确判断“元素是否相同”的规则,那么equals()默认用==判断,也就是判断对象地址值。而每一个对象,不论内容是否相同,都在堆中有自己的内存空间,地址值是不同的。所以即使同内容对象,也会被判断为不同元素,都可以加入容器,无法去重。

推荐重写equals()方法。

HashMap的put()源码浅析

这是JDK1.7的源码,现在JDK1.8改写了HashMap,变化较大。

HashSet/HashMap为什么要重写hashCode()和equals()

String类由于已经重写了hashCode()方法,所以没问题。我们应该考虑:如果自定义的对象不重写hashCode()方法,会有什么后果?首先,它将直接继承Object类的hashCode()方法。

本地方法的实现,和具体的对象内容是无关的。此时每个Person对象,比如p1.hashCode()和p2.hashCode()返回的值都是它们各自地址值的hash映射结果。由于不同对象地址值不同,所以hashCOde()返回值基本是不一样的。

这个会发生什么情况?

学习put()源码时,我们知道,HashMap会根据hash算出一个i,根据table[i]确定元素要插入的位置。而hash和hashCode()是正相关。如果hashCode()不一样,那么hash极大概率也是不同,也就是说,每个元素插进来的位置相同的概率很低。

你看到这种情况,心想,那好吧,你说重写hashCode()和equals()就重写吧。

@Override
public int hashCode(){
    return 0;
}

@Override
public int equals(){
    return ....省略(总之,根据内容判断)
}

这回,会出现什么结果呢?

哈希表正确的做法是,重写hashCode(),使不同内容的元素尽可能返回不同的值。既要避免频繁扩容,又要避免桶过深,比较次数陡增。

 

HashMap中有个叫load factor(加载因子)的东西。如果哈希表中的元素放得太满,就必须进行rehashing(再哈希)。再哈希使哈希表元数增倍,并将原有的对象重新导入新的哈希表元中,而原始的哈希表元被删除。load factor(加载因子)决定何时要对哈希表进行再哈希。在Java中,加载因子默认值为0.75。如果0.75理解起来困难,就举个不恰当的比喻:哈希表默认大小为16,如果加载因子是0.75,16*0.75=12。当12个槽被占满时,就会再哈希扩容。如果加载因子改为0.5呢?8个槽占满就再哈希。而再哈希是比较浪费资源的。

加载因子越大,填满的元素越多。好处是,空间利用率高了,但冲突的机会加大了。反之,加载因子越小,填满的元素越少,好处是冲突的机会减小了,但空间浪费多了。
冲突的机会越大,则查找的成本越高。反之,查找的成本越小。

因此,必须在 “冲突的机会”与“空间利用率”之间寻找一种平衡与折衷。 这种平衡与折衷本质上是数据结构中有名的“时-空”矛盾的平衡与折衷。

 

@转自知乎,https://zhuanlan.zhihu.com/p/36797326

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值