本文部分内容节选自Java Guide, 地址: https://javaguide.cn/java/basis/java-basic-questions-03.html
基础(上) → 基础(中) → 基础(下)
Java 异常类层次图概览
在 Java 中, 所有的异常都有一个共同的祖先 java.lang
包中的 Throwable
类. Throwable
类有两个重要的子类:
Exception
: 程序本身可以处理的异常, 可以通过 catch
来捕获. Exception
又分为 受检查异常和不受检查异常. 受检查异常是必须处理的异常, 不受检查异常可以不处理Error
: 程序本身无法处理的异常, 不建议用 catch
来捕获. 常见的 Error
包括 Java虚拟机运行错误 Virtual Machine Error
, 内存不足的错误 OutOfMemoryError
……当这些异常发生的时候, 线程会自动终止前面已经简单提及, 这里再补充一下: Java 代码在编译阶段, 如果没有 catch
或 throws
关键字来处理 Checked Exception 的话, 就无法通过编译
除了 RuntimeException
及其子类外, 其他的 Exception
都是 Checked Exception
Unchecked Exception 也就是不受检查异常, Java代码在编译阶段, 即使没有 catch
或 throws
关键字来处理 Unchecked Exception, 也能通过编译
try
块:用于捕获异常. 其后可接零个或多个 catch
块, 如果没有 catch
块, 则必须跟一个 finally
块
catch
块:用于处理 try 捕获到的异常
finally
块:无论是否捕获或处理异常, finally
块里的语句都会被执行. 当在 try
块或 catch
块中遇到 return
语句时, finally
语句块将在方法返回之前被执行
代码示例
try {
System.out.println("I will throw an Exception");
throw new RuntimeException("Exception");
} catch (RunException e) {
System.out.println("I catch the Exception");
} finally {
System.out.println("Finally");
}
输出:
I will throw an Exception
I catch the Exception
Finally
⚠️特别注意!!! 千万不能在finally块中使用return! 当try 和 finally中都有 return
时, try 中的 return
会被忽略. 这是因为 try语句中的 return
返回值会保存在一个本地变量中, 如果执行了 finally中的 return
, 这个本地变量就会变成 finally 中 return
的返回值了
代码示例
public static void main(String[] args) {
System.out.println(f(2));
}
public static int f(int value) {
try {
return value * value;
} finally {
if (value == 2) {
return 0;
}
}
}
输出:
0
答案是不一定
以下三种情况会导致 finally 中的代码不会被执行
代码示例
try {
System.out.println("I will throw an Exception");
throw new RuntimeException("Exception");
} catch (RuntimeException e) {
System.out.println("I catch the Exception");
System.exit(1); // 注意, 这里终止掉了Java虚拟机
} finally {
System.out.println("Finally");
}
输出:
I will throw an Exception
I catch the Exception
try-with-resource
代替 try-catch-finally
?java.lang.AutoCloseable
或 java.io.Closeable
的对象try-with-resources
语句中,任何 catch 或 finally 块在声明的资源关闭后运行类似于 InputStream
, OutputStream
, Scanner
, PrintWriter
等资源都需要我们调用 close()
方法来手动关闭
可以在 try-with-resource
的 try语句块中用分号分隔定义多个资源
代码示例
try {
System.out.println("I will throw an Exception");
throw new RuntimeException("Exception");
} catch (RuntimeException re) { // 具体异常放前面
System.out.println("Runtime Exception Catched");
} catch (Exception e) { // 通用异常放后面
System.out.println("Exception catched");
}
错误代码示例
try {
System.out.println("I will throw an Exception");
throw new Runtime Exception("Exception");
} catch (RuntimeException re) {
log.error("Exception. System Error");
}
Java 泛型 是 JDK5 引入的新特性, 使用泛型参数, 可以提高代码的可读性以及稳定性
编译器可以对泛型参数进行检测, 并且通过泛型参数指定传入的参数类型
public class Generic {
private T key;
public Generic(T key) {
this.key = key;
}
public T getKey() {
return key;
}
}
如何实例化泛型类
Generic genericInteger = new Generic(114514);
public interface Generator {
public T method();
}
实现泛型接口, 不指定类型
class GeneratorImpl implements Generator {
@Override
public T method() {
return null;
}
}
实现泛型接口, 指定类型
class GeneratorImpl implements Generator {
@Override
public String method() {
return "Yonagi";
}
}
public static void printArray(E[] array) {
for (E element : array) {
System.out.println("%s", element);
}
}
使用泛型方法:
Integer[] intArray = {1, 1, 4, 5, 1, 4};
String[] stringArray = {"heng", "heng", "aaaaa"};
pringArray(intArray);
pringArray(stringArray);
动态代理的实现(基于反射)
public class DebugInvocationHandler implements InvocationHandler {
private final Object target;
public DebugInvocationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
System.out.println("before method " + method.getName());
Object result = method.invoke(target, args);
System.out.println("after method " + method.getName());
return result;
}
}
注解的实现(基于反射)
优点 : 提高代码的灵活性, 为框架的开箱即用提供了便利(Spring框架不少地方用了动态代理, 而动态代理本身是基于反射实现的)
缺点 : 性能偏差(但是影响不大), 反射带来的运行时分析类的能力, 也带来了安全问题, 例如它可以无视泛型参数的安全检查
Annotation
(注解) 是Java5 开始引入的新特性, 主要用于修饰类, 方法, 变量, 提供某些信息供程序在编译或运行时使用
注解本质上是继承了 Annotation
的特殊接口:
@Target(ElementType.Method)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
public interface Override extends Annotation {
}
注解只有在解析之后才会生效
常见场景:
对于不想序列化的变量, 可以使用 transient
关键字修饰
transient
关键字的作用是: 阻止实例中那些用此关键字修饰的变量序列化, 当对象被反序列化时, 被 transient
修饰的变量值不会被持久化和恢复
transient
只能修饰变量, 不能修饰类和方法transient
修饰的变量, 在反序列化之后变量值会被置成默认值static
变量因为不属于任何对象, 所以有没有 transient
关键字修饰都不会被序列化I/O, 即 Input/Output. 数据输入到内存为输入, 反之输出到外部存储的过程称为输出. I/O流在 Java 中分为 输入流和输出流, 根据数据处理方式又分为字符流和字节流
InputStream
/ Reader
: 所有输入流的基类, 前者是字节流, 后者是字符流OutputStream
/ Reader
: 所有输出流的基类, 前者是字节流, 后者是字符流同步阻塞I/O, 异步I/O, 同步非阻塞I/O, I/O多路复用, 信号驱动I/O
BIO属于同步阻塞IO模型
同步阻塞 IO 模型中, 应用程序发起 read 调用后, 会一直阻塞, 直到内核把数据拷贝到用户空间
NIO属于I/O多路复用模型
对于高负载, 高并发的网络应用程序, 应当使用 NIO
AIO属于异步IO模型
最后简单给一张图, 总结一下 Java 的三种I/O模型