第一,hashCode()方法是定义在Object类中的本地方法,子类如果不重写就会默认使用Object类中的hashCode()方法。在官方文档中规定了重写hashCode()方法或JVM虚拟机厂商实现hashCode()方法的规则:
- 在程序的一次执行过程中,两次调用某一对象的hashCode()方法的返回值是相等的。
- 两个通过调用equals()方法相等的对象,调用hashCode()方法返回值也相等。
第二,在Object类中hashCode()方法底层是调用C++来获得返回值,我看了一下OpenJDK8中C++的实现,hashCode()方法一共有5种具体的实现方式:
- 返回随机数
- 对对象地址做一些逻辑位运算
- 返回1
- 直接使用地址
- 根据线程状态返回结果
第三,在JDK的java.long包下的常用类有许多都重写了hashCode()方法,比如String调用h = 31 * h + val[i];来实现。
2、谈谈equals()方法第一,equals()方法是定义在Object类中的,子类如果不重写就会默认使用Object类中的equals()方法。在官方文档中规定了重写equals()方法的规则:
- 自反性
- 对称性
- 传递性
- 一致性
- x.equals(null) = false
- 两个对象是不同类,返回false
- 重写equals要判断hashCode方法需不需要重写,因为我们要满足equals返回true的两个对象hashCode方法返回值也是true。
第二,equals()方法在Object类中的默认是通过==来比较两个对象的地址是否相同来实现。
第三,在JDK的java.long包下的常用类有许多都重写了equals()方法,比如String通过先比较长度再逐个比较字符串来实现。
3、谈谈clone()方法第一,clone()方法是定义在Object类中的本地方法,调用clone方法的实例要实现Cloneable接口,子类如果不重写就会默认使用Object类中的equals()方法。在官方文档中规定了重写clone()方法的规则:
- x.clone() != x为true
- x.clone().getClass() == x.getClass()为true
- x.clone().equals(x)一般情况下为true
第二,在Object类中clone()方法默认实现是通过浅克隆的方式创建一个对象并将调用者对象的字段复制给新的对象并将新对象返回。
第三,如果想要实现深克隆,通常有两种方式:
- 先对对象进行序列化操作,然后反序列化出。
- 先调用super.clone()方法克隆出一个新对象来,然后调用子类的clone()方法手动set给克隆出来的非基本数据类型(引用类型)赋值。
第一,wait()方法是定义在Object类中的本地方法,调用wait方法的线程要拥有此对象的监视器,此方法使当前线程将自己置于此对象的等待集中。
第二,线程出于线程调度目的而被禁用并处于休眠状态直到发生以下四种情况之一:
- 另一个线程为此对象调用 notify() 方法而恰好被选中
- 另一个线程为此对象调用 notifyAll() 方法
- 指定的时间已过
- 其他线程调用该线程interrupt方法
第三,线程notify后,从该对象的等待集中移除,并重新启用线程调度,也就是说回到就绪状态,当它再次获得CPU执行权的时候,会从 wait 方法的调用中返回,执行之后的语句。
5、谈谈notify()方法notify()方法是定义在Object类中的本地方法,调用notify方法的线程要拥有此对象的监视器,此方法会选择对象等待集中的一个线程来唤醒。
6、谈谈String类第一,String类实现类了CharSequence接口和Comparable接口。
-
CharSequence接口提供对许多不同类型字符序列的统一只读访问。
定义了返回字符序列长度、返回特定位置的字符、返回从start开始到end-1的子字符序列规范 -
Comparable接口代表String类是可比较的。实现者要实现compareTo方法。
第二,String表示 UTF-16 格式的字符串,所以类内部有许多与码点相关的方法,底层维护着一个不可变的value字符数组。
7、谈谈StringBuffer和StringBuilder第一,StringBuffer类继承了AbstractStringBuilder抽象类、实现了CharSequence接口;AbstractStringBuilder实现了Appendable接口和CharSequence接口,代表AbstractStringBuilder是可追加的字符串序列。
-
Appendable接口代表字符序列是可追加的。定义了append方法,将指定字符序列添加到当前实例的某一位置
-
AbstractStringBuilder给两个接口的大部分方法提供了默认实现。里面维护着一个可变的字符数组和元素的个数,默认的扩容是原来的2倍加2且不大于最大int。
第二,StringBuffer类除了继承自父类的属性(可变的字符数组和元素的个数),还维护着一个缓存变量toStringCache来缓存最后一次调用toString的缓存,构造方法都预留了16个单位的空位,其余方法都是在方法上加上synchronized关键字修饰,方法内部调用父类方法来实现。
第三,StringBuilder类除了没有toString缓存、方法上没加synchronized关键字修饰,其余部分和StringBuffer一样。
8、谈谈ArrayList第一,ArrayList类继承了AbstractList抽象类、实现了List接口、RandomAccess接口、Cloneable接口;AbstractList抽象类继承了AbstractColleaction抽象类、实现了List接口;AbstractColleaction抽象类和List接口分别继承和实现了Collection接口;Collection继承了Iterable接口。
- Iterable接口是所有集合都要实现的,它规定了实现类要拥有返回一个迭代器的方法。
- Colleaction接口规定了集合的一般方法,如添加、删除、修改等。
- AbstractColleaction抽象类给出了Collection接口的默认实现,它的实现都是通过迭代器来进行的。
- List接口代表有序集合,实现此接口的实现类可以精确地控制每个元素在列表中的插入位置。其中定义了一个列表迭代器接口,列表迭代器不光可以象普通迭代器一样只能next向后调用,还可以previous让迭代器向前移动。
- AbstractList抽象类给出了List接口的默认实现,但是它没有提供get、set方法,这不是因为它不能使用列表迭代器来实现,而是因为子类的数据结构不同,如果有些支持随机访问的子类仍然调用迭代器就会降低效率。
- RandomAccess接口代表实现类可以进行随机访问。
- Cloneable接口代表实现类可以调用clone()方法。
第二,ArrayList类底层维护着一个Object类型的数组用来存储数据,它的默认容量是10,默认构造方法是不会为数组开辟空间的,有参会。当数组空间不够时,每次扩容为原来的1.5倍,但是尽量不超过最大int - 8,因为有些虚拟机厂商会在数组中加入一些头文件。它提供了迭代器和列表迭代器的实现。
9、谈谈linkedList第一,linkedList类继承了AbstractSequentialList抽象类、实现了List接口、Deque接口、Cloneable接口;AbstractSequentialList抽象类继承了AbstractList抽象类;Deque接口继承了Queue接口;Queue继承了Colleaction接口。
- AbstractSequentialList抽象类为之前我们说得AbstractList中没有提供的get、set、remove方法提供默认实现,它是通过迭代器遍历来实现的。
- Queue接口规定了集合添加和删除是符合队列的,规定了offer、poll、peek等方法。
- Deque接口代表双向队列,可以在头部或尾部进行添加和删除,offerFirst、offerLast。
第二,linkedList类底层维护着头节点和尾节点的指针还有一个链表长度size,它的数据结构是双向链表,没有容量限制。它提供了列表迭代器的实现。
10、谈谈HashMap和Hashtable第一,HashMap继承了AbstractMap抽象类、实现了Map接口、Cloneable接口;AbstractMap抽象类实现了Map接口。
- Map接口规定了get、put、remove等方法,最终要的是定义了一个Entry接口,里面规定了getKey,setValue等方法。
- AbstractMap抽象类通过迭代器遍历get、put、remove方法提供了默认实现。都是通过entrySet集合的迭代器iterator来实现的。它里面提供了两个Entry接口的具体实现,一个是可以修改value的SimpleEntry,一个不可以修改的SimpleImmutableEntry。
第二,HashMap底层维护着一个Node类型的数组,每个Node节点中保存着键、值、下一个节点指针、hashCode缓存,数组默认容量是16,最大容量是2的30次幂,默认负载因子是0.75,默认扩容阈值是12,桶中链表转换为红黑树的阈值是8,桶中红黑树转化为链表的阈值是6,将链表转换为红黑树的最小表容量是64。它的添加方法putVal是这样的:首先判断table是否为空,如果为空就调用resize方法开辟空间,之后判断hash运算后的table位置有没有元素,如果没有就赋值,如果有就根据节点是树节点还是链表节点进行遍历,如果找到键相同的Node就替换值,找不到就添加,添加完之后判断是否满足链表转换为红黑树的条件(容量大于64,链表节点数大于8)而转换,转换红黑树时,它先将链表节点转成红黑树节点的双向链表,之后以第一个元素为根,依次遍历红黑树,将键的hashCode作为红黑树的比较对象,若hashCode相等就调用本地方法根据地址来返回一个不可能相同的hashCode,每在红黑树中添加一个节点就调整一下红黑树结构使之平衡。平衡的过程类似于2-3-4树的操作。添加完毕节点之后判断是否需要扩容为原来的2倍,扩容之后要对桶中元素进行重新计算hashCode,对于链表来说,只要运算e.hash & oldCap即可,若结果等于0元素就不需要移动,若结果大于0就移动到原来位置+oldCap的位置上;对于红黑树来说也是这样,它先根据hashCode的不同将元素转换为两个链表,根据链表大小决定是否转换为红黑树。
第三,Hashtable与HashMap实现几乎一样,但是有几个不同点:
- Hashtable底层数据结构是数组+链表
- key,value均不能为null
- 扩容为原来的2倍+1
- HashTable默认数组大小为 11
- hash方式为(hash & 0x7FFFFFFF) % tab.length而不是通过hash & (Cap - 1)来进行
- 再hash过程更加慢,因为没有了HashMap的巧妙方式,桶中的每个元素的去向不是二选一了,而且还要开辟一个新的table空间
- 唯一的优点就是线程安全,添加了synchronized关键字修饰
- 再吐槽一句,真是失败的设计!!!



