91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java泛型如何使用

發布時間:2021-11-24 14:21:19 來源:億速云 閱讀:132 作者:iii 欄目:互聯網科技

本篇內容主要講解“Java泛型如何使用”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Java泛型如何使用”吧!

什么是泛型

泛型是在JDK 5時就引入的新特性,也就是“參數化類型”,通俗來講就是將原來的具體類型通過參數化來定義,使用或調用時再傳入具體的類型(類型實參)。

泛型的本質是為了參數化類型(在不創建新類型的前提下,通過泛型指定的不同類型來控制形參具體的類型)。在泛型使用過程中,操作的數據類型被指定為一個參數,這種參數類型可以用在類、接口和方法中,分別被稱為泛型類、泛型接口、泛型方法。

為什么使用泛型

未使用泛型時,可以通過Object來實現參數的“任意化”,但這樣做的缺點就是需要顯式的強制類型轉換,這就需要開發者知道實際的類型。

而強制類型轉換是會出現錯誤的,比如Object將實際類型為String,強轉成Integer。編譯期是不會提示錯誤的,而在運行時就會拋出異常,很明顯的安全隱患。

Java通過引入泛型機制,將上述的隱患提前到編譯期進行檢查,開發人員既可明確的知道實際類型,又可以通過編譯期的檢查提示錯誤,從而提升代碼的安全性和健壯性。

使用泛型前后的對比

拿一個經典的例子來演示一下未使用泛型會出現的問題。

List list = new ArrayList();
list.add(1);
list.add("zhuan2quan");
list.add("程序新視界");

for (int i = 0; i < list.size(); i++) {
    String value = (String) list.get(i);
    System.out.println("value=">

上述代碼在編譯器并不會報任何錯誤,但當執行時會拋出如下異常:

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

那么,是否可以在編譯器就解決這個問題,而不是在運行期拋出異常呢?泛型應運而生。上述代碼通過泛型來寫之后,變成如下形式:

List<String> list = new ArrayList<>();
list.add(1);
list.add("zhuan2quan");
list.add("程序新視界");

for (String value : list) {
    System.out.println("value=" + value);
}

可以看出,代碼變得更加清爽簡單,而且list.add(1)這行代碼在IDE中直接會提示錯誤信息:

Required type: String
Provided: int

提示錯誤信息便是泛型對向List中添加的數據產生了約束,只能是String類型。

泛型中通配符

在使用泛型時經常會看到T、E、K、V這些通配符,它們代表著什么含義呢?

本質上它們都是通配符,并沒有什么區別,換成A-Z之間的任何字母都可以。不過在開發者之間倒是有些不成文的約定:

  • T (type) 表示具體的一個java類型;

  • K V (key value) 分別代表java鍵值中的Key Value;

  • E (element) 代表Element;

為什么Java的泛型是假泛型

為了做到向下兼容,Java中的泛型僅僅是一個語法糖,并不是C++那樣的真泛型。

還是上面的例子,在直接向泛型為String的List中添加int類型會提示錯誤:

List<String> list = new ArrayList<>();
list.add(1);

針對上述代碼,我們采用反射間接地調用add方法:

@Test
public void test3() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    List<Integer> list = new ArrayList<>();
    list.add(1);
    Method add = list.getClass().getMethod("add", Object.class);
    add.invoke(list,"程序新視界");
    System.out.println(list);
    System.out.println(list.get(1));
}

執行上述代碼,我們發現程序并沒有拋出異常,正常打印出入:

[1, 程序新視界]
程序新視界

原本只能裝入Integer的List,成功裝入了一個String類型的值。由此可見,所謂的泛型確實是假泛型。

同時,我們還可以通過字節碼來證明。拿上面使用了泛型的實例代碼,通過javap -c命令來看看字節碼:

Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup
       4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
       7: astore_1
       8: aload_1
       9: ldc           #6                  // String zhuan2quan
      11: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      16: pop
      17: aload_1
      18: ldc           #7                  // String 程序新視界
      20: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      25: pop
      26: aload_1
      27: invokeinterface #18,  1           // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
      32: astore_2
      33: aload_2
      34: invokeinterface #19,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z
      39: ifeq          80

從字節碼中可以看出,List.add方法本質上就是一個Object。再次證明,Java的泛型僅僅在編譯期有效,在運行期則會被擦除,也就是說所有的泛型參數類型在編譯后都會被清除掉。這就是我們經常說的類型擦除。

因此,也可以說:泛型類型在邏輯上看以看成是多個不同的類型,實際上都是相同的基本類型。

泛型的定義與使用

泛型有三類,分別為:泛型類、泛型接口、泛型方法。

在學習這三種類型的泛型使用場景之前,我們需要明確一個基本準則,那就是泛型的聲明通常都是通過<>配合大寫字母來定義的,比如<T>。只不過不同類型,聲明的位置不同,使用的方式也有所不同。

泛型類

泛型類的語法形式:

class name<T1, T2, ..., Tn> { /* ... */ }

泛型類的聲明和非泛型類的聲明類似,只是在類名后面添加了類型參數聲明部分。由尖括號(<>)分隔的類型參數部分跟在類名后面。它指定類型參數(也稱為類型變量)T1,T2,…和 Tn。一般將泛型中的類名稱為原型,而將<>指定的參數稱為類型參數。

使用示例:

// T為任意標識,比如用T、E、K、V等表示泛型
public class Foo<T> {

    // 泛化的成員變量,T的類型由外部指定
    private T info;

    // 構造方法類型為T,T的類型由外部指定
    public Foo(T info){
        this.info = info;
    }

    // 方法返回值類型為T,T的類型由外部指定
    public T getInfo() {
        return info;
    }

    public static void main(String[] args) {
        // 實例化泛型類時,必須指定T的具體類型,這里為String。
        // 傳入的實參類型需與泛型的類型參數類型相同,這里為String。
        Foo<String> foo = new Foo<>("程序新視界");
        System.out.println(foo.getInfo());
    }
}

當然,上述示例中在使用泛型類時也可以不指定實際類型,語法上支持,那么此時與未定義泛型一樣,不推薦這種方式。

Foo foo11 = new Foo(1);

比如上述寫法,也是可行的,但時區了定義泛型的意義了。

泛型接口

泛型接口的聲明與泛型類一致,泛型接口語法形式:

public interface Context<T> {
    T getContext();
}

泛型接口有兩種實現方式:子類明確聲明泛型類型和子類不明確聲明泛型類型。

先看子類明確聲明泛型類型的示例:

// 實現泛型接口時已傳入實參類型,則所有使用泛型的地方都要替換成傳入的實參類型
public class TomcatContext implements Context<String> {
    @Override
    public String getContext() {
        return "Tomcat";
    }
}

子類不明確聲明泛型類型:

// 未傳入泛型實參時,與泛型類的定義相同,在聲明類的時候,需將泛型的聲明也一起加到類中
public class SpringContext<T> implements Context<T>{
    @Override
    public T getContext() {
        return null;
    }
}

當然,還有一種情況,就是我們把定義為泛型的類像前面講的一樣當做普通類使用。

上面的示例中泛型參數都是一個,當然也可以指定兩個或多個:

public interface GenericInterfaceSeveralTypes< T, R > {
    R performAction( final T action );
}

多個泛型參數可以用逗號(,)進行分割。

泛型方法

泛型類是在實例化類時指明泛型的具體類型;泛型方法是在調用方法時指明泛型的具體類型。泛型方法可以是普通方法、靜態方法、抽象方法、final修飾的方法以及構造方法。

泛型方法語法形式如下:

public <T> T func(T obj) {}

尖括號內為類型參數列表,位于方法返回值T或void關鍵字之前。尖括號內定義的T,可以用在方法的任何地方,比如參數、方法內和返回值。

protected abstract<T, R> R performAction( final T action );
 
static<T, R> R performActionOn( final Collection< T > action ) {
    final R result = ...;
    // Implementation here
    return result;
}

上述實例中可以看出泛型方法同樣可以定義多個泛型類型。

再看一個示例代碼:

public class GenericsMethodDemo1 {

    //1、public與返回值中間<T>,聲明此方法的泛型類型。
    //2、只有聲明了<T>的方法才是泛型方法,泛型類中的使用了泛型的成員方法并不是泛型方法。
    //3、<T>表明該方法將使用泛型類型T,此時才可以在方法中使用泛型類型T。
    //4、T可以為任意標識,如T、E、K、V等。
    public static <T> T printClass(T obj) {
        System.out.println(obj);
        return obj;
    }

    public static void main(String[] args) {
        printClass("abc");
        printClass(123);
    }
}

需要注意的是,泛型方法與類是否是泛型無關。另外,靜態方法無法訪問類上定義的泛型;如果靜態方法操作的引用數據類型不確定的時候,必須要將泛型定義在方法上。

上述示例中如果GenericsMethodDemo1定義為GenericsMethodDemo1<T>,則printClass方法是無法直接使用到類上的T的,只能像上面代碼那樣訪問自身定義的T。

泛型方法與普通方法區別

下面,我們對比一下泛型方法和非泛型方法的區別:

// 方法一
public T getKey(){
    return key;
}

// 方法二
public <T> T showKeyName(T t){
    return t;
}

其中方法一雖然使用了T這個泛型聲明,但它用的是泛型類中定義的變量,因此這個方法并不是泛型方法。而像方法二中通過兩個尖括號聲明了T,這個才是真正的泛型方法。

對于方法二,還有一種情況,那就是類中也聲明了T,那么該方法參數的T指的只是此方法的T,而并不是類的T。

泛型方法與可變參數
@SafeVarargs
public final <T> void print(T... args){
	for(T t : args){
		System.out.println("t=" + t);
	}
}

public static void main(String[] args) {
	GenericDemo2 demo2 = new GenericDemo2();
	demo2.print("abc",123);
}

print方法打印出可變參數args中的結果,而且可變參數可以傳遞不同的具體類型。

打印結果:

t=abc
t=123

關于泛型方法總結一下就是:如果能使用泛型方法盡量使用泛型方法,這樣能將泛型所需到最需要的范圍內。如果使用泛型類,則整個類都進行了泛化處理。

泛型通配符

類型通配符一般是使用?代替具體的類型實參(此處是類型實參,而不是類型形參)。當操作類型時不需要使用類型的具體功能時,只使用Object類中的功能,那么可以用?通配符來表未知類型。例如List<?>在邏輯上是List<String>、List<Integer>等所有List<具體類型實參>的父類。

/**
 * 在使用List<Number>作為形參的方法中,不能使用List<Ingeter>的實例傳入,
 * 也就是說不能把List<Integer>看作為List<Number>的子類;
 */
public static void getNumberData(List<Number> data) {
    System.out.println("data :" + data.get(0));
}

/**
 * 在使用List<String>作為形參的方法中,不能使用List<Number>的實例傳入;
 */
public static void getStringData(List<String> data) {
    System.out.println("data :" + data.get(0));
}

/**
 * 使用類型通配符可以表示同時是List<Integer>和List<Number>、List<String>的引用類型。
 * 類型通配符一般是使用?代替具體的類型實參,注意此處是類型實參;
 * 和Number、String、Integer一樣都是一種實際的類型,可以把?看成所有類型的父類。
 */
public static void getData(List<?> data) {
    System.out.println("data :" + data.get(0));
}

上述三個方法中,getNumberData只能傳遞List<Number>類型的參數,getStringData只能傳遞List<String>類型的參數。如果它們都只使用了Object類的功能,則可以通過getData方法的形式進行聲明,則同時支持各種類型。

上述這種類型的通配符也稱作無界通配符,有兩種應用場景:

  • 可以使用Object類中提供的功能來實現的方法。

  • 使用不依賴于類型參數的泛型類中的方法。

在getData中使用了?作為通配符,但在某些場景下,需要對泛型類型實參進行上下邊界的限制。如:類型實參只準傳入某種類型的父類或某種類型的子類。

上界通配符示例如下:

/**
 * 類型通配符上限通過形如List來定義,如此定義就是通配符泛型值接受Number及其下層子類類型。
 */
public static void getUperNumber(List<? extends Number> data) {
    System.out.println("data :" + data.get(0));
}

通過extends限制了通配符的上邊界,也就是只接受Number及其子類類型。接口的實現和類的集成都可以通過extends來表示。

而這里的Number也可以替換為T,表示該通配符所代表的類型是T類型的子類。

public static void getData(List<? extends T> data) {
    System.out.println("data :" + data.get(0));
}

與上界通配符示對照也有下界通配符:

public static void getData(List<? super Integer> data) {
    System.out.println("data :" + data.get(0));
}

下界通配符表示該通配符所代表的類型是T類型的父類。

泛型的限制

原始類型(比如:int,long,byte等)無法用于泛型,在使用的過程中需要通過它們的包裝類(比如:Integer, Long, Byte等)來替代。

final List< Long > longs = new ArrayList<>();
final Set< Integer > integers = new HashSet<>();

當然,在使用的過程中會涉及到自動拆箱和自動裝箱的操作:

final List< Long > longs = new ArrayList<>();
longs.add( 0L ); // 'long' 包裝為 'Long'
 
long value = longs.get( 0 ); // 'Long'解包'long'

泛型的類型推斷

當引入泛型之后,每處用到泛型的地方都需要開發人員加入對應的泛型類型,比如:

final Map<String, Collection<String>> map =
    new HashMap<String, Collection<String>>();
 
for(final Map.Entry< String, Collection<String> > entry: map.entrySet()) {
}

為了解決上述問題,在Java7中引入了運算符<>,編譯器可以推斷出該運算符所代表的原始類型。

因此,Java7及以后,泛型對象的創建變為如下形式:

final Map< String, Collection<String>> map = new HashMap<>();

到此,相信大家對“Java泛型如何使用”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

晴隆县| 志丹县| 光山县| 阿瓦提县| 都昌县| 昆山市| 朝阳区| 磴口县| 凭祥市| 阿鲁科尔沁旗| 郑州市| 湖口县| 潮州市| 锡林浩特市| 中西区| 桐乡市| 花垣县| 丘北县| 朔州市| 溧阳市| 临沭县| 黄冈市| 株洲市| 内江市| 成武县| 霍山县| 天镇县| 宝清县| 镇江市| 南宫市| 奎屯市| 关岭| 乐至县| 盐源县| 肥西县| 南投县| 鲁山县| 榆林市| 调兵山市| 旅游| 新泰市|