Java?中的泛型超全詳解
一、泛型概述
1. 什么是泛型?為什么要使用泛型?
泛型,即“參數(shù)化類型”。一提到參數(shù),最熟悉的就是定義方法時有形參列表,普通方法的形參列表中,每個形參的數(shù)據(jù)類型是確定的,而變量是一個參數(shù)。在調(diào)用普通方法時需要傳入對應(yīng)形參數(shù)據(jù)類型的變量(實參),若傳入的實參與形參定義的數(shù)據(jù)類型不匹配,則會報錯
那
參數(shù)化類型
是什么?以方法的定義為例,在方法定義時,將方法簽名中的形參的數(shù)據(jù)類型
也設(shè)置為參數(shù)(也可稱之為類型參數(shù)),在調(diào)用該方法時再從外部傳入一個具體的數(shù)據(jù)類型和變量。
泛型的本質(zhì)是為了將類型參數(shù)化, 也就是說在泛型使用過程中,數(shù)據(jù)類型被設(shè)置為一個參數(shù),在使用時再從外部傳入一個數(shù)據(jù)類型;而一旦傳入了具體的數(shù)據(jù)類型后,傳入變量(實參)的數(shù)據(jù)類型如果不匹配,編譯器就會直接報錯。這種參數(shù)化類型可以用在類、接口和方法中,分別被稱為泛型類、泛型接口、泛型方法。
//沒有泛型的時候,集合如何存儲數(shù)據(jù)
//結(jié)論:
//如果我們沒有給集合指定類型,默認認為所有的數(shù)據(jù)類型都是Object類型
//此時可以往集合添加任意的數(shù)據(jù)類型。
//帶來一個壞處:我們在獲取數(shù)據(jù)的時候,無法使用他的特有行為。
//此時推出了泛型,可以在添加數(shù)據(jù)的時候就把類型進行統(tǒng)一。
//而且我們在獲取數(shù)據(jù)的時候,也省的強轉(zhuǎn)了,非常的方便。
2. 泛型使用場景
在 ArrayList 集合中,可以放入所有類型的對象,假設(shè)現(xiàn)在需要一個只存儲了 String 類型對象的 ArrayList 集合。
public class demo1 { public static void main(String[] args) { ArrayList<String> list=new ArrayList<>(); list.add("a"); list.add("b"); list.add("c"); for(String s:list){ System.out.println(s); } } }
上面代碼沒有任何問題,在遍歷 ArrayList 集合時,只需將 Object 對象進行向下轉(zhuǎn)型成 String 類型即可得到 String 類型對象。
但如果在添加 String 對象時,不小心添加了一個 Integer 對象,會發(fā)生什么?看下面代碼:
public static void main(String[] args) { ArrayList list = new ArrayList(); list.add("aaa"); list.add("bbb"); list.add("ccc"); list.add(666); for (int i = 0; i < list.size(); i++) { System.out.println((String)list.get(i)); } }
上述代碼在編譯時沒有報錯,但在運行時卻拋出了一個 ClassCastException 異常
,其原因是 Integer 對象不能強轉(zhuǎn)為 String 類型。
那如何可以避免上述異常的出現(xiàn)?即我們希望當我們向集合中添加了不符合類型要求的對象時,編譯器能直接給我們報錯,而不是在程序運行后才產(chǎn)生異常。這個時候便可以使用
泛型
了。
使用泛型代碼如下:
public static void main(String[] args) { ArrayList<String> list = new ArrayList(); list.add("aaa"); list.add("bbb"); list.add("ccc"); //list.add(666);// 在編譯階段,編譯器會報錯 for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } }
< String > 是一個泛型,其限制了 ArrayList 集合中存放對象的數(shù)據(jù)類型只能是 String,當添加一個非 String 對象時,編譯器會直接報錯。這樣,我們便解決了上面產(chǎn)生的 ClassCastException 異常的問題(這樣體現(xiàn)了泛型的類型安全檢測機制)。
3.總結(jié)
泛型的出現(xiàn)就是為了統(tǒng)一集合當中數(shù)據(jù)類型的
二、泛型類 泛型類的定義
- 尖括號 <> 中的 泛型標識被稱作是
類型參數(shù)
,用于指代任何數(shù)據(jù)類型。 - 泛型標識是任意設(shè)置的(如果你想可以設(shè)置為 Hello都行),Java 常見的泛型標識以及其代表含義如下:
T :代表一般的任何類。
E :代表 Element 元素的意思,或者 Exception 異常的意思。
K :代表 Key 的意思。
V :代表 Value 的意思,通常與 K 一起配合使用。
S :代表 Subtype 的意思,文章后面部分會講解示意。
自己實現(xiàn)集合
代碼如下:
package fangxing; import java.util.Arrays; public class MyArrayList<E> { Object[] obj = new Object[10]; int size = 0; /* E: 表示不確定的類型,該類型在類名后面已經(jīng)定義過了 e: 形參的名字,變量名 */ public boolean add(E e) { obj[size++] = e; return true; //當添加成功以后,集合還是會把這些數(shù)據(jù)當做Object類型處理 } public E get(int index) { return (E) obj[index]; //獲取的時候集合在把他強轉(zhuǎn)<E>類型 } @Override public String toString() { return Arrays.toString(obj); } }
package fangxing; import javax.xml.stream.events.StartDocument; public class demo3 { public static void main(String[] args) { MyArrayList<String> list = new MyArrayList<>(); list.add("aaa"); list.add("bbb"); list.add("ccc"); System.out.println(list); } }
三、泛型方法
格式
package fangxing; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class ListUtil { private ListUtil() { } /* 參數(shù)一:集合 參數(shù)二: 最后要添加的元素 */ public static <E> void addAll(ArrayList<E> list, E e1, E e2) { list.add(e1); list.add(e2); } }
package fangxing; import java.util.ArrayList; public class demo4 { public static void main(String[] args) { ArrayList<String>list=new ArrayList<>(); ListUtil.addAll(list,"zhangsan","lisi"); System.out.println(list);//[zhangsan, lisi] } }
添加很多元素
public static <E> void addAll(ArrayList<E> list, E ...e1) { for (E e : e1) { list.add(e); }
四、泛型接口
方法1:實現(xiàn)類給出具體類型
舉例:
public class MyArrayList2 implements List<String>
public static void main(String[] args) { MyArrayList2 list2=new MyArrayList2(); }
方法2: 實現(xiàn)類延續(xù)泛型,創(chuàng)建對象再確定
public class MyArrayList3 <E> implements List<E>
MyArrayList3<String> list = new MyArrayList3<>();
五、類型擦除
1. 什么是類型擦除
泛型的本質(zhì)是將數(shù)據(jù)類型參數(shù)化,它通過擦除的方式來實現(xiàn),即編譯器會在編譯期間擦除代碼中的所有泛型語法并相應(yīng)的做出一些類型轉(zhuǎn)換動作。
換而言之,泛型信息只存在于代碼編譯階段,在代碼編譯結(jié)束后,與泛型相關(guān)的信息會被擦除掉,專業(yè)術(shù)語叫做類型擦除。也就是說,成功編譯過后的 class 文件中不包含任何泛型信息,泛型信息不會進入到運行時階段。
其實Java中的泛型本質(zhì)是偽泛型
當把集合定義為string類型的時候,當數(shù)據(jù)添加在集合當中的時候,僅僅在門口檢查了一下數(shù)據(jù)是否符合String類型, 如果是String類型,就添加成功,當添加成功以后,集合還是會把這些數(shù)據(jù)當做Object類型處理,當往外獲取的時候,集合在把他強轉(zhuǎn)String類型
當代碼編譯到class文件的時候,泛型就消失,叫泛型的擦除
看一個例子,假如我們給 ArrayList 集合傳入兩種不同的數(shù)據(jù)類型,并比較它們的類信息。
public class GenericType { public static void main(String[] args) { ArrayList<String> arrayString = new ArrayList<String>(); ArrayList<Integer> arrayInteger = new ArrayList<Integer>(); System.out.println(arrayString.getClass() == arrayInteger.getClass());// true } }
在這個例子中,我們定義了兩個 ArrayList 集合,不過一個是 ArrayList< String>,只能存儲字符串。一個是 ArrayList< Integer>,只能存儲整型對象。我們通過 arrayString 對象和 arrayInteger 對象的 getClass() 方法獲取它們的類信息并比較,發(fā)現(xiàn)結(jié)果為true。
明明我們在 <> 中傳入了兩種不同的數(shù)據(jù)類型,那為什么它們的類信息還是相同呢? 這是因為,在編譯期間,所有的泛型信息都會被擦除, ArrayList< Integer > 和 ArrayList< String >類型,在編譯后都會變成ArrayList< Objec t>類型。
那么是不是所有的類型參數(shù)被擦除后都以 Object 類進行替換呢?
答案是否定的,大部分情況下,類型參數(shù) T 被擦除后都會以 Object 類進行替換;而有一種情況則不是,那就是使用到了 extends 和 super 語法的有界類型參數(shù)
(即泛型通配符
,后面我們會詳細解釋
2. 類型擦除的原理
假如我們定義了一個 ArrayList< Integer > 泛型集合,若向該集合中插入 String 類型的對象,不需要運行程序,編譯器就會直接報錯。這里可能有小伙伴就產(chǎn)生了疑問:
不是說泛型信息在編譯的時候就會被擦除掉嗎?那既然泛型信息被擦除了,如何保證我們在集合中只添加指定的數(shù)據(jù)類型的對象呢?
換而言之,我們雖然定義了 ArrayList< Integer > 泛型集合,但其泛型信息最終被擦除后就變成了 ArrayList< Object > 集合,那為什么不允許向其中插入 String 對象呢?
Java 是如何解決這個問題的?
其實在創(chuàng)建一個泛型類的對象時, Java 編譯器是先檢查代碼中傳入 < T > 的數(shù)據(jù)類型,并記錄下來,然后再對代碼進行編譯,編譯的同時進行類型擦除;如果需要對被擦除了泛型信息的對象進行操作,編譯器會自動將對象進行類型轉(zhuǎn)換。
可以把泛型的類型安全檢查機制和類型擦除想象成演唱會的驗票機制:以 ArrayList< Integer> 泛型集合為例。
當我們在創(chuàng)建一個 ArrayList< Integer > 泛型集合的時候,ArrayList 可以看作是演唱會場館,而< T >就是場館的驗票系統(tǒng),Integer 是驗票系統(tǒng)設(shè)置的門票類型;
當驗票系統(tǒng)設(shè)置好為< Integer >后,只有持有 Integer 門票的人才可以通過驗票系統(tǒng),進入演唱會場館(集合)中;若是未持有 Integer 門票的人想進場,則驗票系統(tǒng)會發(fā)出警告(編譯器報錯)。
在通過驗票系統(tǒng)時,門票會被收掉(類型擦除),但場館后臺(JVM)會記錄下觀眾信息(泛型信息)。
進場后的觀眾變成了沒有門票的普通人(原始數(shù)據(jù)類型)。但是,在需要查看觀眾的信息時(操作對象),場館后臺可以找到記錄的觀眾信息(編譯器會自動將對象進行類型轉(zhuǎn)換)。
舉例如下:
public class GenericType { public static void main(String[] args) { ArrayList<Integer> arrayInteger = new ArrayList<Integer>();// 設(shè)置驗票系統(tǒng) arrayInteger.add(111);// 觀眾進場,驗票系統(tǒng)驗票,門票會被收走(類型擦除) Integer n = arrayInteger.get(0);// 獲取觀眾信息,編譯器會進行強制類型轉(zhuǎn)換 System.out.println(n); } }
擦除 ArrayList< Integer > 的泛型信息后,get() 方法的返回值將返回 Object 類型,但編譯器會自動插入 Integer 的強制類型轉(zhuǎn)換。也就是說,編譯器把 get() 方法調(diào)用翻譯為兩條字節(jié)碼指令:
對原始方法 get() 的調(diào)用,返回的是 Object 類型;
將返回的 Object 類型強制轉(zhuǎn)換為 Integer 類型;
代碼如下:
Integer n = arrayInteger.get(0);// 這條代碼底層如下: //(1)get() 方法的返回值返回的是 Object 類型 Object object = arrayInteger.get(0); //(2)編譯器自動插入 Integer 的強制類型轉(zhuǎn)換 Integer n = (Integer) object;
3. 類型擦除小結(jié)
1.泛型信息(包括泛型類、接口、方法)只在代碼編譯階段存在,在代碼成功編譯后,其內(nèi)的所有泛型信息都會被擦除,并且類型參數(shù) T 會被統(tǒng)一替換為其原始類型(默認是 Object 類,若有 extends 或者 super 則另外分析);
2.在泛型信息被擦除后,若還需要使用到對象相關(guān)的泛型信息,編譯器底層會自動進行類型轉(zhuǎn)換(從原始類型轉(zhuǎn)換為未擦除前的數(shù)據(jù)類型)。
六、泛型通配符
1. 泛型的繼承
泛型不具備繼承性,但是數(shù)據(jù)具備繼承性
此時,泛型里面寫的什么類型,那么就傳遞什么類型的數(shù)據(jù)
泛型不具備繼承性舉例
package fangxing; import java.util.ArrayList; public class demo5 { public static void main(String[] args) { /* 泛型不具備繼承性,但是數(shù)據(jù)具備繼承性 */ ArrayList<Ye> list1=new ArrayList<>(); ArrayList<Fu> list2=new ArrayList<>(); ArrayList<Zi> list3=new ArrayList<>(); //調(diào)用method方法 method(list1); //method(list2);//編譯錯誤 //method(list3);//編譯錯誤 } /* 此時,泛型里面寫的什么類型,那么就傳遞什么類型的數(shù)據(jù) */ public static void method(ArrayList<Ye> list){ } } class Ye{ } class Fu extends Ye{ } class Zi extends Fu{ }
數(shù)據(jù)具備繼承性
//數(shù)據(jù)具備繼承性 list1.add(new Ye());//添加爺爺?shù)膶ο蟮? list1.add(new Fu()); list1.add(new Zi());
定義一個方法,形參是一個集合,但是集合中的數(shù)據(jù)類型不確定。
應(yīng)用場景:
* 1.如果我們在定義類、方法、接口的時候,如果類型不確定,就可以定義泛型類、泛型方法、泛型接口。
* 2.如果類型不確定,但是能知道以后只能傳遞某個繼承體系中的,就可以泛型的通配符
* 泛型的通配符:
* 關(guān)鍵點:可以限定類型的范圍。
/*
* 此時,泛型里面寫的是什么類型,那么只能傳遞什么類型的數(shù)據(jù)。
* 弊端:
* 利用泛型方法有一個小弊端,此時他可以接受任意的數(shù)據(jù)類型
* Ye Fu Zi Student
*
* 希望:本方法雖然不確定類型,但是以后我希望只能傳遞Ye Fu Zi
*
* 此時我們就可以使用泛型的通配符:
* ?也表示不確定的類型
* 他可以進行類型的限定
* ? extends E: 表示可以傳遞E或者E所有的子類類型
* ? super E:表示可以傳遞E或者E所有的父類類型
*
舉例
package fangxing; import java.util.ArrayList; /* * 需求: * 定義一個方法,形參是一個集合,但是集合中的數(shù)據(jù)類型不確定。 * * */ /* * 此時,泛型里面寫的是什么類型,那么只能傳遞什么類型的數(shù)據(jù)。 * 弊端: * 利用泛型方法有一個小弊端,此時他可以接受任意的數(shù)據(jù)類型 * Ye Fu Zi Student * * 希望:本方法雖然不確定類型,但是以后我希望只能傳遞Ye Fu Zi * * 此時我們就可以使用泛型的通配符: * ?也表示不確定的類型 * 他可以進行類型的限定 * ? extends E: 表示可以傳遞E或者E所有的子類類型 * ? super E:表示可以傳遞E或者E所有的父類類型 * * 應(yīng)用場景: * 1.如果我們在定義類、方法、接口的時候,如果類型不確定,就可以定義泛型類、泛型方法、泛型接口。 * 2.如果類型不確定,但是能知道以后只能傳遞某個繼承體系中的,就可以泛型的通配符 * 泛型的通配符: * 關(guān)鍵點:可以限定類型的范圍。 * * */ public class demo6 { public static void main(String[] args) { //創(chuàng)建集合的對象 ArrayList<Ye> list1 = new ArrayList<>(); ArrayList<Fu> list2 = new ArrayList<>(); ArrayList<Zi> list3 = new ArrayList<>(); ArrayList<Student2> list4 = new ArrayList<>(); method(list1); method(list2); //method(list3); //method(list4); } public static void method(ArrayList<? super Fu> list) { } } class Ye { } class Fu extends Ye { } class Zi extends Fu { } class Student2{}
2.練習(xí)
/*
需求:
定義一個繼承結(jié)構(gòu):
動物
| |
貓 狗
| | | |
波斯貓 貍花貓 泰迪 哈士奇
屬性:名字,年齡
行為:吃東西
波斯貓方法體打?。阂恢唤凶鯴XX的,X歲的波斯貓,正在吃小餅干
貍花貓方法體打?。阂恢唤凶鯴XX的,X歲的貍花貓,正在吃魚
泰迪方法體打?。阂恢唤凶鯴XX的,X歲的泰迪,正在吃骨頭,邊吃邊蹭
哈士奇方法體打印:一只叫做XXX的,X歲的哈士奇,正在吃骨頭,邊吃邊拆家測試類中定義一個方法用于飼養(yǎng)動物
public static void keepPet(ArrayList<???> list){
//遍歷集合,調(diào)用動物的eat方法
}
要求1:該方法能養(yǎng)所有品種的貓,但是不能養(yǎng)狗
要求2:該方法能養(yǎng)所有品種的狗,但是不能養(yǎng)貓
要求3:該方法能養(yǎng)所有的動物,但是不能傳遞其他類型
*/
測試類
package lx; import java.util.ArrayList; public class demo1 { /* 需求: 定義一個繼承結(jié)構(gòu): 動物 | | 貓 狗 | | | | 波斯貓 貍花貓 泰迪 哈士奇 屬性:名字,年齡 行為:吃東西 波斯貓方法體打?。阂恢唤凶鯴XX的,X歲的波斯貓,正在吃小餅干 貍花貓方法體打印:一只叫做XXX的,X歲的貍花貓,正在吃魚 泰迪方法體打?。阂恢唤凶鯴XX的,X歲的泰迪,正在吃骨頭,邊吃邊蹭 哈士奇方法體打?。阂恢唤凶鯴XX的,X歲的哈士奇,正在吃骨頭,邊吃邊拆家 測試類中定義一個方法用于飼養(yǎng)動物 public static void keepPet(ArrayList<???> list){ //遍歷集合,調(diào)用動物的eat方法 } 要求1:該方法能養(yǎng)所有品種的貓,但是不能養(yǎng)狗 要求2:該方法能養(yǎng)所有品種的狗,但是不能養(yǎng)貓 要求3:該方法能養(yǎng)所有的動物,但是不能傳遞其他類型 */ public static void main(String[] args) { HuskyDog h = new HuskyDog("哈士奇", 1); LihuaCat l = new LihuaCat("貍花貓", 2); PersianCat p = new PersianCat("波斯貓", 3); TeddyDog t = new TeddyDog("泰迪", 4); ArrayList<LihuaCat> list1 = new ArrayList<>(); ArrayList<PersianCat> list2 = new ArrayList<>(); // 向列表中添加一些貓的實例 list1.add(l); list2.add(p); //調(diào)用方法 keepPet1(list1); keepPet1(list2); System.out.println("-------------------------------------------"); ArrayList<HuskyDog> list3 = new ArrayList<>(); ArrayList<TeddyDog> list4 = new ArrayList<>(); // 向列表中添加一些狗的實例 list3.add(h); list4.add(t); //調(diào)用方法 keepPet2(list3); keepPet2(list4); System.out.println("-------------------------------------------"); list1.add(l); list2.add(p); list3.add(h); list4.add(t); keepPet3(list1); keepPet3(list2); keepPet3(list3); keepPet3(list4); } /* 此時我們就可以使用泛型的通配符: ?也表示不確定的類型 他可以進行類型的限定 ? extends E: 表示可以傳遞E或者E所有的子類類型 ? super E:表示可以傳遞E或者E所有的父類類型 */ // 要求1:該方法能養(yǎng)所有品種的貓,但是不能養(yǎng)狗 public static void keepPet1(ArrayList<? extends Cat> list) { //遍歷集合,調(diào)用動物的eat方法 for (Cat cat : list) { cat.eat(); } } // 要求2:該方法能養(yǎng)所有品種的狗,但是不能養(yǎng)貓 public static void keepPet2(ArrayList<? extends Dog> list) { //遍歷集合,調(diào)用動物的eat方法 for (Dog dog : list) { dog.eat(); } } // 要求3:該方法能養(yǎng)所有的動物,但是不能傳遞其他類型 public static void keepPet3(ArrayList<? extends Animal> list) { //遍歷集合,調(diào)用動物的eat方法 for (Animal animal : list) { animal.eat(); } } }
Animal類
package lx; public abstract class Animal { private String name; private int age; public Animal() { } public Animal(String name, int age) { this.name = name; this.age = age; } /** * 獲取 * @return name */ public String getName() { return name; } /** * 設(shè)置 * @param name */ public void setName(String name) { this.name = name; } /** * 獲取 * @return age */ public int getAge() { return age; } /** * 設(shè)置 * @param age */ public void setAge(int age) { this.age = age; } public String toString() { return "Animal{name = " + name + ", age = " + age + "}"; } public abstract void eat(); }
cat類型
package lx; public abstract class Cat extends Animal{ public Cat() { } public Cat(String name, int age) { super(name, age); } }
Dog類
package lx; public abstract class Dog extends Animal{ public Dog() { } public Dog(String name, int age) { super(name, age); } }
哈士奇類
package lx; public class HuskyDog extends Dog{ @Override public void eat() { System.out.println("一只叫做"+getName()+"的,"+getAge()+"歲的哈士奇,正在吃骨頭,邊吃邊拆家"); } public HuskyDog() { } public HuskyDog(String name, int age) { super(name, age); } }
貍花貓類
package lx; public class LihuaCat extends Cat { @Override public void eat() { System.out.println("一只叫做" + getName() + "的," + getAge() + "歲的貍花貓,正在吃魚"); } public LihuaCat() { } public LihuaCat(String name, int age) { super(name, age); } }
波斯貓類
package lx; public class PersianCat extends Cat{ @Override public void eat() { System.out.println("一只叫做"+getName()+"的,"+getAge()+"歲的波斯貓,正在吃小餅干"); } public PersianCat() { } public PersianCat(String name, int age) { super(name, age); } }
泰迪貓類
package lx; public class TeddyDog extends Dog{ @Override public void eat() { System.out.println("一只叫做"+getName()+"的,"+getAge()+"歲泰迪,正在吃骨頭,邊吃邊蹭"); } public TeddyDog() { } public TeddyDog(String name, int age) { super(name, age); } }
總結(jié)
到此這篇關(guān)于Java 中的泛型超全詳解的文章就介紹到這了,更多相關(guān)java泛型內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺談@RequestParam(required = true)的誤區(qū)
這篇文章主要介紹了@RequestParam(required = true)的誤區(qū),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11使用Spring掃描Mybatis的mapper接口的三種配置
這篇文章主要介紹了使用Spring掃描Mybatis的mapper接口的三種配置,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-082020macOS Big Sur配置Java開發(fā)環(huán)境之jdk安裝過程
這篇文章主要介紹了2020macOS Big Sur配置Java開發(fā)環(huán)境之jdk安裝,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-02-02springboot項目使用Disruptor做內(nèi)部消息隊列的實現(xiàn)
本文主要介紹了springboot項目使用Disruptor做內(nèi)部消息隊列的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07ShardingSphere結(jié)合MySQL實現(xiàn)分庫分表的項目實踐
在實際開發(fā)中,如果表的數(shù)據(jù)過大我們需要把一張表拆分成多張表,本文主要介紹了使用ShardingSphere實現(xiàn)MySQL分庫分表,具有一定的參考價值,感興趣的可以了解一下2024-03-03