JAVA基础
Exception 和 Error 的区别
在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。
Throwable 类有两个重要的子类:Exception :程序本身可以处理的异常,可以通过 catch 来进行捕获。
Exception 又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)。
Error :Error 属于程序无法处理的错误 ,不建议通过catch捕获 。例如 Java 虚拟机运行错误(Virtual MachineError)、虚拟机内存不够错误(OutOfMemoryError)、类定义错误(NoClassDefFoundError)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。
Checked Exception 和 Unchecked Exception 的区别
Checked Exception
即 受检查异常 ,Java 代码在编译过程中,如果受检查异常没有被 catch或者throws 关键字处理的话,就没办法通过编译。
注:
除了RuntimeException及其子类以外,其他的Exception类及其子类都属于受检查异常 。常见的受检查异常有: IO 相关的异常、ClassNotFoundException 、SQLException…。
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 对象封装的异常信息
finally 中的代码不一定会执行
就比如说 finally 之前虚拟机被终止运行的话,finally 中的代码就不会被执行。
eg: System.exit(1);
还有:
程序所在的线程死亡。
关闭 CPU。
使用 try-with-resources 代替try-catch-finally的场景
- 适用范围(资源的定义): 任何实现 java.lang.AutoCloseable或者 java.io.Closeable 的对象
- 关闭资源和 finally 块的执行顺序: 在 try-with-resources 语句中,任何 catch 或 finally 块在声明的资源关闭后运行
注:
面对必须要关闭的资源,我们总是应该优先使用 try-with-resources 而不是try-finally。随之产生的代码更简短,更清晰,产生的异常对我们也更有用。try-with-resources语句让我们更容易编写必须要关闭的资源的代码,若采用try-finally则几乎做不到这点。
Java 中类似于InputStream、OutputStream 、Scanner 、PrintWriter等的资源都需要我们调用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();
}
}
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();
}
异常使用的注意点
- 不要把异常定义为静态变量,因为这样会导致异常栈信息错乱。每次手动抛出异常,我们都需要手动 new 一个异常对象抛出。
- 抛出的异常信息一定要有意义。
- 建议抛出更加具体的异常比如字符串转换为数字格式错误的时候应该抛出NumberFormatException而不是其父类IllegalArgumentException。
- 使用日志打印异常之后就不要再抛出异常了(两者不要同时存在一段代码逻辑中)。
泛型
泛型的定义
JAVA泛型是JDK5中引入的特性。使用泛型参数,可以增强代码的可读性和稳定性。
编辑器对泛型参数进行检查,通过泛型参数可以指定传入的对象类型。
使用方式
泛型类,泛型接口,泛型方法
泛型类
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);
泛型接口
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();
}
// 创建不同类型数组: Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3 };
String[] stringArray = { "Hello", "World" };
printArray( intArray );
printArray( stringArray );
在 java 中泛型只是一个占位符,必须在传递类型后才能使用。类在实例化时才能真正的传递类型参数,由于静态方法的加载先于类的实例化,也就是说类中的泛型还没有传递真正的类型参数,静态的方法的加载就已经完成了,所以静态泛型方法是没有办法使用类上声明的泛型的。只能使用自己声明的
泛型使用地方
- 自定义接口通用返回结果 CommonResult 通过参数 T 可根据具体的返回类型动态指定结果的数据类型
- 定义 Excel 处理类 ExcelUtil 用于动态指定 Excel 导出的数据类型
- 构建集合工具类(参考 Collections 中的 sort, binarySearch 方法)
反射
定义
反射被称为框架的灵魂,可以在程序运行时分析类和执行类中的方法的能力。通过反射可以获取任意一个类的所有属性和方法,可以进行调用。
优点
反射可以让代码更灵活,为各种框架提供开箱即用的功能提供便利。
缺点
在运行时有分析操作类的能力的同时,增加了安全问题。例如:无视泛型参数的安全检查,这个安全检查发生在编译。反射的性能稍差,对框架影响不大。
应用场景
框架大量使用了反射机制。框架中的动态代理的实现也依赖反射。
举例:实现动态代理,使用了反射类来调用指定方法:
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;
}
}
注解也是用到了反射
注解
定义
注解英文是Annotation。Java5开始引入的新特性,是一种特殊的注释。
用于修饰类,方法或者变量,提供一些信息令程序在编译或者运行时使用。
注解的本质:
是一个继承了Annotation的特殊接口
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
public interface Override extends Annotation{
}
JDK提供了一些内置注解,例如:@Override @Deprecated。也可以自定义注解
解析方法有两种
注解只有被解析才会生效,常见的解析方法有两种:
- 编译期直接扫描
编译器在编译代码的时候扫描对应的注解并处理,比如某个方法使用@Override注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。 - 运行期通过反射处理
框架中自带的注解,例如:Spring框架的@Value,@Component都是通过反射处理的。
SPI
字面意思是:服务提供者的接口。理解为:专门提供给服务提供者或者扩展框架功能的开发者去使用的一个接口
将服务接口和具体的服务实现分离开来,把服务调用方和服务实现者解耦。能够提升程序的扩展性,可维护性。修改或者替换服务实现并不需要修改调用方。
SPI和API
API:调用方依赖于接口,但是接口是 被调用方 实现 的接口。
SPI:调用方依赖于接口,但是接口是 调用方 实现 的接口。被调用方制式确定接口规则。
SPI的优点
通过SPI机制可以大幅度提升接口设计的灵活性
SPI的缺点
- 需要遍历加载所有的实现类,不能按需加载,效率较低
- 多个ServiceLoader同时load,会出现并发问题
序列化和反序列化
需要持久化Java对象。例如:将对象保存在文件中,或者在网络传输Java对象。
序列化:将数据结构或对象转换成二进制字节流的过程
反序列化:将在序列化过程中所生成的二进制字节流转换成数据机构或者对象的过程
Java–面向对象编程语言
序列化的是对象,即实例化后的类。在C++半面向对象的语言中,结构体定义的是数据结构类型,而class对应的是对象类型。
常见应用场景
- 对象在进行网络传输(比如远程方法调用 RPC 的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化
- 将对象存储到文件之前需要进行序列化,将对象从文件中读取出来需要进行反序列化
- 将对象存储到数据库(如 Redis)之前需要用到序列化,将对象从缓存数据库中读取出来需要反序列化
- 将对象存储到内存之前需要进行序列化,从内存中读取出来之后需要进行反序列化
序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。
序列化协议对应于TCP/IP中的协议应用层
TCP/IP四层模型:
应用层,传输层,网络层,网络接口层
OSI七层协议模型中:
应用层–为计算机用户提供服务
表示层–数据处理(编码解码,加密解密,压缩解压缩)
会话层–管理应用程序之间的会话(建立,维护,重连)
传输层–为两台主机进程之间的通信提供的数据传输服务
网络层–路由和寻址(决定数据在网络的游走路径)
数据链路层–帧编码和误差纠正控制
物理层–透明地传送比特流传输
表示层主要是对应用层的用户数据进行处理转换为二进制流。向上说就是将二进制流转换成为应用层的用户数据。
某些字段不进行序列化
使用 transient 关键字修饰,作用是:阻止实例中用该关键字修饰的变量进行序列化。当对象呗反序列化时,被该关键字修饰的变量,值,不会被持久化和恢复
- 只能修饰变量,不能修饰类和方法
- 修饰的变量,在反序列化后变量值会被置成类型的默认值
- static变量因为不属于任何对象,所以被transient修饰与否,都不会被序列化
序列化协议
JDK 自带的序列化方式一般不会用 ,因为序列化效率低并且存在安全问题。比较常用的序列化协议有 Hessian、Kryo、Protobuf、ProtoStuff,这些都是基于二进制的序列化协议。像 JSON 和 XML 这种属于文本类序列化方式。虽然可读性比较好,但是性能较差,一般不会选择
JDK自带序列化
- 不支持跨语言调用 : 如果调用的是其他语言开发的服务的时候就不支持了
- 性能差 :相比于其他序列化框架性能更低,主要原因是序列化之后的字节数组体积较大,导致传输成本加大
- 存在安全问题 :序列化和反序列化本身并不存在问题。但当输入的反序列化的数据可被用户控制,那么攻击者即可通过构造恶意输入,让反序列化产生非预期的对象,在此过程中执行构造的任意代码
IO
含义
IO是Input/Output,输入,输出。
输入:数据输入到计算机内存的过程
输出:输出到外部存储,例如(数据库,文件,远程主机)的过程
根据数据处理方式:分为字节流和字符流
4个抽象类基类
InputStream:字节输入流;Reader:字符输入流
OutputStream:字节输出流;Writer:字符输出流
区分字节和字符流的目的
- 字符流是通过Java虚拟机将字节转换得到的,过程较为耗时
- 不知道编码类型,使用字节流容易出现乱码
设计模式
后续文章中体现。
BIO,NIO,AIO
后续文字中体现。
语法糖
定义
编程语言为了方便程序员开发程序而设计的一种特殊语法,这种语法对编程语言的功能没有影响。实现相同功能,基于语法糖写出的代码,更简洁且更容易阅读。
用法
JVM不能识别语法糖,需要先通过编辑器进行解糖,就是在程序编译阶段将其转化为JVM认识的基本语法。
Java中真正支持语法糖的是Java编译器,而不是JVM。在源码:com.sun.tools.javac.main.JavaCompiler,compile()中有一个步骤就是调用desugar(),这个方法就是负责解语法糖的实现的