目录
异常
Java的异常体系
异常的层次结构图
Throwable
Throwable 是 Java 语言中所有错误与异常的超类。
Throwable 包含两个子类:Error(错误)和 Exception(异常),它们通常用于指示发生了异常情况。
Error(错误)
Error 类及其子类:程序中无法处理的错误,表示运行应用程序中出现了严重的错误。
此类错误一般表示代码运行时 JVM 出现问题。通常有 Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如 OutOfMemoryError:内存不足错误;StackOverflowError:栈溢出错误。此类错误发生时,JVM 将终止线程。
这些错误是不受检异常,非代码性错误。因此,当此类错误发生时,应用程序不应该去处理此类错误。按照Java惯例,我们是不应该实现任何新的Error子类的!
Exception(异常)
程序本身可以捕获并且可以处理的异常。Exception 这种异常又分为两类:运行时异常和编译时异常。
- 运行时异常
都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。
- 非运行时异常 (编译异常)
是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
异常的捕获
异常捕获处理的方法通常有:
- try-catch
- 在一个 try-catch 语句块中可以捕获多个异常类型,并对不同类型的异常做出不同的处理
private static void readFile(String filePath) {
try {
// code
} catch (FileNotFoundException e) {
// handle FileNotFoundException
} catch (IOException e){
// handle IOException
}
}
2.同一个 catch 也可以捕获多种类型异常,用 | 隔开
private static void readFile(String filePath) {
try {
// code
} catch (FileNotFoundException | UnknownHostException e) {
// handle FileNotFoundException or UnknownHostException
} catch (IOException e){
// handle IOException
}
}
- try-catch-finally
- try-finally
- try-with-resource
异常基础总结
- try、catch和finally都不能单独使用,只能是try-catch、try-finally或者try-catch-finally。
- try语句块监控代码,出现异常就停止执行下面的代码,然后将异常移交给catch语句块来处理。
- finally语句块中的代码一定会被执行,常用于回收资源 。
- throws:声明一个异常,告知方法调用者。
- throw :抛出一个异常,至于该异常被捕获还是继续抛出都与它无关。
泛型
什么是泛型?有什么作用?
泛型(Generics)是 Java 中的一种特性,它允许类、接口和方法在定义时使用类型参数,从而实现代码的通用性和灵活性。泛型使得代码可以在编译时具有更强的类型安全性,并且提高了代码的重用性和可读性。
泛型的使用方式有几种?
泛型类
class Point<T>{ // 此处可以随便写标识符号,T是type的简称
private T var ; // var的类型由T指定,即:由外部指定
public T getVar(){ // 返回值的类型由外部决定
return var ;
}
public void setVar(T var){ // 设置的类型也由外部决定
this.var = var ;
}
}
public class GenericsDemo06{
public static void main(String args[]){
Point<String> p = new Point<String>() ; // 里面的var类型为String类型
p.setVar("it") ; // 设置字符串
System.out.println(p.getVar().length()) ; // 取得字符串的长度
}
}
泛型接口
interface Info<T>{ // 在接口上定义泛型
public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型
}
class InfoImpl<T> implements Info<T>{ // 定义泛型接口的子类
private T var ; // 定义属性
public InfoImpl(T var){ // 通过构造方法设置属性内容
this.setVar(var) ;
}
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
}
public class GenericsDemo24{
public static void main(String arsg[]){
Info<String> i = null; // 声明接口对象
i = new InfoImpl<String>("汤姆") ; // 通过子类实例化对象
System.out.println("内容:" + i.getVar()) ;
}
}
泛型方法
public class GenericMethodExample {
// 泛型方法,T 是类型参数
public static <T extends Comparable<T>> T max(T x, T y) {
// 调用 compareTo 方法比较两个对象的大小
return x.compareTo(y) > 0 ? x : y;
}
public static void main(String[] args) {
// 测试泛型方法
Integer maxInteger = max(10, 20);
System.out.println("Max Integer: " + maxInteger);
Double maxDouble = max(3.5, 5.8);
System.out.println("Max Double: " + maxDouble);
String maxString = max("apple", "banana");
System.out.println("Max String: " + maxString);
}
}
项目中哪里用到了泛型?
集合类(Collection Classes): Java 中的集合类(如 ArrayList
、HashMap
等)经常使用泛型来定义元素的类型。例如:
ArrayList<String> list = new ArrayList<>();
HashMap<String, Integer> map = new HashMap<>();
泛型方法(Generic Methods): 有些方法可能会使用泛型以增加其通用性和灵活性,使其能够处理不同类型的数据。例如:
public static <T> T max(T x, T y) {
return x.compareTo(y) > 0 ? x : y;
}
自定义数据结构(Custom Data Structures): 在定义自定义的数据结构时,也可以使用泛型来增加其通用性和灵活性。例如:
public class Pair<K, V> {
private K key;
private V value;
// 构造方法、getter和setter等...
}
反射
何为反射?
反射(Reflection)是一种在运行时检查或操作类、方法、属性等程序结构的能力。在 Java 中,反射允许程序在运行时检查类的信息、调用类的方法、访问或修改类的属性等。通过反射,程序可以在运行时获取类的完整结构信息,并且可以动态地创建对象、调用方法、操作属性,从而实现更加灵活和通用的编程。
反射的优缺点?
优点:
-
灵活性: 反射允许程序在运行时动态地获取类的信息、调用方法、操作属性等,从而实现更加灵活和通用的代码编写。通过反射,程序可以在运行时根据需要动态地创建对象、调用方法,而不需要在编译时就确定类的具体结构。
-
通用性: 反射可以使得代码具有更高的通用性,因为它允许程序处理未知类型的对象。通过反射,程序可以在运行时操作不同类型的对象,从而实现更加通用的代码。
-
框架开发: 反射在框架开发中具有重要作用,例如 Spring 框架和 Hibernate 框架等都广泛使用了反射机制。通过反射,框架可以动态地加载类、创建对象、调用方法,从而实现框架的灵活性和可扩展性。
但是也会带来一些性能、安全性和代码复杂度的问题,因此在使用反射时需要根据具体情况进行权衡和选择
反射的应用场景?
框架开发: 许多 Java 框架(如 Spring、Hibernate、JUnit 等)都广泛使用了反射机制。
注解处理: 注解处理器通常使用反射来分析和处理注解。在编译时,注解处理器可以使用反射来获取注解的信息,并根据注解的内容生成相应的代码。通过反射,可以实现自定义的注解处理逻辑,从而实现代码生成、静态检查等功能。
序列化和反序列化: Java 中的序列化和反序列化机制通常会使用反射来实现。在序列化过程中,程序会使用反射来获取对象的结构信息,并将对象的状态保存到字节流中;在反序列化过程中,程序会使用反射来根据字节流恢复对象的状态。通过反射,可以实现对象的序列化和反序列化,从而实现对象的持久化存储和网络传输。
动态代理: 动态代理是指在运行时动态地生成代理类或者代理对象,从而实现对原始对象的方法调用进行拦截和增强。Java 中的动态代理通常会使用反射来实现代理类或者代理对象的动态生成和方法调用的动态处理。通过反射,可以实现基于接口或者类的动态代理,从而实现各种功能,如日志记录、性能监控、事务管理等。
动态代理的几种方式?
-
基于接口的动态代理(JDK 动态代理): 这是 Java 中最常见的动态代理方式。在基于接口的动态代理中,代理类实现了目标接口,并且使用
java.lang.reflect.Proxy
类来动态生成代理对象。该方式要求目标类必须实现一个接口,代理类通过实现同一接口来代理目标类的方法调用。 -
基于类的动态代理(CGLIB 动态代理): 这种方式是基于类继承的动态代理方式。在基于类的动态代理中,代理类继承了目标类,并且使用 CGLIB 库来动态生成代理类。与基于接口的动态代理相比,基于类的动态代理可以代理没有实现接口的类。
注解
何为注解
注解是 Java 中一种重要的语言特性,主要用于修饰类,方法或者变量,提供某些信息供程序在编译或者运行时使用。
注解的解析方式有几种?
编译期直接扫描
编译器在编译java代码的时候扫描对应的注解并处理,比如某个方法使用了@Override,注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法
运行期通过反射处理
像框架中自带的注解(比如Spring框架的@Value,@Component)都是通过反射来进行处理的。
SPI
何谓SPI?
SPI 全称为 Service Provider Interface,是 Java 提供的一种服务提供接口。它是一种用于扩展和替换组件的机制,允许程序在运行时动态地加载并替换服务实现类。
SPI和API有什么区别?
IO
BIO/NIO/AIO有什么区别?
-
BIO(Blocking I/O):
- 同步阻塞模式,传统的 I/O 模型。
- 在进行 I/O 操作时,线程会被阻塞,直到数据准备好并读取或写入完成。
- 适用于连接数较少且数据量较小的情况,如传统的 Socket 编程。
-
NIO(Non-blocking I/O):
- 同步非阻塞模式,引入了通道(Channel)和缓冲区(Buffer)的概念。
- 可以使用单个线程管理多个通道,通过轮询的方式实现非阻塞的 I/O 操作。
- 提供了选择器(Selector)的机制,可以监听多个通道的事件,并在事件就绪时进行处理。
- 适用于连接数较多且并发量较大的情况,如高性能的网络服务器编程。
-
AIO(Asynchronous I/O):
- 异步非阻塞模式,引入了异步 I/O 操作的概念。
- 使用了回调函数(Callback)的机制,当 I/O 操作完成时会触发回调函数来处理结果。
- 可以在进行 I/O 操作时不阻塞当前线程,而是在操作完成后再通知线程进行处理。
- 适用于需要处理大量并发连接且 I/O 操作耗时较长的情况,如高性能的网络编程和文件 I/O 操作。
总的来说,BIO 是传统的阻塞式 I/O 模型,NIO 是基于事件驱动的非阻塞式 I/O 模型,AIO 是异步非阻塞式 I/O 模型。它们各有优缺点,并且适用于不同的场景和需求。在实际应用中,可以根据具体的业务场景和性能需求选择合适的 I/O 模型。