【 Java八股文面试 | JavaSE篇 】

摘要:本文围绕Java 核心基础八股文,聚焦面试高频考点展开。先讲 Java 语言特性与生态,含跨平台(依赖 JVM)、纯面向对象等特点,分析跨平台原理,明确 JVM/JDK/JRE 包含关系,说明编译与解释共存机制,厘清仅值传递的参数传递规则。再详解数据类型体系,明确八大基本类型分类及相关属性,讲类型转换规则与常见问题,最后聚焦包装类(以 Integer 为例),说明装箱拆箱、存在意义、与 int 差异及缓存池特性。

本文对小林coding进行二次整理的内容,网址:小林coding | Java面试学习

常见概念

1 说一下Java的特点

主要有以下的特点:

  • 平台无关性 :Java的 "编写一次,运行无处不在" 哲学是其最大的特点之一。Java编译器将源代码编译成字节码,该字节码可以在任何++安装了Java虚拟机(JVM)的系统上运行++。

  • 面向对象 :Java是一门严格的面向对象编程语言,几乎一切都是对象。面向对象编程特性使得代码更易于维护和重用,包括类、对象、继承、多态、抽象和封装。

  • 内存管理 :Java有自己的垃圾回收机制 ,++自动管理内存和回收不再使用的对象++。这样,开发者不需要手动管理内存,从而减少内存泄漏和其他内存相关的问题。

2 Java 的优势和劣势是什么?

优势:

1. 跨平台性("一次编写,到处运行"):得益于 Java 虚拟机(JVM),Java 程序可以在任何安装了 JVM 的操作系统上运行。

2. 强大的生态系统与社区:拥有极其丰富且成熟的库、框架(如 Spring, Hibernate)和工具,企业级应用支持极佳。

3. 面向对象:纯粹的面向对象设计,有助于构建大型、复杂且易于维护的应用程序。

4. 自动内存管理(垃圾回收):开发者无需手动管理内存,降低了内存泄漏等错误的可能性。

5. 多线程支持:提供了内置的、易于使用的多线程机制,方便进行并发编程。

6. 安全性与稳定性:拥有稳健的安全模型(如沙箱机制),并且语言本身和核心 API 非常稳定,版本更新注重向后兼容。

劣势:

1. 性能开销:由于通过 JVM 解释执行,相比 C/C++、Rust 等编译型语言,其执行效率和启动速度存在一定差距。

2. 内存消耗大:JVM 本身以及运行时的环境需要占用较多的内存资源。

3. 语法相对繁琐:代码冗长,样板代码多,尽管 Java 8 引入了 Lambda 表达式等,但开发效率仍不及 Python 等动态语言。

4. 不够灵活:严格的面向对象规定有时会显得笨重,对于快速脚本编写或简单任务不够简洁。

3 Java为什么是跨平台的?

Java 能支持跨平台,主要依赖于JVM关系比较大。

JVM也是一个软件,不同的平台有不同的版本。我们编写的Java源码,编译后会生成一种 .class 文件,称为++字节码文件++ 。++Java虚拟机就是负责将字节码文件翻译成特定平台下的机器码然后运行。也就是说,只要在不同平台上安装对应的JVM,就可以运行字节码文件,运行编写的Java程序。++

而这个过程中,我们编写的Java程序没有做任何改变,仅仅是通过JVM这一"中间层",就能在不同平台上运行,真正实现了"一次编译,到处运行"的目的。

JVM是一个"桥梁",是一个"中间件",是实现跨平台的关键,Java代码首先被编译成字节码文件,再由JVM将字节码文件翻译成机器语言,从而达到运行Java程序的目的。

编译的结果不是生成机器码,而是生成字节码,字节码不能直接运行,必须通过JVM翻译成机器码才能运行。不同平台下编译生成的字节码是一样的,但是由JVM翻译成的机器码却不一样。

所以,运行Java程序必须有JVM的支持,因为编译的结果不是机器码,必须要经过JVM的再次翻译才能执行。即使你将Java程序打包成可执行文件(例如 .exe),仍然需要JVM的支持。

++跨平台的是Java程序,不是JVM。++JVM是用C/C++开发的,是编译后的机器码,不能跨平台,不同平台下需要安装不同版本的JVM。

4 JVM、JDK、JRE三者关系?

它们之间的关系如下:

  • ++JVM是Java虚拟机,是Java程序运行的环境。++它负责将Java字节码解释或编译成机器码,并执行程序。JVM提供了内存管理、垃圾回收、安全性等功能,使得Java程序具备跨平台性。

  • ++JDK是Java开发工具包,是开发Java程序所需的工具集合。++它包含了JVM、编译器、调试器等开发工具,以及一系列的类库。JDK提供了开发、编译、调试和运行Java程序所需的全部工具和环境。

  • ++JRE是Java运行时环境,是Java程序运行所需的最小环境。++它包含了JVM和一组Java类库,用于支持Java程序的执行。JRE不包含开发工具,只提供Java程序运行所需的运行环境。

JDK = JRE+ 开发工具集 JRE = JVM+ 运行时类库

5 为什么Java解释和编译都有?

首先在Java经过编译之后生成字节码文件,++接下来进入JVM,就有两个步骤编译和解释。++如图:

编译性

  • Java源代码首先被编译成字节码,JIT 进行编译生成机器码文件并保存起来,以备下次使用。

解释性:

  • ++JVM中一个方法调用计数器,当累计计数大于一定值的时候,就使用JIT进行编译生成机器码文件。否则就是用解释器进行解释执行,然后字节码也是经过解释器进行解释运行的。++

所以Java既是编译型也是解释性语言,默认采用的是解释器和编译器混合的模式。

6 jvm是什么?

++JVM是 java 虚拟机,主要工作是解释自己的指令集(字节码)并映射到本地的CPU指令集和OS的系统调用。++

JVM屏蔽了与操作系统平台相关的信息,使得Java程序只需要生成在Java虚拟机上运行的目标代码(字节码),就可在多种平台上不加修改的运行,这是Java能够"一次编译,到处运行的"原因。

7 值传递和引用传递的区别?

在 Java 中,参数传递只有值传递一种方式,不存在真正的 "引用传递"。但很多人会混淆这两个概念,核心区别在于传递的是 "值的副本" 还是 "引用的副本"。

++值传递(Pass by Value)。传递的是实际值的副本 ,适用于基本数据类型 (如 intchar 等)++ ,修改方法内的参数副本,不会影响原变量的值。例子:

java 复制代码
public static void main(String[] args) {
    int num = 10;
    changeValue(num);
    System.out.println(num); // 输出 10(原变量未被修改)
}
​
public static void changeValue(int a) {
    a = 20; // 仅修改副本
}

++引用传递的误解(本质仍是值传递)。对于对象(引用类型) ,传递的是对象引用的副本(而非对象本身)。++

++两个引用(原引用和副本)指向同一个对象,因此通过++++副本修改对象内部数据++++,++++会影响原对象++++。++ 但如果修改副本的指向(如重新赋值),不会影响原引用的指向。示例:

java 复制代码
public class Person {
    String name;
    Person(String name) { this.name = name; }
}
​
public static void main(String[] args) {
    Person p = new Person("Alice");
    changeName(p);
    System.out.println(p.name); // 输出 "Bob"(对象内部被修改)
    
    changeReference(p);
    System.out.println(p.name); // 仍输出 "Bob"(原引用指向未变)
}
​
// 修改对象内部数据
public static void changeName(Person obj) {
    obj.name = "Bob"; // 副本和原引用指向同一个对象
}
​
// 修改副本的指向(不影响原引用)
public static void changeReference(Person obj) {
    obj = new Person("Charlie"); // 副本指向新对象,原引用仍指向旧对象
}

简单来说,Java 中所有参数传递都是值传递

  • 基本类型传递 "值的副本",修改副本不影响原值。

  • 引用类型传递 "引用的副本",通过副本可修改对象内容,但无法改变原引用的指向。

数据类型

1 八种基本的数据类型

Java支持数据类型分为两类: 基本数据类型和引用数据类型。

基本数据类型共有8种,可以分为三类:

  • 数值型:整数类型(byte、short、int、long)和浮点类型(float、double)

  • 字符型:char

  • 布尔型:boolean

8种基本数据类型的默认值、位数、取值范围,如下表所示:

数据类型 占用大小(字节) 位数 取值范围 默认值 描述
byte 1 8 -128(-2^7) 到 127(2^7 - 1) 0 是最小的整数类型,适合用于节省内存,例如在处理文件或网络流时存储小范围整数数据。
short 2 16 -32768(-2^15) 到 32767(2^15 - 1) 0 较少使用,通常用于在需要节省内存且数值范围在该区间的场景。
int 4 32 -2147483648(-2^31) 到 2147483647(2^31 - 1) 0 最常用的整数类型,可满足大多数日常编程中整数计算的需求。
long 8 64 -9223372036854775808(-2^63) 到 9223372036854775807(2^63 - 1) 0L 用于表示非常大的整数,当 int 类型无法满足需求时使用,定义时数值后需加 Ll
float 4 32 1.4E - 45 到 3.4028235E38 0.0f 单精度浮点数,用于表示小数,精度相对较低,定义时数值后需加 Ff
double 8 64 4.9E - 324 到 1.7976931348623157E308 0.0d 双精度浮点数,精度比 float 高,是 Java 中表示小数的默认类型。
char 2 16 '\u0000'(0) 到 '\uffff'(65535) '\u0000' 用于表示单个字符,采用 Unicode 编码,可表示各种语言的字符。
boolean truefalse false 用于逻辑判断,只有两个取值,常用于条件判断和循环控制等逻辑场景。

Float和Double的最小值和最大值都是以科学记数法的形式输出的,结尾的"E+数字"表示E之前的数字要乘以10的多少倍。比如3.14E3就是3.14×1000=3140,3.14E-3就是3.14/1000=0.00314。

注意一下几点:

  • Java八种基本数据类型的字节数:1字节(byte、boolean)、 2字节(short、char)、4字节(int、float)、8字节(long、double)

  • 浮点数的默认类型为double(如果需要声明一个常量为float型,则必须要在末尾加上f或F)

  • 整数的默认类型为int(声明Long型在末尾加上l或者L)

  • 基本数据类型的包装类:除了char的是Character、int类型的是Integer,其他都是首字母大写

  • char类型是无符号的,不能为负,所以是0开始的

2 int和long是多少位,多少字节?

  • int类型是 32 位(bit),占 4 个字节(byte),int 是有符号整数类型,其取值范围是从 -2^31 到 2^31-1 。例如,在一个简单的计数器程序中,如果使用int类型来存储计数值,它可以表示的最大正数是 2,147,483,647。如果计数值超过这个范围,就会发生溢出,导致结果不符合预期。
  • long类型是 64 位,占 8 个字节,long类型也是有符号整数类型,它的取值范围是从 -2^63 到 2^63 -1 ,在处理较大的整数数值时,果int类型的取值范围不够,就需要使用long类型。例如,在一个文件传输程序中,文件的大小可能会很大,使用int类型可能无法准确表示,而long类型就可以很好地处理这种情况。

3 long和int可以互转吗 ?

可以的,Java中的longint可以相互转换。++由于long类型的范围比int类型大,因此将int转换为long是安全的,而将long转换为int可能会导致数据丢失或溢出。++

++将int转换为long可以通过直接赋值或强制类型转换来实现。例如:++

java 复制代码
int intValue = 10;
long longValue = intValue; // 自动转换,安全的

long转换为int需要使用强制类型转换,但需要注意潜在的数据丢失或溢出问题。

java 复制代码
long longValue = 100L;
int intValue = (int) longValue; // 强制类型转换,可能会有数据丢失或溢出

在将long转换为int时,如果longValue的值超出了int类型的范围,转换结果将是截断后的低位部分。因此,在进行转换之前,建议先检查longValue的值是否在int类型的范围内,以避免数据丢失或溢出的问题。

4 数据类型转换方式你知道哪些?

1. 自动类型转换(隐式转换) :++当目标类型的范围大于源类型时,Java会自动将源类型转换为目标类型,不需要显式的类型转换。++ 例如,将int转换为long、将float转换为double等。

2. 强制类型转换(显式转换) :++当目标类型的范围小于源类型时,需要使用强制类型转换将源类型转换为目标类型。这可能导致数据丢失或溢出。++ 例如,将long转换为int、将double转换为int等。语法为:目标类型 变量名 = (目标类型) 源类型。

3. 字符串转换 :Java提供了将字符串表示的数据转换为其他类型数据的方法。将字符串转换为整型,可以使用**Integer.parseInt()** ;将字符串转换为浮点型,可以使用**Double.parseDouble()**等。

4. 数值之间的转换 :Java提供了一些数值类型之间的转换方法,如将整型转换为字符型、将字符型转换为整型等。这些转换方式可以通过类型的包装类来实现,例如Character类、Integer类等提供了相应的转换方法。

5 类型互转会出现什么问题吗?

基本数据类型转换的问题

当把小范围数据类型赋值给大范围数据类型时,Java 会自动进行类型转换,这种一般是安全的。

java 复制代码
int num = 100;
long bigNum = num; // 自动将int转换为long

++但是大范围数据类型赋值给小范围数据类型时,会发生数据数据溢出或者精度损失的问题。++

  • 数据溢出: 如果大范围数据类型赋值给小范围数据类型时,当目标类型无法容纳原数据时,就会发生数据溢出。比如下面,byte 类型的取值范围是 - 128 到 127。300 的二进制表示为00000001 00101100,强制转换为 byte 类型时,会丢弃高位字节,只保留低位的 8 位00101100,其十进制值为 44。
java 复制代码
int largeNum = 300;
byte b = (byte) largeNum; // b的值为44
  • 精度损失: 在进行浮点数类型的转换时,可能会发生精度损失。由于浮点数的表示方式不同,将一个单精度浮点数(float)转换为双精度浮点数(double)时,精度可能会损失,如果 double 转换为 int 也会发生精度损失的问题,如下:
java 复制代码
double d = 3.14;
int i = (int) d; // i的值为3,小数部分0.14被舍弃

对象引用转换的问题

++向上转型是自动进行的,而且是安全的,++如下:

java 复制代码
class Animal {}
class Dog extends Animal {}

Dog dog = new Dog();
Animal animal = dog; // 自动向上转型

++但是向下转型需要手动进行,并且存在风险。如果父类对象实际上并不是目标子类的实例,在转型时就会抛出异常:++

java 复制代码
Animal animal = new Animal();
Dog dog = (Dog) animal; // 运行时抛出ClassCastException

原因是Java 的对象在运行时会记录其真实类型,当进行向下转型时,Java 会检查对象的实际类型是否与目标类型兼容。如果不兼容,就会抛出ClassCastException

++解决方式是需要使用 instanceof 检查:++

java 复制代码
if (animal instanceof Dog) {
    Dog dog = (Dog) animal; // 只有确认animal是Dog的实例时才进行转型
}

6 为何用bigDecimal不用double?

++double会出现精度丢失的问题++,double执行的是二进制浮点运算,二进制有些情况下不能准确的表示一个小数,就像十进制不能准确的表示1/3(1/3=0.3333...),也就是说二进制表示小数的时候只能够表示能够用1/(2^n)的和的任意组合,但是0.1不能够精确表示,因为它不能够表示成为1/(2^n)的和的形式。

java 复制代码
System.out.println(0.05 + 0.01);
System.out.println(1.0 - 0.42);
System.out.println(4.015 * 100);
System.out.println(123.3 / 100);

输出:
0.060000000000000005
0.5800000000000001
401.49999999999994
1.2329999999999999

可以看到在Java中进行浮点数运算的时候,会出现丢失精度的问题。那么我们如果在进行商品价格计算的时候,就会出现问题。很有可能造成我们手中有0.06元,却无法购买一个0.05元和一个0.01元的商品。因为如上所示,他们两个的总和为0.060000000000000005。这无疑是一个很严重的问题,尤其是当电商网站的并发量上去的时候,出现的问题将是巨大的。可能会导致无法下单,或者对账出现问题。

而 Decimal 是精确计算 , 所以一般牵扯到金钱的计算 , 都使用 Decimal。

java 复制代码
import java.math.BigDecimal;

public class BigDecimalExample {
    public static void main(String[] args) {
        BigDecimal num1 = new BigDecimal("0.1");
        BigDecimal num2 = new BigDecimal("0.2");

        BigDecimal sum = num1.add(num2);
        BigDecimal product = num1.multiply(num2);

        System.out.println("Sum: " + sum);
        System.out.println("Product: " + product);
    }
}

//输出
Sum: 0.3
Product: 0.02

在上述代码中,我们创建了两个BigDecimal对象num1num2,分别表示0.1和0.2这两个十进制数。然后,我们使用add()方法计算它们的和,并使用multiply()方法计算它们的乘积。最后,我们通过System.out.println()打印结果。

这样的使用BigDecimal可以确保精确的十进制数值计算,避免了使用double可能出现的舍入误差。++需要注意的是,在创建BigDecimal对象时,应该使用字符串作为参数,而不是直接使用浮点数值,以避免浮点数精度丢失。++

7 装箱和拆箱是什么?

++装箱(Boxing)拆箱(Unboxing) 是将基本数据类型和对应的包装类之间进行转换的过程。++

java 复制代码
Integer i = 10;  //装箱
int n = i;   //拆箱

++自动装箱主要发生在两种情况,一种是赋值时,另一种是在方法调用的时候。++

这是最常见的一种情况,在Java 1.5以前我们需要手动地进行转换才行,而现在所有的转换都是由编译器来完成。

java 复制代码
//before autoboxing
Integer iObject = Integer.valueOf(3);
Int iPrimitive = iObject.intValue()

//after java5
Integer iObject = 3; //autobxing - primitive to wrapper conversion
int iPrimitive = iObject; //unboxing - object to primitive conversion

当我们在方法调用时,我们可以传入原始数据值或者对象,同样编译器会帮我们进行转换。

java 复制代码
public static Integer show(Integer iParam){
   System.out.println("autoboxing example - method invocation i: " + iParam);
   return iParam;
}

//autoboxing and unboxing in method invocation
show(3); //autoboxing
int result = show(3); //unboxing because return type of method is Integer

show方法接受Integer对象作为参数,当调用show(3)时,会将int值转换成对应的Integer对象,这就是所谓的自动装箱,show方法返回Integer对象,而int result = show(3);中result为int类型,所以这时候发生自动拆箱操作,将show方法的返回的Integer对象转换成int值。

自动装箱的弊端

自动装箱有一个问题,那就是在一个循环中进行自动装箱操作的情况,可能就会创建多余的对象,影响程序的性能。

8 Java为什么要有Integer?

Integer对应是int类型的包装类,就是把int类型包装成Object对象,对象封装有很多好处++,可以把属性也就是数据跟处理这些数据的方法结合在一起++,比如Integer就有parseInt()等方法来专门处理int型相关的数据。

另一个非常重要的原因就是在++Java中绝大部分方法或类都是用来处理类类型对象的++,如ArrayList集合类就只能以类作为他的存储对象,而这时如果想把一个int型的数据存入list是不可能的,必须把它包装成类,也就是Integer才能被List所接受。所以Integer的存在是很必要的。

泛型中的应用:

++在Java中,泛型只能使用引用类型,而不能使用基本类型。++因此,如果要在泛型中使用int类型,必须使用Integer包装类。例如,假设我们有一个列表,我们想要将其元素排序,并将排序结果存储在一个新的列表中。如果我们使用基本数据类型int,无法直接使用Collections.sort()方法。但是,如果我们使用Integer包装类,我们就可以轻松地使用Collections.sort()方法。

复制代码
List<Integer> list = new ArrayList<>();
list.add(3);
list.add(1);
list.add(2);
Collections.sort(list);
System.out.println(list);

转换中的应用:

++在Java中,基本类型和引用类型不能直接进行转换,必须使用包装类来实现。++例如,将一个int类型的值转换为String类型,必须首先将其转换为Integer类型,然后再转换为String类型。

复制代码
int i = 10;
Integer integer = new Integer(i);
String str = integer.toString();
System.out.println(str);

集合中的应用:

++Java集合中只能存储对象,而不能存储基本数据类型。++因此,如果要将int类型的数据存储在集合中,必须使用Integer包装类。例如,假设我们有一个列表,我们想要计算列表中所有元素的和。如果我们使用基本数据类型int,我们需要使用一个循环来遍历列表,并将每个元素相加。但是,如果我们使用Integer包装类,我们可以直接使用stream()方法来计算所有元素的和。

复制代码
List<Integer> list = new ArrayList<>();
list.add(3);
list.add(1);
list.add(2);
int sum = list.stream().mapToInt(Integer::intValue).sum();
System.out.println(sum);

9 Integer相比int有什么优点?

int是Java中的原始数据类型,而Integer是int的包装类。

Integer和 int 的区别:

1)基本类型和引用类型: 首先,int是一种基本数据类型,而Integer是一种引用类型。基本数据类型是Java中最基本的数据类型,它们是预定义的,不需要实例化就可以使用。++而引用类型则需要通过实例化对象来使用。++ 这意味着,使用int来存储一个整数时,不需要任何额外的内存分配,而++使用Integer时,必须为对象分配内存。在性能方面,基本数据类型的操作通常比相应的引用类型快。++

**2)自动装箱和拆箱:**其次,Integer作为int的包装类,它可以实现自动装箱和拆箱。自动装箱是指将基本类型转化为相应的包装类类型,而自动拆箱则是将包装类类型转化为相应的基本类型。这使得Java程序员更加方便地进行数据类型转换。例如,当我们需要将int类型的值赋给Integer变量时,Java可以自动地将int类型转换为Integer类型。同样地,当我们需要将Integer类型的值赋给int变量时,Java可以自动地将Integer类型转换为int类型。

3)空指针异常: 另外,++int变量可以直接赋值为0,而Integer变量必须通过实例化对象来赋值。如果对一个未经初始化的Integer变量进行操作,就会出现空指针异常。++这是因为它被赋予了null值,而null值是无法进行自动拆箱的。

10 那为什么还要保留int类型?

包装类是引用类型,对象的引用和对象本身是分开存储的,而对于基本类型数据,变量对应的内存块直接存储数据本身。

类型 变量存储位置 变量里存的内容 数据存储位置 访问方式
基本类型 数据本身 直接访问变量
包装类 堆里对象的地址 堆(对象里) 先按地址找对象,再取数据

++因此,基本类型数据在读写效率方面,要比包装类高效。++除此之外,在64位JVM上,在开启引用压缩的情况下,一个Integer对象占用16个字节的内存空间,而一个int类型数据只占用4字节的内存空间,前者对空间的占用是后者的4倍。

也就是说,不管是读写效率,还是存储效率,基本类型都比包装类高效。

11说一下 integer的缓存

++Java的Integer类内部实现了一个静态缓存池,用于存储特定范围内的整数值对应的Integer对象。++

++默认情况下,这个范围是-128至127。当通过Integer.valueOf(int)方法创建一个在这个范围内的整数对象时,并不会每次都生成新的对象实例,而是复用缓存中的现有对象,会直接从内存中取出,不需要新建一个对象。++

相关推荐
wen__xvn13 小时前
代码随想录算法训练营DAY10第五章 栈与队列part01
java·前端·算法
独自破碎E14 小时前
解释一下NIO、BIO、AIO
java·开发语言·nio
辞砚技术录14 小时前
MySQL面试题——索引2nd
数据库·mysql·面试
国强_dev14 小时前
在 Java 开发及其生态圈中“声东击西”的误导性错误
java·开发语言
FG.14 小时前
LangChain4j
java·spring boot·langchain4j
linweidong14 小时前
C++thread pool(线程池)设计应关注哪些扩展性问题?
java·数据库·c++
zfj32115 小时前
从源码层面解析一下ThreadLocal的工作原理
java·开发语言·threadlocal
墨笔之风15 小时前
java后端根据双数据源进行不同的接口查询
java·开发语言·mysql·postgres
程序猿阿伟15 小时前
《Python复杂结构静态分析秘籍:递归类型注解的深度实践指南》
java·数据结构·算法