文章标题带有*号是指热门的知识点
前言
参考 三分恶博主、JavaGuide等文章
一、Java概述
Java是一门面向对象的编程语言,吸收了C++语言的各种优点(所以Java的前身语言是C++,笔试题考过)
1. Java语言特点
- 面向对象(封装,继承,多态)
- 跨平台性
Java 是“一次编写,到处运行(Write Once,Run any Where)”的语言,采用 Java 语言编写的程序具有很好的可移植性,而保证这一点的正是 Java 的虚拟机机制。在引入虚拟机之后,Java 语言在不同的平台上运行不需要重新编译。
字节码
*,Java程序经过编译之类产生的.class文件,字节码能够被虚拟机识别,从而实现Java程序的跨平台性。
Java 程序从源代码到运行主要有三步:
- 编译 :将代码(.java)编译成虚拟机可以识别理解的字节码(.class)
- 解释 :虚拟机执行Java字节码,将字节码翻译成机器能识别的机器码(执行速度会相对比较慢)
- 引进 JIT(Just in Time Compilation) 编译器,即运行时编译。当 JIT 编译器完成第一次编译后,会将字节码对应的机器码保存下来,下次可以直接使用
- HotSpot 采用了惰性评估(Lazy Evaluation)。根据二八定律,消耗大部分系统资源的只有那一小部分的代码(热点代码),而这也是 JIT 所需要编译的部分。JVM 会根据代码每次被执行的情况收集信息并相应地做出一些优化,因此执行的次数越多,它的速度就越快。
- 执行 :对应的机器执行二进制机器码
跨平台性:只需要把Java程序编译成Java虚拟机能识别的Java字节码,不同的平台安装对应的Java虚拟机,这样就可以可以实现Java语言的平台无关性。
目前为止,跨平台已经不是 Java 最大的卖点,各种 JDK 新特性也不是。目前市面上虚拟化技术已经非常成熟,通过 Docker 很容易实现跨平台。
JDK 9 引入了一种新的编译模式 AOT(Ahead of Time Compilation) 。这种编译模式会在程序被执行前就将其编译成机器码,属于静态编译(C、 C++,Rust,Go 等语言就是静态编译)。
| 区别 | JIT | AOT |
|---|---|---|
| 优点 | 具备更高的极限处理能力,降低请求的最大延迟 | 提高 Java 程序的启动速度,避免预热时间长 |
| 缺点 | 预热等各方面的开销 | 无法支持 Java 的一些动态特性,如反射、动态代理、动态加载、JNI(Java Native Interface)等,需要针对性地去做适配和优化 |
| 适用 | 支持动态特性 | 当下的云原生场景,支持微服务架构 |
- 支持多线程
C++11开始(2011年)引入了多线程库,在 Windows、Linux、macOS 都可以使用std::thread和std::async创建线程。 - 编译与解释并存
*
- 编译型语言是指编译器针对特定的操作系统将源代码一次性翻译成可被该平台执行的机器码,执行速度比较快,开发效率比较低。【 C、C++、Go、Rust 】
- 解释型语言是指解释器对源程序逐行解释成特定平台的机器码并立即执行,开发效率比较快,执行速度比较慢。【 Python、JavaScript、PHP 】
Java 语言具有编译型语言和解释型语言的特征。因为 Java 程序要经过先编译,后解释两个步骤。
由 Java 编写的程序需要:
- 先编译,生成字节码(.class 文件)
- 字节码必须再经过JVM,解释成操作系统能识别的机器码,在由操作系统执行
2. JDK vs JRE vs JVM
- JDK
Java Development Kit
作为功能⻬全的 Java SDK(Software Development Kit,即软件开发工具包),拥有 JRE 所拥有的⼀切,还有编译器(javac)和⼯具(如 javadoc 和 jdb),能够创建和编译程序。 - JRE
Java Runtime Environment
Java 运⾏时环境。作为运⾏已编译 Java 程序所需的所有内容的集合,包括Java 虚拟机(JVM)、Java 类库、Java 命令和其他的⼀些基础构件,但它不能⽤于创建新程序。 - JVM
Java Virtual Machine
Java虚拟机,Java程序运行在Java虚拟机上。针对不同系统的实现(Windows,Linux,macOS)不同的JVM(只要满足 JVM 规范,每个公司、组织或者个人都可以开发自己的专属 JVM)。因此,Java语言可以实现跨平台。

3. Java SE vs Java EE
- Java SE
Java Platform,Standard Edition
Java 平台标准版,Java 编程语言的基础,包含了支持 Java 应用程序开发和运行的核心类库以及虚拟机等核心组件。 - Java EE
Java Platform, Enterprise Edition
Java 平台企业版,建立在 Java SE 的基础上,包含了支持企业级应用程序开发和部署的标准和规范(如 Servlet、JSP、EJB、JDBC、JPA、JTA、JavaMail、JMS)。 Java EE 可以用于构建分布式、可移植、健壮、可伸缩和安全的服务端 Java 应用程序。
| 区别 | JavaSE | Java EE |
|---|---|---|
| Java | 基础版本 | 高级版本 |
| 开发 | 桌面应用程序或简单的服务器应用程序 | 复杂的企业级应用程序或 Web 应用程序 |
4. Java vs C++
| 区别 | Java | C++ |
|---|---|---|
| 访问内存 | 程序内存 | 指针 |
| 继承 | 单继承 | 多继承 |
| 释放内存 | GC | 手动释放 |
| 重载 | 方法重载 | 方法重载+操作符重载 |
5. 标识符 vs 关键字
- 标识符:为程序、类、变量、方法等取的名字
- 关键字:赋予了特殊含义的标识符,只能用于特定的地方
6. 自增自减运算符*
b = a++:先赋值给 b,再自增 1
int i = 1;
i = i++;
System.out.println(i); // 1
运行结果为 1
对于JVM而言,它对自增运算的处理步骤如下:
- 定义一个
临时变量来接收i的值,此时值为1 i自增运算,值为2临时变量赋给了第二步 值为2 的i
所以最后的结果为1,相当于以下代码:
int i = 1;
int temp = i;
i++;
i = temp;
System.out.println(i); // 1
以下运行结果则为2
int i = 1;
i = ++i;
System.out.println(i); // 2
7. 移位运算符*
被操作的数据被视为二进制数,移位就是将其向左或向右移动若干位的运算。
- 可以从小数点的左右移动去理解
<<:左移运算符,向左移若干位,高位丢弃,低位补零。x << 3等价于 x * 23(不溢出的情况下)。>>:带符号右移,向右移若干位,高位补符号位,低位丢弃。正数高位补 0,负数高位补 1。x >> 1,相当于x 除以 2。>>>:无符号右移,忽略符号位,空位都以 0 补齐。- double,float 不能移位操作
- 支持的类型只有int和long,
- 编译器在对short、byte、char类型进行移位前,都会将其转换为int类型再操作。
引入二进制中负数的加减运算(补码)
- 原码:将负数的绝对值转换成二进制数,然后最高位加上负号。例如,-3的原码表示为:10011。
- 补码:将负数的绝对值转换成二进制数,然后按位取反,再加上1。例如,-3的补码表示为:11101。
补码在进行加减运算时具有特殊性质,可以简化计算,没有符号位的特殊处理。这使得补码表示更方便用于数值计算,并且可以统一处理正数和负数。
- 在原码表示中,最高位表示符号位,0表示正数,1表示负数。
- 在补码表示中,最高位表示权重最大的负数的符号位,0表示正数,1表示负数。
原码和补码两种表示方式的区别在于符号位的不同,这也是采用补码表示的优势所在。
- 原码表示方式中,如果要进行加减运算,需要先判断操作数的符号,然后再进行运算。
- 补码表示方式中,直接进行加减运算,无需额外的符号判断,可以简化运算过程。
假设我们要计算两个数的和:
- A = 5(原码为00000101) B = -3(原码为10000011)。
- A(补码) = 00000101 B(补码) = 11111101
- 和(补码)= 00000010
可以看到,通过补码运算得到的和为2,同时也不需要对符号位进行特殊操作。
Integer.toBinaryString(i):将i转为二进制
| 数值变化 | 十进制 | 二进制 |
|---|---|---|
| 初始值 | -1 | 11111111111111111111111111111111 |
| i <<= 10 | (-1) * 210 = -1024 | 11111111111111111111110000000000 |
注:-1的所有二进制表示中全为数字1,由于int是32位,因此有32个1
如果移位的位数超过数值所占有的位数会怎样?
如 int 类型左移/右移位数大于等于 32 位操作时,会先求余后再进行左移/右移操作
同理,long类型则对应64位
在int类型中:
- 左移/右移 32 位相当于不进行移位操作(32%32=0)
- 左移/右移 42 位相当于左移/右移 10 位(42%32=10)
二、基础语法
1. 数据类型*
| 字节数 | 1 | 2 | 4 | 8 |
|---|---|---|---|---|
| 数据类型 | byte | short | float | double |
| boolean | char | int | long |
- byte short char -> int -> long -> float -> double(从低到高)
- byte short char之间不能互相转换
- 在 ASCII 表中,char字符对应的十进制数值:
- A ~ Z:65 - 90
- a ~ z :97 - 122
- 超过
long整型的数值- long l = Long.MAX_VALUE;
- l + 1 == Long.MIN_VALUE; // 超过最大值
- 1Byte(字节) = 8 bit(位),即字节数乘以8为各数据类型的位数,如long型的位数 = 8字节 * 8 = 64位
- byte、short、int、long取值范围:-2位数-1 ~ 2位数-1-1
最高位表示符号(0 表示正数,1 表示负数),其余位表示数值部分。所以,如果我们要表示最大的正数,我们需要把除了最高位之外的所有位都设为 1。如果我们再加 1,就会导致溢出,变成一个负数
byte a = (byte)129; // 输出 - 127
byte 表示的范围为 [-128 ~ 127],-129超了两个数,为-127
2. 自动类型转换 vs 强制类型转换*
(1)自动类型转换
当把一个表数范围小的数值或变量直接赋给另一个表数范围大的变量时,可以进行自动类型转换
(2)强制转换*
小杯里的水倒进大杯没问题,但大杯的水倒进小杯就不行了,可能会溢出
float f=3.4;不正确
3.4 是单精度数,将双精度型(double)赋值给浮点型(float)属于下转型(down-casting,也称为窄化)会造成精度损失,因此需要强制类型转换为float f =(float)3.4;float f =3.4F
short s1 = 1; s1 = s1 + 1;编译出错
1 是 int 类型,因此 s1+1 运算结果也是 int 型,需要强制转换类型才能赋值给 short 型。short s1 = 1; s1 += 1;
正确编译,因为 s1+= 1;相当于 s1 = (short)(s1 + 1); 其中有隐含的强制类型转换- long 类型的数据一定要在数值后面加上
L,否则解析为 int 类型
参考了数值类型数据混合运算文章
| 情况 | 具体运算 |
|---|---|
| byte,short,char混合运算 | 各自数据 转换成 int类型 |
| 浮点型、整型的混合运算 | 运算式转换成最大容量(即取值范围最大)的数据类型 |
| 数值型+数值型 | 普通加法 |
| 字符串类型+任意类型 | 字符串拼接 |
注:以float x = a + b + c;为例,运算时,先计算右侧的a + b + c,在右侧计算完得出了右侧的综合数据类型,再与左侧的float类型比较大小。
3. 包装类型的缓存机制
(1)复用缓存内对象 VS 创建新对象
- Byte,Short,Integer,Long 默认创建了数值 [-128,127] 的相应类型缓存数据
- Character 创建了数值在 [0,127] 范围的缓存数据
- Float,Double 并没有实现缓存机制
超出对应范围仍然会去创建新的对象,缓存的范围区间的大小只是在性能和资源之间的权衡。
- 装箱:将基本类型用它们对应的引用类型包装起来
Integer i = 10;底层:装箱调用包装类的
valueOf()方法
- 拆箱:将包装类型转换为基本数据类型
int n = i底层:拆箱调用
xxxValue()方法
Integer i1 = 40;等价于Integer i1=Integer.valueOf(40);
发生了装箱,即直接从缓存获取 40 这个值Integer i2 = new Integer(40);
创建新的对象- 因此,
i1==i2为 false。
结合以下代码方便理解
Integer i1 = 33;
Integer i2 = 33;
System.out.println(i1 == i2);// true
Float i11 = 333f;
Float i22 = 333f;
System.out.println(i11 == i22);// false
(2)整型包装类对象的值比较
结论:包装类对象的等值比较使用equals判断*
- [ -128 ~ 127 ] 范围内:复用已有对象,
使用==判断- 虽然在这个范围内,但IDEA 里代码提示包装类等值比较只能用
equals
- 虽然在这个范围内,但IDEA 里代码提示包装类等值比较只能用
- [ -128 ~ 127 ] 范围外:在堆上产生,不复用已有对象,使用
equals判断
Integer a = 127;
Integer b = 127;
Integer b1 = new Integer(127);
System.out.println(a.equals(b)); //true
System.out.println(b.equals(b1)); //false
Integer c = 128;
Integer d = 128;
System.out.println(c.equals(d)); //false
4. 浮点数运算精度丢失问题
通过BigDecimal解决,参考另一篇笔记 BgiDecimal的使用。
5. switch参数类型*
- byte、short、char、int、enum(Java5)、String(Java7)
- 不支持 long
6. public、private、protected、default*

7. 重载(overload) vs 重写(override)
-
重载:编译期的多态性
发生在一个类中,同名的方法有不同的参数列表(参数类型不同、参数个数不同或者二者都不同) -
重写:运行期的多态性
子类与父类之间,要求子类被重写方法与父类被重写方法:- 返回类型是 void 和基本数据类型,则返回值重写时不可修改
- 返回值是引用类型,可以返回该引用类型的子类
- 访问符不能做更严格的限制(可以降低限制,使访问权限更大)
- 不能比父类被重写方法声明更多的异常(里氏代换原则)
8. 可变长参数(Java5)
- 可变参数编译后实际会被转换成一个数组
- 本身接受 0 个或者多个参数
- 只能作为函数的最后一个参数,其前面允许0到多个其他参数
// 接受 0 个或者多个参数
public static void method1(String... args) {
//......
}
public static void method2(String arg1, String... args) {
//......
}
注:方法重载的会优先匹配固定参数的方法。
9. 成员变量 vs 静态变量 vs 局部变量
| 区别 | 成员变量 | 静态变量 | 局部变量 |
|---|---|---|---|
| 归属 | 类实例 | 类 | 方法 / 代码块 |
| 修饰符 | public , private ,final | static,final | final |
| 储存位置 | 堆内存 | 方法区(共享数据区)的静态区 | 栈内存 |
| 生存时间 | 与对象共存亡 | 与类共存亡 | 与方法共存亡 |
| 赋值 | 以类型的默认值赋值 | 必须显式赋值 | 不会⾃动赋值 |
10. 静态变量、实例变量、静态方法、实例方法
-
静态变量: static 修饰的变量,也称为类变量
属于类,不属于类的任何一个对象。一个类不管创建多少个对象,静态变量在内存中有且仅有一个副本
实现让多个对象共享内存 -
实例变量:也称为成员变量
必须依存于某一实例,需要先创建对象然后通过对象才能访问到它 -
静态方法:static修饰的方法,也被称为类方法
在外部调⽤静态⽅法时,可以使⽤类名.⽅法名、(易和实例方法调用搞混,不建议)的⽅式。对象名.⽅法名
静态方法里不能访问类的非静态成员变量和方法(即不能访问实例成员变量和实例方法) -
实例⽅法
依存于类的实例,需要使用对象名.⽅法名的⽅式调用;可以访问类的所有成员变量和方法
静态方法为什么不能调用非静态成员?
- 静态方法属于类,在类加载时会分配内存,可以通过类名直接访问。而非静态成员属于实例对象,只有在对象实例化之后才存在,需要通过类的实例对象去访问。
- 在类的非静态成员不存在时,静态方法已经存在,此时调用在内存中还不存在的非静态成员,属于非法操作。
11. 参数传递*
Java 的参数是以值传递的形式传入方法中,而不是引用传递。
一个对象以参数形式传入,若在该方法内定义了新的对象,则方法内的操作都属于新的对象,与原对象无关。
public class Dog {
String name;
Dog(String name) {
this.name = name;
}
String getName() {
return this.name;
}
void setName(String name) {
this.name = name;
}
String getObjectAddress() {
return super.toString();
}
}
public class PassByValueExample {
public static void main(String[] args) {
Dog dog = new Dog("A");
System.out.println(dog.getObjectAddress()); // Dog@4554617c
func(dog);
System.out.println(dog.getObjectAddress()); // Dog@4554617c
System.out.println(dog.getName()); // A
}
private static void func(Dog dog) {
System.out.println(dog.getObjectAddress()); // Dog@4554617c
dog = new Dog("B"); // 创建新的
System.out.println(dog.getObjectAddress()); // Dog@74a14482
System.out.println(dog.getName()); // B
}
}
以参数的形式传入,在方法内没有创建新对象,此时的操作就是修改原对象。
class PassByValueExample {
public static void main(String[] args) {
Dog dog = new Dog("A");
func(dog);
System.out.println(dog.getName()); // B
}
private static void func(Dog dog) {
dog.setName("B"); // 操作传入的参数
}
}
三、面向对象基础
1. 构造方法
一个类即使没有声明构造方法也会有默认的不带参数的构造方法。这也是为什么我们在创建对象的时候后面要加一个括号(因为要调用无参的构造方法)
- 名字与类名相同
- 没有返回值,但不能用 void 声明构造函数
- 生成类的对象时自动执行,无需调用。
2. 面向对象(封装、继承、多态)
- 封装
封装把⼀个对象的属性私有化,同时提供⼀些可以被外界访问的属性的⽅法。 - 继承
关于继承有以下三个要点:
- ⼦类拥有⽗类对象所有的属性和⽅法(包括私有属性和私有⽅法),但是⽗类中的私有属性和⽅法⼦类是⽆法访问,只是拥有。
- ⼦类可以拥有⾃⼰属性和⽅法,即⼦类可以对⽗类进⾏扩展。
- ⼦类可以⽤⾃⼰的⽅式实现⽗类的⽅法。
- 多态
- 程序中定义的引⽤变量所指向的具体类型和通过该引⽤变量发出的⽅法调⽤在编程时并不确定,⽽是在程序运⾏期间才确定,即⼀个引⽤变量到底会指向哪个类的实例对象,该引⽤变量发出的⽅法调⽤到底是哪个类中实现的⽅法,必须在由程序运⾏期间才能决定。
- 实现多态
- 继承(多个⼦类对同⼀⽅法的重写)
- 接口(实现接⼝并覆盖接⼝中同⼀⽅法)
3. 抽象类(abstract class) vs 接口(interface)
| 区别 | 接口 | 抽象类 |
|---|---|---|
| 方法 | 抽象方法(Java 8 开始接⼝⽅法可以有默认实现) | 抽象方法+非抽象方法 |
| 方法修饰符 | public | public 、 protected 和 default |
| 变量 | 仅能有static、final修饰 | 不限 |
| 实现 | 类 → 多个接口 | 类 → 一个抽象类 |
JDK7 ~ JDK9 Java 中接⼝的变化:
| 版本 | 区别 |
|---|---|
| ≤ JDK7 | 有常量变量和抽象⽅法 |
| JDK8 | 有默认⽅法和静态⽅法 |
| JDK9 | 私有⽅法和私有静态⽅法 |
4. 浅拷贝 vs 深拷贝
(1)浅拷贝
在堆上创建一个新的对象(区别于引用拷贝的一点),如果原对象内部的属性是引用类型,浅拷贝会直接复制内部对象的引用地址,即拷贝对象和原对象共用同一个内部对象
(2)深拷贝
完全复制整个对象,包括这个对象所包含的内部对象
(3)引用拷贝
两个不同的引用指向同一个对象。
来自JavaGuide的解释图片如下:

5. == vs equals*
equals()方法存在于Object类中,Object类是所有类的直接或间接父类因此,所有的类都有
equals()方法
| 区别 | == | equals() |
|---|---|---|
| 基本类型 | 值是否相等 | – |
| 引用类型 | 是否引用同个对象(比较地址) | 原始的equals作用和==一样,比较对象地址;重写的equals,一般比较对象的值是否相等 |
重写equals(),则必须重写hashcode()
hashCode()的作用是获取哈希码(int 整数),也称为散列码。哈希码的作用是确定该对象在哈希表中的索引位置。

总结:hashcode和equals同时判断,才能确定两个对象是否相等
规定:重写 equals() 时必须重写 hashCode() 方法
理由:两个相等对象的 hashCode 值必须是相等。
如果重写 equals() 时没有重写 hashCode() 方法,可能会出现 equals 方法判断是相等的两个对象,hashCode 值却不相等的情况。
6. String
- 不属于基本数据类型
- final修饰,不可继承
(1)String,StringBuffer,StringBuilder
| 区别 | String | StringBuilder | StringBuffer |
|---|---|---|---|
| 可变性 | 不可变 | 可变 | 可变 |
| 线程安全 | 安全(不可变) | 不安全 | 安全(内部使用 synchronized 进行同步) |
| 使用 | 操作少量 | 单线程+大量数据 | 多线程+大量数据 |
String不可变:
被 final 关键字:
- 修饰的类不能被继承
- 修饰的方法不能被重写
- 修饰的变量是
- 基本数据类型则值不能改变
- 引用类型则不能再指向其他对象(但在字符串中仅仅只是引用不指向其他对象,内容是可变的)
因此,final 关键字修饰的数组保存字符串并不是 String 不可变的根本原因,因为这个数组保存的字符串是可变的(final 修饰引用类型变量的情况)。
String 真正不可变有下面几点原因:
- 保存字符串的数组被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法
- String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变
Java 9 为何要将 String 的底层实现由 char[] 改成了 byte[] ?
新版的 String 其实支持两个编码方案:Latin-1 和 UTF-16。
如果字符串中包含的汉字没有超过 Latin-1 可表示范围内的字符,那就会使用 Latin-1 作为编码方案。Latin-1 编码方案下,byte 占一个字节(8 位),char 占用 2 个字节(16),byte 相较 char 节省一半的内存空间。
JDK 官方就说了绝大部分字符串对象只包含 Latin-1 可表示的字符。如果字符串中包含的汉字超过 Latin-1 可表示范围内的字符,byte 和 char 所占用的空间是一样的。
(2)创建对象
String str1 = new String("abc")
通过 new String() 在堆里创建一个"abc" 字符串对象实例。String str2 = "abc"
两个语句都会去字符串常量池中检查是否已经存在 “abc”
- 有则直接使用
- 没有则会在常量池中创建 “abc” 对象。
String s = new String("abc")创建了几个对象?
当字符串常量池没有 “abc”,此时会创建如下两个对象:
- 字符串常量池中的字符串字面量 “abc” 所对应的的实例
- 在堆中,通过 new String() 创建并初始化的,内容与"abc"相同的实例
若字符串常量池中已存在字符串对象“abc”的引用,则只会在堆中创建 1 个字符串对象“abc”
(3)字符串拼接
String的确是不可变的,“+”的拼接操作,其实是会生成新的对象。
String a = "hello ";
String b = "world!";
String ab = a + b;
JDK8优化后,以上代码相当于下列代码
String a = "hello ";
String b = "world!";
StringBuilder sb = new StringBuilder();
sb.append(a);
sb.append(b);
String ab = sb.toString();
即,通过“+”的字符串拼接方式,实际上是
- 通过 StringBuilder 调用 append() 方法实现
- 拼接完成后调用 toString() 得到一个 String 对象
(4)intern方法
使用 String.intern() 可以保证相同内容的字符串变量引用同一的内存对象。
// 判断在常量池中没有“Java”,则在堆中创建字符串对象"Java-1"
// 将字符串对象”Java“的引用保存在字符串常量池中
String s1 = "Java";
// 直接返回字符串常量池中字符串对象”Java“对应的引用
String s2 = s1.intern(); // s1、s2->"Java-1"
// 常量池已经存在“Java”,则s3在堆中创建对象"Java-2"
String s3 = new String("Java"); // s3->"Java-2"
// 优先去常量池搜索,发现已有“Java”,则直接返回字符串常量池中字符串对象”Java“对应的引用,即指向"Java-1"
String s4 = s3.intern(); // s4->"Java-1"
// s1 和 s2 指向的是堆中的同一个对象
System.out.println(s1 == s2); // true
// s3 和 s4 指向的是堆中不同的对象
System.out.println(s3 == s4); // false
// s1 和 s4 指向的是堆中的同一个对象
System.out.println(s1 == s4); //true
常量运算时,编译器的优化:常量折叠,即在编译器就可以确定的值。如
String str3 = "str" + "ing";编译器会优化成String str3 = "string";
- 基本数据类型( byte、boolean、short、char、int、float、long、double)以及字符串常量。
- final 修饰的基本数据类型和字符串变量
- 字符串通过 “+”拼接得到的字符串、基本数据类型之间算数运算(加减乘除)、基本数据类型的位运算(<<、>>、>>> )
四、异常
在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable 类有两个重要的子类:
-
Exception:程序本身可以处理的异常,可以通过 catch 来进行捕获。Exception 又可以分为 以下
- Checked Exception 即 受检查异常 ,Java 代码在编译过程中,如果受检查异常没有被 catch或者throws 关键字处理的话,就没办法通过编译。
- Unchecked Exception 即 不受检查异常 ,Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。RuntimeException 及其子类都统称为非受检查异常
-
Error:程序无法处理的错误,无法通过并且不建议通过catch捕获 。例如 Java 虚拟机运行错误(Virtual MachineError)、虚拟机内存不够错误(OutOfMemoryError)、类定义错误(NoClassDefFoundError)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。
1. finally 中的代码不一定会执行
正常情况下,finally 中的代码一定会执行,从而有了不建议finally代码块里面包含return的说法(一定会执行的特质,使finally前面代码块的return会被覆盖)
在以下特殊情况时,finally 中的代码不会被执行:
- finally 之前虚拟机被终止运行,则finally 中的代码就不会被执行
- 程序所在的线程死亡
- 关闭 CPU
2. try-with-resources 代替 try-catch-finally
try-with-resources:创建资源的操作写在try()的括号中,则不需要在finally中编写关闭资源的操作
适用范围(资源的定义): 任何实现 java.lang.AutoCloseable或者 java.io.Closeable 的对象。类似于InputStream、OutputStream、Scanner、PrintWriter等的资源都需要调用close()方法来手动关闭
Scanner scanner = null;
try {
scanner = new Scanner(new File("D://read.txt"));
while (scanner.hasNext()) {
System.out.println(scanner.nextLine());
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (scanner != null) {
scanner.close();
}
}
try (Scanner scanner = new Scanner(new File("test.txt"))) {
while (scanner.hasNext()) {
System.out.println(scanner.nextLine());
}
} catch (FileNotFoundException fnfe) {
fnfe.printStackTrace();
}
15万+

被折叠的 条评论
为什么被折叠?



