Java基础知识总结(下)

本文部分内容节选自Java Guide, 地址: https://javaguide.cn/java/basis/java-basic-questions-03.html

基础(上) → 基础(中) → 基础(下)

异常

Java 异常类层次图概览

Java基础知识总结(下)_第1张图片

Exception 和 Error 有什么区别?

在 Java 中, 所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类. Throwable 类有两个重要的子类:

  • Exception : 程序本身可以处理的异常, 可以通过 catch 来捕获. Exception 又分为 受检查异常和不受检查异常. 受检查异常是必须处理的异常, 不受检查异常可以不处理
  • Error : 程序本身无法处理的异常, 不建议用 catch 来捕获. 常见的 Error 包括 Java虚拟机运行错误 Virtual Machine Error , 内存不足的错误 OutOfMemoryError……当这些异常发生的时候, 线程会自动终止

Checked Exception(受检查异常) 和 Unchecked Exception(不受检查异常) 有什么区别?

前面已经简单提及, 这里再补充一下: Java 代码在编译阶段, 如果没有 catchthrows 关键字来处理 Checked Exception 的话, 就无法通过编译

除了 RuntimeException 及其子类外, 其他的 Exception 都是 Checked Exception

Unchecked Exception 也就是不受检查异常, Java代码在编译阶段, 即使没有 catchthrows 关键字来处理 Unchecked Exception, 也能通过编译

try-catch-finally 如何使用?

  • 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 中的代码一定会被执行吗?

答案是不一定

以下三种情况会导致 finally 中的代码不会被执行

  1. 关闭 Java 虚拟机
  2. 程序所在的线程死亡
  3. 关闭CPU

代码示例

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 ?

  1. 适用范围 : 任何实现java.lang.AutoCloseablejava.io.Closeable 的对象
  2. 关闭资源和finally块的执行顺序 : 在 try-with-resources 语句中,任何 catch 或 finally 块在声明的资源关闭后运行

类似于 InputStream , OutputStream , Scanner , PrintWriter 等资源都需要我们调用 close() 方法来手动关闭

可以在 try-with-resource 的 try语句块中用分号分隔定义多个资源

异常使用有哪些要注意的地方?

  • 不要把异常定义为静态变量, 因为这样会导致异常栈信息错乱. 正确的做法是每次要抛出一个异常的时候就 new 一个出来
  • 抛出的异常信息一定要有意义
  • 建议抛出更加具体的异常. 同理, 在异常处理的时候, 具体的异常放前面, 通用的异常放后面

代码示例

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");
}
  • 如果使用了日志 (例如log4j) 打印异常, 那么就不要再抛出异常, 或者说两者不应该存在于同一段代码中

错误代码示例

try {
	System.out.println("I will throw an Exception");
    throw new Runtime Exception("Exception");
} catch (RuntimeException re) {
	log.error("Exception. System Error");
}

泛型

什么是泛型? 有什么用?

Java 泛型 是 JDK5 引入的新特性, 使用泛型参数, 可以提高代码的可读性以及稳定性

编译器可以对泛型参数进行检测, 并且通过泛型参数指定传入的参数类型

泛型的使用方式有哪几种?

  1. 泛型类
public class Generic {
    private T key;
    
    public Generic(T key) {
        this.key = key;
    }
    
    public T getKey() {
        return key;
    }
}

如何实例化泛型类

Generic genericInteger = new Generic(114514);
  1. 泛型接口
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";
    }
}
  1. 泛型方法
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 {
    
}

注解的解析方式有哪几种?

注解只有在解析之后才会生效

  • 编译期直接扫描 : 编译器在编译Java代码的时候扫描出对应的注解并处理, 例如某个方法使用@Override注解, 编译器在编译的时候就会检测当前方法是否重写了父类对应的方法
  • 运行期通过反射处理 : 框架自带的注解一般都是通过反射来处理的

序列化和反序列化

什么是序列化和反序列化?

  • 序列化 : 把数据结构或对象转换成二进制字节流的过程
  • 反序列化 : 通过序列化形成的二进制字节流转换成数据结构或对象的过程

常见场景:

  • 对象进行网络传输, 发送方要进行序列化, 接收到对象后接收方要进行反序列化
  • 将对象存储到文件要进行序列化, 将对象从文件读出要进行反序列化
  • 将对象存储到数据库之前要进行序列化, 从数据库读取出来要用到反序列化
  • 对象存储到内存之前要进行序列化, 从内存读取要进行反序列化

如果有些字段不想序列化怎么办

对于不想序列化的变量, 可以使用 transient 关键字修饰

transient 关键字的作用是: 阻止实例中那些用此关键字修饰的变量序列化, 当对象被反序列化时, 被 transient 修饰的变量值不会被持久化和恢复

  • transient 只能修饰变量, 不能修饰类和方法
  • transient 修饰的变量, 在反序列化之后变量值会被置成默认值
  • static 变量因为不属于任何对象, 所以有没有 transient 关键字修饰都不会被序列化

I/O

I/O流了解吗?

I/O, 即 Input/Output. 数据输入到内存为输入, 反之输出到外部存储的过程称为输出. I/O流在 Java 中分为 输入流和输出流, 根据数据处理方式又分为字符流和字节流

  • InputStream / Reader : 所有输入流的基类, 前者是字节流, 后者是字符流
  • OutputStream / Reader : 所有输出流的基类, 前者是字节流, 后者是字符流

I/O流为什么要分为字节流和字符流

  • 字节流是由 Java 虚拟机将字节转换得到的, 这个过程比较耗时
  • 如果不知道编码类型的话, 使用字节流容易出现乱码

有哪些常用的I/O模型?

同步阻塞I/O, 异步I/O, 同步非阻塞I/O, I/O多路复用, 信号驱动I/O

Java 中常见的3种I/O模型

BIO(Blocking I/O)

BIO属于同步阻塞IO模型

同步阻塞 IO 模型中, 应用程序发起 read 调用后, 会一直阻塞, 直到内核把数据拷贝到用户空间

Java基础知识总结(下)_第2张图片

NIO (Non-blocking I/O)

NIO属于I/O多路复用模型

对于高负载, 高并发的网络应用程序, 应当使用 NIO

AIO(Asynchronous I/O)

AIO属于异步IO模型

最后简单给一张图, 总结一下 Java 的三种I/O模型

Java基础知识总结(下)_第3张图片

你可能感兴趣的:(Java,面经and八股,java,开发语言,职场和发展,程序人生,spring)