Java基础3(补档)


异常

异常类接口有哪些

Throwable

  • Error
  • Exception
    • Checked Exception
    • Unchecked Exception

Exception 和 Error 有什么区别

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

  • **Exception**:程序本身可以处理的异常,可以通过 catch 来进行捕获。
  • ErrorError:一般是指与虚拟机相关的问题,如:系统崩溃、虚拟机错误、内存空间不足、方法调用栈溢出等。这类错误将会导致应用程序中断,仅靠程序本身无法恢复和预防;

Checked Exception 和 Unchecked Exception 有什么区别

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

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

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

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

  • NullPointerException(空指针错误)
  • ArrayIndexOutOfBoundsException(数组越界错误)
  • ClassCastException(类型转换错误)
  • IllegalArgumentException(参数错误)
  • NumberFormatException(字符串转换为数字格式错误)
  • ArithmeticException(算术错误)
  • SecurityException (安全错误,比如权限不够)

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 语句块将在方法返回之前被执行。

注意:不要在 finally 语句块中使用 return! 当 try 语句和 finally 语句中都有 return 语句时,try 语句块中的 return 语句会被忽略。

finally 中的代码一定会执行吗

不一定

比如,finally 之前虚拟机被终止运行的话(exit 函数),finally 中的代码就不会被执行

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

catch 和 finally可以省略其中一个

运行时异常是非受检异常,普通异常是受检异常;

运行时异常:try+catch/finally
普通异常:try+catch(+finally),因为普通异常必须用catch显示声明进一步处理,否则编译器不通过。

常见的异常类有哪些

  1. NullPointerException:空指针异常
  2. SQLException:数据库访问异常
  3. IndexOutOfBoundsException:数组越界等异常
  4. FileNotFoundException:当试图打开指定路径名表示的文件失败时,抛出此异常
  5. IOException:当发生某种 I/O 异常时,抛出此异常
  6. ClassCastException:类型转换异常
  7. IllegalArgumentException:向方法传递了一个不合法或不正确的参数
  8. ArithmeticException:除0异常
  9. ConcurrentModificationException:多线程异常

泛型

什么是泛型

泛型(Generics)是一种提供参数化类型的机制。允许我们在定义类、接口和方法时使用类型参数,从而实现通用性和类型安全性。

泛型有什么作用

泛型的作用有以下几个方面:

  1. 提高代码复用性:泛型使得我们可以编写更加通用的代码,以适用于多种数据类型
  2. 增强类型安全性:泛型在编译时进行类型检查,可以帮助我们在编译期间发现类型错误
  3. 简化代码开发:减少了手动进行类型转换的繁琐操作。

项目中哪里用到了泛型

  • 自定义接口通用返回结果 CommonResult<T> 通过参数 T 可根据具体的返回类型动态指定结果的数据类型
  • 集合类中,List< String >

泛型的通配符有哪些

// 1:表示类型参数可以是任何类型
public class Apple<?>{} 
// 2:表示类型参数必须是A或者是A的子类
public class Apple<T extends A>{} 
// 3: 表示类型参数必须是A或者是A的超类型
public class Apple<T supers A>{}
  1. 无边界的通配符, < ? >:让泛型能够接受未知类型的数据.
  2. 固定上边界的通配符,< ? extends E >:接受指定类及其子类类型的数据。
  3. 固定下边界的通配符,< ? super E >:接受指定类及其父类类型的数据.。

泛型擦除与泛型转换是什么

泛型擦除:泛型擦除指的是在编译器将源代码转换成字节码的过程中,擦除了泛型类型的具体参数信息。在Java中,泛型是一种编译时的机制,在运行时并不存在泛型类型对象。编译器会把带有泛型参数的代码擦除为非泛型的原始类型。

List<String> stringList = new ArrayList<>(); 
stringList.add("Hello"); 
stringList.add("World"); // 泛型类型被擦除为原始类型 

List rawList = stringList; // rawList将元素当做Object处理
rawList.add(10); // 由于泛型擦除,在编译时不会发生错误 

String firstElement = stringList.get(0); // 不需要进行强制类型转换 
String secondElement = (String) rawList.get(1); // 需要进行强制类型转换

泛型转换:泛型转换是指在使用泛型时进行类型转换的过程。当使用一个泛型类或方法时,需要传入具体的类型参数。这些类型参数会被编译器进行类型检查,并在必要时进行类型转换,以保证类型的安全性。

List<Integer> integerList = new ArrayList<>(); 
integerList.add(10); integerList.add(20); 

List<? extends Number> numberList = integerList; // 泛型转换 

// 无法添加新元素,但可以获取元素 
// numberList.add(30); // 编译错误 因为编译器无法确定元素类型
Number firstElement = numberList.get(0);

反射

什么是反射

反射(Reflection)是一种在运行时获取和操作类信息的能力。通过反射,可以动态地获取和使用在编译时可能无法确定的类和对象的信息。

使用反射,可以获取类的构造器、方法和字段等信息,并且可以动态地创建对象、调用方法、读取和修改字段的值,甚至可以操作私有成员。

java.lang.reflect 类库主要包含了以下三个类:

(1)Field :可以使用 get() 和 set() 方法读取和修改 Field 对象关联的字段;
(2)Method :可以使用 invoke() 方法调用与 Method 对象关联的方法;
(3)Constructor :可以用 Constructor 创建新的对象。

反射的作用有什么

  1. 获取类的信息:可以通过反射获得类的名称、父类、接口、字段、方法、构造器等信息。
  2. 创建对象:可以通过反射动态地创建类的实例,即使在编译时无法确定具体的类名。
  3. 调用方法:可以通过反射调用类的方法,包括公有方法、私有方法以及静态方法。
  4. 操作字段:可以通过反射读取和修改类的字段的值,包括公有字段和私有字段。
  5. 动态代理:反射可以用于实现动态代理,动态地生成代理类并在运行时处理方法的调用。

反射的优缺点有哪些

优点:

运行期类型的判断,class.forName() 动态加载类,提高代码的灵活度;

缺点:

(1)性能开销 :反射涉及了动态类型的解析,JVM 无法对这些代码进行优化。

(2)内部暴露,安全问题:由于反射允许代码执行一些在正常情况下不被允许的操作(比如:访问私有的属性和方法)可能导致代码功能失调并破坏可移植性。

Java反射在实际项目中的应用场景

  • 使用JDBC时,如果要创建数据库的连接,则需要先通过反射机制加载数据库的驱动程序;
  • 多数框架都支持注解/XML配置,从配置中解析出来的类是字符串,需要利用反射机制实例化;
  • 面向切面编程(AOP)的实现方案,是在程序运行时创建目标对象的代理类,这必须由反射机制来实现。

Java 中的动态代理是什么

动态代理(Dynamic Proxy)是一种在运行时创建代理类和对象的机制。

通过动态代理,可以在不事先知道接口具体实现类的情况下,动态地创建一个代理对象,该代理对象可以代替原始对象执行相同的操作,并且可以在方法调用前后进行额外的处理

动态代理有哪些应用场景

  • 添加日志、性能统计等横切逻辑,而不必修改原始类的代码。
  • 实现AOP(面向切面编程)中的切面对象。
  • 实现远程调用(RPC)框架中的代理对象。

怎么实现动态代理?

Java 中,实现动态代理有两种方式:

1、JDK 动态代理:java.lang.reflect 包中的 Proxy 类和 InvocationHandler 接口提供了生成动态代理类的能力。
2、Cglib 动态代理:Cglib (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。

JDK 动态代理和 Cglib 动态代理的区别

  1. JDK 的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。 cglib 代理的对象则无需实现接口,达到代理类无侵入。

  2. JDK 动态代理的实现方式是反射;CGLib 实现动态代理是基于 ASM(一个 Java 字节码操作框架)而非反射实现的。

注解

什么是注解

Annotation 可以看作是一种特殊的注释,主要用于修饰类、方法或者变量,提供某些信息供程序在编译或者运行时使用。

注解本质是一个继承了Annotation 的特殊接口

注解的解析方法有哪几种

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

  • 编译期直接扫描:编译器在编译 Java 代码的时候扫描对应的注解并处理,比如某个方法使用@Override 注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。

  • 运行期通过反射处理:像框架中自带的注解(比如 Spring 框架的 @Value@Component)都是通过反射来进行处理的。

序列化

序列化和反序列化是什么

序列化(serialization):指将数据结构或对象状态转换成可取用格式,以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。

序列化协议属于 TCP/IP 协议应用层的一部分。

简单来说:

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

序列化和反序列化常见应用场景

  • 网络传输:对象在进行网络传输(比如远程方法调用 RPC 的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化;
  • 存储到文件:将对象存储到文件之前需要进行序列化,将对象从文件中读取出来需要进行反序列化;
  • 存储到Redis:将对象存储到数据库(如 Redis)之前需要用到序列化,将对象从缓存数据库中读取出来需要反序列化;
  • 存储到内存:将对象存储到内存之前需要进行序列化,从内存中读取出来之后需要进行反序列化。

常见的序列化协议

  • 1、XML
  • 2、JSON
  • 3、Fastjson
  • 4、Thrift
  • 5、Avro
  • 6、Protobuf

JDK 自带的序列化方式

JDK 自带的序列化,只需实现 java.io.Serializable接口即可。

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

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

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

关于 transient 还有几点注意:

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

为什么不推荐使用 JDK 自带的序列化?

  • 不支持跨语言调用 : 如果调用的是其他语言开发的服务的时候就不支持了。
  • 性能差 :相比于其他序列化框架性能更低。
  • 存在安全问题 :序列化和反序列化本身并不存在问题。但是输入的反序列化的数据可被用户控制

I/O

Java IO流了解吗

IO 即 Input/Output,输入和输出。数据输入到计算机内存的过程即输入,反之输出到外部存储(比如数据库,文件,远程主机)的过程即输出。数据传输过程类似于水流,因此Java中称为 IO 流。

Java 中的 IO 流的分类?说说几个你熟悉的实现类?

IO 流在 Java 中分为输入流和输出流,而根据数据的处理方式又分为字节流和字符流。

Java IO 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。

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

字节流和字符流有什么区别?

字节流按 8 位传输,以字节为单位输入输出数据,
字符流按 16 位传输,以字符为单位输入输出数据。

但是不管文件读写还是网络发送接收,信息的最小存储单元都是字节。

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

各有优缺点

  • 字节流更快:字符流是由 Java 虚拟机将字节转换得到的,这个过程还算是比较耗时;
  • 字节流可能会乱码:如果我们不知道编码类型的话,使用字节流的过程中很容易出现乱码问题。

BIO、NIO、AIO 有什么区别

BIO(Blocking I/O)、NIO(Non-blocking I/O)和AIO(Asynchronous I/O)是Java中不同的I/O模型,它们有以下区别:

  1. 阻塞与非阻塞:
    • BIO是阻塞式I/O模型,即在执行I/O操作时,线程会被阻塞,直到操作完成。
    • NIO是非阻塞式I/O模型,使用了较少的线程处理更多的连接,并且线程在执行I/O操作时可以继续处理其他任务,提高了并发性能。
    • AIO是异步I/O模型,它使用了操作系统提供的异步通知机制,当I/O操作完成时,操作系统会通知应用程序进行处理。
  2. 编程方式:
    • BIO使用同步阻塞的方式,编程模型比较简单,但在大量连接的情况下,性能较低。
    • NIO使用选择器(Selector)和缓冲区(Buffer)来实现非阻塞,需要通过事件轮询来获取I/O事件的通知,编程模型稍微复杂。
    • AIO使用了回调机制,可以在I/O操作完成后得到通知,编程模型相对更为复杂。
  3. 应用场景:
    • BIO适用于连接数相对较少、请求响应时间要求较低的简单应用。
    • NIO适用于大量并发连接和高吞吐量的网络应用,如聊天服务器、即时通讯等。
    • AIO适用于大规模连接、每个连接处理时间较长的网络应用,如异步消息通知、Web后端处理等

语法糖

什么是语法糖

语法糖(Syntactic sugar) 代指的是编程语言为了方便程序员开发程序而设计的一种特殊语法,这种语法对编程语言的功能并没有影响。实现相同的功能,基于语法糖写出来的代码往往更简单简洁且更易阅读。

Java 中有哪些常见的语法糖

Java 中最常用的语法糖主要有泛型自动拆装箱、变长参数、枚举、内部类、增强 for 循环、try-with-resources 语法、lambda 表达式等


文章作者: Aiaa
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Aiaa !
  目录