你好,我是 Guide。秋招行将到来,我对 JavaGuide 的内容进行了重构欠缺,同步一下最新更新,心愿可能帮忙你。

前两篇:

  • Java 根底常见知识点&面试题总结(上),2022 最新版!
  • Java 根底常见知识点&面试题总结(中),2022 最新版

异样

Java 异样类层次结构图概览

Exception 和 Error 有什么区别?

在 Java 中,所有的异样都有一个独特的先人 java.lang 包中的 Throwable 类。Throwable 类有两个重要的子类:

  • Exception :程序自身能够解决的异样,能够通过 catch 来进行捕捉。Exception 又能够分为 Checked Exception (受查看异样,必须解决) 和 Unchecked Exception (不受查看异样,能够不解决)。
  • ErrorError 属于程序无奈解决的谬误 ,咱们没方法通过 catch 来进行捕捉不倡议通过catch捕捉 。例如 Java 虚拟机运行谬误(Virtual MachineError)、虚拟机内存不够谬误(OutOfMemoryError)、类定义谬误(NoClassDefFoundError)等 。这些异样产生时,Java 虚拟机(JVM)个别会抉择线程终止。

Checked Exception 和 Unchecked Exception 有什么区别?

Checked Exception 即 受查看异样 ,Java 代码在编译过程中,如果受查看异样没有被 catch或者throws 关键字解决的话,就没方法通过编译。

比方上面这段 IO 操作的代码:

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

Unchecked Exception不受查看异样 ,Java 代码在编译过程中 ,咱们即便不解决不受查看异样也能够失常通过编译。

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

  • NullPointerException(空指针谬误)
  • IllegalArgumentException(参数谬误比方办法入参类型谬误)
  • NumberFormatException(字符串转换为数字格局谬误,IllegalArgumentException的子类)
  • ArrayIndexOutOfBoundsException(数组越界谬误)
  • ClassCastException(类型转换谬误)
  • ArithmeticException(算术谬误)
  • SecurityException (平安谬误比方权限不够)
  • UnsupportedOperationException(不反对的操作谬误比方反复创立同一用户)
  • ......

Throwable 类罕用办法有哪些?

  • String getMessage(): 返回异样产生时的简要形容
  • String toString(): 返回异样产生时的详细信息
  • String getLocalizedMessage(): 返回异样对象的本地化信息。应用 Throwable 的子类笼罩这个办法,能够生成本地化信息。如果子类没有笼罩该办法,则该办法返回的信息与 getMessage()返回的后果雷同
  • void printStackTrace(): 在管制台上打印 Throwable 对象封装的异样信息

try-catch-finally 如何应用?

  • try块 : 用于捕捉异样。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。
  • *catch块 : 用于解决 try 捕捉到的异样。
  • finally 块 : 无论是否捕捉或解决异样,finally 块里的语句都会被执行。当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在办法返回之前被执行。

代码示例:

try {    System.out.println("Try to do something");    throw new RuntimeException("RuntimeException");} catch (Exception e) {    System.out.println("Catch Exception -> " + e.getMessage());} finally {    System.out.println("Finally");}

输入:

Try to do somethingCatch Exception -> RuntimeExceptionFinally

留神:不要在 finally 语句块中应用 return! 当 try 语句和 finally 语句中都有 return 语句时,try 语句块中的 return 语句会被疏忽。这是因为 try 语句中的 return 返回值会先被暂存在一个本地变量中,当执行到 finally 语句中的 return 之后,这个本地变量的值就变为了 finally 语句中的 return 返回值。

jvm 官网文档中有明确提到:

If the try clause executes a _return_, the compiled code does the following:

  1. Saves the return value (if any) in a local variable.
  2. Executes a jsr to the code for the finally clause.
  3. Upon return from the finally clause, returns the value saved in the local variable.

代码示例:

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 中的代码不会被执行。

就比如说 finally 之前虚拟机被终止运行的话,finally 中的代码就不会被执行。

try {    System.out.println("Try to do something");    throw new RuntimeException("RuntimeException");} catch (Exception e) {    System.out.println("Catch Exception -> " + e.getMessage());    // 终止以后正在运行的Java虚拟机    System.exit(1);} finally {    System.out.println("Finally");}

输入:

Try to do somethingCatch Exception -> RuntimeException

另外,在以下 2 种非凡状况下,finally 块的代码也不会被执行:

  1. 程序所在的线程死亡。
  2. 敞开 CPU。

相干 issue: https://github.com/Snailclimb...。

进阶一下:从字节码角度剖析try catch finally这个语法糖背地的实现原理。

如何应用 try-with-resources 代替try-catch-finally

  1. 适用范围(资源的定义): 任何实现 java.lang.AutoCloseable或者 java.io.Closeable 的对象
  2. 敞开资源和 finally 块的执行程序:try-with-resources 语句中,任何 catch 或 finally 块在申明的资源敞开后运行

《Effective Java》中明确指出:

面对必须要敞开的资源,咱们总是应该优先应用 try-with-resources 而不是try-finally。随之产生的代码更简短,更清晰,产生的异样对咱们也更有用。try-with-resources语句让咱们更容易编写必须要敞开的资源的代码,若采纳try-finally则简直做不到这点。

Java 中相似于InputStreamOutputStreamScannerPrintWriter等的资源都须要咱们调用close()办法来手动敞开,个别状况下咱们都是通过try-catch-finally语句来实现这个需要,如下:

//读取文本文件的内容Scanner scanner = null;try {    scanner = new Scanner(new File("D://read.txt"));    while (scanner.hasNext()) {        System.out.println(scanner.nextLine());    }} catch (FileNotFoundException e) {    e.printStackTrace();} finally {    if (scanner != null) {        scanner.close();    }}

应用 Java 7 之后的 try-with-resources 语句革新下面的代码:

try (Scanner scanner = new Scanner(new File("test.txt"))) {    while (scanner.hasNext()) {        System.out.println(scanner.nextLine());    }} catch (FileNotFoundException fnfe) {    fnfe.printStackTrace();}

当然多个资源须要敞开的时候,应用 try-with-resources 实现起来也非常简单,如果你还是用try-catch-finally可能会带来很多问题。

通过应用分号分隔,能够在try-with-resources块中申明多个资源。

try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));     BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))) {    int b;    while ((b = bin.read()) != -1) {        bout.write(b);    }}catch (IOException e) {    e.printStackTrace();}

异样应用有哪些须要留神的中央?

  • 不要把异样定义为动态变量,因为这样会导致异样栈信息错乱。每次手动抛出异样,咱们都须要手动 new 一个异样对象抛出。
  • 抛出的异样信息肯定要有意义。
  • 倡议抛出更加具体的异样比方字符串转换为数字格局谬误的时候应该抛出NumberFormatException而不是其父类IllegalArgumentException
  • 应用日志打印异样之后就不要再抛出异样了(两者不要同时存在一段代码逻辑中)。
  • ......

泛型

什么是泛型?有什么作用?

Java 泛型(Generics) 是 JDK 5 中引入的一个新个性。应用泛型参数,能够加强代码的可读性以及稳定性。

编译器能够对泛型参数进行检测,并且通过泛型参数能够指定传入的对象类型。比方 ArrayList<Persion> persons = new ArrayList<Persion>() 这行代码就指明了该 ArrayList 对象只能传入 Persion 对象,如果传入其余类型的对象就会报错。

ArrayList<E> extends AbstractList<E>

并且,原生 List 返回类型是 Object ,须要手动转换类型能力应用,应用泛型后编译器主动转换。

泛型的应用形式有哪几种?

泛型个别有三种应用形式:泛型类泛型接口泛型办法

1.泛型类

//此处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);

2.泛型接口

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";    }}

3.泛型办法

   public static < E > void printArray( E[] inputArray )   {         for ( E element : inputArray ){            System.out.printf( "%s ", element );         }         System.out.println();    }

应用:

// 创立不同类型数组: Integer, Double 和 CharacterInteger[] intArray = { 1, 2, 3 };String[] stringArray = { "Hello", "World" };printArray( intArray  );printArray( stringArray  );
留神: public static < E > void printArray( E[] inputArray ) 个别被称为动态泛型办法;在 java 中泛型只是一个占位符,必须在传递类型后能力应用。类在实例化时能力真正的传递类型参数,因为静态方法的加载先于类的实例化,也就是说类中的泛型还没有传递真正的类型参数,动态的办法的加载就曾经实现了,所以动态泛型办法是没有方法应用类上申明的泛型的。只能应用本人申明的 <E>

我的项目中哪里用到了泛型?

  • 自定义接口通用返回后果 CommonResult<T> 通过参数 T 可依据具体的返回类型动静指定后果的数据类型
  • 定义 Excel 解决类 ExcelUtil<T> 用于动静指定 Excel 导出的数据类型
  • 构建汇合工具类(参考 Collections 中的 sort, binarySearch 办法)。
  • ......

反射

何为反射?

如果说大家钻研过框架的底层原理或者咱们本人写过框架的话,肯定对反射这个概念不生疏。

反射之所以被称为框架的灵魂,次要是因为它赋予了咱们在运行时剖析类以及执行类中办法的能力。通过反射你能够获取任意一个类的所有属性和办法,你还能够调用这些办法和属性。

反射机制优缺点

  • 长处 : 能够让咱们的代码更加灵便、为各种框架提供开箱即用的性能提供了便当
  • 毛病 :让咱们在运行时有了剖析操作类的能力,这同样也减少了平安问题。比方能够忽视泛型参数的安全检查(泛型参数的安全检查产生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说理论是影响不大的。Java Reflection: Why is it so slow?

反射的利用场景

像咱们平时大部分时候都是在写业务代码,很少会接触到间接应用反射机制的场景。

然而,这并不代表反射没有用。相同,正是因为反射,你能力这么轻松地应用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量应用了反射机制。

这些框架中也大量应用了动静代理,而动静代理的实现也依赖反射。

比方上面是通过 JDK 实现动静代理的示例代码,其中就应用了反射类 Method 来调用指定的办法。

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;    }}

另外,像 Java 中的一大利器 注解 的实现也用到了反射。

为什么你应用 Spring 的时候 ,一个@Component注解就申明了一个类为 Spring Bean 呢?为什么你通过一个 @Value注解就读取到配置文件中的值呢?到底是怎么起作用的呢?

这些都是因为你能够基于反射剖析类,而后获取到类/属性/办法/办法的参数上的注解。你获取到注解之后,就能够做进一步的解决。

注解

Annotation (注解) 是 Java5 开始引入的新个性,能够看作是一种非凡的正文,次要用于润饰类、办法或者变量。

注解实质是一个继承了Annotation 的非凡接口:

@Target(ElementType.METHOD)@Retention(RetentionPolicy.SOURCE)public @interface Override {}public interface Override extends Annotation{}

注解只有被解析之后才会失效,常见的解析办法有两种:

  • 编译期间接扫描 :编译器在编译 Java 代码的时候扫描对应的注解并解决,比方某个办法应用@Override 注解,编译器在编译的时候就会检测以后的办法是否重写了父类对应的办法。
  • 运行期通过反射解决 :像框架中自带的注解(比方 Spring 框架的 @Value@Component)都是通过反射来进行解决的。

JDK 提供了很多内置的注解(比方 @Override@Deprecated),同时,咱们还能够自定义注解。

I/O

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

如果咱们须要长久化 Java 对象比方将 Java 对象保留在文件中,或者在网络传输 Java 对象,这些场景都须要用到序列化。

简略来说:

  • 序列化: 将数据结构或对象转换成二进制字节流的过程
  • 反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程

对于 Java 这种面向对象编程语言来说,咱们序列化的都是对象(Object)也就是实例化后的类(Class),然而在 C++这种半面向对象的语言中,struct(构造体)定义的是数据结构类型,而 class 对应的是对象类型。

维基百科是如是介绍序列化的:

序列化(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换成可取用格局(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在雷同或另一台计算机环境中,能复原原先状态的过程。按照序列化格局从新获取字节的后果时,能够利用它来产生与原始对象雷同语义的正本。对于许多对象,像是应用大量援用的简单对象,这种序列化重建的过程并不容易。面向对象中的对象序列化,并不概括之前原始对象所关系的函数。这种过程也称为对象编组(marshalling)。从一系列字节提取数据结构的反向操作,是反序列化(也称为解编组、deserialization、unmarshalling)。

综上:序列化的次要目标是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。

<p style="text-align:right;font-size:13px;color:gray">https://www.corejavaguru.com/...</p>

Java 序列化中如果有些字段不想进行序列化,怎么办?

对于不想进行序列化的变量,应用 transient 关键字润饰。

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

对于 transient 还有几点留神:

  • transient 只能润饰变量,不能润饰类和办法。
  • transient 润饰的变量,在反序列化后变量值将会被置成类型的默认值。例如,如果是润饰 int 类型,那么反序列后后果就是 0
  • static 变量因为不属于任何对象(Object),所以无论有没有 transient 关键字润饰,均不会被序列化。

获取用键盘输入罕用的两种办法

办法 1:通过 Scanner

Scanner input = new Scanner(System.in);String s  = input.nextLine();input.close();

办法 2:通过 BufferedReader

BufferedReader input = new BufferedReader(new InputStreamReader(System.in));String s = input.readLine();

Java 中 IO 流分为几种?

  • 依照流的流向分,能够分为输出流和输入流;
  • 依照操作单元划分,能够划分为字节流和字符流;
  • 依照流的角色划分为节点流和解决流。

Java IO 流共波及 40 多个类,这些类看上去很芜杂,但实际上很有规定,而且彼此之间存在十分严密的分割, Java IO 流的 40 多个类都是从如下 4 个抽象类基类中派生进去的。

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

按操作形式分类结构图:

按操作对象分类结构图:

既然有了字节流,为什么还要有字符流?

问题实质想问:不论是文件读写还是网络发送接管,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?

答复:字符流是由 Java 虚拟机将字节转换失去的,问题就出在这个过程还算是十分耗时,并且,如果咱们不晓得编码类型就很容易呈现乱码问题。所以, I/O 流就罗唆提供了一个间接操作字符的接口,不便咱们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比拟好,如果波及到字符的话应用字符流比拟好。

后记

专一 Java 原创干货分享,大三开源 JavaGuide (「Java学习+面试指南」一份涵盖大部分 Java 程序员所须要把握的外围常识。筹备 Java 面试,首选 JavaGuide!),目前曾经 120k+ Star。

原创不易,欢送点赞分享,欢送关注我在思否的账号,我会继续分享原创干货!加油,冲!

如果本文对你有帮忙的话,欢送点赞分享,这对我持续分享&创作优质文章十分重要。感激