Java八股文
1.为什么 Java 语言不支持多重继承?
为了程序的结构能够更加清晰从而便于维护。假设 Java 语言支持多重继承,类 C 继承自类 A 和类 B,如果类 A 和 B 都有自定义的成员方法
f()
,那么当代码中调用类 C 的
f()
会产生二义性。
Java 语言通过实现多个接口间接支持多重继承,接口由于只包含方法定义,不能有方法的实现,类 C 继承接口 A 与接口 B 时即使它们都有方法
f()
,也不能直接调用方法,需实现具体的
f()
方法才能调用,不会产生二义性。
多重继承会使类型转换、构造方法的调用顺序变得复杂,会影响到性能
2.重载与覆盖(重写)的区别?
覆盖是父类与子类之间的关系,是垂直关系;重载是同一类中方法之间的关系,是水平关系。
覆盖只能由一个方法或一对方法产生关系;重载是多个方法之间的关系。
覆盖要求参数列表相同;重载要求参数列表不同。
覆盖中,调用方法体是根据对象的类型来决定的,而重载是根据调用时实参表与形参表来对应选择方法体。
重载方法可以改变返回值的类型,覆盖方法不能改变返回值的类型
3.equals和==
==比较的是两个对象在栈内存中存放的堆内存的地址是否相同,即用来判断两个指针是否指向同一对象。==两边的操作数必须是同一类型。例如int a=10与long b=10L,double c=10.00都是==为true的。因为他们都指向地址为10的堆。
equals比较的是两个对象的值是否相同。
4.instanceof关键字:
instanceof是一个用来测试一个对象是不是一个类的实例的,用法为:
boolean result = obj instanceof Class
5.java的自动拆箱和自动装箱
自当拆箱和自动装箱是Java的封装数据类型和基本数据类型发生转换的时候出现的,javaSE5之后提供了自动装箱和自动拆箱机制方便的进行转换,当基本数据类型转换为封装类型,叫做自动装箱,当封装类型转换为基本类型,叫做自动拆箱。自动装箱调用方法valueOf,自动拆箱调用方法xxValue。
注意:在通过valueOf方法创建Integer对象的时候,如果数值在[-128,127]之间,就返回指向IntegerCache.cache中以及存在的对象的引用,否则创建一个新的对象。
Java 基本数据类型的包装类型的大部分都用到了缓存机制来提升性能。
Byte,Short,Integer,Long 这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character 创建了数值在 [0,127] 范围的缓存数据,Boolean 直接返回 True or False。
如果超出对应范围仍然会去创建新的对象,缓存的范围区间的大小只是在性能和资源之间的权衡。
两种浮点数类型的包装类 Float,Double 并没有实现缓存机制。
浮点数类型特殊的在于,每一次创建的对象都是不相等的,因为某个范围内浮点数的个数是不确定的。
以下代码输出false,fasle
public class Main { public static void main(String[] args) { Double i1 = 100.0; Double i2 = 100.0; Double i3 = 200.0; Double i4 = 200.0; System.out.println(i1==i2); System.out.println(i3==i4); } }
6.String,StringBuffer.StringBuilder
String是只读字符串,它并不是基本数据类型,而是一个对象。从底层源码来看是一个final类型的 字符数组,所引用的字符串不能被改变,一经定义,无法再增删改。每次对String的操作都会生成 新的String对象。StringBuffer和StringBuilder他们两都继承了AbstractStringBuilder抽象类,从 AbstractStringBuilder抽象类中我们可以看到 他们的底层都是可变的字符数组,所以在进行频繁的字符串操作时,建议使用StringBuffer和 StringBuilder来进行操作。 另外StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所 以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
7.ArrayList的底层原理和扩容机制
ArrayList是一个能够实现自动扩容的数组,可以看作一个可扩展可变的数组,它的底层依然是一个数组,初始容量是10,每次扩容扩大到原来的1.5倍。ArrayList是线程不安全的
ArrayList方便查找,查询效率高,但是增删数据的效率低。
ArrayList的扩容机制:
扩容可分为两种情况:
第一种情况,当ArrayList的容量为0时,此时添加元素的话,需要扩容,三种构造方法创建的ArrayList在扩容时略有不同:
1.无参构造,创建ArrayList后容量为0,添加第一个元素后,容量变为10,此后若需要扩容,则正常扩容。
2.传容量构造,当参数为0时,创建ArrayList后容量为0,添加第一个元素后,容量为1,此时ArrayList是满的,下次添加元素时需正常扩容。
3.传列表构造,当列表为空时,创建ArrayList后容量为0,添加第一个元素后,容量为1,此时ArrayList是满的,下次添加元素时需正常扩容。
第二种情况,当ArrayList的容量大于0,并且ArrayList是满的时,此时添加元素的话,进行正常扩容,每次扩容到原来的1.5倍。
详细参考:浅谈 ArrayList 及其扩容机制 - 城北有个混子 - 博客园 (cnblogs.com)
8.ArrayList和LinkedList的区别
ArrayList
优点:ArrayList 是实现了基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查询操作效率会比较高(在内存里是连着放的)。
缺点:因为地址连续,ArrayList 要移动数据,所以插入和删除操作效率比较低。
LinkedList
优点:LinkedList 基于链表的数据结构,地址是任意的,所以在开辟内存空间的时候不需要等一个连续的地址。对于新增和删除操作,LinkedList 比较占优势。LinkedList 适用于要头尾操作或插入指定位置的场景。
缺点:因为 LinkedList 要移动指针,所以查询操作性能比较低。
适用场景分析
当需要对数据进行对随机访问的时候,选用 ArrayList。
当需要对数据进行多次增加删除修改时,采用 LinkedList。
如果容量固定,并且只会添加到尾部,不会引起扩容,优先采用 ArrayList。
当然,绝大数业务的场景下,使用 ArrayList 就够了,但需要注意避免 ArrayList 的扩容,以及非顺
序的插入。
9.HashMap和Hashtable的区别:
1.两者的父类不同
HashMap继承自AbstractMap类,而Hashtable是继承自Dictionary类。不过它们都同时实现了map、Cloneable、Serializable(可序列化)这三个接口。
2.对外提供的接口不同
Hashtable比HashMap多提供了elments()和contains()两个方法。elments()方法继承自Hashtable的父类Dictionary。elments()方法用于返回此Hashtable中的value的枚举。
contains()方法判断该Hashtable是否包含传入的value。它的作用与containsValue()一致。事实上,containsValue()就是调用了一下contains()方法。
3.对null的支持不同:
Hashtable:key和value都不能为null、
HashMap:key可以为null,但是这样的key只能有一个,因为必须保证key的唯一性;可以有多个key值对应的value为null。
所以HashMap的key和value是可以同时为null的
4.安全性不同
HashMap是线程不安全的,在多线程并发的环境下,可能会产生死锁等问题,因此需要开发人员自己处理多线程的安全问题。
Hashtable是线程安全的,他的每个方法都有synchronized关键字,因此可直接用于多线程中。虽然HashMap是线程不安全的,但是它的效率远远高于Hashtable,这样设计是合理的,因为大部分的使用场景都是单线程。需要多线程时可以使用线程安全的ConcurrenthashMap。
5.hash值的计算方式不同
hash表获取值的方式:为了得到元素的位置,首先需要根据元素的key计算出一个hash值,然后再用这个hash值来计算得到的最终的位置。
HashMap:
使用了一个新的hash方法计算了key的hash值,降低hash冲突的可能性,先调用hashCode方法计算出来一个hash值,再将hash值与右移16位之后hash值相异或,从而得到新的hash值。源码如下:
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
Hashtable:计算key的hashCode()作为hash值。
6.利用hash值求索引的方式不同:
HashMap在求索引的时候,利用Hash值与哈希表的长度-1相与,即index = (n - 1) & hash,这也是为什么Hashmap的表长度必须是2的幂次,因为是取模得到索引值,故这样取模时,不需要做除法,只需要做位运算。位运算比除法的效率要高很多。
//取模 static int indexFor(int h, int length) { //jdk1.7的源码,jdk1.8没有这个方法,但是实现原理一样的 return h & (length-1); }
Hashtable:HashTable在求hash值位置索引时计算index的方法:
int index = (hash & 0x7FFFFFFF) % tab.length;
&0x7FFFFFFF的目的是为了将负的hash值转化为正值,因为hash值有可能为负数,而&0x7FFFFFFF后,只有符号位改变,而后面的位都不变。
7.扩容方式不同
当容量不足时要进行resize方法,而resize的两个步骤:
①扩容;
②rehash:这里HashMap和HashTable都会会重新计算hash值而这里的计算方式就不同了;
HashMap 哈希扩容必须要求为原容量的2倍,而且一定是2的幂次倍扩容结果,而且每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入;(详情后面讲)
而Hashtable扩容为原容量2倍加1;
9.HashMap底层原理和扩容机制:(建议看看源码)
10.HashMap和ConcurrentHashMap的区别
单开一帖写这两个,明天写
11.Java 和 C++ 的区别?
虽然,Java 和 C++ 都是面向对象的语言,都支持封装、继承和多态,但是,它们还是有挺多不相同的地方:
Java 不提供指针来直接访问内存,程序内存更加安全
Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承。
Java 有自动内存管理垃圾回收机制(GC),不需要程序员手动释放无用内存。
C ++同时支持方法重载和操作符重载,但是 Java 只支持方法重载(操作符重载增加了复杂性,这与 Java 最初的设计思想不符)