mirror of
https://github.com/Snailclimb/JavaGuide
synced 2025-06-20 22:17:09 +08:00
This commit is contained in:
commit
7c4976807e
@ -5,215 +5,22 @@ tag:
|
||||
- Java基础
|
||||
---
|
||||
|
||||
## 泛型
|
||||
|
||||
#### Java 泛型了解么?什么是类型擦除?介绍一下常用的通配符?
|
||||
|
||||
**Java 泛型(generics)** 是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
|
||||
|
||||
Java 的泛型是伪泛型,这是因为 Java 在运行期间,所有的泛型信息都会被擦掉,这也就是通常所说类型擦除 。
|
||||
|
||||
```java
|
||||
List<Integer> list = new ArrayList<>();
|
||||
|
||||
list.add(12);
|
||||
//这里直接添加会报错
|
||||
list.add("a");
|
||||
Class<? extends List> clazz = list.getClass();
|
||||
Method add = clazz.getDeclaredMethod("add", Object.class);
|
||||
//但是通过反射添加是可以的
|
||||
//这就说明在运行期间所有的泛型信息都会被擦掉
|
||||
add.invoke(list, "kl");
|
||||
System.out.println(list);
|
||||
```
|
||||
|
||||
泛型一般有三种使用方式: 泛型类、泛型接口、泛型方法。
|
||||
|
||||
**1.泛型类**:
|
||||
|
||||
```java
|
||||
//此处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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
如何实例化泛型类:
|
||||
|
||||
```java
|
||||
Generic<Integer> genericInteger = new Generic<Integer>(123456);
|
||||
```
|
||||
|
||||
**2.泛型接口** :
|
||||
|
||||
```java
|
||||
public interface Generator<T> {
|
||||
public T method();
|
||||
}
|
||||
```
|
||||
|
||||
实现泛型接口,不指定类型:
|
||||
|
||||
```java
|
||||
class GeneratorImpl<T> implements Generator<T>{
|
||||
@Override
|
||||
public T method() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
实现泛型接口,指定类型:
|
||||
|
||||
```java
|
||||
class GeneratorImpl implements Generator<String>{
|
||||
@Override
|
||||
public String method() {
|
||||
return "hello";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**3.泛型方法** :
|
||||
|
||||
```java
|
||||
public static <E> void printArray(E[] inputArray) {
|
||||
for (E element : inputArray) {
|
||||
System.out.printf("%s ", element);
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
```
|
||||
|
||||
使用:
|
||||
|
||||
```java
|
||||
// 创建不同类型数组: Integer, Double 和 Character
|
||||
Integer[] intArray = { 1, 2, 3 };
|
||||
String[] stringArray = { "Hello", "World" };
|
||||
printArray(intArray);
|
||||
printArray(stringArray);
|
||||
```
|
||||
|
||||
#### 常用的通配符有哪些?
|
||||
|
||||
**常用的通配符为: T,E,K,V,?**
|
||||
|
||||
- ? 表示不确定的 Java 类型
|
||||
- T (type) 表示具体的一个 Java 类型
|
||||
- K V (key value) 分别代表 Java 键值中的 Key Value
|
||||
- E (element) 代表 Element
|
||||
|
||||
#### 你的项目中哪里用到了泛型?
|
||||
|
||||
- 可用于定义通用返回结果 `CommonResult<T>` 通过参数 `T` 可根据具体的返回类型动态指定结果的数据类型
|
||||
- 定义 `Excel` 处理类 `ExcelUtil<T>` 用于动态指定 `Excel` 导出的数据类型
|
||||
- 用于构建集合工具类。参考 `Collections` 中的 `sort`, `binarySearch` 方法
|
||||
- ......
|
||||
|
||||
## 反射
|
||||
|
||||
### 何为反射?
|
||||
|
||||
如果说大家研究过框架的底层原理或者咱们自己写过框架的话,一定对反射这个概念不陌生。
|
||||
|
||||
反射之所以被称为框架的灵魂,主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力。
|
||||
|
||||
通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。
|
||||
|
||||
### 反射机制优缺点
|
||||
|
||||
- **优点** : 可以让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利
|
||||
- **缺点** :让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。[Java Reflection: Why is it so slow?](https://stackoverflow.com/questions/1392351/java-reflection-why-is-it-so-slow)
|
||||
|
||||
### 反射的应用场景
|
||||
|
||||
像咱们平时大部分时候都是在写业务代码,很少会接触到直接使用反射机制的场景。
|
||||
|
||||
但是,这并不代表反射没有用。相反,正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。
|
||||
|
||||
**这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。**
|
||||
|
||||
比如下面是通过 JDK 实现动态代理的示例代码,其中就使用了反射类 `Method` 来调用指定的方法。
|
||||
|
||||
```java
|
||||
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` 的特殊接口:
|
||||
|
||||
```java
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Override {
|
||||
|
||||
}
|
||||
|
||||
public interface Override extends Annotation{
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
注解只有被解析之后才会生效,常见的解析方法有两种:
|
||||
|
||||
- **编译期直接扫描** :编译器在编译 Java 代码的时候扫描对应的注解并处理,比如某个方法使用`@Override` 注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。
|
||||
- **运行期通过反射处理** :像框架中自带的注解(比如 Spring 框架的 `@Value` 、`@Component`)都是通过反射来进行处理的。
|
||||
|
||||
JDK 提供了很多内置的注解(比如 `@Override` 、`@Deprecated`),同时,我们还可以自定义注解。
|
||||
|
||||
## 异常
|
||||
|
||||
**Java 异常类层次结构图概览** :
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### Exception 和 Error 有什么区别?
|
||||
|
||||
在 Java 中,所有的异常都有一个共同的祖先 `java.lang` 包中的 `Throwable` 类。`Throwable` 类有两个重要的子类:
|
||||
|
||||
- **`Exception`** :程序本身可以处理的异常,可以通过 `catch` 来进行捕获。`Exception` 又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)。
|
||||
- **`Error`** :`Error` 属于程序无法处理的错误 ,~~我们没办法通过 `catch` 来进行捕获~~不建议通过`catch`捕获 。例如Java 虚拟机运行错误(`Virtual MachineError`)、虚拟机内存不够错误(`OutOfMemoryError`)、类定义错误(`NoClassDefFoundError`)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。
|
||||
- **`Error`** :`Error` 属于程序无法处理的错误 ,~~我们没办法通过 `catch` 来进行捕获~~不建议通过`catch`捕获 。例如 Java 虚拟机运行错误(`Virtual MachineError`)、虚拟机内存不够错误(`OutOfMemoryError`)、类定义错误(`NoClassDefFoundError`)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。
|
||||
|
||||
### Checked Exception 和 Unchecked Exception 有什么区别?
|
||||
|
||||
**Checked Exception** 即受检查异常,Java 代码在编译过程中,如果受检查异常没有被 `catch`/`throw` 处理的话,就没办法通过编译 。
|
||||
**Checked Exception** 即 受检查异常 ,Java 代码在编译过程中,如果受检查异常没有被 `catch`/`throw` 处理的话,就没办法通过编译 。
|
||||
|
||||
比如下面这段 IO 操作的代码:
|
||||
|
||||
@ -223,7 +30,17 @@ JDK 提供了很多内置的注解(比如 `@Override` 、`@Deprecated`),
|
||||
|
||||
**Unchecked Exception** 即 **不受检查异常** ,Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。
|
||||
|
||||
`RuntimeException` 及其子类都统称为非受检查异常,例如:`NullPointerException`、`NumberFormatException`(字符串转换为数字)、`ArrayIndexOutOfBoundsException`(数组越界)、`ClassCastException`(类型转换错误)、`ArithmeticException`(算术错误)等。
|
||||
`RuntimeException` 及其子类都统称为非受检查异常,常见的有(建议记下来,日常开发中会经常用到):
|
||||
|
||||
- `NullPointerException`(空指针错误)
|
||||
- `IllegalArgumentException`(参数错误比如方法入参类型错误)
|
||||
- `NumberFormatException`(字符串转换为数字格式错误,`IllegalArgumentException`的子类)
|
||||
- `ArrayIndexOutOfBoundsException`(数组越界错误)
|
||||
- `ClassCastException`(类型转换错误)
|
||||
- `ArithmeticException`(算术错误)
|
||||
- `SecurityException` (安全错误比如权限不够)
|
||||
- `UnsupportedOperationException`(不支持的操作错误比如重复创建同一用户)
|
||||
- ......
|
||||
|
||||

|
||||
|
||||
@ -236,11 +53,11 @@ JDK 提供了很多内置的注解(比如 `@Override` 、`@Deprecated`),
|
||||
|
||||
### try-catch-finally 如何使用?
|
||||
|
||||
- **`try`块:** 用于捕获异常。其后可接零个或多个 `catch` 块,如果没有 `catch` 块,则必须跟一个 `finally` 块。
|
||||
- **`catch`块:** 用于处理 try 捕获到的异常。
|
||||
- **`finally` 块:** 无论是否捕获或处理异常,`finally` 块里的语句都会被执行。当在 `try` 块或 `catch` 块中遇到 `return` 语句时,`finally` 语句块将在方法返回之前被执行。
|
||||
- `try`块 : 用于捕获异常。其后可接零个或多个 `catch` 块,如果没有 `catch` 块,则必须跟一个 `finally` 块。
|
||||
- \*`catch`块 : 用于处理 try 捕获到的异常。
|
||||
- `finally` 块 : 无论是否捕获或处理异常,`finally` 块里的语句都会被执行。当在 `try` 块或 `catch` 块中遇到 `return` 语句时,`finally` 语句块将在方法返回之前被执行。
|
||||
|
||||
示例:
|
||||
代码示例:
|
||||
|
||||
```java
|
||||
try {
|
||||
@ -261,19 +78,17 @@ Catch Exception -> RuntimeException
|
||||
Finally
|
||||
```
|
||||
|
||||
**注意:不要在 finally 语句块中使用 return!** 当 try 语句和 finally 语句中都有 return 语句时,try 语句块中的 return 语句会被忽略。这是因为 try 语句中的 return 返回值会先被暂存在一个本地变量中,当执行到 finally 语句中的 return 之后,这个本地变量的值就变为了 finally 语句中的 return 返回值。
|
||||
**注意:不要在 finally 语句块中使用 return!** 当 try 语句和 finally 语句中都有 return 语句时,try 语句块中的 return 语句会被忽略。这是因为 try 语句中的 return 返回值会先被暂存在一个本地变量中,当执行到 finally 语句中的 return 之后,这个本地变量的值就变为了 finally 语句中的 return 返回值。
|
||||
|
||||
[jvm 官方文档](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.10.2.5)中有明确提到:
|
||||
|
||||
> If the `try` clause executes a *return*, the compiled code does the following:
|
||||
> 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.
|
||||
> 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.
|
||||
|
||||
先执行一部分的,先把返回的结果存到一段内存中;
|
||||
|
||||
示例:
|
||||
代码示例:
|
||||
|
||||
```java
|
||||
public static void main(String[] args) {
|
||||
@ -301,7 +116,7 @@ public static int f(int value) {
|
||||
|
||||
不一定的!在某些情况下,finally 中的代码不会被执行。
|
||||
|
||||
就比如说 `finally` 之前虚拟机被终止运行的话,finally 中的代码就不会被执行。
|
||||
就比如说 finally 之前虚拟机被终止运行的话,finally 中的代码就不会被执行。
|
||||
|
||||
```java
|
||||
try {
|
||||
@ -330,7 +145,7 @@ Catch Exception -> RuntimeException
|
||||
|
||||
相关 issue: <https://github.com/Snailclimb/JavaGuide/issues/190>。
|
||||
|
||||
🧗🏻进阶一下:从字节码角度分析`try catch finally`这个语法糖背后的实现原理。
|
||||
🧗🏻 进阶一下:从字节码角度分析`try catch finally`这个语法糖背后的实现原理。
|
||||
|
||||
### 如何使用 `try-with-resources` 代替`try-catch-finally`?
|
||||
|
||||
@ -378,17 +193,203 @@ try (Scanner scanner = new Scanner(new File("test.txt"))) {
|
||||
|
||||
```java
|
||||
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();
|
||||
}
|
||||
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` 对象,如果传入其他类型的对象就会报错。
|
||||
|
||||
```java
|
||||
ArrayList<E> extends AbstractList<E>
|
||||
```
|
||||
|
||||
并且,原生 `List` 返回类型是 `Object` ,需要手动转换类型才能使用,使用泛型后编译器自动转换。
|
||||
|
||||
### 泛型的使用方式有哪几种?
|
||||
|
||||
泛型一般有三种使用方式:**泛型类**、**泛型接口**、**泛型方法**。
|
||||
|
||||
**1.泛型类**:
|
||||
|
||||
```java
|
||||
//此处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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
如何实例化泛型类:
|
||||
|
||||
```java
|
||||
Generic<Integer> genericInteger = new Generic<Integer>(123456);
|
||||
```
|
||||
|
||||
**2.泛型接口** :
|
||||
|
||||
```java
|
||||
public interface Generator<T> {
|
||||
public T method();
|
||||
}
|
||||
```
|
||||
|
||||
实现泛型接口,不指定类型:
|
||||
|
||||
```java
|
||||
class GeneratorImpl<T> implements Generator<T>{
|
||||
@Override
|
||||
public T method() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
实现泛型接口,指定类型:
|
||||
|
||||
```java
|
||||
class GeneratorImpl<T> implements Generator<String>{
|
||||
@Override
|
||||
public String method() {
|
||||
return "hello";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**3.泛型方法** :
|
||||
|
||||
```java
|
||||
public static < E > void printArray( E[] inputArray )
|
||||
{
|
||||
for ( E element : inputArray ){
|
||||
System.out.printf( "%s ", element );
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
```
|
||||
|
||||
使用:
|
||||
|
||||
```java
|
||||
// 创建不同类型数组: Integer, Double 和 Character
|
||||
Integer[] intArray = { 1, 2, 3 };
|
||||
String[] stringArray = { "Hello", "World" };
|
||||
printArray( intArray );
|
||||
printArray( stringArray );
|
||||
```
|
||||
|
||||
### 项目中哪里用到了泛型?
|
||||
|
||||
- 自定义接口通用返回结果 `CommonResult<T>` 通过参数 `T` 可根据具体的返回类型动态指定结果的数据类型
|
||||
- 定义 `Excel` 处理类 `ExcelUtil<T>` 用于动态指定 `Excel` 导出的数据类型
|
||||
- 构建集合工具类(参考 `Collections` 中的 `sort`, `binarySearch` 方法)。
|
||||
- ......
|
||||
-
|
||||
|
||||
## 反射
|
||||
|
||||
### 何为反射?
|
||||
|
||||
如果说大家研究过框架的底层原理或者咱们自己写过框架的话,一定对反射这个概念不陌生。
|
||||
|
||||
反射之所以被称为框架的灵魂,主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力。通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。
|
||||
|
||||
### 反射机制优缺点
|
||||
|
||||
- **优点** : 可以让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利
|
||||
- **缺点** :让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。[Java Reflection: Why is it so slow?](https://stackoverflow.com/questions/1392351/java-reflection-why-is-it-so-slow)
|
||||
|
||||
### 反射的应用场景
|
||||
|
||||
像咱们平时大部分时候都是在写业务代码,很少会接触到直接使用反射机制的场景。
|
||||
|
||||
但是,这并不代表反射没有用。相反,正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。
|
||||
|
||||
**这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。**
|
||||
|
||||
比如下面是通过 JDK 实现动态代理的示例代码,其中就使用了反射类 `Method` 来调用指定的方法。
|
||||
|
||||
```java
|
||||
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` 的特殊接口:
|
||||
|
||||
```java
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Override {
|
||||
|
||||
}
|
||||
|
||||
public interface Override extends Annotation{
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
注解只有被解析之后才会生效,常见的解析方法有两种:
|
||||
|
||||
- **编译期直接扫描** :编译器在编译 Java 代码的时候扫描对应的注解并处理,比如某个方法使用`@Override` 注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。
|
||||
- **运行期通过反射处理** :像框架中自带的注解(比如 Spring 框架的 `@Value` 、`@Component`)都是通过反射来进行处理的。
|
||||
|
||||
JDK 提供了很多内置的注解(比如 `@Override` 、`@Deprecated`),同时,我们还可以自定义注解。
|
||||
|
||||
## I/O
|
||||
|
||||
### 什么是序列化?什么是反序列化?
|
||||
|
@ -9,14 +9,14 @@ tag:
|
||||
|
||||
### 什么是序列化?什么是反序列化?
|
||||
|
||||
如果我们需要持久化Java对象比如将Java对象保存在文件中,或者在网络传输Java对象,这些场景都需要用到序列化。
|
||||
如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中,或者在网络传输 Java 对象,这些场景都需要用到序列化。
|
||||
|
||||
简单来说:
|
||||
|
||||
- **序列化**: 将数据结构或对象转换成二进制字节流的过程
|
||||
- **反序列化**:将在序列化过程中所生成的二进制字节流的过程转换成数据结构或者对象的过程
|
||||
|
||||
对于Java这种面向对象编程语言来说,我们序列化的都是对象(Object)也就是实例化后的类(Class),但是在C++这种半面向对象的语言中,struct(结构体)定义的是数据结构类型,而class 对应的是对象类型。
|
||||
对于 Java 这种面向对象编程语言来说,我们序列化的都是对象(Object)也就是实例化后的类(Class),但是在 C++这种半面向对象的语言中,struct(结构体)定义的是数据结构类型,而 class 对应的是对象类型。
|
||||
|
||||
维基百科是如是介绍序列化的:
|
||||
|
||||
@ -30,11 +30,11 @@ tag:
|
||||
|
||||
### 实际开发中有哪些用到序列化和反序列化的场景?
|
||||
|
||||
1. 对象在进行网络传输(比如远程方法调用RPC的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化;
|
||||
1. 对象在进行网络传输(比如远程方法调用 RPC 的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化;
|
||||
2. 将对象存储到文件中的时候需要进行序列化,将对象从文件中读取出来需要进行反序列化。
|
||||
3. 将对象存储到缓存数据库(如 Redis)时需要用到序列化,将对象从缓存数据库中读取出来需要反序列化。
|
||||
|
||||
### 序列化协议对应于TCP/IP 4层模型的哪一层?
|
||||
### 序列化协议对应于 TCP/IP 4 层模型的哪一层?
|
||||
|
||||
我们知道网络通信的双方必须要采用和遵守相同的协议。TCP/IP 四层模型是下面这样的,序列化协议属于哪一层呢?
|
||||
|
||||
@ -45,17 +45,17 @@ tag:
|
||||
|
||||

|
||||
|
||||
如上图所示,OSI七层协议模型中,表示层做的事情主要就是对应用层的用户数据进行处理转换为二进制流。反过来的话,就是将二进制流转换成应用层的用户数据。这不就对应的是序列化和反序列化么?
|
||||
如上图所示,OSI 七层协议模型中,表示层做的事情主要就是对应用层的用户数据进行处理转换为二进制流。反过来的话,就是将二进制流转换成应用层的用户数据。这不就对应的是序列化和反序列化么?
|
||||
|
||||
因为,OSI七层协议模型中的应用层、表示层和会话层对应的都是TCP/IP 四层模型中的应用层,所以序列化协议属于TCP/IP协议应用层的一部分。
|
||||
因为,OSI 七层协议模型中的应用层、表示层和会话层对应的都是 TCP/IP 四层模型中的应用层,所以序列化协议属于 TCP/IP 协议应用层的一部分。
|
||||
|
||||
## 常见序列化协议对比
|
||||
|
||||
JDK自带的序列化方式一般不会用 ,因为序列化效率低并且部分版本有安全漏洞。比较常用的序列化协议有 hessian、kyro、protostuff。
|
||||
JDK 自带的序列化方式一般不会用 ,因为序列化效率低并且部分版本有安全漏洞。比较常用的序列化协议有 hessian、kyro、protostuff。
|
||||
|
||||
下面提到的都是基于二进制的序列化协议,像 JSON 和 XML这种属于文本类序列化方式。虽然 JSON 和 XML可读性比较好,但是性能较差,一般不会选择。
|
||||
下面提到的都是基于二进制的序列化协议,像 JSON 和 XML 这种属于文本类序列化方式。虽然 JSON 和 XML 可读性比较好,但是性能较差,一般不会选择。
|
||||
|
||||
### JDK自带的序列化方式
|
||||
### JDK 自带的序列化方式
|
||||
|
||||
JDK 自带的序列化,只需实现 `java.io.Serializable`接口即可。
|
||||
|
||||
@ -76,7 +76,7 @@ public class RpcRequest implements Serializable {
|
||||
}
|
||||
```
|
||||
|
||||
> 序列化号 serialVersionUID 属于版本控制的作用。序列化的时候serialVersionUID也会被写入二级制序列,当反序列化时会检查serialVersionUID是否和当前类的serialVersionUID一致。如果serialVersionUID不一致则会抛出 `InvalidClassException` 异常。强烈推荐每个序列化类都手动指定其 `serialVersionUID`,如果不手动指定,那么编译器会动态生成默认的序列化号
|
||||
> 序列化号 serialVersionUID 属于版本控制的作用。序列化的时候 serialVersionUID 也会被写入二级制序列,当反序列化时会检查 serialVersionUID 是否和当前类的 serialVersionUID 一致。如果 serialVersionUID 不一致则会抛出 `InvalidClassException` 异常。强烈推荐每个序列化类都手动指定其 `serialVersionUID`,如果不手动指定,那么编译器会动态生成默认的序列化号
|
||||
|
||||
我们很少或者说几乎不会直接使用这个序列化方式,主要原因有两个:
|
||||
|
||||
@ -85,11 +85,11 @@ public class RpcRequest implements Serializable {
|
||||
|
||||
### Kryo
|
||||
|
||||
Kryo是一个高性能的序列化/反序列化工具,由于其变长存储特性并使用了字节码生成机制,拥有较高的运行速度和较小的字节码体积。
|
||||
Kryo 是一个高性能的序列化/反序列化工具,由于其变长存储特性并使用了字节码生成机制,拥有较高的运行速度和较小的字节码体积。
|
||||
|
||||
另外,Kryo 已经是一种非常成熟的序列化实现了,已经在Twitter、Groupon、Yahoo以及多个著名开源项目(如Hive、Storm)中广泛的使用。
|
||||
另外,Kryo 已经是一种非常成熟的序列化实现了,已经在 Twitter、Groupon、Yahoo 以及多个著名开源项目(如 Hive、Storm)中广泛的使用。
|
||||
|
||||
[guide-rpc-framework](https://github.com/Snailclimb/guide-rpc-framework) 就是使用的 kyro 进行序列化,序列化和反序列化相关的代码如下:
|
||||
[guide-rpc-framework](https://github.com/Snailclimb/guide-rpc-framework) 就是使用的 kyro 进行序列化,序列化和反序列化相关的代码如下:
|
||||
|
||||
```java
|
||||
/**
|
||||
@ -146,15 +146,15 @@ Github 地址:[https://github.com/EsotericSoftware/kryo](https://github.com/Es
|
||||
|
||||
### Protobuf
|
||||
|
||||
Protobuf出自于Google,性能还比较优秀,也支持多种语言,同时还是跨平台的。就是在使用中过于繁琐,因为你需要自己定义 IDL 文件和生成对应的序列化代码。这样虽然不然灵活,但是,另一方面导致protobuf没有序列化漏洞的风险。
|
||||
Protobuf 出自于 Google,性能还比较优秀,也支持多种语言,同时还是跨平台的。就是在使用中过于繁琐,因为你需要自己定义 IDL 文件和生成对应的序列化代码。这样虽然不然灵活,但是,另一方面导致 protobuf 没有序列化漏洞的风险。
|
||||
|
||||
> Protobuf包含序列化格式的定义、各种语言的库以及一个IDL编译器。正常情况下你需要定义proto文件,然后使用IDL编译器编译成你需要的语言
|
||||
> Protobuf 包含序列化格式的定义、各种语言的库以及一个 IDL 编译器。正常情况下你需要定义 proto 文件,然后使用 IDL 编译器编译成你需要的语言
|
||||
|
||||
一个简单的 proto 文件如下:
|
||||
|
||||
```protobuf
|
||||
// protobuf的版本
|
||||
syntax = "proto3";
|
||||
syntax = "proto3";
|
||||
// SearchRequest会被编译成不同的编程语言的相应对象,比如Java中的class、Go中的struct
|
||||
message Person {
|
||||
//string类型字段
|
||||
@ -164,36 +164,35 @@ message Person {
|
||||
}
|
||||
```
|
||||
|
||||
Github地址:[https://github.com/protocolbuffers/protobuf](https://github.com/protocolbuffers/protobuf)。
|
||||
Github 地址:[https://github.com/protocolbuffers/protobuf](https://github.com/protocolbuffers/protobuf)。
|
||||
|
||||
### ProtoStuff
|
||||
|
||||
由于Protobuf的易用性,它的哥哥 Protostuff 诞生了。
|
||||
由于 Protobuf 的易用性,它的哥哥 Protostuff 诞生了。
|
||||
|
||||
protostuff 基于Google protobuf,但是提供了更多的功能和更简易的用法。虽然更加易用,但是不代表 ProtoStuff 性能更差。
|
||||
protostuff 基于 Google protobuf,但是提供了更多的功能和更简易的用法。虽然更加易用,但是不代表 ProtoStuff 性能更差。
|
||||
|
||||
Gihub地址:[https://github.com/protostuff/protostuff](https://github.com/protostuff/protostuff)。
|
||||
Github 地址:[https://github.com/protostuff/protostuff](https://github.com/protostuff/protostuff)。
|
||||
|
||||
### hession
|
||||
|
||||
hessian 是一个轻量级的,自定义描述的二进制RPC协议。hessian是一个比较老的序列化实现了,并且同样也是跨语言的。
|
||||
hessian 是一个轻量级的,自定义描述的二进制 RPC 协议。hessian 是一个比较老的序列化实现了,并且同样也是跨语言的。
|
||||
|
||||

|
||||
|
||||
dubbo RPC默认启用的序列化方式是 hession2 ,但是,Dubbo对hessian2进行了修改,不过大体结构还是差不多。
|
||||
dubbo RPC 默认启用的序列化方式是 hession2 ,但是,Dubbo 对 hessian2 进行了修改,不过大体结构还是差不多。
|
||||
|
||||
### 总结
|
||||
|
||||
Kryo 是专门针对Java语言序列化方式并且性能非常好,如果你的应用是专门针对Java语言的话可以考虑使用,并且 Dubbo 官网的一篇文章中提到说推荐使用 Kryo 作为生产环境的序列化方式。(文章地址:[https://dubbo.apache.org/zh/docs/v2.7/user/references/protocol/rest/](https://dubbo.apache.org/zh/docs/v2.7/user/references/protocol/rest/))
|
||||
Kryo 是专门针对 Java 语言序列化方式并且性能非常好,如果你的应用是专门针对 Java 语言的话可以考虑使用,并且 Dubbo 官网的一篇文章中提到说推荐使用 Kryo 作为生产环境的序列化方式。(文章地址:[https://dubbo.apache.org/zh/docs/v2.7/user/references/protocol/rest/](https://dubbo.apache.org/zh/docs/v2.7/user/references/protocol/rest/))
|
||||
|
||||

|
||||
|
||||
像Protobuf、 ProtoStuff、hession这类都是跨语言的序列化方式,如果有跨语言需求的话可以考虑使用。
|
||||
像 Protobuf、 ProtoStuff、hession 这类都是跨语言的序列化方式,如果有跨语言需求的话可以考虑使用。
|
||||
|
||||
除了我上面介绍到的序列化方式的话,还有像 Thrift,Avro 这些。
|
||||
|
||||
## 其他推荐阅读
|
||||
|
||||
- 美团技术团队-序列化和反序列化:[https://tech.meituan.com/2015/02/26/serialization-vs-deserialization.html](https://tech.meituan.com/2015/02/26/serialization-vs-deserialization.html)
|
||||
- 在Dubbo中使用高效的Java序列化(Kryo和FST): [https://dubbo.apache.org/zh-cn/docs/user/serialization.html](https://dubbo.apache.org/zh-cn/docs/user/serialization.html)
|
||||
|
||||
- 在 Dubbo 中使用高效的 Java 序列化(Kryo 和 FST): [https://dubbo.apache.org/zh-cn/docs/user/serialization.html](https://dubbo.apache.org/zh-cn/docs/user/serialization.html)
|
||||
|
Loading…
x
Reference in New Issue
Block a user