异常
Java异常类层次结构图概览:
Exception和Error有什么区别?
首先都属于同一类 Throwable
Exception是程序本身可以处理的异常,可以通过catch捕获.(Checked Exception 和 Unchecked Exception)
Error是程序无法处理的错误,比如虚拟机错误
Checked Exception 和 Unchecked Exception有什么区别
Checked Exception是受检查的异常,也就是说必须在编译时解决的异常,否则没有办法通过编译
Unchecked Exception 也就是Runtime Exception(五大异常)
1.算术异常 比如分母为0
2.空指针异常
3.数组越界异常
4.字符串转数字格式异常
5.类型转换异常
Throwable类常用的方法有哪些?
getMessage() 返回异常发生时的简要描述
toString()
printStackTrace() 在控制台上打印异常信息
try-catch-finally 如何使用?
try 捕获
catch 可接多个catch(异常类型由小到大放)
finally 可加可不加 加了一般情况下一定会执行(一般用于关流之类的)
注意:不要在 finally 语句块中使用 return!
finally 中的代码一定会执行吗?
不一定,如果虚拟机被终止运行的话
关闭了cpu
程序所在的线程死亡了
都不会执行
异常使用有哪些需要注意的地方?
-
不要把异常定义为静态变量,因为这样会导致异常栈信息错乱。每次手动抛出异常,我们都需要手动 new 一个异常对象抛出。
-
抛出的异常信息一定要有意义。
-
建议抛出更加具体的异常比如字符串转换为数字格式错误的时候应该抛出
NumberFormatException
而不是其父类IllegalArgumentException
。 -
使用日志打印异常之后就不要再抛出异常了(两者不要同时存在一段代码逻辑中)。
泛型
什么是泛型?泛型有什么用?😢
泛型可增强代码的可读性和稳定性,编译器可以对泛型参数进行检测,并且通过泛型参数只能传入指定的对象类型.
泛型的使用方式有几种?
三种 泛型类 泛型接口 泛型方法
//此处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;
}
}
泛型接口
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";
}
}
泛型方法
public static < E > void printArray( E[] inputArray )
{
for ( E element : inputArray ){
System.out.printf( "%s ", element );
}
System.out.println();
}
在 java 中泛型只是一个占位符,必须在传递类型后才能使用。类在实例化时才能真正的传递类型参数,由于静态方法的加载先于类的实例化,也就是说类中的泛型还没有传递真正的类型参数,静态的方法的加载就已经完成了,所以静态泛型方法是没有办法使用类上声明的泛型的。只能使用自己声明的
//自我解释 就是静态泛型的方法 不能用类的对象泛型 只能自己声明
反射
注解(Annotation)
可以看做特殊的注释
注解只有被解析后才会生效
编译期直接扫描 运行期通过反射处理
SPI
Service Provider Interface
序列化和反序列化
什么是序列化?什么是反序列化?
序列化:将数据结构或对象转换成二进制的字节流的过程
反序列化:将在序列化过程锁生成的二进制字节流 转换成数据结构或者对象的过程
下面是序列化和反序列化常见应用场景:
-
对象在进行网络传输(比如远程方法调用 RPC 的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化;
-
将对象存储到文件之前需要进行序列化,将对象从文件中读取出来需要进行反序列化;
-
将对象存储到数据库(如 Redis)之前需要用到序列化,将对象从缓存数据库中读取出来需要反序列化;
-
将对象存储到内存之前需要进行序列化,从内存中读取出来之后需要进行反序列化。
综上:序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。
序列化协议属于 TCP/IP 协议应用层的一部分
如果有些字段不想序列化怎么办?
通过transient关键字修饰
只能修饰变量
常见序列化协议有哪些?
JDK 自带的序列化方式一般不会用 ,因为序列化效率低并且存在安全问题。比较常用的序列化协议有 Hessian、Kryo、Protobuf、ProtoStuff,这些都是基于二进制的序列化协议。
像 JSON 和 XML 这种属于文本类序列化方式。虽然可读性比较好,但是性能较差,一般不会选择。
为什么不推荐使用 JDK 自带的序列化?
我们很少或者说几乎不会直接使用 JDK 自带的序列化方式,主要原因有下面这些原因:
-
不支持跨语言调用 : 如果调用的是其他语言开发的服务的时候就不支持了。
-
性能差:相比于其他序列化框架性能更低,主要原因是序列化之后的字节数组体积较大,导致传输成本加大。
-
存在安全问题:序列化和反序列化本身并不存在问题。但当输入的反序列化的数据可被用户控制,那么攻击者即可通过构造恶意输入,让反序列化产生非预期的对象,在此过程中执行构造的任意代码。
I/O
Java IO 流了解吗?
IO 即 Input/Output
,输入和输出。数据输入到计算机内存的过程即输入,反之输出到外部存储(比如数据库,文件,远程主机)的过程即输出。数据传输过程类似于水流,因此称为 IO 流。IO 流在 Java 中分为输入流和输出流,而根据数据的处理方式又分为字节流和字符流
I/O 流为什么要分为字节流和字符流呢?
问题本质想问:不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?
个人认为主要有两点原因:
-
字符流是由 Java 虚拟机将字节转换得到的,这个过程还算是比较耗时;
-
如果我们不知道编码类型的话,使用字节流的过程中很容易出现乱码问题
语法糖Syntactic
什么是语法糖?
编程语言为了方便程序员开发程序而设计的一种特殊语法,这种语法对编程语言的功能并没有影响。实现相同的功能,基于语法糖写出来的代码往往更简单简洁且更易阅读。
for-each就是一个语法糖
但是JVM并不能识别语法糖,java语法糖要想别正确执行,需要先通过编译器解糖
Java中有哪些常见的语法糖?
泛型、自动拆装箱、变长参数、枚举、内部类、增强 for 循环、try-with-resources 语法、lambda 表达式