Java核心知识1

Java基础

基础概念与常识

Java的优势和劣势

Java 是一门面向对象的编程语言

优势:跨平台性,丰富的库和框架,稳定性和安全性,强大的社区支持

劣势:性能较低,语法较为冗长,启动速度慢。

1.JVM vs JDK vs JRE?

JVM、JDK 和 JRE 是 Java 编程里很基础且重要的概念。

  • JVM(Java Virtual Machine)
    运行 Java 字节码的虚拟机,它屏蔽了操作系统的差异,让 Java 可以做到 一次编写,到处运行。JVM 就像是一个翻译,能把 Java 程序编译或解释成对应操作系统能理解的机器语言。比如内存管理、垃圾回收、即时编译(JIT)都在 JVM 里完成。

  • JRE(Java Runtime Environment)
    运行 Java 程序所需要的环境,里面包含了 JVMJava 核心类库,有了 JRE,就能运行 Java 程序,但不能编译。

  • JDK(Java Development Kit)
    提供给开发者的工具包,里面包含了 JRE + 开发工具(javac 编译器、调试工具等)。有了 JDK,才能写代码、编译、运行。

所以关系就是:JDK ⊃ JRE ⊃ JVM

如果只安装 JVM,本质上只能跑字节码,但没有标准类库,很多程序跑不起来;JRE 解决了运行环境的问题;而 JDK 则是完整的开发环境。。

JDK中两个最重要的程序:javac编译程序 java执行程序

img

2.什么是字节码?采用字节码的好处是什么?

字节码是 Java 等编程语言中一种中间形式的代码。当我们编写好 Java 源代码后,Java 编译器会把这些源代码编译成字节码,字节码文件的扩展名通常是**.class**。字节码既不是我们编写的高级 Java 代码,也不是计算机能直接执行的机器语言,它是一种通用的中间表示形式,但能方便地被转换成不同计算机能懂的指令。

采用字节码有以下几个好处:

  • 跨平台性。不同计算机的规则不一样,而字节码是一种与平台无关的中间表示,Java 虚拟机(JVM)可以在不同的平台上把字节码解释或编译成对应平台的机器语言,实现了‘一次编写,到处运行’,极大地提高了开发效率和程序的可移植性。

  • 安全性。字节码不是直接的机器语言,它隐藏了程序的底层实现细节,减少了信息泄露的风险。同时,JVM 在加载和执行字节码之前会进行严格的安全检查,防止恶意代码的执行,保障了程序运行的安全性。

  • 性能优化。JVM可以根据情况对字节码优化,让程序运行更快。。

Java 程序从源代码到运行的过程如下图所示:

img

  1. .java: 这是 Java 源文件的后缀名。Java 程序员使用文本编辑器或集成开发环境(IDE)编写 Java 代码,代码文件以 .java 为扩展名 ,它是用人类易读的高级编程语言编写的。
  2. javac 编译javac 是 Java Development Kit(JDK)提供的编译器命令。运行 javac 命令对 .java 源文件进行编译,将程序员编写的 Java 代码转换为一种中间形式。
  3. .class:编译后生成的文件,其扩展名为 .class ,这些文件包含的就是字节码。字节码是一种与平台无关的二进制格式,它不是机器能直接执行的代码,但可以被 Java 虚拟机(JVM)识别和处理。
  4. 解释器 & JIT
    • 解释器:在 JVM 中,解释器负责逐行读取并执行字节码。它会将字节码一条一条地转换为机器可理解的指令并执行,这种方式比较灵活,但执行效率相对较低。
    • JIT(Just - In - Time Compiler,即时编译器):为了提高执行效率,JVM 引入了 JIT。JIT 会分析字节码,找出那些被频繁执行的代码段(热点代码),然后将这些热点代码直接编译成机器码,下次执行时就无需再经过解释器解释,从而大大提高了程序的运行速度。
  5. 机器可理解的代码:经过解释器解释或者 JIT 编译后,最终生成计算机硬件能够直接理解和执行的机器语言代码。
  6. 计算机(显示器代表计算机终端 ):机器可理解的代码在计算机硬件上执行,实现 Java 程序的功能,在终端上呈现运行结果等。

3.为什么不全部使用 AOT 呢?

AOT(Ahead - Of - Time,提前编译) 就是提前把代码编译成机器能直接执行的指令。但我们不全用 AOT,主要有三个原因。

  • 一是占空间,AOT 编译后产生的机器码文件很大,占用大量硬盘空间。
  • 二是不灵活,程序运行时情况多变,AOT 提前编译好后没办法根据新情况调整。
  • 三是时间长,编译大型程序时,AOT 要花很长时间,要是修改代码重新编译,会降低开发效率。

4.为什么说 Java 语言“编译与解释并存”?

“说 Java 语言‘编译与解释并存’是因为 Java 程序的运行有两个关键步骤。

  • 首先,Java 代码会通过编译器一次性翻译成字节码,从 .java 文件变成 .class 文件,也就是生成字节码,这是编译的过程。
  • 然后,Java 虚拟机(JVM)会把字节码一句一句解释成计算机能直接执行的机器指令,让程序得以运行,这是解释的过程。
  • 所以 Java 兼具编译和解释两种特性。”

5.Oracle JDK vs OpenJDK?

“Oracle JDK 和 OpenJDK 有以下主要区别:

  • 首先,从开源性质上看,OpenJDK 是开源的,意味着它的源代码是公开的,大家可以免费使用,并且能参与到它的开发和改进中。而 Oracle JDK 是基于 OpenJDK 开发的商业版本
  • 其次,在支持服务方面,OpenJDK 依靠社区来提供支持,社区里的开发者会共同解决问题,但响应速度和专业性可能参差不齐。Oracle JDK 则有 Oracle 公司提供专业的技术支持,能更高效地解决遇到的问题。
  • 最后,在使用场景上,OpenJDK 适合个人开发者或者对成本敏感、对技术支持要求不高的项目。而 Oracle JDK 更适合大型的商业项目,这些项目需要稳定的性能和可靠的技术支持。”

5.1 OpenJDK存储库中的源代码与构建 Oracle JDK 的代码之间有什么区别?

“OpenJDK 存储库中的源代码与构建 Oracle JDK 的代码主要有以下区别:

  • 首先,它们有共同的基础。Oracle JDK 的代码是基于 OpenJDK 的,所以大部分核心代码是一样的。
  • 其次,Oracle JDK 有特色代码。Oracle 公司会在 OpenJDK 的基础上添加一些自己的代码,这些代码包含了独家的功能、性能优化或,安全增强特性。
  • 最后,许可方面有差异。OpenJDK 遵循开源许可协议,代码是公开的,开发者可以自由使用、修改和分发。而 Oracle JDK 在商业使用时可能会有不同的许可要求,甚至可能需要付费。”

5.2 Oracle JDK这么好,为什么还要有OpenJDK?

可以这样回答面试官:“虽然 Oracle JDK 在性能优化和官方支持方面表现出色,但 OpenJDK 也有其存在的重要意义。

  • 首先,从成本角度来看,OpenJDK 是开源免费的,对于一些预算有限的企业或者个人开发者来说,使用 OpenJDK 可以节省大量的费用。企业可以将节省下来的资金投入到其他更重要的业务研发中。
  • 其次,在开放性方面,OpenJDK 的代码是公开的,全球的开发者都可以参与到它的开发和维护中。这使得 OpenJDK 能够快速地吸收各方的优秀代码和建议,不断地进行改进和创新。
  • 最后,从技术发展的角度来看,OpenJDK 和 Oracle JDK 在很多方面是相互促进的。Oracle JDK 的很多更新和优化也会借鉴 OpenJDK 的成果,而 OpenJDK 也会吸收 Oracle JDK 中的一些优秀特性。两者共同推动了 Java 技术的发展。

所以,OpenJDK 的存在丰富了 Java 生态,为不同需求的用户提供了更多的选择。”

基本语法

自己笔记

img

img

img点击并拖拽以移动编辑

img

6.标识符和关键字的区别?

‘A’对应数字65,’a’对应数字97,‘0’对应数字48。

在我们编写程序的时候,需要大量地为程序、类、变量、方法等取名字,于是就有了 标识符 。简单来说, 标识符就是一个名字

关键字是被赋予特殊含义的标识符 。就是Java自己要用到的词。

Java 关键字可以按照功能分类:

  • 数据类型与定义类相关
    基本数据类型:byteshortintlongfloatdoublecharboolean
    定义类、接口、枚举:classinterfaceenum

  • 流程控制相关
    条件判断:ifelseswitchcasedefault
    循环:forwhiledo(先执行一次的循环)、break(跳出循环)、continue(跳过本次循环)、return(返回结果或提前结束方法)。
    类型判断:instanceof(判断一个对象是不是某个类或者接口的实例)。

  • 访问控制与修饰符相关
    访问控制:publicprivateprotected
    修饰符:staticfinalabstractsynchronized(线程同步)、volatile(保证多线程可见性)。

  • 类与对象操作相关
    extendsimplementsthissuper(父类对象)、new

  • 异常处理相关
    try(尝试执行代码)、catch(捕获 try 里出现的异常并处理)、finally(不管 try 里有没有异常,都会执行)、throw(主动抛出一个异常)、throws(在方法声明处表明这个方法可能会抛出的异常)。

  • 包相关
    packageimport

  • 其他
    transient(序列化时忽略该字段)、native(调用本地方法)。

虽然true,false,null看起来像关键字,实际上它们是字面值,也不可以作为标识符来使用。

7.自增自减运算符?

  • ++ 和 – 运算符可以放在变量之前,也可以放在变量之后。当运算符放在变量之前时,先自增/减,再赋值;当运算符放在变量之后时,先赋值,再自增/减。
  • 用一句口诀就是:“符号在前就先加/减,符号在后就后加/减”。
  • 自增自减运算符只能用于变量,不能用于常量或者表达式。比如 5++ 或者 (a + b)++ 这样的写法是错误的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
int a = 0;
int b = a++; // b = 0(先赋值,后自增),a 变成 1

int x = 0;
int y = ++x; // y = 1(先自增,后赋值),x 变成 1

// eg1
int[] arr = {10, 20, 30};
int i = 0;
System.out.println(arr[i++]); // 输出arr[0](10),然后i变成1
System.out.println(arr[++i]); // i先变成2,再输出arr[2](30)

// eg2
int[] nums = {5, 10, 15};
int index = 1;
nums[index]++; // nums[1]从10变成11
System.out.println(nums[index]); // 输出11

// eg3
for (int i = 0; i < 5; i++) {
// for (int i = 0; i < 5; ++i) { 与上面写法等效
System.out.print(i + " "); // 输出:0 1 2 3 4
}


// eg4
int i = 0;
while (i < 5) {
System.out.print(i++ + " "); // 输出:0 1 2 3 4
}

i = 0;
while (i < 5) {
System.out.print(++i + " "); // 输出:1 2 3 4 5
}

8.移位运算符

移位运算符是对二进制位进行操作的运算符,主要有左移运算符(<<)、右移运算符(>>)和无符号右移运算符(>>>)。

  • 左移运算符(<<):将一个数的二进制位向左移动指定的位数,右边空出的位用 0 填补。每左移一位,相当于该数乘以 2。例如,3 << 2,3 的二进制是 0000 0011,左移 2 位后变成 0000 1100,即 12,相当于 3 乘以 2 的 2 次方。
  • 右移运算符(>>):将一个数的二进制位向右移动指定的位数。正数左边空出的位用 0 填补;负数左边空出的位用 1 填补。每右移一位,相当于该数除以 2(取整)。例如,-8 >> 2,-8 的二进制是 1111 1000,右移 2 位后变成 1111 1110,即 - 2。这里要注意负数右移的结果和直接用除法计算有所不同。
  • 无符号右移运算符(>>>):无论操作数是正数还是负数,都将二进制位向右移动指定的位数,左边空出的位一律用 0 填补。例如,-8 >>> 2,-8 的二进制是 1111 1000,无符号右移 2 位后变成 0011 1110,即 62。

9.⭐️continue、break 和 return 的区别?

  • continue 只能用在循环语句中,当程序执行到 continue 语句时,会跳过本次循环体中剩余的语句,直接开始下一次循环。例如:
1
2
3
4
5
6
7
8
for (int i = 1; i <= 5; i++) {
if (i == 3) {
continue;
// 当i等于3时,continue语句会使Sout(i); 这行代码被跳过,直接进入下一次循环
}
System.out.println(i);
}
// 输出结果是 1、2、4、5
  • break 可用于循环语句switch 语句中,当执行到 break 语句时,会立即终止当前所在的循环或 switch 语句,跳出相应的代码块。
1
2
3
4
5
6
7
for (int i = 1; i <= 5; i++) {
if (i == 3) {
break;
}
System.out.println(i);
}
// 当 i 等于 3 时,break 语句会使整个 for 循环终止,所以输出结果是 1、2
  • return 用于方法中,当执行到 return 语句时,会立即结束当前方法的执行,并将返回值(如果有)返回给调用该方法的地方。
1
2
3
4
5
6
7
8
9
10
11
public class Main {
public static int getSum(int a, int b) {
return a + b;
// 计算 a 和 b 的和并返回结果
}
public static void main(String[] args) {
int result = getSum(2, 3);
System.out.println(result);
}
}
// return a + b; 语句会计算 a 和 b 的和,并将结果返回给 main 方法中的 result 变量

10.⭐️final关键字 final、finally、finalize 的区别?

  • 可修饰类、方法、变量
  • 修饰类:该类被称为最终类,特点是不能被继承
  • 修饰方法:该方法被称为最终方法,特点是不能被重写
  • 修饰变量:该变量只能被赋值一次
  • final 修饰基本类型的变量,变量存储的数据不能被改变
  • final 修饰引用类型的变量,变量存储的地址不能被改变,但地址所指向对象的内容可以被改变
  • 使用了static final 修饰的成员变量就被称为常量
    建议使用大写英文单词,多个单词使用下划线连接。SCHOOL_NAME = “大连理工大学”
  • finally 是 Java 中异常处理的一部分,用来创建 try 块后面的 finally 块。
  • 无论 try 块中的代码是否抛出异常,finally 块中的代码总是会被执行。
  • 通常,finally 块被用来释放资源,如关闭文件、数据库连接等。
  • finalize 是Object 类的一个方法,用于在垃圾回收器将对象从内存中清除出去之前做一些必要的清理工作。

变量

11.字符型常量和字符串常量的区别

  • 字符常量是单引号引起的一个字符,字符串常量是双引号引起的 0 个或若干个字符。
  • 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算;字符串常量代表一个地址值(该字符串在内存中存放位置)。
  • 字符常量只占 2 个字节; 字符串常量占若干个字节。
1
2
3
4
5
// 字符型常量 占2字节
public static final char LETTER_A = 'A';

// 字符串常量 占13字节。Hello, world!每个字符在UTF-8编码下都占用 1 个字节
public static final String GREETING_MESSAGE = "Hello, world!";

12.⭐️成员变量与局部变量, 实例变量与静态变量的区别?

img

成员变量与局部变量的区别?

img

1
2
3
4
5
6
7
8
9
class Example {
int instanceVar; // 实例变量(成员变量)
static int staticVar; // 静态变量(成员变量)

void method() {
int localVar = 10; // 局部变量(必须初始化)
System.out.println(localVar);
}
}

实例变量与静态变量的区别?

  • 静态变量(类变量):可以被类的所有实例共享。无论一个类创建了多少个对象,它们都共享同一份静态变量。有static修饰。
  • 实例变量每个对象中都有一份。无static修饰。

黄金法则:当思考”这个数据是否需要被所有对象共享?” → 是:用static → 否:用实例变量

img

  • 实例变量:存储在堆内存中,因为对象是在堆中创建的,实例变量作为对象的一部分也存于堆
  • 静态变量:存储在方法区,方法区主要存放类的静态数据等信息。

img

13.静态方法和实例方法的区别?

  • 静态方法(类方法):属于类本身,不依赖于类的任何实例
  • 实例方法:属于类的实例(对象),只有创建了对象之后才能使用

实例方法中可以出现this关键字,静态方法中不可以出现this关键字。

img

  • 静态方法:可以直接通过类名调用,也可以通过对象调用(但不推荐,容易混淆其性质)。
  • 静态方法没对象的时候也能执行,所以不能直接用对象的东西(实例变量/实例方法),因为可能“没人(对象)到场”。

14.⭐️⭐️⭐️重载和重写的区别?

重载:

  • 发生在同一个类中。
  • 方法名相同,但参数列表必须不同,返回值类型可以相同也可以不同。
  • 访问修饰符可以不同。

重写:

  • 发生在子类和父类之间,是一种继承关系的体现。
  • 方法名,参数列表,返回值类型必须与父类被重写的方法完全相同。
  • 子类方法的访问修饰符不能比父类被重写方法的访问修饰符更严格。例如,如果父类方法是 public 的,子类重写方法不能是 privateprotected 的。

15.什么是可变长参数?

  • 可变长参数是 Java 5 引入的语法,形如 Type… name,本质是一个数组,
  • 可变长参数就是允许方法接受不定数量的参数。只能有一个,且必须放在参数列表最后。
1
public static void method1(String arg1, String... args) { ... }
  • 遇到方法重载的情况,会优先匹配固定参数的方法,因为固定参数的方法匹配度更高
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class VariableLengthArgument {

public static void printVariable(String... args) {
for (String s : args) {
System.out.println(s);
}
}

public static void printVariable(String arg1, String arg2) {
System.out.println(arg1 + arg2);
}

public static void main(String[] args) {
printVariable("a", "b");
printVariable("a", "b", "c", "d");
}
}
// 输出 ab a b c d
  • java的可变参数编译后实际会转成一个数组。

java数据类型

16.java的数据类型

在Java中,数据类型分为两大类:

  • 基本数据类型(Primitive Types)
    直接存储值,内存分配在栈上,效率高。4大类8大种。整数,浮点,字符,布尔。

  • 引用数据类型(Reference Types)
    存储对象的引用(内存地址),内存分配在堆上,通过栈中的引用访问。
    1️⃣数组(Array):存储相同类型的多个元素。
    2️⃣​​​​​​​类(Class):用户自定义类:通过 class 关键字定义。系统定义类:如String类用于存储字符串,Date类用于处理日期和时间,Math类包含了很多数学计算的静态方法,如求绝对值(Math.abs())、求平方根(Math.sqrt())、求最大值(Math.max())等。ArrayList类是一种动态数组,能自动扩容,方便存储和操作多个元素。HashMap用于存储键值对,通过键可以快速查找对应的值。File:用于表示文件或目录,可以进行文件和目录的创建、删除、重命名等操作。BufferedReaderBufferedWriter:用于高效地读写文本文件。Exception:是所有异常类的父类,当程序出现异常时,可以捕获和处理异常。

    3️⃣接口(Interface):是一种抽象类型,定义了一组方法签名,类可以实现接口
    ArrayList list = new ArrayList<>();

17.⭐️Java中的几种基本数据类型?

整数类型

  • byte:1 字节,-128 到 127,默认值 0。
  • short:2 字节,-32768 到 32767,默认值 0。
  • int:4 字节,常用,默认值 0。
  • long:8 字节,默认值 0L。

浮点类型

  • float:4 字节,精度较低,默认值 0f。
  • double:8 字节,精度高,默认值 0d。

字符类型

  • char:2 字节,存储单个字符,默认值 ‘\u0000’。

布尔类型

  • boolean:只有 true 和 false,默认值 false,占 1 位。但在 Java 中,通常认为 boolean 类型的变量占 1 个字节。

18.⭐️⭐️⭐️基本类型和包装类型的区别

img点击并拖拽以移动编辑

img

img

  • 类型本质
    基本类型:是 Java 最基础的数据类型,是语言内置的。Java 有 8 种基本类型。
    包装类型:是为基本类型提供的面向对象的封装形式,是类。每种基本类型都有对应的包装类,例如 Integer 对应 intDouble 对应 double
  • 存储方式
    基本类型:变量直接存储数据值。对于局部变量,数据存储在栈内存中,访问速度快,因为栈内存的读写效率高。
    包装类型:属于对象,变量保存的是对象的引用,对象存储在堆内存中,这个引用存储在栈内存。访问时需要通过引用找到堆中的对象,相对来说速度会慢一些。
  • 默认值
    基本类型:有各自的默认值,像 int 的默认值是 0,boolean 的默认值是 false
    包装类型:默认值是 null,意味着不指向任何对象。
  • 使用场景
    基本类型:适用于普通的数学运算、逻辑判断等场景,操作简单直接,性能较高。
    包装类型:在泛型和集合框架中是必须使用的,因为泛型和集合只能存储对象;在进行序列化操作时,基本类型无法直接序列化,而包装类型可以。
  • 比较方式
    基本类型:使用 == 比较的是值是否相等。例如 int a = 5; int b = 5; a == b 比较的就是两个变量的值。
    包装类型:使用 == 比较的是引用是否指向同一个对象,若要比较值是否相等,需使用 equals() 方法

19.⭐️包装类型的缓存机制了解么?

在 Java 中,包装类型的缓存机制用于提高性能,减少内存消耗。对于某些包装类型,特定范围内的值会被缓存,避免了频繁创建新对象

  • ByteShortIntegerLong:缓存范围是 -128 到 127。例如,当我们使用 Integer a = 100; 时,会先检查缓存中是否已经有值为 100 的 Integer 对象,如果有就直接返回缓存中的对象,而不是重新创建。
  • Character:缓存范围是 0 到 127,也就是 ASCII 码表中前 128 个字符对应的 Character 对象会被缓存。
  • Boolean 也有缓存机制,它缓存了 truefalse 对应的两个 Boolean 对象。

如果超出对应范围,仍然会去创建新的对象,缓存的范围区间大小只是在性能和资源之间的平衡。两种浮点数类型的包装类Float Double并没有实现缓存机制。

所有整型包装类对象之间值的比较,全部使用equals 方法比较。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Integer i1 = 33;
Integer i2 = 33;
System.out.println(i1 == i2);// true

Float i11 = 333f;
Float i22 = 333f;
System.out.println(i11 == i22);// false

Double i3 = 1.2;
Double i4 = 1.2;
System.out.println(i3 == i4);// false

Integer i1 = 40;
Integer i2 = new Integer(40);
System.out.println(i1==i2);
//false

当使用 Integer i1 = 40; 时,Java 会自动进行装箱操作,也就是把基本数据类型 int40 转换为包装类型 Integer。由于 40Integer 缓存的范围(-128 到 127)之内,所以 i1 会直接引用缓存中的 Integer 对象。
Integer i2 = new Integer(40); 这种方式,无论数值是多少,都会在堆内存中创建一个新的 Integer 对象。也就是说,i2 指向的是新创建的对象,并非缓存中的对象。

20.⭐️⭐️⭐️自动装箱与拆箱了解吗?原理是什么?

自动装箱与拆箱是 Java 为了方便基本数据类型和包装类型之间的转换而提供的特性,以下是详细介绍:

概念

  • 自动装箱:是指将基本数据类型自动转换为对应的包装类型
  • 自动拆箱:是指将包装类型自动转换为对应的基本数据类型。
1
2
3
4
5
6
Integer i = 10; //装箱  
int n = i; //拆箱

// 等价于
Integer i = Integer.valueOf(10) // 自动装箱
int n = i.intValue() // 自动拆箱
  • 自动装箱:编译器会在需要进行装箱操作的地方,自动调用包装类的 valueOf() 方法。valueOf() 方法会先检查该值是否在缓存范围内(对于 Integer 是 -128 到 127),如果在范围内则返回缓存中的对象,否则创建一个新的对象。
  • 自动拆箱:编译器会在需要进行拆箱操作的地方,自动调用包装类的 xxxValue() 方法(xxx 代表基本数据类型)。

如果频繁拆装箱的话,会严重影响系统的性能,我们应该尽量避免不必要的拆装箱操作。

21.为什么浮点数运算的时候会有精度丢失的风险?

  • 计算机内部是用二进制来存储和处理数据的
  • float32 位来存储,double64 位存储,由于位数有限无法精确表示所有的小数,只能存储一个近似值。
  • 在进行浮点数运算时,这些近似值的误差会累积,从而导致最终结果出现精度丢失。

img

十进制 0.1 的分母是 10,质因数分解为 2 × 5。二进制仅能精确表示分母为 2 的幂次(如 1/2、1/4、1/8)的小数。

22.如何解决浮点数运算的精度丢失问题?

  • BigDecimal可以实现对浮点数的运算,不会造成精度丢失。
  • 使用字符串形式来创建BigDecimal对象,避免使用 double 类型的构造方法,因为 double 类型本身就存在精度问题。
1
2
3
// 使用字符串构造 BigDecimal 对象
BigDecimal num1 = new BigDecimal("0.1");
BigDecimal num2 = new BigDecimal("0.2");

23.超过 long 整型的数据应该如何表示?

当遇到超过 long 整型范围的数据时,可使用 BigInteger 类来表示。

  • BigInteger可以表示任意大小的整数,不受 long 类型范围的限制。
  • 可以使用字符串来创建 BigInteger 对象,因为字符串可以表示任意大的数字。
1
BigInteger bigNum = new BigInteger("123456789012345678901234567890");

面向对象基础

  • this 是一个变量,在实例方法或构造方法中指向当前对象
  • 当前对象 = 调用这个方法的那个对象。

img

24.面向对象和面向过程的区别?

面向过程编程(Procedural-Oriented Programming,POP)和面向对象编程(Object-Oriented Programming,OOP)是两种常见的编程范式,两者的主要区别:

  • 面向过程编程(POP):以函数为核心,强调步骤和流程,数据与方法分离
    面向过程把解决问题的过程拆成一个个方法,通过一个个方法的执行解决问题。
  • 面向对象编程(OOP):以对象为核心,强调封装、继承和多态,把数据和方法封装在一起,更易维护和扩展。
    面向对象会先抽象出对象,然后用对象执行方法的方式解决问题。
  • 简单任务适合面向过程,复杂系统更适合面向对象。

img

25.⭐️创建一个对象用什么运算符?对象实体与对象引用有何不同?

  • 创建对象用new 运算符,它会在堆中分配内存并调用构造器初始化,返回对象的引用。
  • 对象实体存放在堆中,是实际数据;
  • 对象引用存放在栈中,保存的是对象实体的地址,通过引用才能访问实体。
  • 一个实体可以有多个引用指向它。

img

26.如果⼀个类没有声明构造方法,该程序能正确执行吗?

如果一个类没有声明构造方法,也可以执行。

  • 若类没有声明任何构造方法,编译器会自动生成一个无参构造方法
  • 但如果声明了其他构造方法,编译器就不会自动生成,需要自己手写无参构造方法。

我们一直在不知不觉地使用构造方法,这也是为什么我们在创建对象的时候后面要加一个括号(因为要调用无参的构造方法)。

如果我们重载了有参的构造方法,记得都要把无参的构造方法也写出来(无论是否用到),因为这可以帮助我们在创建对象的时候少踩坑。

27.构造方法有哪些特点?是否可被 override?

构造方法的特点

  • 方法名必须与类名相同
  • 没有返回值类型,连 void 也不能有。
  • 生成类的对象时自动执行,无需调用

构造方法不能被重写

  • 重写(override)是要求方法名、参数列表和返回值类型都相同。而构造方法的名称必须与类名相同,子类和父类的类名不同,所以构造方法不符合重写的条件,不能被重写。
  • 重载(overload)可以,所以可以看到一个类中有多个构造函数的情况。

28.⭐️⭐️⭐️面向对象三大特征

面向对象编程有三大特征,分别是封装、继承和多态。

  • 封装,把数据和方法包装在对象中,通过访问修饰符控制外部访问,提高安全性和可维护性。
1
2
3
4
5
6
7
8
9
10
11
class Person {
private String name;
// 私有属性,外部无法直接访问
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//name属性被声明为private,外部类不能直接访问,只能通过getName()和setName()方法来获取和修改name值
  • 继承,指一个类(子类)可以继承另一个类(父类)的属性和方法。子类可以复用父类的代码,并且可以在此基础上添加自己的特性和方法,实现代码的复用和扩展。

子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但父类中私有属性和方法子类是无法访问,只是拥有。

1
2
3
4
5
6
7
8
9
10
11
class Animal {
public void eat() {
System.out.println("动物吃东西");
}
}
class Dog extends Animal {
public void bark() {
System.out.println("猫叫");
}
}
// Dog 类继承了 Animal 类的 eat() 方法,同时还添加了自己的 bark() 方法
  • 多态同一接口,不同实现,运行时可根据对象实际类型调用对应方法,提高扩展性和灵活性。
  • 多态代码的具体体现:People p1=new Student(); People p1=new Teacher();

多态分为两种形式:编译时多态(方法重载)和运行时多态(方法重写)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Shape {
public void draw() {
System.out.println("绘制图形");
}
}
class Circle extends Shape {
@Override
public void draw() {
System.out.println("绘制圆形");
}
}
class Rectangle extends Shape {
@Override
public void draw() {
System.out.println("绘制矩形");
}
}
public class Main {
public static void main(String[] args) {
Shape circle = new Circle();
Shape rectangle = new Rectangle();
circle.draw();
// 输出:绘制圆形
rectangle.draw();
// 输出:绘制矩形
}
}

//circle 和 rectangle 都是 Shape 类型的引用,
//但分别指向 Circle 和 Rectangle 对象,调用 draw() 方法时会根据实际对象执行不同的实现

29.⭐️⭐️⭐️接口和抽象类有什么共同点和区别?

共同点

  • 都不能直接实例化:只能被实现或继承后才能创建具体的对象
  • 都包含抽象方法:抽象方法没有方法体,必须在子类或实现类中实现。
  • 支持多态:可以通过父类引用或接口引用指向子类对象,实现运行时多态。

区别

  • 继承和实现
    一个类只能继承一个类(包括抽象类),因为 Java 不支持多继承。
    一个类可以实现多个接口,一个接口也可以继承多个其他接口。
  • 成员变量
    接口中的成员变量只能是public static final类型的,不能被修改且必须有初始值。
    抽象类的成员变量可以有任何修饰符(private, protected, public),可以在子类中被重新定义或赋值。

img

img

img

img


img

img


img


匿名内部类(Anonymous Inner Class)是没有名字的内部类,通常在创建对象的同时定义类,多用于临时实现一个接口或继承一个类。

我需要一个接口的实现类,但我只用一次,不想专门写一个文件或类名。

img

30.⭐️⭐️⭐️深拷贝和浅拷贝区别了解吗?什么是引用拷贝?

定义上的区别

  • 引用拷贝(Reference Copy):两个引用变量指向同一个对象,没有创建新对象。修改一个引用所指对象的内容,会影响另一个引用。

    1
    2
    3
    Person p1 = new Person("张三");
    Person p2 = p1; // p2 是对 p1 的引用拷贝
    // p1 和 p2 指向同一个 Person 对象。
  • 浅拷贝(Shallow Copy):会创建一个新对象,但如果对象中包含引用类型的成员变量,只会复制引用地址,不会复制引用指向的对象。结果是新旧对象共享引用对象,修改引用对象的内容会相互影响。

    1
    2
    3
    Person p1 = new Person("张三");
    Person p2 = p1.clone(); // 浅拷贝
    // p2 是新创建的对象,但 p2 和 p1 中的引用类型成员指向同一内存地址。
  • 深拷贝(Deep Copy):不仅复制当前对象本身,还会递归复制其引用类型成员所指向的对象,生成完全独立的副本。新旧对象互不影响。

    1
    2
    3
    Person p1 = new Person("张三");
    Person p2 = p1.deepClone(); // 深拷贝
    // p2 是完全独立的对象,包括 p2 中的引用类型成员也有自己的副本。

使用场景

  • 浅拷贝:适用于不需要独立复制引用类型数据的情况。
  • 深拷贝:适用于需要完全独立复制引用类型数据的情况。
  • 引用拷贝:无新对象,引用相同。
  • 浅拷贝:有新对象,但内部引用共享。
  • 深拷贝:有新对象,内部引用也独立。

shallow&deep-copy


Java核心知识1
https://blog.xirui.work/posts/63a01799.html
作者
xirui
发布于
2025年3月29日
许可协议