Java 基础面试题总结(2022最新版)
关于作者
🐶 程序猿周周
⌨️ 短视频小厂BUG攻城狮
🤺 如果文章对你有帮助,记得关注、点赞、收藏,一键三连哦,你的支持将成为我最大的动力
关于本文
本文是 Java 面试总结系列的第7️⃣篇文章,该专栏将整理和梳理笔者作为 Java 后端程序猿在日常工作以及面试中遇到的实际问题,通过这些问题的系统学习,也帮助笔者顺利拿到阿里、字节、华为、快手等Offer,也祝愿大家能够早日斩获自己心仪的Offer。由于笔者能力有限,如若有错误或疏忽还望各位大佬们不吝指出…
标题 | 地址 |
---|---|
MySQL数据库面试题总结(2022版) | https://blog.csdn.net/adminpd/article/details/122910606 |
Redis面试题总结(2022版) | https://blog.csdn.net/adminpd/article/details/122934938 |
计算机网络面试题总结(2022版) | https://blog.csdn.net/adminpd/article/details/122973684 |
操作系统面试题总结(2022版) | https://blog.csdn.net/adminpd/article/details/122994599 |
Linux面试题总结(2022版) | https://blog.csdn.net/adminpd/article/details/122994862 |
Spring面试题总结(2022版) | https://blog.csdn.net/adminpd/article/details/123016872 |
Java基础面试题总结(2022版) | https://blog.csdn.net/adminpd/article/details/123080189 |
Java集合面试题总结(2022版) | https://blog.csdn.net/adminpd/article/details/123171501 |
Java并发面试题总结(2022版) | https://blog.csdn.net/adminpd/article/details/123266624 |
Java虚拟机面试题总结(2022版) | https://blog.csdn.net/adminpd/article/details/123412605 |
Java异常面试题总结(2022版) | https://blog.csdn.net/adminpd/article/details/123462676 |
设计模式面试题总结(2022版) | https://blog.csdn.net/adminpd/article/details/123490442 |
Dubbo面试题总结(2022版) | https://blog.csdn.net/adminpd/article/details/123538243 |
1 基本概念
1.1 基本类型
1、Java 有哪些基本数据类型?
众所周知,Java 是一门强类型语言,对于程序中的每一个变量都明确定义了一种数据类型,以及分配不同大小的内存空间。
其中,Java 提供了八种基本数据类型格式,包括 4 种整型,2 种浮点,1 种字符(char 是两字节)以及 1 种布尔型类型。
数据类型 | 位数 | 默认值 | 取值范围 |
---|---|---|---|
byte | 8 | 0 | -128 ~ 127 |
short | 16 | 0 | -32768 ~ 32767 |
int | 32 | 0 | -2^31 ~ 2^31-1 |
long | 64 | 0 | -2^63 ~ 2^63-1 |
float | 32 | 0f | -2^63 ~ 2^63-1 |
double | 64 | 0d | -2^63 ~ 2^63-1 |
char | 16 | 空字符 | 0 ~ 2^16-1 |
boolean | - | false | false、true |
2、Java 有哪些数据类型?
除了上述所说的八种基本类型外,Java 还提供了一种引用数据类型的支持,比如我们常说的类(class)、接口(interface)和数组。
3、类型有哪些转换方式?
对于基本类型而言,主要分为自动(隐式)类型转换和强制(显式)类型转换两种方式。
- 自动类型转换:是一种小类型到大类型的转换,不需要强制转换符。
自动类型转换常出现在以下常景:
1)小的类型自动转化为大的类型;
2)整数类型可以自动转化为浮点类型,可能会产生舍入误差;
3)字符可以自动提升为整数。
- 强制类型转换:需要在强制类型转换的变量前面加上括号,然后在括号里面标注要转换的类型。
强制类型转换中一些需要注意的事项:
1)强制类型转换可能导致溢出或损失精度;;
2)浮点数到整数的转换是通过舍弃小数得到,而不是四舍五入;
3)不能对布尔值进行转换;
4)不能把对象类型转换为不相干的类型。
4、什么是自动拆装箱?
为什么会出现自动拆装箱
Java 语言是一个面向对象的语言,但是 Java 中的基本数据类型却是不面向对象的,这在实际使用时存在很多的不便,为了解决这个不足,在设计类时为每个基本数据类型设计了一个对应的类进行代表,这样八个和基本数据类型对应的类统称为包装类(Wrapper Class)。使得基本数据类型也具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。
既然有了基本类型以及对应的包装类,那么必然会出现二者之间的转换操作,于是在Java SE5中,为了减少开发人员的工作,Java 提供了自动拆箱与自动装箱功能。
- 自动装箱:就是将基本数据类型自动转换成对应的包装类。
- 自动拆箱:就是将包装类自动转换成对应的基本数据类型。
Integer i =10; //自动装箱
int b= i; //自动拆箱
自动拆装箱的原理
通过代码反编译可以发现,自动装箱都是通过包装类的 valueOf()
方法来实现的。而自动拆箱都是通过包装类对象的 xxxValue()
来实现的。
自动拆装箱的使用场景
1)基本数据类型放入集合类;
2)包装类型和基本类型的大小比较;
3)包装类型的运算符计算;
4)函数参数与返回值。
5、什么是基本数据类型的缓冲池?
由上文可知包装类以及自动拆装箱的好处,同时在开发中的大量应用,所以 Java 为了进一步提高性能和节省空间,整型 Integer 对象通过使用相同的对象引用实现了缓存和重用。
即当需要进行自动装箱时,在 -128 至 127 之间的整型数字会直接使用缓存中的对象,而不是重新创建一个新对象。当然,这个范围是可以通过 -XX:AutoBoxCacheMax=size
参数进行调整的。
6、布尔类型占多少字节?
在 Java 的定义中,除了 boolean 其它七种类型都有明确的内存占用字节数,因为对虚拟机来说根本就不存在 boolean 这种类型,布尔类型在编译后会使用其它数据类型来表示,如 Hotspot 虚拟机使用 int 类型表示 boolean。
总结一下当前常见的说法:
- 1 bit
位是计算机最小的存储单位,且布尔类型的值只有 true 和 false,这两个数在内存中只需要1位(bit)即可存储,
- 1 byte
虽然 boolean 编译后只需占用 1 bit 空间,但计算机处理数据的最小单位是字节。
- 4 byte
在《Java 虚拟机规范》一书中说到:JVM 中没有任何可以供 boolean 值专用的字节码指令,Java 语言表达式所操作的 boolean 值,在编译之后都使用 int 类型来代替,而 boolean 数组将会被编码成 Java 虚拟机的 byte 数组,每个元素 boolean 元素占 8 位。
1.2 类
1、抽象类和接口的区别?
首先了解什么是抽象类和接口:
- 抽象类:被 abstract 修饰的类就是抽象类,用来捕捉子类的通用性。它不能被实例化,只能用作子类的超类,抽象类是被用来创建继承层级里子类的模板。
- 接口:可以说是一种特殊抽象类,接口中的所有方法都必须是抽象的。接口中的方法定义默认被
public abstract
修饰,接口中的成员变量类型默认public static final
。
二者具体区别:
1)抽象类可以有非抽象方法,接口不存在非抽象方法;
2)使用 extends
继承抽象类并实现抽象方法,是 implements
实现接口中所有方法;
3)抽象类支持构造函数(但不能被 abstract 修饰),接口无构造函数;
4)抽象类的抽象方法可以被 public、protected、default 修饰,接口只能是 public;
5)抽象类可以有 main 函数,接口不支持;
2、什么是内部类?
在 Java 中,内部类就是将自身类的定义放在另外一个类的定义内部的类。内部类本身就是类的一个属性,与其他属性定义方式一致。
常见的内部类可以被分为四种:成员内部类、局部内部类、匿名内部类和静态内部类。
- 成员内部类:定义在类内部,成员位置上的非静态类。
成员内部类可以访问外部类所有的变量和方法,包括静态和非静态,私有和公有。成员内部类依赖于外部类的实例,它的创建方式是 外部类实例.new 内部类()
。
- 静态内部类:定义在类内部的静态类。
静态内部类可以访问外部类所有的静态变量,而不可访问外部类的非静态变量。静态内部类的创建方式:new 外部类.静态内部类()
。
- 局部内部类:定义在方法中的内部类。
定义在实例方法中的局部类可以访问外部类的所有变量和方法,定义在静态方法中的局部类只能访问外部类的静态变量和方法。局部内部类的创建方式,new 内部类()
,且仅能在对应方法内使用。
- 匿名内部类:就是没有名字的内部类,日常开发中使用的比较多。
局部内部类和匿名内部类访问局部变量时为什么须要加 final?
public class Outer {
void outMethod(){
final int a =10;
class Inner {
void innerMethod(){
System.out.println(a);
}
}
}
}
因为生命周期不一致,局部变量直接存储在栈中,当方法执行结束后,非 final 的局部变量就被销毁。而局部内部类对局部变量的引用依然存在,如果局部内部类要调用局部变量时,就会出错。 使用 final 后可以确保局部内部类使用的变量与外层的局部变量区分开,解决了这个问题。
3、内部类有哪些优点?
- 一个内部类对象可以访问创建它的外部类对象的内容,包括私有数据;
- 内部类不为同一包的其他类所见,具有很好的封装性;
- 内部类有效实现了"多重继承",优化 java 单继承的缺陷;
- 匿名内部类可以很方便的定义回调。
4、Java 有哪四种引用类型?
JDK 1.2 之前,一个对象只有“已被引用”和"未被引用"两种状态,这将无法描述某些特殊情况下的对象,比如,当内存充足时需要保留,而内存紧张时才需要被抛弃的一类对象。于是 Java 对引用的概念进行了扩充,将引用分为了:强引用、软引用、弱引用、虚引用,这 4 种引用的强度依次减弱。
- 强引用(Strong Reference)
是 Java 中默认声明的引用类型,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足时, JVM 也会直接抛出 OutOfMemoryError 而不会去回收。
- 软引用(Soft Reference)
软引用是用来描述一些非必需但仍有用的对象。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。 这种特性常常被用来实现缓存技术,比如网页缓存,图片缓存等。
- 弱引用(Weak Reference)
弱引用的引用强度比软引用要更弱一些,无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。 使用 java.lang.ref.WeakReference
来表示弱引用。
- 虚引用(Phantom Reference)
虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收,用 PhantomReference 类来表示。通过查看这个类的源码,发现它只有一个构造函数和一个 get() 方法,而且它的 get() 方法仅仅是返回一个null,也就是说将永远无法通过虚引用来获取对象,虚引用必须要和 ReferenceQueue 引用队列一起使用。
引用队列可以与软引用、弱引用以及虚引用一起配合使用,当垃圾回收器准备回收一个对象时,如果发现它还有引用,那么就会在回收对象之前,把这个引用加入到与之关联的引用队列中去。程序可以通过判断引用队列中是否已经加入了引用,来判断被引用的对象是否将要被垃圾回收,这样就可以在对象被回收之前采取一些必要的措施。
5、类初始化顺序?
1)父类–静态变量/静态初始化块(按代码顺序);
2)子类–静态变量/静态初始化块;
3)父类–变量/初始化块;
4)父类–构造器;
5)子类–变量/初始化块;
6)子类–构造器。
1.3 修饰符
1、Java 中常见的修饰符?
Java 语言提供了很多修饰符,常见的可以分为两类:访问权限修饰符和非访问权限修饰符。
访问权限修饰符
- public:共有访问,对所有的类都可见。
- protected:保护型访问,对同一个包可见,对不同的包的子类可见。
- default:默认访问权限,只对同一个包可见,注意对不同的包的子类不可见。
- private:私有访问,只对同一个类可见,其余都不见
即访问范围:public > protected > default > private。
修饰符 | 同类 | 同包 | 子类 | 其他包 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | x |
default | √ | √ | x | x |
private | √ | X | x | x |
非访问权限修饰符
- static,用来创建类方法和类变量。
- final,用来修饰类、方法和变量,final 修饰的类不能够被继承,修饰的方法不能被继承类重新定义,修饰的变量为常量,是不可修改的。
- abstract,用来创建抽象类和抽象方法。
- synchronized 用于多线程的同步。
- volatile 修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。
- transient:序列化的对象包含被 transient 修饰的实例变量时,java 虚拟机(JVM)跳过该特定的变量。
2、谈谈 final 关键字?
在 Java 中,final 关键字可以用来修饰类、方法和变量(包括成员变量和局部变量)。
- 修饰类
当一个类被 final 修饰时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用 final 进行修饰。
- 修饰方法
final 修饰的方法表示此方法已经是”最后的、最终的”含义,即此方法不能被重写(可以重载多个final修饰的方法)。
需要注意的一点是,如果父类中 final 修饰的方法同时访问控制权限为 private,将会导致子类中不能直接继承到此方法,因此,此时可以在子类中定义相同的方法名和参数。因为此时没有产生重写,而是在子类中重新定义了新的方法。
- 修饰变量
当 final 修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化;如果 final 修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。因此,被 final 修饰的成员变量必须要显示初始化。
1.4 语法
1、Java 中有哪些取整方法?
1)强制类型转换;
2)Math 取整函数:
- Math.ceil(double num):向上;
- Math.floor(double num):向下;
- Math.round(double num):四舍六入,五取正。
3)BigDecimal#setScale 函数;
4)String#format 方法。
2、Switch 支持哪些数据类型?
一般情况下使用整型类型,包括 byte、short、char 以及 int。但在 JDK 1.5 和 1.7 又分别增加了对枚举类型和 String 的支持。
同时,Switch 语句会跳转到匹配的 case 位置执行剩下的语句,直到最后遇见第一个 break 为止。
3、Switch 支持 String 的原理?
Switch 对 String 类型的支持是利用 String 的 hash 值,本质上还是 switch-int 结构。并且利用了 equals 方法来防止 hash 冲突的问题。最后利用 switch-byte 结构,精确匹配。
public static void main(String[] args) {
switch (args[0]) {
case "A" : break;
case "B" : break;
default :
}
}
// 经过 JVM 编译后:
public static void main(String[] var0) {
String var1 = var0[0];
byte var2 = -1;
switch(var1.hashCode()) {
case 65:
if (var1.equals("A")) {
var2 = 0;
}
break;
case 66:
if (var1.equals("B")) {
var2 = 1;
}
}
switch(var2) {
case 0:
case 1:
default:
}
}
2 面向对象
1、面向对象的三大特性?
在此之前,先来了解一下抽象的概念,抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。
- 封装
封装就是把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法。
- 继承
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。
- 多态
多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
2、Java 是如何实现多态的?
在 Java 中有两种形式可以使用多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。
方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。
3、重写和重载的区别?
-
重写(override):一般都是表示子类和父类之间的关系,其主要的特征是方法名相同,参数相同,但是具体的实现不同。
-
重载(Overload):首先是位于一个类之中或者其子类中,具有相同的方法名,但是方法的参数不同,返回值类型可以相同也可以不同。
主要区别如下:
1)重写发生在子类继承或接口实现类中,重载只发生在本类中;
2)二者均需要具有相同的方法名称;
3)重写的参数列表必须保持一致,重载的参数列表必须修改;
4)重写的返回参数必须保持一致,重载的参数列表可以修改;
5)重写的访问修饰符不能比父类中被重写的方法的访问权限更低,重载可以修改;
5)重写的异常可以减少或删除,但不能扩展,重载可以修改。
4、什么是向前引用?
所谓向前引用,就是在定义类、接口、方法、变量之前使用它们。
class MyClass {
int method() {return n; }
int m = method();
int n = 1;
}
// 如果简单地执行下面的代码,毫无疑问会输出1.
System.out.println(new MyClass().method());
// 使用下面的代码输出变量m,却得到0。
System.out.println(new MyClass().m);
比如上面的代码中,n 在 method 方法后定义,但 method 方法中可以先使用该变量。
至于为何两次输出结果不同,这是因为当 Runtime 运行 MyClass.class 文件时,首先会进行装载成员字段,而且这种装载是按顺序执行的,并不会因为 Java 支持向前引用,就首先初始化所有值。
首先,Runtime 会初始化 m 字段,这时就会调用 method 方法,在 method 方法中利用向前引用技术使用了 n。Runtime 为了实现向前引用,在进行初始化所有字段之前,还需要将所有的字段添加到符号表中。以便在任何地方(但需要满足 Java 的调用规则)都可以引用这些字段,不过由于还没有初始化这些字段,所以这时符号表中所有的字段都使用默认的值。
3 常用类库
3.1 基本包
1、java 和 javax 类库的区别?
Java 和 Javax 本质上是与 Java 编程语言的上下文一起使用的包。
起初标准 API 的内容都是使用 java 包发布,而非标准 API 内容在 javax 下发布。因此,API 所必需的包是 java,而 javax 包含 API 的扩展名。甚至可以说 javax 只是一个带有 x 的java,代表了扩展。
随着时间的推移,作为 javax 发布的扩展成为 Java API 的组成部分。但将扩展从 javax 包移动到 java 包太麻烦,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准 API 的一部分。
3.2 Object
1、Object 有哪些公共方法?
-
clone() 创建斌返回此对象的副本;
-
equals() 判断;
-
getclass() 返回该对象的运行类;
-
hashcode() 返回对象的哈希码值;
-
notify() 唤醒正在等待对象监听器的线程;
-
notifyAll() 唤醒正在等待对象监听器的所有线程;
-
wait() 导致当前线程等待,直到另一个线程调用该对象的
notify()
或notifyAll()
方法; -
toString() 返回此对象的字符串表示形式;
-
finalize() 当垃圾收集确定不需要该对象时,垃圾回收器调用该方法。
2、equals 和 == 的区别?
equals()
方法是被用来检测两个对象的内容是否相同。而 ==
操作符是用来比较两个变量的值是否相等,即就是比较变量在内存中的存储地址是否相同。
3、equals 和 hashcode 的联系?
hashCode()
是 Object 类的公共方法,返回一个哈希值。如果两个对象根据 equals()
方法比较相等,那么调用这两个对象中任意一个对象的 hashCode()
方法必须产生相同的哈希值,如果两个对象根据 eqauls()
方法比较不相等,那么产生的哈希值不一定相等(碰撞的情况下还是会相等的)。
以下是关于 hashcode 的一些结论:
- 两个对象相等,hashcode 一定相等,但两个对象不等,hashcode不一定不等;
- hashcode 相等,两个对象不一定相等,但 hashcode 不等,两个对象一定不等。
4、深拷贝和浅拷贝的区别?
- 浅拷贝
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。
- 深拷贝
被复制对象的所有变量都含有与原来的对象相同的值。而那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深拷贝把要复制的对象所引用的对象都复制了一遍。
3.3 String
1、String s=new String(“x”) 创建了几个对象?
1 个或 2 个都有可能。首先会创建一个 String 类型的变量 s。在类加载到此处之前没有出现 “x” 字面量的话,加载此处时还会额外创建一个对应 “x” 的 String 常量对象。在符合规范的JVM上,执行到此处 new 关键字时会创建。
2、说说 String 的 intern() 方法?
Stirng 中的 intern()
是个 Native 方法,它会首先从常量池中查找是否存在该常量值的字符串,若不存在则先在常量池中创建,否则直接返回常量池已经存在的字符串的引用。
String s1="aa";
String s2=s1.intern();
System.out.print(s1==s2); // true
如上述一段代码中,对象 s1 与 s2 就是同一个对象。
3、String、StringBuffer 和 StringBuilder 区别?
首先 String 和 StringBuffer 主要区别是性能上。因为 String 自身是不可变对象,每次对 String 类型进行操作都等同于产生了一个新的 String 对象,然后指向新的 String 对象。所以尽量不要对 String 进行大量的拼接操作,否则会产生很多临时对象,导致 GC 影响系统性能。
当然,JVM 也对 String 拼接做了一定优化,如果几个在编译期就能够确定的字符串常量进行拼接,则直接优化成拼接结果。
又因为 StringBuffer 中的每个方法都被 synchronized 修饰,是线程安全的,但也影响了一定性能,故 JDK 1.5 中,新增了 StringBuilder 这个非线程安全类。