Skip to content

核心特性

1、异常

基础概念

image.png

try-catch-finally

try-catch-finally 中哪个部分可以省略

// 示例:try-catch-finally结构
try {
    // 代码块,可能会产生异常
} catch (ExceptionType1 e) {
    // 处理ExceptionType1异常
} catch (ExceptionType2 e) {
    // 处理ExceptionType2异常
} finally {
    // 清理代码,总是会执行
}

在Java中的try-catch-finally异常处理结构中:

  1. try:
    • 不可省略。必须有try块,因为它定义了可能产生异常的代码段。
  2. catch:
    • 可以省略,但如果省略catch块,必须有finally块。
    • 如果try块中的代码可能抛出异常,通常需要至少一个catch块来处理这些异常。
  3. finally:
    • 可以省略,但如果省略finally块,必须有至少一个catch块。
    • finally块用于执行清理操作,无论是否捕获到异常,finally块总是会执行。

因此,可以省略catch块或finally块中的任何一个,但不能两个都省略。你至少需要trycatch,或者tryfinally组合。


Checked Exception 和 Unchecked Exception

Checked Exception 即 受检查异常 ,Java 代码在编译过程中,如果受检查异常没有被 catch或者throws 关键字处理的话,就没办法通过编译。

比如下面这段 IO 操作的代码:

image.png

除了RuntimeException及其子类以外,其他的Exception类及其子类都属于受检查异常 。常见的受检查异常有:IO 相关的异常、ClassNotFoundExceptionSQLException...。

Unchecked Exception不受检查异常 ,Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。

RuntimeException 及其子类都统称为非受检查异常,常见的有(建议记下来,日常开发中会经常用到):

  • NullPointerException(空指针错误)
  • IllegalArgumentException(参数错误比如方法入参类型错误)
  • NumberFormatException(字符串转换为数字格式错误,IllegalArgumentException的子类)
  • ArrayIndexOutOfBoundsException(数组越界错误)
  • ClassCastException(类型转换错误)
  • ArithmeticException(算术错误)
  • SecurityException (安全错误比如权限不够)
  • UnsupportedOperationException(不支持的操作错误比如重复创建同一用户)
  • ……
空指针异常

空指针异常是不受检测异常下运行期异常中的一种;当我们在尝试使用 null 引用的时候,会发生 NPE。

最基础也是比较需要注意的一个情况是字符串比较,在字符串比较的时候,保证常量放前面

image.png

如何避免指针异常

    1. 在使用之前先检查一下是否为 null
    1. 调用方法的时候进行参数检查
    1. 在设计方法或者API 的时候,尽量不要返回 null 值,而且返回一个空集合或者默认值;
    1. 初始化使用字段,保证不为 null
    1. 使用 Optional
    1. 使用断言(开发和测试环境)

使用示例:

// 示例:避免空指针异常的方法
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包中的类(如MethodFieldConstructor等)提供。
  • 类元数据: 反射允许程序获取任何类的内部信息,包括类的成员方法、字段、构造函数、注解等。

为什么你使用 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机制可以为某个接口寻找服务实现

image.png

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、语法糖


参考: