沉浸式阅读:Java 基础面试题汇总
1. JVM、JRE和JDK的关系
JavaSE:Java 平台标准版,为 Java EE 和 Java ME 提供了基础。
JDK:Java 开发工具包,JDK 是 JRE 的超集,包含 JRE 中的所有内容,以及开发程序所需的编译器和调试程序等工具。
JRE:Java SE 运行时环境 ,提供库、Java 虚拟机和其他组件来运行用 Java 编程语言编写的程序。主要类库,包括:程序部署发布、用户界面工具类、继承库、其他基础库,语言和工具基础库。
JVM:java 虚拟机,负责JavaSE平台的硬件和操作系统无关性、编译执行代码(字节码)和平台安全性。
JVM 全称 Java Virtual Machine,也就是我们耳熟能详的 Java 虚拟机。它能识别 .class 后缀的文件,并且能够解析它的指令,最终调用操作系统上的函数,完成我们想要的操作。
一个 Java 程序,首先需要经过 javac 编译成 .class 文件,然后 JVM 将其加载到方法区,执行引擎将会执行这些字节码。执行时,会翻译成操作系统相关的函数。JVM 作为 .class 文件的翻译存在,输入字节码,调用操作系统函数。
2. 面向对象的特征
面向对象的三个基本特征是:封装、继承、多态。
封装
封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。
封装隐藏对象的属性和实现细节,仅对外提供公共访问方式,将变化隔离,便于使用,提高复用性和安全性。
继承
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。
关于继承如下 3 点请记住:
- 子类拥有父类非 private 的属性和方法。
- 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
- 子类可以用自己的方式实现父类的方法。
多态
多态性(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。
实现多态,有二种方式,覆盖,重载。
3. 接口和抽象类
接口的意义
规范,扩展,回调。
抽象类的意义
- 为其他子类提供一个公共的类型;
- 封装子类中重复定义的内容;
- 定义抽象方法,子类虽然有不同的实现,但是定义时一致的。
两者的区别
比较 | 抽象类 | 接口 |
---|---|---|
默认方法 | 抽象类可以有默认的方法实现 | java 8之前,接口中不存在方法的实现 |
实现方式 | 子类使用extends关键字来继承抽象类,如果子类不是抽象类,子类需要提供抽象类中所声明方法的实现 | 子类使用implements来实现接口,需要提供接口中所有声明的实现 |
构造器 | 抽象类中可以有构造器 | 接口中不能 |
访问修饰符 | 抽象方法可以有public,protected和default等修饰 | 接口默认是public,不能使用其他修饰符 |
多继承 | 一个子类只能存在一个父类 | 一个子类可以存在多个接口 |
访问新方法 | 想抽象类中添加新方法,可以提供默认的实现,因此可以不修改子类现有的代码 | 如果往接口中添加新方法,则子类中需要实现该方法 |
4. 父类的静态方法能否被子类重写
不能。重写只适用于实例方法,不能用于静态方法,而子类当中含有和父类相同签名的静态方法,我们一般称之为隐藏。
5. 什么是不可变对象
不可变对象指对象一旦被创建,状态就不能再改变。任何修改都会创建一个新的对象,如 String、Integer及其它包装类。即 final 修饰的类。
6. Overload和Override的区别
方法的重写 Override 和重载 Overload 是Java多态性的不同表现。重写 Override 是父类与子类之间多态性的一种表现,重载 Overload 是一个类中多态性的一种表现。
重载
一个类中允许同时存在一个以上的同名方法,这些方法的参数个数或者类型不同
重写
在子类中将父类的成员方法的名称保留,重新编写成员方法的实现内容,更改方法的访问权限,修改返回类型的为父类返回类型的子类。
如果说你熟悉 JVM,那么你可以在 JVM 的角度去讲解其实现,具体可参考:方法调用的底层实现之重载与重写的区别
7. 什么是值传递和引用传递
值传递
是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
引用传递
是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
那就有人有疑问了,既然 String 是引用类型,为什么它的值不发生改变?
因为 String,Long 等都是 final
修饰的类,当然不会被修改。
可以参考:值传递和引用传递
8. 基本数据类型和引用类型
Java中一共有四类八种基本数据类型,如下表:
注:String 不是基本数据类型。
除了这四类八种基本类型,其它的都是对象,也就是引用类型,包括数组。
9. float f = 5.6 是否正确
不正确。5.6 是双精度数,将双精度型(double)赋值给浮点型(float)属于下转型(down-casting,也称为窄化)会造成精度损失,因此需要强制类型转换 float f = (float)5.6
;或者写成 float f = 5.6F
。
10. short s1 = 1; s1 = s1 + 1;有错吗? short s1 = 1;s1 += 1;有错吗
short s1 = 1; s1 = s1 + 1;
由于 1 是 int 类型,因此 s1+1 运算结果也是 int型,需要强制转换类型才能赋值给 short 型,所以错误。
short s1 = 1; s1 += 1;
可以正确编译,因为 s1+= 1;
相当于 s1 = (short(s1 + 1);
其中有隐含的强制类型转换。
11. int 和 Integer 有什么区别
Java 是一个近乎纯洁的面向对象编程语言,但是为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java 为每一个基本数据类型都引入了对应的包装类型(wrapper class),int 的包装类就是 Integer,从 Java 5 开始引入了自动装箱/拆箱机制,使得二者可以相互转换。
Java 为每个原始类型提供了包装类型:
原始类型 | boolean | char | byte | short | int | long | float | double |
---|---|---|---|---|---|---|---|---|
包装类型 | Boolean | Character | Byte | Short | Integer | Long | Float | Double |
那么对于下面这段代码输出为什么:
public static void main(String[] args) {
Integer a = 100, b = 100, c = 200, d = 200;
System.out.println(a == b);
System.out.println(c == d);
}
答案是:第一个为 true,第二个为 false。为什么?
装箱的本质是什么?
当我们给一个 Integer 对象赋一个 int 值的时候,会调用 Integer 类的静态方法 valueOf,如下:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
即存在一个缓存,如果整型字面量的值在 -128 到 127 之间,那么不会 new 新的 Integer 对象,而是直接引用常量池中的 Integer 对象。
12. final 有什么用
用于修饰类、属性和方法:
- 被 final 修饰的类不可以被继承;
- 被 final 修饰的方法不可以被重写;
- 被 final 修饰的变量不可以被改变,被 final 修饰不可变的是变量的引用,而不是引用指向的内容,引用指向的内容是可以改变的;
- 被 final 修饰的常量,在编译阶段会存入常量池中。
13. final、finally、finalize 的区别
-
final
可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表
示该变量是一个常量不能被重新赋值。 -
finally
一般作用在 try-catch 代码块中,在处理异常的时候,通常我们将一定要执行的代码方法 finally 代码块
中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。 -
finalize
是一个方法,属于 Object 类的一个方法,而 Object 类是所有类的父类,该方法一般由垃圾回收器来调
用,当我们调用 System.gc() 方法的时候,由垃圾回收器调用 finalize(),回收垃圾,一个对象是否可回收的
最后判断。
对于 finalize 方法,在进行垃圾回收的时候需要判断对象是否还存活,一般通过可达性分析来判断,但是,即使通过可达性分析判断不可达的对象,也不是非死不可,它还会处于缓刑阶段,真正要宣告一个对象死亡,需要经过两次标记过程,一次是没有找到与 GCRoots 的引用链,它将被第一次标记。随后进行一次筛选(如果对象覆盖了 finalize),我们可以在 finalize 中去拯救。如下:
public class FinalizeGC {
public static FinalizeGC instance;
@Override
protected void finalize() throws Throwable {
super.finalize();
FinalizeGC.instance = this;
}
public static void main(String[] args) throws Exception {
//创建对象
instance = new FinalizeGC();