前言: 本章主要是為了后面學習集合框架所做的知識補充。補充了泛型以及包裝類兩個知識,但是該章泛型的講解不夠全面,主要是為了集合框架學習做鋪墊。
1. 預備知識-泛型(Generic)
1.1 泛型的引入
我們之前實現(xiàn)過的順序表,實現(xiàn)的是保存某一類型的元素(如 int
型)
示例代碼:
public class MyArrayList{ private int[] array; // 保存順序表的元素,元素都為 int 類型 private int size; // 保存順序表內存數(shù)據個數(shù) public MyArrayList(){ this.array=new int[10]; } public void add(int val){ // 尾插 this.array[size]=val; this.size++; } public int get(int index){ // 獲取 index 位置的元素 return this.array[index]; } ... }
但是這樣寫的話,這個順序表就只能存儲 int
類型的元素了
如果現(xiàn)在需要保存指向 Person
類型對象的引用的順序表,該如何解決呢?如果又需要保存指向 Book
類型對象的引用呢?
- 首先,我們在學習多態(tài)的時了解到:基類的引用可以指向子類的對象
-
其次,我們也知道
Object
類是 Java 中所有所有類的祖先類
因此,要解決上述問題,我們可以這樣做
將我們的順序表的元素類型定義成 Object
類型,這樣我們的 Object
類型的引用可以指向 Person
類型的對象或者指向 Book
類型的對象
示例代碼:
public class MyArrayList{ private Object[] array; // 保存順序表的元素,即 Object 類型的引用 private int size; // 保存順序表內存數(shù)據個數(shù) public MyArrayList(){ this.array=new Object[10]; } public void add(Object val){ // 尾插 this.array[size]=val; this.size++; } public Object get(int index){ // 獲取 index 位置的元素 return this.array[index]; } ... }
這樣,我們就可以很自由的存儲指向任意類型的對象的引用到我們的順序表了
示例代碼:
MyArrayList books = new MyArrayList(); for(int i=0; i<10;i++){ books.add(new Book()); // 插入10本書到順序表 } MyArrayList people = new MyArrayList(); for(int i=0; i<10; i++){ people.add(new Person()); // 插入10個人到順序表 }
遺留問題: 現(xiàn)在的 MyArrayList
雖然可以做到添加任意類型的引用到其中,但會遇到下面的問題
當我們使用這樣的代碼時,明知道存儲的是哪種類型的元素,但還是要進行強制轉換。如
MyArrayList books = new MyArrayList(); books.add(1); // 將 Object 類型轉換為 int 類型 (需要類型轉換才能成功) int val=(int)books.get(0); System.out.println(val); // 結果為:1
雖然知道返回的元素是 int 類型,但還是要進行強制類型轉換
創(chuàng)建的一個 MyArrayList
中可以存放各種類型,形成了一個大雜燴。并且將 Object 類型(具體是 A 類型)轉換為 B 類型時,即使強制轉換,也會產生異常 ClassCastException
MyArrayList books = new MyArrayList(); books.add(new Book()); // 將 Object 類型轉換為 Person (需要類型轉換才能成功) Person person = (Person)books.get(0); // 但是雖然編譯正確了,運行時還是會拋出異常 ClassCastException
因此 Java 針對這一問題就出現(xiàn)了泛型
Java 泛型(generics)是 JDK 5 中引入的一個新特性,泛型提供了編譯時類型安全檢測機制,該機制允許程序員在編譯時檢測到非法的類型。
泛型的本質是參數(shù)化類型,也就是說所操作的數(shù)據類型被指定為一個參數(shù)。
1.2 泛型的分類
泛型可以分為兩類
- 泛型類
- 泛型方法
預備知識主要是為了學習、理解集合框架,所以這里只簡單介紹泛型類,后面將會專門為泛型寫一個章節(jié)。
1.3 泛型類的定義
規(guī)則:
- 在類名后面添加了類型參數(shù)聲明
- 泛型類的類型參數(shù)聲明部分包含一個或多個類型參數(shù),參數(shù)間用逗號隔開。一個泛型參數(shù),也被稱為一個類型變量,是用于指定一個泛型類型名稱的標識符
- 泛型的泛型參數(shù)一定是類類型,如果是簡單類型,那么必須是對應的包裝類
這里直接將上面定義的 MyArrayList
類改寫成泛型類
示例代碼:
public class MyArrayList<T>{ private T[] array; private int size; public MyArrayList(){ this.array=(T[])new Object[10]; } public void add(T val){ this.array[size]=val; this.size++; } public T get(int index){ return this.array[index]; } ... }
此時我們就將這個順序表改寫成了一個泛型類,接下來我們來使用它
示例代碼:
MyArrayList<String> myArrayList = new MyArrayList<>(); myArrayList.add("Hello"); myArrayList.add("Goodbye"); String s = myArrayList.get(0); System.out.println(s); // 結果為:Hello
上述的 myArrayList
只能存放 String
類型的元素,并且不需要再添加強制類型轉換
泛型的意義:
- 自動進行類型的檢查
- 自動進行類型的轉換
Java 中泛型標記符: 類型形參一般使用一個大寫字母表示,如:
- E ― Element(在集合中使用,因為集合中存放的是元素)
- T ― Type(Java 類)
- K ― Key(鍵)
- V ― Value(值)
- N ― Number(數(shù)值類型)
- ? ―表示不確定的 Java 類型
1.4 泛型編譯的機制
如果不重寫 toString
方法,輸出某個類的實例化對象,如
代碼示例:
// 假設創(chuàng)建了一個 Person 類 Person person = new Person(); System.out.println(person);
結果為:
如果用上述的泛型類,輸出其實例化對象,如
代碼示例:
MyArrayList<String> myArrayList1 = new MyArrayList<>(); System.out.println(myArrayList1); MyArrayList<Integer> myArrayList2 = new MyArrayList<>(); System.out.println(myArrayList2); MyArrayList<Boolean> myArrayList3 = new MyArrayList<>(); System.out.println(myArrayList3);
結果為:
我們發(fā)現(xiàn):
泛型類和非泛型類輸出的樣例格式都是一樣的:類名@地址
為什么泛型類的實例化對象結果不是輸出泛型類后面的泛型參數(shù) < T >
呢?
這里就要了解泛型是怎么編譯的
泛型的編譯使用了一種機制:擦除機制
擦除機制只作用于編譯期間,換句話說,泛型就是編譯時期的一種機制,運行期間沒有泛型的概念
解釋:
-
當我們存放元素的時候,泛型就會根據
<T>
自動進行類型的檢查。 -
但編譯的時候,這些
<T>
就被擦除成了Object
2. 預備知識-包裝類(Wrapper Class)
Object 引用可以指向任意類型的對象,但有例外出現(xiàn)了,8 種基本數(shù)據類型不是對象,那豈不是剛才的泛型機制要失效了?
實際上也確實如此,為了解決這個問題,Java 中引入了一類特殊的類,即這 8 種基本數(shù)據類型的包裝類。在使用過程中,會將類似 int 這樣的值包裝到一個對象中去。
2.1 基本數(shù)據類型和包裝類的對應關系
基本數(shù)據類型 | 包裝類 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
2.2 包裝類介紹
Java 是一個面向對象的語言,基本類型并不具有對象的性質,為了與其他對象“接軌”就出現(xiàn)了包裝類型
既然包裝類是一個類,那么就有它對應的成員變量和成員方法。打孔大家可以具體的去查看文檔了解各個包裝類
2.3 裝箱(boxing)和拆箱(unboxing)
包裝類中有兩個重要的知識點,裝箱和拆箱
- 裝箱: 把基本數(shù)據類型轉為對應的包裝類型
- 拆箱: 把包裝類型轉換為基本數(shù)據類型
裝箱示例代碼:
// 方式一 Integer i1 = 10; // 方式二 Integer i2 = Integer.valueOf(10); // 方式三 Integer i3 = new Integer(10);
拆箱示例代碼:
// 方式一 int i = i1; // 方式二 int i = i1.intValue();
2.4 自動裝箱(autoboxing)和自動拆箱(autounboxing)
那自動裝箱又是什么呢?我們可以對下面這份代碼進行反編譯(反編譯指令為 javap -c 類名
)
代碼示例:
public class TestDemo { public static void main(String[] args) { Integer i = 10; int j = i; } }
通過反編譯指令,得到了如下結果:
-
我們發(fā)現(xiàn)在底層中 10 是通過
Integer.valueOf
這個靜態(tài)方法賦值給了 i,進行裝箱操作 -
再將 i 通過
Integer.intValue
這個方法復制給了 j,進行拆箱操作
那么什么是手動裝箱和手動拆箱呢?
就是和底層原理一樣,通過 Integer.valueOf
和 Integer.intValue
方法進行的裝箱和拆箱就是手動的
而不是通過這些方法進行的裝箱和拆箱就是自動的
2.5 包裝類面試題
思考下列代碼結果:
Integer a = 120; Integer b = 120; System.out.println(a == b);
結果為:true
再看一個代碼:
Integer a = 130; Integer b = 130; System.out.println(a == b);
結果為:false
這是為什么呢?
- 首先我們看到 a 和 b 都進行了裝包操作,因此我們就要去了解裝包的時候發(fā)生了什么
-
通過轉到
Integer.valueOf
的定義我們看到
-
該定義意思就是:如果 i 大于等于
IntegerCache
的最小值,小于它的最大值,就返回IntegerCache.cache[i + (-IntegerCache.low)]
,否則就返回new Integer(i)
- 而 new 一個對象的話,相當于比較的就是地址的值了,所以是 false
-
因此我們要知道
IntegerCache
的最大值以及最小值是多少,此時我們轉到它的定義
-
上圖中我們了解到 low 為 -128、high為 127,而 cache 其實就是一個數(shù)組。我們知道數(shù)組的下標是從 0 開始的,而
i + (-IntegerCache.low)
表示的最小值正好就是 0,也就是說明數(shù)組下標為 0 時存儲的值就為 -128,并且依次往后遞推。 - 因此數(shù)值在 -128 到 127 之間時返回的就是和這個數(shù)相同的值,所以結果為 true
那為什么要專門創(chuàng)建一個數(shù)組呢?所有數(shù)字返回 new 的對象不就行了嗎?
這是因為,這樣做可以提高效率。實例化對象是需要消耗資源的。而數(shù)組其實就是一個對象,可以減少資源的消耗。
到此這篇關于Java集合框架入門之泛型和包裝類的文章就介紹到這了,更多相關Java 泛型詳解內容請搜索服務器之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://blog.csdn.net/weixin_51367845/article/details/120818850?utm_medium=distribute.pc_category.none-task-blog-hot-1.nonecase&depth_1-utm_source=distribute.pc_category.none-task-blog-hot-1.nonecase