java面试宝典学习 Day1
== 和 equals 有什么区别?
== 对于基本数据类型来说,是用于比较 “值”是否相等的;而对于引用类型来说,是用于比较引用地址是否相同的;而 equals 本质上就是 ==,只不过 String 和 Integer 等重写了 equals 方法,把它变成了值比较。
查看源码我们可以知道 Object 中也有 equals() 方法,源码如下:
public boolean equals(Object obj) {
return (this == obj);
}
可以看出,Object 中的 equals() 方法其实就是 ==,而 String 重写了 equals() 方法把它修改成比较两个字符串的值是否相等。
源码如下:
public boolean equals(Object anObject) {
// 对象引用相同直接返回 true
if (this == anObject) {
return true;
}
// 判断需要对比的值是否为 String 类型,如果不是则直接返回 false
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
// 把两个字符串都转换为 char 数组对比
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
// 循环比对两个字符串的每一个字符
while (n-- != 0) {
// 如果其中有一个字符不相等就 true false,否则继续对比
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
String 属于基本数据类型吗?
String 不属于基础类型,在 Java 中,一共有 8 种基本类型(primitive type),其中有 4 种整型、2 种浮点类型、1 种用于表示 Unicode 编码的字符类型 char 和 1 种用于表示真假值的 boolean 类型。
4 种整型:int、short、long、byte2 种浮点类型:float、double字符类型:char真假类型:boolean
基本数据类型是指不可再分的原子数据类型,内存中直接存储此类型的值,通过内存地址即可直接访问到数据,并且此内存区域只能存放这种类型的值。
基本数据类型不能应用于泛型,而 String 却可以。
为什么使用 final 修饰 String?
使用 final 修饰 String 的第一个好处是安全,第二个好处是高效。
Java 语言之父 James Gosling 的回答,他会更倾向于使用 final,因为它能够缓存结果,当你在传参时不需要考虑谁会修改它的值;如果是可变类的话,则有可能需要重新拷贝出来一个新值进行传参,这样在性能上就会有一定的损失。
James Gosling 还说,迫使 String 类设计成不可变的另一个原因是安全,当你在调用其他方法时,比如调用一些系统级操作指令之前,可能会有一系列校验,如果是可变类的话,可能在你校验过后,它的内部的值又被改变了,这样有可能会引起严重的系统崩溃问题,这是迫使 String 类设计成不可变类的一个重要原因。
String、StringBuilder 和 StringBuffer 有什么区别?
因为 String 类型是不可变的,所以在字符串拼接的时候如果使用 String 的话性能会很低,因此我们就需要使用另一个数据类型 StringBuffer,它提供了 append 和 insert 方法可用于字符串的拼接,它使用 synchronized 来保证线程安全,如下源码所示:
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
因为它使用了 synchronized 来保证线程安全,所以性能不是很高,于是在 JDK 1.5 就有了 StringBuilder,它同样提供了 append 和 insert 的拼接方法,但它没有使用 synchronized 来修饰,因此在性能上要优于 StringBuffer,所以在非并发操作的环境下可使用 StringBuilder 来进行字符串拼接。
小结:String 是不可变的,所以在进行字符拼接时效率很低,而 StringBuffer 可以替代 String 来进行字符串拼接,并且能保证线程安全,但因为使用 synchronized 所以效率不高,而 StringBuilder 可以更高效的拼接字符串,但不能保证线程安全。
为什么重写 equals 时,必须重写 hashCode?
equals 和 hashCode 两个方法是用来协同判断两个对象是否相等的,采用这种方式的原因是可以提高程序插入和查询的速度,如果在重写 equals 时,不重写 hashCode,就会导致在某些场景下,例如将两个相等的自定义对象存储在 Set 集合时,就会出现程序执行的异常,为了保证程序的正常执行,所以我们就需要在重写 equals 时,也一并重写 hashCode 方法才行。
异常问题分析
比如在 Set 集合存储数据时,如果只重写了 equals 方法,那么默认情况下,Set 进行去重判断时,会先判断两个对象的 hashCode 是否相同,而此时因为没有重写 hashCode 方法,所以会默认调用 Object 中的 hashCode 方法,而 Object 中的 hashCode 方法会得到两个对象的哈希值,而两个对象因为引用地址不同,所以哈希的结果也是不同的,因此会直接返回 false,于是 Set 集合就插入了两个相等的对象。
但是,如果在重写 equals 方法时,也重写了 hashCode 方法,那么在执行判断时会去执行重写的 hashCode 方法,此时对比的是两个对象的所有属性的 hashCode,因为属性为基础数据类型或 String,所以哈希之后得到的 hashCode 值也是相同的,于是再去调用 equals 方法,发现两个对象确实是相等的,于是就返回 true 了,因此 Set 集合就不会存储两个相等的数据了,那么整个程序也能正常执行了。
int 和 Integer 有什么区别?
int 和 Integer的区别主要体现在以下几个方面:
数据类型不同:int 是基础数据类型,而 Integer 是包装数据类型;默认值不同:int 的默认值是 0,而 Integer 的默认值是 null;内存中存储的方式不同:int 在内存中直接存储的是数据值,而 Integer 实际存储的是对象引用,当 new 一个 Integer 时实际上是生成一个指针指向此对象;实例化方式不同:Integer 必须实例化才可以使用,而 int 不需要;变量的比较方式不同:int 可以使用 == 来对比两个变量是否相等,而 Integer 一定要使用 equals 来比较两个变量是否相等。
Integer 有什么优点?
包装类 Integer 的优点是解决了基本数据类型无法做到的事情泛型类型参数、序列化、类型转换、高频区间数据缓存等问题。
Integer 可以使用 == 进行比较吗?为什么?
Integer 不能使用 == 比较,因为 Integer 本身是对象,而 == 比较的是对象地址,所以不能使用== 比较。
final、finally 和 finalize 有什么区别?
final、finally 和 finalize 从英文字面角度来看,看似很像,实则 3 者在 Java 中没任何关系。final 是用来修饰类、方法、变量和参数的关键字,被 final 修饰的对象不允许修改或替换其原始值或定义;finally 是 Java 中保证重点代码一定要被执行的一种机制;finalize 是 Object 类中的一个基础方法,它的设计目的是保证对象在被垃圾收集前完成特定资源的回收的,但其执行“不稳定”,且有一定的性能问题,已经在 JDK 9 中被设置为弃用的方法了。
普通类和抽象类有哪些区别?
普通类不能包含抽象方法,抽象类可以包含抽象方法。抽象类不能直接实例化,普通类可以直接实例化。
抽象类必须要有抽象方法吗?
不需要,抽象类不一定非要有抽象方法。
抽象类能使用 final 修饰吗?
不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类。
抽象类和接口有什么区别?
接口和抽象类都是用来定义对象的公共行为的,但二者有以下 7 点不同:
定义的关键字不同;子类继承或实现关键字不同;类型扩展不同:抽象类是单继承,而接口是多继承;方法访问控制符:抽象类无限制,只是抽象类中的抽象方法不能被 private 修饰;而接口有限制,接口默认的是 public 控制符;属性方法控制符:抽象类无限制,而接口有限制,接口默认的是 public 控制符;方法实现不同:抽象类中的普通方法必须有实现,抽象方法必须没有实现;而接口中普通方法不能有实现,但在 JDK 8 中的 static 和 defualt 方法必须有实现;静态代码块的使用不同:抽象类可以有静态代码块,而接口不能有。
Java 中 this 和 super 有什么区别?
this 和 super 都是 Java 中的关键字,都起指代作用,当显式使用它们时,都需要将它们放在方法的首行(否则编译器会报错)。this 表示当前对象,super 用来指代父类对象,它们有四点不同:
指代对象:super 指代的是父类,是用来访问父类的;而 this 指代的是当前类;查找访问:super 只能查找父类,而 this 会先从本类中找,如果找不到则会去父类中找;本类属性赋值:this 可以用来为本类的实例属性赋值,而 super 则不能实现此功能;配合 synchronized 使用不同:因为 this 表示当前对象,所以this 可用于 synchronized(this){…} 加锁,而 super 则不能实现此功能。
为什么返回类型不算方法重载?
因为不同的返回值类型,JVM 没办法分辨到底要调用哪个方法,JVM 调用方法是通过方法签名来判断到底要调用哪个方法的,而方法签名 = 方法名称 + 参数类型 + 参数个数组成的一个唯一值,这个唯一值就是方法签名。
从方法签名的组成可以看出,返回类型不是方法签名的组成部分,所以不同的返回类型也就不算方法重载了,因为它不能让 JVM 确定要调用的具体方法。
方法重写时需要注意什么问题?
Java 中的方法重写(Override)是在子类重新定义父类已有方法的过程,它是面向对象编程中多态的具体表现。我们可以通过 @Override 关键字重写父类中的某个方法,但在重写的过程中需要注意以下 5 个问题:
子类方法的权限控制符不能变小;子类方法返回的类型只能变小;子类抛出异常的类型只能变小;子类方法名必须和父类保持一致;子类方法的参数类型和个数必须和父类保持一致。