Java基础常见面试题
基本数据类型
Java中有8种基本数据类型,分别为:
6 种数字类型:
- 4 种整数型:byte,short,int,long
- 2 种浮点型:float,double
1 种字符类型:char
1 种布尔型:boolean
| 基本类型 | 位数 | 字节 | 默认值 |
|---|---|---|---|
| byte | 8 | 1 | 0 |
| short | 16 | 2 | 0 |
| int | 32 | 4 | 0 |
| long | 64 | 8 | 0L |
| char | 16 | 2 | ‘u0000’ |
| float | 32 | 4 | 0.0f |
| double | 64 | 8 | 0.0d |
| boolean | 1 | 1 | false |
这八种类型对应的包装类型分别为:Byte、Short、Integer、Long、Float、Double、Character、Boolean。
基本类型和包装类型的区别?
- 用途:除了定义一些常量和局部变量之外,我们在其他地方比如方法参数、对象属性中很少使用基本数据类型来定义变量,并且,包装类型可以有多个值,而基本类型不可以。
- 存储方式:基本数据类型的局部变量存放在 Java 虚拟机的局部变量表中,基本数据类型的成员变量(未做 static 修饰)存放在 Java 虚拟机的堆中。包装类型属于对象类型,我们知道几乎所有对象实例都存在于堆中。
- 占用空间:相比于包装类型(对象类型),基本数据类型占用的空间往往非常小。
- 默认值:成员变量是包装类型时默认值是
null,而基本数据类型默认值不是null。 - 比较方式:对于基本数据类型来说,
==比较的是值, 对于包装类型来说,==比较的是对象的内存地址。所有包装类对象之间的比较,都应该使用equals()方法。
注:基本数据类型存储位置取决于其作用域及生命周期。
包装类的缓存机制?
Byte、Short、Integer、Long 这 4 种包装类默认创建数值 [-128,127] 的相应类型的缓存数据,
Character 创建了数值在 [0,127] 范围的缓存数据,Boolean 直接返回 True 或 False。
如果超出对应范围仍会创建新对象,缓存的范围区间大小只是在性能和资源之间权衡。
两种浮点类型的包装类Float、Double并未实现缓存机制。
Integer缓存源码:
1 | |
自动装箱与拆箱原理?
什么是自动拆装箱?
- 装箱:将基本类型用对应的有引用类型包装起来。
- 拆箱:将包装类型转换为基本数据类型。
装箱就是调用包装类的valueOf()方法,拆箱就是调用xxxValue()方法。
为什么浮点数运算会有精度丢失的风险?
计算机是二进制的,计算机在表示一个数字时,宽度是有限的,无限循环小数存储在计算机中会被截断导致小数精度丢失问题。
如何解决浮点数精度丢失问题?
BigDecimal可以实现对浮点数的运算,不会导致精度丢失。
超过long整型的数字如何表示?
BigInteger内部使用int[]数组来储存,运算效率较低。在使用 BigDecimal 时,为了防止精度丢失,推荐使用它的BigDecimal(String val)构造方法或者 BigDecimal.valueOf(double val) 静态方法来创建对象。
面向对象基础
面向对象三大特征
封装
封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。
继承
继承是使用已存在的类的定义作为基础建立新类的技术,新类可以增加新的数据和功能,也可以使用父类的功能,但不能选择性继承。继承可以提高代码的复用性,程序的可维护性。
继承的三大特点:
- 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问的,只有拥有。
- 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
- 子类可以拥有自己的方法形式定义父类的方法。(以后介绍)
多态
一个对象具有多种状态,具体表现为父类的引用指向子类的实例。
多态的特点:
- 对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
- 引用类型是从其方法调用的返回值类型来决定哪个方法,必须在程序运行时才能确定;
- 多态不能单用,只在子类存在任意父类方法不存在的时候;
- 如果子类没有重写父类方法,具体执行的是父类的写的方法,执行的就是父类的。
接口和抽象类异同?
接口和抽象类的共同点
实例化: 接口和抽象类都不能直接实例化对象,只能被实现(接口)或继承(抽象类)后才能创建具体的对象。
抽象方法: 接口和抽象类可以包含抽象方法。抽象方法没有方法体,必须在子类或实现类中实现。
接口和抽象类的区别
设计目的: 接口主要用于对类的行为进行约束,接口就拥有了对应的行为。抽象类主要用于代码复用,强调的是所属关系。
继承和实现: 一个类只能继承一个类(包括抽象类),因为 Java 不支持多继承。一个接口可以继承多个其他接口。
成员变量: 接口中的成员变量只能是
public static final,不能被修改且必须有初始值。抽象类的成员变量可以有任意修饰符,可以在子类被重新定义或赋值。方法:
在 Java 8 之前,接口中的方法默认是
public abstract,也就是只有方法声明。自 Java 8 起,可以在接口中定义default(默认)方法和static(静态)方法。自 Java 9 起,接口也可以包含private方法。抽象类可以包含抽象方法和具体方法。抽象类方法有具体实现,可以直接在抽象类中使用或在子类中实现。非抽象方法具有具体实现。
在 Java 8 以及以后的版本中,接口可以拥有新的方法声明:default 方法、static 方法和 private 方法。这些方法被接口的实现类使用时必须满足一定规则。
深拷贝和浅拷贝的区别?什么是引用拷贝?
浅拷贝: 浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的引用类型的变量,浅拷贝会直接复制内部对象的引用地址,也就是说浅拷贝对象和原对象共用一个内部对象。
深拷贝: 深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。
引用拷贝:两个不同的引用指向同一个对象。

Object类
Object类是一个特殊的类,是所有java类的父类,主要提供以下11种方法:
1 | |
== 与 equals() 的区别?
== 对基本类型与引用类型是不同的:
对基本类型是比较值。
对引用类型是比较对象的地址。
注:java只有值传递,对于 == 而言,不论是对于基本数据类型还是引用类型都是比较值。
equals()不能用于判断基本类型,只能用于判断对象是否相对。
equals()方法存在两种使用方法:
类没有重写
equals():等价于 == 。类重写
equals():比较两个对象中属性是否相等。
hashcode() 有什么用?
hashcode的作用是获取对象的hash码。有了 hashCode() 之后,判断元素是否在对应容器中的效率会更高(参考添加元素进HashSet的过程)。
为什么重写 equals() 时必须重写 hashCode() 方法?
因为两个相等的对象的 hashCode 值必须是相等。也就是说如果 equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。
如果重写 equals() 时没有重写 hashCode() 方法的话就可能会导致 equals 方法判断是相等的两个对象,hashCode 值却不相等。
String
String、StringBuffer、StringBuilder 的区别?
可变性:
String是不可变的。StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,不过没有使用final和private关键字修饰,最关键的是这个AbstractStringBuilder类还提供了很多修改字符串的方法比如append方法。线程安全:
String中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder是StringBuilder与 StringBuffer的公共父类。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。性能:每次对
String类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String对象。StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StringBuilder相比使用StringBuffer仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
string 类为什么是不可变的?
保存字符串的数组被
final修饰且为私有的,并且String类没有提供/暴露修改这个字符串的方法。String类被final修饰导致其不能被继承,进而避免了子类破坏String不可变。
字符串拼接使用 + 还是 StringBuilder?
Java自身并不支持运算符重载, + 与 += 是专门为 String类重载的,也是Java仅有的重载运算符。
字符串对象通过调用 + 拼接字符串的方式,本质上是通过StringBuilder调用append()方法,拼接完成后调用toString()得到String对象。
String s1 = new String(“abc”);这句话创建了几个字符串对象?
会创建 1 或 2 个字符串对象。
字符串常量池中不存在 “abc”:会创建 2 个 字符串对象。一个在字符串常量池中,由
ldc指令触发创建。一个在堆中,由new String()创建,并使用常量池中的 “abc” 进行初始化。字符串常量池中已存在 “abc”:会创建 1 个 字符串对象。该对象在堆中,由
new String()创建,并使用常量池中的 “abc” 进行初始化。
String#intern 方法有什么作用?
String.intern() 是一个 native (本地) 方法,用来处理字符串常量池中的字符串对象引用。
intern()方法的主要作用是确保字符串引用在常量池中的唯一性。当调用
intern()时,如果常量池中已经存在相同内容的字符串,则返回常量池中已有对象的引用;否则,将该字符串添加到常量池并返回其引用。
Java的反射机制
何为反射?
通过反射可以获取任意一个类的所有属性和方法,还可以调用。
反射的运用场景
Spring/Spring Boot、Mybatis框架都使用到了反射。
JDK动态代理的实现也依赖于反射:
1 | |
Java中的注解也运用到了反射。
反射机制的优缺点
优点:使代码更加灵活,为各种框架提供便利。
缺点:让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点。
获取Java对象的方式
- 知道具体类的情况:
1 | |
- 通过
Class.forName()方法传入类的全路径获取
1 | |
- 通过对象实例
Instance.getClass()获取
1 | |
- 通过类加载器
xxxClassLoader.loadClass()传入类路径获取:
1 | |
通过类加载器获取 Class 对象不会进行初始化,意味着不进行包括初始化等一系列步骤,静态代码块和静态对象不会得到执行。