Java 泛型示例 - 泛型方法,類,接口


Java Genrics 是 Java 5 中引入的最重要的功能之一。

如果您一直在使用Java Collections並使用版本 5 或更高版本,那么我確定您已經使用過它。

Java 中具有集合類的泛型非常容易,但是它提供了比僅創建集合類型更多的功能。

我們將在本文中嘗試學習泛型的功能。如果我們使用專業術語,對泛型的理解有時會變得混亂,因此,我將盡量保持其簡單易懂。

1. Java 中的泛型

Java 5 中添加了泛型,以提供編譯時類型檢查,並消除了ClassCastException使用集合類時常見的風險。整個收集框架都進行了重寫,以使用泛型進行類型安全。讓我們看看泛型如何幫助我們安全地使用集合類。

List list = new ArrayList();
list.add("abc");
list.add(new Integer(5)); 

for(Object obj : list){
	
    String str=(String) obj; 
}


上面的代碼可以很好地編譯,但是在運行時會引發ClassCastException,因為我們試圖將列表中的對象強制轉換為String,而其中一個元素是Integer類型。在Java 5之后,我們使用如下收集類。

List<String> list1 = new ArrayList<String>(); // java 7 ? List<String> list1 = new ArrayList<>(); 
list1.add("abc");
//list1.add(new Integer(5)); //編譯錯誤

for(String str : list1){
     //no type casting needed, avoids ClassCastException
}

請注意,在創建列表時,我們已指定列表中元素的類型為String。因此,如果我們嘗試在列表中添加任何其他類型的對象,則該程序將引發編譯時錯誤。還要注意,在循環中中,我們不需要列表中元素的類型轉換,因此在運行時刪除了ClassCastException。

  1. Java通用類

我們可以使用泛型類型定義自己的類。泛型類型是通過類型進行參數化的類或接口。我們使用尖括號(<>)來指定類型參數。

為了了解其好處,我們假設有一個簡單的類:


package com.journaldev.generics;

public class GenericsTypeOld {

	private Object t;

	public Object get() {
		return t;
	}

	public void set(Object t) {
		this.t = t;
	}

        public static void main(String args[]){
		GenericsTypeOld type = new GenericsTypeOld();
		type.set("Pankaj"); 
		String str = (String) type.get(); //type casting, error prone and can cause ClassCastException
	}
}



請注意,在使用此類時,我們必須使用類型轉換,並且它可以在運行時產生ClassCastException。現在,我們將使用Java通用類替換如下所示的相同類。


package com.journaldev.generics;

public class GenericsType<T> {

	private T t;
	
	public T get(){
		return this.t;
	}
	
	public void set(T t1){
		this.t=t1;
	}
	
	public static void main(String args[]){
		GenericsType<String> type = new GenericsType<>();
		type.set("Pankaj"); //valid
		
		GenericsType type1 = new GenericsType(); //raw type
		type1.set("Pankaj"); //valid
		type1.set(10); //valid and autoboxing support
	}
}



注意main方法中GenericsType類的使用。我們不需要進行類型轉換,並且可以在運行時刪除ClassCastException。如果我們在創建時未提供類型,則編譯器將發出警告,“ GenericsType是原始類型。

泛型類型GenericsType 的引用應參數化”。當我們不提供類型時,該類型就變成了類型 Object,因此它允許String和Integer對象。但是,我們應始終嘗試避免這種情況,因為在處理可能產生運行時錯誤的原始類型時,我們必須使用類型轉換。

還要注意,它支持Java自動裝箱。

3. Java通用接口

Comparable接口是接口中泛型的一個很好的例子,它寫為:


package java.lang;
import java.util.*;

public interface Comparable<T> {
    public int compareTo(T o);
}



以類似的方式,我們可以在Java中創建通用接口。我們也可以像Map界面具有多個類型參數。同樣,我們也可以為參數化類型提供參數化值,例如new HashMap<String, List<String>>();有效。

  1. Java通用類型

Java通用類型命名約定可以幫助我們輕松理解代碼,並且具有命名約定是Java編程語言的最佳實踐之一。因此,泛型也帶有自己的命名約定。通常,類型參數名稱是單個大寫字母,以可以實現與Java變量區分開。最常用的類型參數名稱為:

  • E –元素由Java Collections Framework廣泛使用,例如ArrayList,Set等
  • K –鍵(在Map中使用)
  • N –數字
  • T –類型
  • V –值(在Map中使用)
  • S,U,V等–第二,第三,第四類型

5. Java通用方法

有時我們不希望整個類都被參數化,在這種情況下,我們可以創建java泛型方法。由於構造函數是一種特殊的方法,因此我們也可以在構造函數中使用泛型類型。

這是一個顯示Java泛型方法示例的類。


package com.journaldev.generics;

public class GenericsMethods {

	//Java Generic Method
	public static <T> boolean isEqual(GenericsType<T> g1, GenericsType<T> g2){
		return g1.get().equals(g2.get());
	}
	
	public static void main(String args[]){
		GenericsType<String> g1 = new GenericsType<>();
		g1.set("Pankaj");
		
		GenericsType<String> g2 = new GenericsType<>();
		g2.set("Pankaj");
		
		boolean isEqual = GenericsMethods.<String>isEqual(g1, g2);
		//above statement can be written simply as
		isEqual = GenericsMethods.isEqual(g1, g2);
		//This feature, known as type inference, allows you to invoke a generic method as an ordinary method, without specifying a type between angle brackets.
		//Compiler will infer the type that is needed
	}
}



注意_的isEqual_方法簽名顯示了在方法中使用泛型類型的語法。另外,請注意如何在我們的Java的程序中使用這些方法。我們可以在調用這些方法時指定類型,也可以像普通方法一樣調用它們。Java編譯器足夠聰明,可以確定要使用的變量的類型,這種功能稱為類型變量

6. Java泛型綁定類型參數

假設我們要限制可以在參數化類型中使用的對象的類型,例如在比較兩個對象的方法中,並且我們要確保接受的對象是可比較的。要聲明一個有界的類型參數,請列出類型參數的名稱,然后列出擴展關鍵字,再加上其上限,以下下面的方法。

public static <T extends Comparable<T>> int compare(T t1, T t2){
		return t1.compareTo(t2);
}

這些方法的調用與無界方法類似,不同之處在於,如果我們嘗試使用任何非Comparable的類,則引發編譯時錯誤。

綁定類型參數可以與方法以及類和接口一起使用。

Java泛型也支持多個范圍,即<T擴展A&B&C>。在這種情況下,A可以是接口或類。如果A是類,則B和C應該是接口。在多個范圍內,我們不能有多個類。

7. Java泛型和繼承

我們知道,如果A是B的子類,則Java繼承允許我們將變量A分配給另一個變量B。因此,我們可能認為可以將A的任何泛型類型分配給B的泛型類型,但事實並非如此。讓我們用一個簡單的程序看看。

package com.journaldev.generics;

public class GenericsInheritance {

	public static void main(String[] args) {
		String str = "abc";
		Object obj = new Object();
		obj=str; // works because String is-a Object, inheritance in java
		
		MyClass<String> myClass1 = new MyClass<String>();
		MyClass<Object> myClass2 = new MyClass<Object>();
		//myClass2=myClass1; // compilation error since MyClass<String> is not a MyClass<Object>
		obj = myClass1; // MyClass<T> parent is Object
	}
	
	public static class MyClass<T>{}

}

8. Java通用類和子類型

我們可以通過擴展或實現來泛型一個通用類或接口。一個類或接口的類型參數與另一類或接口的類型參數之間的關系由extend和實現子句確定。

例如,ArrayList 實現了擴展Collection 的List ,因此ArrayList 是List 的子類型,而List 是Collection 的子類型。

只要不更改type參數,子類型關系就會保留,下面顯示了多個type參數的示例。

interface MyList<E,T> extends List<E>{
}

List 的子類型可以是MyList <String,Object>,MyList <String,Integer>等。

9. Java通用通配符

問號(?)是泛型中的通配符,表示未知類型。通配符可以用作參數,字段或局部變量的類型,有時還可以用作返回類型。在調用通用方法或實例化通用類時,不能使用通配符。在以下各節中,我們將學習上界通配符,下界通配符和通配符捕獲。

9.1)Java泛型上界通配符

上限通配符用於在方法中放寬對變量類型的限制。假設我們要編寫一個將返回列表中數字總和的方法,那么我們的實現將是這樣的。

public static double sum(List<Number> list){
	double sum = 0;
	for(Number n : list){
		sum += n.doubleValue();
	}
	return sum;
}

現在,上述實現的問題在於它不適用於Integers或Doubles,因為我們知道List 和List 不相關,這在使用高層通配符時很有用。我們將通用通配符與 extends關鍵字和 上級類或接口一起使用,這將允許我們傳遞上級子類類型的參數。

可以像下面的程序一樣修改上面的實現。

package com.journaldev.generics;

import java.util.ArrayList;
import java.util.List;

public class GenericsWildcards {

	public static void main(String[] args) {
		List<Integer> ints = new ArrayList<>();
		ints.add(3); ints.add(5); ints.add(10);
		double sum = sum(ints);
		System.out.println("Sum of ints="+sum);
	}

	public static double sum(List<? extends Number> list){
		double sum = 0;
		for(Number n : list){
			sum += n.doubleValue();
		}
		return sum;
	}
}

就像按照接口編寫代碼一樣,在上述方法中,我們可以使用上限類號碼的所有方法。請注意,對於上界列表,除空之外,我們不允許將任何對象添加到列表中。如果我們嘗試在sum方法內將元素添加到列表中,則該程序將無法編譯。

9.2)Java泛型無限制通配符

有時,我們希望通用方法適用於所有類型,在這種情況下,可以使用無界通配符。與使用<?extends Object>。

public static void printData(List<?> list){
	for(Object obj : list){
		System.out.print(obj + "::");
	}
}

我們可以為_PrintData_方法提供List 或List 或任何其他類型的Object列表參數。與上限列表類似,我們可以在列表中添加任何內容。

9.3)Java泛型下界通配符

假設我們要在方法中將整體添加到整數列表中,我們可以將參數類型保持為List ,但可以與Integers捆綁在一起,而List 和List 也可以容納整數,因此我們可以使用下限通配符來實現。我們使用超級關鍵字和下限類的泛型通配符(?)來實現此目的。

我們可以傳遞下界或下界的任何超類型作為參數,在這種情況下,java編譯器允許將下界對象類型添加到列表中。

public static void addIntegers(List<? super Integer> list){
	list.add(new Integer(50));
}

10.使用泛型通配符進行子類型化

List<? extends Integer> intList = new ArrayList<>();
List<? extends Number>  numList = intList;  // OK. List<? extends Integer> is a subtype of List<? extends Number>

11. Java泛型類型重構

添加了Java泛型以在編譯時提供類型檢查,並且在運行時沒有使用,因此Java編譯器使用類型更改功能刪除字節碼中的所有泛型類型檢查代碼,並在必要時插入類型轉換。類型定義可確保不會為參數化類型創建新的類;因此,泛型不會產生運行時浪費。

例如,如果我們有如下通用類;


public class Test<T extends Comparable<T>> {

    private T data;
    private Test<T> next;

    public Test(T d, Test<T> n) {
        this.data = d;
        this.next = n;
    }

    public T getData() { return this.data; }
}

Java編譯器用第一個綁定接口Comparable替換有界類型參數T,如下代碼:


public class Test {

    private Comparable data;
    private Test next;

    public Node(Comparable d, Test n) {
        this.data = d;
        this.next = n;
    }

    public Comparable getData() { return data; }
}

12.泛型常見問題解答

12.1)為什么我們在Java中使用泛型?

泛型提供了強大的編譯時類型檢查,並降低了ClassCastException和顯式對象轉換的風險。

12.2)泛型中的T是什么?

我們使用 創建通用類,接口和方法。我們在使用T時將其替換為實際類型。

12.3)泛型如何在Java中工作?

通用代碼可確保類型安全。編譯器使用類型預先在編譯時刪除所有類型參數,以減少運行時的重載。

13. Java泛型–進一步閱讀

  • 泛型不支持子類型,因此List<Number> numbers = new ArrayList<Integer>();將不進行編譯
  • 我們無法創建通用副本,因此List<Integer>[] array = new ArrayList<Integer>[10]無法編譯

這是所有的Java泛型,Java泛型是非常龐大的,需要大量的時間來了解和有效地使用它。本文提供了泛型的基本細節,以及如何使用泛型來擴展程序的類型安全性。


“不積跬步,無以至千里”,希望未來的你能:有夢為馬 隨處可棲!加油,少年!

關注公眾號:「Java 知己」,每天更新Java知識哦,期待你的到來!

  • 發送「Group」,與 10 萬程序員一起進步。
  • 發送「面試」,領取BATJ面試資料、面試視頻攻略。
  • 發送「玩轉算法」,領取《玩轉算法》系列視頻教程。
  • 千萬不要發送「1024」...


每日福利

posted @ 2019-10-25 13:37  Java知己  閱讀( 304)  評論( 0編輯  收藏


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM