Java基础拾遗
Hash相关
首先,可以被hash的值一定是final的,即不可以被改变的,如String。
HashMap相关参数
- size - 元素总数量
- Capacity - 容量(HashMap中桶的数量),默认为16,且总时2的幂次。
- loadFactor - 装载因子(衡量HashMap满的程度,实时装载因子 = size / capacity; 默认0.75f)
- threshold - 门限值(当size大于threshold时触发resize操作。threshold = capacity x loadFactor)
HashMap为了存取高效,要尽量较少碰撞,就是要尽量把数据分配均匀,每个链表长度大致相同,这个实现就在把数据存到哪个链表中的算法;
这个算法实际就是取模,hash%length,计算机中直接求余效率不如位移运算,源码中做了优化hash&(length-1), hash%length==hash&(length-1)的前提是length是2的n次方;
为什么这样能均匀分布减少碰撞呢?2的n次方实际就是1后面n个0,2的n次方-1 实际就是n个1; 例如长度为9时候,3&(9-1)=0 2&(9-1)=0 ,都在0上,碰撞了; 例如长度为8时候,3&(8-1)=3 2&(8-1)=2 ,不同位置上,不碰撞;
解决哈希冲突的方法
拉链法:Java HashMap实现的方法,不深说
开放地址法
- 开放地址法有个非常关键的特征,就是所有输入的元素全部存放在哈希表里,也就是说,位桶的实现是不需要任何的链表来实现的,换句话说,也就是这个哈希表的装载因子不会超过1。它的实现是在插入一个元素的时候,先通过哈希函数进行判断,若是发生哈希冲突,就以当前地址为基准,根据再寻址的方法(探查序列),去寻找下一个地址,若发生冲突再去寻找,直至找到一个为空的地址为止。
- 再散列法
- 再散列法其实很简单,就是再使用另一个哈希函数去散列一个输入的时候,输出是同一个位置就再次散列,直至不发生冲突位置
- 建立公共溢出区
- 将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表
1 | for (Map.Entry<Integer, Integer> entry : hashmap.entrySet()) { |
JDK1.7扩容时死循环成因
https://cloud.tencent.com/developer/article/1120823
equals()和hashCode()
而涉及到HashMap的时候,重写了equals(),就需要重写hashCode()
Object版本的equal只是简单地判断是不是同一个实例。但是有的时候,我们想要的的是逻辑上的相等。比如有一个学生类student,有一个属性studentID,只要studentID相等,不是同一个实例我们也认为是同一学生。当我们认为判定equals的相等应该是逻辑上的相等而不是只是判断是不是内存中的同一个东西的时候,就需要重写equal()。 但是hashMap是通过hashCode()来判断相同的,因此此时若不重写hashcode方法,则hashMap就会出错。
Comparable 和 Comparator的区别
Comparable是某个实体类内部实现的比较功能,其接口含义也表示该实体的可比较的(其泛型往往就是该实体类型)
1 | public int compareTo(Demo o) { |
Comparator是从外部类构造的方式对实体类进行排序:
1 | public int compare(Integer o1, Integer o2) { |
访问控制符的区别
注:default控制符如果子类和父类在同一个包中,则可以访问,如果在不同的包里,则不可访问。
继承与组合的区别
继承(is-a关系)
- 代码复用
- 子类可以重写父类方法
- 创建子类对象时,无需创建父类对象,子类自动拥有父类的成员变量和行为
- 子类在父类的基础上可根据自己的业务需求扩展
- 会破坏封装(子类的访问控制符不能低于父类访问控制符的级别)
组合(has-a关系)
- 不破坏封装,松耦合
- 具有可扩展性
- 支持动态组合
绑定(解释什么是多态)
静态绑定
- final static private 在程序执行前已经被棒顶,也就是说在编译过程中就已经知道这个方法是哪个类的方法。
- private:不能被继承,则不能通过子类对象调用,而只能通过类本身的对象进行调用,所以可以说private方法和方法所属的类绑定;
- final:final方法虽然可以被继承,但是不能被重写(覆盖),虽然子类对象可以调用,但是调用的都是父类中的final方法(因此可以看出当类中的方法声明为final的时候,一是为了防止方法被覆盖,而是为了有效关闭java的动态绑定);
- static:static方法可以被子类继承,但是不能被子类重写(覆盖),但是可以被子类隐藏。(这里意思是说如果父类里有一个static方法,它的子类里如果没有对应的方法,那么当子类对象调用这个方法时就会使用父类中的方法。而如果子类中定义了相同的方法,则会调用子类的中定义的方法。唯一的不同就是,当子类对象上转型为父类对象时,不论子类中有没有定义这个静态方法,该对象都会使用父类中的静态方法。因此这里说静态方法可以被隐藏而不能被覆盖。这与子类隐藏父类中的成员变量是一样的。隐藏和覆盖的区别在于,子类对象转换成父类对象后,能够访问父类被隐藏的变量和方法,而不能访问父类被覆盖的方法)。
动态绑定
- 调用的方法依赖于隐式参数的实际类型,并且在运行时实现动态绑定。动态绑定的过程分为以下几个环节:
- 编译器查看对象的声明类型和方法名;
- 编译器查看调用方法时提供的参数类型。例如x.f(“hello”),编译器将会挑选f(String),而不是f(int),由于存在类型转换(int转换为double),所以可能会更复杂。如果编译器没找到参数类型匹配的方法,或者发现有多个方法与之匹配,就会报告一个错误。
- 至此,编译器获得了需要调用的方法名字和参数类型
- 采用动态绑定调用方法的时候,一定调用与所引用对象的实际类型最合适的类的方法。如果x的实际类型是D,它是C类的子类,如果D定义了一个方法f(String),就直接调用它,否则将在D类的超类中寻找f(String),以此类推。
- 每次调用方法都要进行搜索,时间开销太大,所以虚拟机预先为每个类创建一个方法表(method table),其中列出了所有方法的签名和实际调用的方法。这样在调用方法的时候,只需要查找这个表即可。
什么是多态
- 所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
- Java实现多态有三个必要条件:继承、重写、向上转型。
自动装箱与拆箱(Autoboxing and unboxing)
Java是一种面向对象语言,很多地方都需要使用对象而不是基本数据类型。比如,在集合类中,我们是无法将int 、double等类型放进去的。因为集合的容器要求元素是Object类型。
装箱就是自动将基本数据类型转换为包装器类型(对象);拆箱就是自动将包装器类型转换为基本数据类型。
自动装箱:Integer i = 100;相当于编译器自动作以下的语法编译:Integer i = Integer.valueOf(100);
自动拆箱:int i = integer.intValue();