自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

你,可能沒完全搞懂 Java 泛型

開發(fā) 后端
今天我們來談談泛型。其實在初學的時候,我就對泛型有點蒙,因為看到有人說 Java 的泛型不是真的泛型,我搞不懂。

[[437732]]

本文轉載自微信公眾號「yes的練級攻略」,作者是yes呀。轉載本文請聯(lián)系yes的練級攻略公眾號。

大家好,我是yes。

今天我們來談談泛型。其實在初學的時候,我就對泛型有點蒙,因為看到有人說 Java 的泛型不是真的泛型,我搞不懂。

還有人說 Java 的泛型在實際運行時候會把類型給擦除了,我想著擦除是什么意思?為什么要擦除?

那把類型給擦除了為什么反射的時候還能得到泛型的類型信息?

我們今天就來盤一盤泛型:

  • 為什么需要泛型?
  • 為什么都說Java的泛型是偽泛型?
  • 為什么Java泛型的實現(xiàn)是類型擦除?
  • 既然擦除了類型,為什么在運行期仍能反射獲得類型?

話不多說,發(fā)車!

為什么需要泛型

我們都知道在 Java5 之前是沒有泛型的,沒泛型都能用的好好的,那為什么要加個泛型呢,能給我們帶來什么呢?

我們先來看下下面這段代碼:

  1. List list = new ArrayList(); 
  2.  list.add("yes"); // 加入string 
  3.  list.add(233); // 加入int 

在沒有泛型的時候,加入的集合的數(shù)據(jù)并不會做任何約束,都會被當作成 Object 類型。

可能有人說,這很好呀,多自由!確實,自由是自由了,但是代碼的約束能力越低,就越容易出錯,使用上也有諸多不便,比如獲取的時候需要強轉。

如果一不小心取錯類型,編譯的時候能過,但是運行的時候卻拋錯。

綜上,Java 引入了泛型。

而泛型的作用就是加了一層約束,約束了類型。

有了這一層約束就好辦事兒了,由于聲明了類型,可以在編譯的時候就識別出不準確的類型元素。使得錯誤提早拋出,避免運行時才發(fā)現(xiàn)。

并且也不需要在代碼上顯示的強轉,從以下代碼可以看出,能直接獲取 String 類型元素。

我們再小結一下泛型的好處:

提高了代碼的可讀性,一眼就能看出集合(其它泛型類)的類型

可在編譯期檢查類型安全,增加程序的健壯性

省心不需要強轉(其實內(nèi)部幫做了強轉,下面會說)

提高代碼的復用率,定義好泛型,一個方法(類)可以適配所有類型 (其實以前 Object 也行,就是比較麻煩)

為什么都說Java的泛型是偽泛型

看起來我們平日用的一些泛型好像沒啥毛病啊?為什么都說Java的泛型是偽泛型?哪里偽了?

我們再來看一段代碼:

可以看到,我聲明的是一個 String 類型的集合,但是通過反射往集合中插入了 int 類型的數(shù)據(jù),居然成功了???

這說明在運行時泛型根本沒有起作用!也就是說在運行的時候 JVM 獲取不到泛型的信息,也會不對其做任何的約束。

你可以認為 Java 的泛型就是編譯的時候生效,運行的時候沒有泛型,所以大家才說 Java 是偽泛型!

因此,雖然在 IDE 寫代碼的時候泛型生效了,而實際上在運行的時候泛型的類型是被擦除的。

一言蔽之,Java的泛型只在編譯時生效,JVM 運行時沒有泛型。

為什么Java泛型的實現(xiàn)是類型擦除?

類型擦除 (type Erasure)。

Java 之所以在運行時將類型擦除的原因是為了向下兼容,即兼容 Java5 之前的編譯的 class 文件。

例如 Java 1.2 上正在跑的代碼,可以在 Java 5 的 JRE 上運行。

就是為了這該死的向下兼容,才使得 Java 實現(xiàn)的是偽泛型。

我從現(xiàn)有的實現(xiàn)倒推偽泛型的設計可能思路(我個人瞎掰的,您隨意聽聽)是這樣的:

  • 這 Java 5 以前的版本,線上已經(jīng)有很多應用在跑了,我好像不能新加一套,影響推廣還可能被罵的很慘
  • 咋辦,泛型畢竟是加一個約束,以前的代碼沒這個約束啊,該如何兼容?
  • 有了,要不我在編譯器上動手腳,在編譯的時候識別和約束泛型,然后編譯過了就把泛型的信息擦除了。這樣運行的時候約束不是沒了嗎?不就和之前保持一致了嗎?好,就這樣干了!

總而言之,就是為了向下兼容才采用類型擦除來實現(xiàn)的。

這里還有個坑,也就是泛型不支持基本類型,比如 int。因為泛型擦除后就變成了Object,這個 int 和 Object 兼容有點麻煩。

我在網(wǎng)上看 R大的解釋如下:

GJ / Java 5說:這個問題有點麻煩,趕不及在這個版本發(fā)布前完成了,就先放著不管吧。于是Java 5的泛型就不支持原始類型,而我們不得不寫惡心的ArrayList、ArrayList…

這就是一個偷懶了的地方。

emmm,這說明啥?寫 Java 的也是程序員,也是要發(fā)版有上線需求的,所以說......

好了,言歸正傳,現(xiàn)在 Java 的泛型實現(xiàn)確實是偽泛型??吹竭@不經(jīng)有人會發(fā)問?難道就只能一直偽泛型了嗎?

那啥,我覺得吧,只要時間允許,只要錢夠,應該都能做?哈哈哈。

既然擦除了類型,為什么在運行期仍能反射獲得類型?

難道是沒擦干凈?別急,我們慢慢看。

我們先來回顧一下這段代碼:

我們定義了泛型類型為 String 的 list,并且獲取的 str 不需要強轉,這一步是怎么做的呢?我們 javap -c 看下字節(jié)碼:

我們從反編譯看生成的字節(jié)碼可以看到, new 的 list 沒有保存泛型的信息,所以是被擦除了。

然后看到 #7 沒,有個 checkcast ,強轉的類型是 String,看到這大伙兒應該都明白,為什么類型擦除了,但是我們 get 的時候不需要強轉呢?因為編譯器隱性的幫我們插入了強轉的代碼!所以我們的 Java 代碼中不需要寫強轉。

再回到此小節(jié)標題:既然擦除了類型,為什么在運行期仍能反射獲得類型?

答案就藏在 class 文件中。我們來看下這段代碼:

通過反射,我確實獲得了 list 的類型。那既然類型被擦除了,這又是怎么做到的呢?

我們直接進行一手 javap -v,反編譯看到字節(jié)碼里面有這樣的記錄:

這下很好理解了,class 文件里面存了這個信息,所以我們通過反射自然而然的就能得到這個類型。沒錯,就是這么簡單。

也正因為原理如此,所以我們只能對以下三種情況利用反射獲取泛型類型:

  • 成員變量的泛型
  • 方法入?yún)⒌姆盒?/li>
  • 方法返回值的泛型

對于局部變量這種是無能為力的。

最后

好了,今天關于泛型的文章暫時先到這,其實泛型的東西還沒講完,比如通配符、上界下界的限制(泛型的 PECS 原則),再如泛型的橋接,以及橋接的坑。

東西還挺多的,所以放下篇!等著哈。

參考

 

https://www.zhihu.com/question/28665443/answer/118148143

 

責任編輯:武曉燕 來源: yes的練級攻略
相關推薦

2022-01-03 18:07:56

泛型場景demo

2021-02-08 11:20:27

Java類型數(shù)組

2021-03-01 07:34:42

Java泛型ArrayList

2009-09-25 10:03:51

Java泛型

2021-10-17 13:10:56

函數(shù)TypeScript泛型

2024-04-23 08:23:36

TypeScript泛型Generics

2021-12-30 19:34:15

Java泛型JDK

2011-06-03 08:49:54

Java

2021-07-09 05:56:28

云計算IaaS公有云

2021-06-17 06:51:32

Java泛型Java編程

2021-07-01 06:47:30

Java泛型泛型擦除

2021-09-29 18:17:30

Go泛型語言

2020-10-20 10:17:20

Java泛型Type

2017-03-06 16:51:52

Java泛型實現(xiàn)

2011-03-21 16:26:28

java泛型

2021-06-18 08:25:42

Java泛型通配符

2022-03-02 14:00:46

Nest.jsExpress端口

2021-10-29 10:55:07

Go 泛型語言

2022-02-22 08:25:51

typeScript泛型概念泛型使用

2022-09-15 14:04:07

Go語言泛型
點贊
收藏

51CTO技術棧公眾號