Appearance
1、异常
基础概念

try-catch-finally
try-catch-finally 中哪个部分可以省略
// 示例:try-catch-finally结构
try {
// 代码块,可能会产生异常
} catch (ExceptionType1 e) {
// 处理ExceptionType1异常
} catch (ExceptionType2 e) {
// 处理ExceptionType2异常
} finally {
// 清理代码,总是会执行
}在Java中的try-catch-finally异常处理结构中:
try块:- 不可省略。必须有
try块,因为它定义了可能产生异常的代码段。
- 不可省略。必须有
catch块:- 可以省略,但如果省略
catch块,必须有finally块。 - 如果
try块中的代码可能抛出异常,通常需要至少一个catch块来处理这些异常。
- 可以省略,但如果省略
finally块:- 可以省略,但如果省略
finally块,必须有至少一个catch块。 finally块用于执行清理操作,无论是否捕获到异常,finally块总是会执行。
- 可以省略,但如果省略
因此,可以省略catch块或finally块中的任何一个,但不能两个都省略。你至少需要try和catch,或者try和finally组合。
Checked Exception 和 Unchecked Exception
Checked Exception 即 受检查异常 ,Java 代码在编译过程中,如果受检查异常没有被 catch或者throws 关键字处理的话,就没办法通过编译。
比如下面这段 IO 操作的代码:

除了RuntimeException及其子类以外,其他的Exception类及其子类都属于受检查异常 。常见的受检查异常有:IO 相关的异常、ClassNotFoundException、SQLException...。
Unchecked Exception 即 不受检查异常 ,Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。
RuntimeException 及其子类都统称为非受检查异常,常见的有(建议记下来,日常开发中会经常用到):
NullPointerException(空指针错误)IllegalArgumentException(参数错误比如方法入参类型错误)NumberFormatException(字符串转换为数字格式错误,IllegalArgumentException的子类)ArrayIndexOutOfBoundsException(数组越界错误)ClassCastException(类型转换错误)ArithmeticException(算术错误)SecurityException(安全错误比如权限不够)UnsupportedOperationException(不支持的操作错误比如重复创建同一用户)- ……
空指针异常
空指针异常是不受检测异常下运行期异常中的一种;当我们在尝试使用 null 引用的时候,会发生 NPE。
最基础也是比较需要注意的一个情况是字符串比较,在字符串比较的时候,保证常量放前面

如何避免指针异常
- 在使用之前先检查一下是否为 null
- 调用方法的时候进行参数检查
- 在设计方法或者API 的时候,尽量不要返回 null 值,而且返回一个空集合或者默认值;
- 初始化使用字段,保证不为 null
- 使用 Optional
- 使用断言(开发和测试环境)
使用示例:
// 示例:避免空指针异常的方法
public class NullPointerAvoidance {
public static void main(String[] args) {
String str = null;
// 1. 检查是否为 null
if (str != null) {
System.out.println(str.length());
}
// 2. 使用Optional类(Java 8+)
Optional<String> optionalStr = Optional.ofNullable(str);
optionalStr.ifPresent(System.out::println);
// 3. 安全调用方法(例如使用Apache Commons Lang)
int length = StringUtils.length(str); // 安全的,不会抛出空指针异常
// 4. 使用断言(仅在开发和测试环境中)
assert str != null : "字符串不应该为 null";
}
}Throwable 类常用方法
String getMessage(): 返回异常发生时的简要描述String toString(): 返回异常发生时的详细信息String getLocalizedMessage(): 返回异常对象的本地化信息。使用Throwable的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与getMessage()返回的结果相同void printStackTrace(): 在控制台上打印Throwable对象封装的异常信息
2、泛型
基本用法
Java 泛型(Generics) 是 JDK 5 中引入的一个新特性。
一般有三种使用方式:泛型类、泛型接口、泛型方法
泛型类
定义
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{
private T key;
public Generic(T key) {
this.key = key;
}
public T getKey(){
return key;
}
}使用:
Generic<Integer> genericInteger = new Generic<Integer>(123456);泛型接口
定义
public interface Generator<T> {
public T method();
}实现泛型接口,不指定类型:
class GeneratorImpl<T> implements Generator<T>{
@Override
public T method() {
return null;
}
}实现泛型接口,指定类型:
class GeneratorImpl<T> implements Generator<String>{
@Override
public String method() {
return "hello";
}
}泛型方法
public static < E > void printArray( E[] inputArray )
{
for ( E element : inputArray ){
System.out.printf( "%s ", element );
}
System.out.println();
}使用:
// 创建不同类型数组:Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3 };
String[] stringArray = { "Hello", "World" };
printArray( intArray );
printArray( stringArray );注意:静态泛型方法是没有办法使用类上声明的泛型的。只能使用自己声明的
<E>(静态方法的加载在类前面)
类型擦除、通配符
to be contined...
3、反射
反射是什么
Java反射是一种强大的机制,它允许程序在运行时检查或修改Java虚拟机中的类的行为。基本上,反射使得程序可以操作那些在编译时还不知道的类和对象。
使用场景:像我们常用的框架 Spring/Spring Boot、MyBatis 都大量使用了动态代理,而动态代理的实现也依赖于反射。
理解Java反射
- 动态性: 反射的核心是动态性。它使得你可以在运行时动态地创建对象、调用方法、访问字段,而不必在编译时具体知道类的名称和方法。
- API: Java反射的功能主要通过
java.lang.Class类及java.lang.reflect包中的类(如Method、Field、Constructor等)提供。 - 类元数据: 反射允许程序获取任何类的内部信息,包括类的成员方法、字段、构造函数、注解等。
为什么你使用 Spring 的时候 ,一个
@Component注解就声明了一个类为 Spring Bean 呢?为什么你通过一个@Value注解就读取到配置文件中的值呢?究竟是怎么起作用的呢?
这些都是因为你可以基于反射分析类,然后获取到类/属性/方法/方法的参数上的注解。你获取到注解之后,就可以做进一步的处理。
反射的优缺点:
- 优点:强大和灵活
- 缺点:
- 性能开销较大,使用不当可能导致代码难以理解和维护
- 可能带来安全问题
反射相关内容:
1、定义: 反射机制是在运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意个对象, 都能够调用它的任意一个方法。在java中,只要给定类的名字,就可以通过反射机制来获得类的所 有信息。 这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
哪里会用到反射机制?
jdbc就是典型的反射
4、注解
5、SPI
SPI 是什么东西
要知道它的作用,需要清楚为什么会有这个机制。
参考: https://javaguide.cn/java/basis/java-basic-questions-03.html#spi
参考: https://pdai.tech/md/java/advanced/java-advanced-spi.html
可以看一下这句话:SPI(Service Provider Interface),是JDK内置的一种 服务提供发现机制,可以用来启用框架扩展和替换组件。
Java 会提供一个公开的接口(标准服务接口),第三方会提供该接口的服务实现(不同厂商可以针对同一接口做出不同的实现)
而Java的SPI机制可以为某个接口寻找服务实现。

SPI 是什么
- SPI是一种服务发现机制。
- 它允许服务提供者通过配置文件提供服务实现,而服务使用者可以加载这些实现
SPI 实现原理
- Java SPI的实现基于
ServiceLoader类。 - 服务提供者在
META-INF/services目录下提供配置文件,其中指定接口的实现类。 ServiceLoader可以加载这些实现。
关于 SPI 如何为某个公开的标准服务接口找到其实现服务,可以看一个示例。
SPI机制的简单示例
网上示例:这里在新窗口打开
我们现在需要使用一个内容搜索接口,搜索的实现可能是基于文件系统的搜索,也可能是基于数据库的搜索。
- 先定义好接口
public interface Search {
public List<String> searchDoc(String keyword);
}- 文件搜索实现
public class FileSearch implements Search{
@Override
public List<String> searchDoc(String keyword) {
System.out.println("文件搜索 "+keyword);
return null;
}
}- 数据库搜索实现
public class DatabaseSearch implements Search{
@Override
public List<String> searchDoc(String keyword) {
System.out.println("数据搜索 "+keyword);
return null;
}
}- resources 接下来可以在resources下新建META-INF/services/目录,然后新建接口全限定名的文件:
com.cainiao.ys.spi.learn.Search,里面加上我们需要用到的实现类
com.cainiao.ys.spi.learn.FileSearch- 测试方法
public class TestCase {
public static void main(String[] args) {
ServiceLoader<Search> s = ServiceLoader.load(Search.class);
Iterator<Search> iterator = s.iterator();
while (iterator.hasNext()) {
Search search = iterator.next();
search.searchDoc("hello world");
}
}
}可以看到输出结果:文件搜索 hello world
如果在com.cainiao.ys.spi.learn.Search文件里写上两个实现类,那最后的输出结果就是两行了。
这就是因为ServiceLoader.load(Search.class)在加载某接口时,会去META-INF/services下找接口的全限定名文件,再根据里面的内容加载相应的实现类。
这就是spi的思想,接口的实现由provider实现,provider只用在提交的jar包里的META-INF/services下根据平台定义的接口新建文件,并添加进相应的实现类内容就好。
6、序列化和反序列化
7、语法糖
参考: