Java泛型-泛型类,接口和方法
Java 5中引入了Java泛型,以在编译时提供严格的类型检查。
Java泛型中的类型参数
Java中的泛型使我们能够编写可与不同数据类型一起使用的泛型类,接口和方法。可能是因为在定义类,接口和方法时指定了类型参数。类型参数可以是任何类或者接口,例如Integer,String,自定义类或者接口。
例如,在Collection API中,ArrayList类写为
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { .. .. }
这里E是一个类型参数,包含在尖括号(<>)中。由于指定了类型参数ArrayList <E>,这意味着ArrayList类是泛型类,其中E是此List中元素的类型。
使用此通用类定义,ArrayList可以使用不同的数据类型。初始化ArrayList时必须指定实际类型。
//List that stores Integers List<Integer> nList = new ArrayList<Integer>(); nList.add(1); nList.add(2); nList.add(3); // List that stores Strings List<String> sList = new ArrayList<String>(); sList.add("A"); sList.add("B"); sList.add("C"); // List that stores objects of type Employee List<Employee> eList = new ArrayList<Employee>(); Employee emp1 = new Employee("Jean", "HR", 6000); eList.add(emp1);
为什么需要泛型
我们可能会争辩说,Java中的Object类已经可以用来引用任何类对象,这使其成为用作通用参数的良好候选对象。例如,在下面的类中,有一个方法将Object类型作为参数,因此我们可以将任何类型传递给此方法。
public class Test { public static void main(String[] args) throws IOException { Test t = new Test(); t.display(1); t.display("Hello"); t.display(5.67); } public void display(Object o) { System.out.println("passed argument is- " + o); System.out.println("passed argument's type is- " + o.getClass().getTypeName()); } }
输出:
passed argument is- 1 passed argument's type is- java.lang.Integer passed argument is- Hello passed argument's type is- java.lang.String passed argument is- 5.67 passed argument's type is- java.lang.Double
如我们所见,通过使用Object作为参数,我可以拥有一个通用方法,该方法可以与任何类型一起使用,因此为什么需要泛型。答案是Java中的泛型为代码带来类型安全性。我们将在下一部分中讨论该功能。
Java泛型的好处
1.编译时进行严格的类型检查
泛型在编译时提供严格的类型检查,因此任何类型冲突都将在编译时本身而不是在运行时引发java.lang.ClassCastException时产生错误。
例如,我们已将List初始化为非通用List,而意图是在其中存储字符串。由于不是通用的,这意味着其所有元素都将作为对象类的对象存储。如果我们错误地将Integer添加到此列表,则不会有任何编译时错误,因为Integer也是Object类型。
从List检索元素时,必须将其显式转换为该类型,并且当遇到Integer时,它将引发ClassCastException。
public class Test { public static void main(String[] args) throws IOException { // Not generic List sList = new ArrayList(); sList.add("A"); sList.add("B"); // Adding Integer sList.add(1); sList.add("C"); Iterator itr = sList.iterator(); while(itr.hasNext()){ // Casting to string when retrieving String str = (String)itr.next(); System.out.println("" + str); } } }
输出:
A B Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap') at com.theitroad.programs.Test.main(Test.java:27)
使用泛型,我们可以指定可以存储在列表中的元素类型,从而提供类型安全性。如果尝试将任何其他类型的元素添加到此List,则在编译时本身会出错。
public class Test { public static void main(String[] args) throws IOException { // Generic List List<String> sList = new ArrayList<String>(); sList.add("A"); sList.add("B"); // Not allowed, Error at compile time if // Integer is added //sList.add(1); sList.add("C"); Iterator<String> itr = sList.iterator(); while(itr.hasNext()){ String str = itr.next(); System.out.println("" + str); } } }
2.不需要显式强制转换
由于类型是通过泛型指定的,因此可以确保我们只能存储指定类型的元素,因此在检索元素时不需要显式转换。
在上面的代码中,当使用非通用列表时,需要强制类型转换。
Iterator itr = sList.iterator(); while(itr.hasNext()){ // Casting to string when retrieving String str = (String)itr.next(); System.out.println("" + str); }
使用通用列表类型时,不需要强制转换。
Iterator<String> itr = sList.iterator(); while(itr.hasNext()){ String str = itr.next(); System.out.println("" + str); }
3.实现通用算法
通过使用泛型,程序员可以实现可在不同类型上工作,易于阅读且类型安全的泛型算法。这是一个简单的泛型类示例,可以设置和获取任何类型的值。
public class Test<T> { T obj; public T getObj() { return obj; } public void setObj(T obj) { this.obj = obj; } public static void main(String[] args) throws IOException { // With Integer type Test<Integer> intParam = new Test<Integer>(); intParam.setObj(7); int value = intParam.getObj(); System.out.println("Integer value- " + value); // With String type Test<String> strParam = new Test<String>(); strParam.setObj("Test Value"); String str = strParam.getObj(); System.out.println("String value- " + str); // With Double type Test<Double> doubleParam = new Test<Double>(); doubleParam.setObj(23.45); double dblValue = doubleParam.getObj(); System.out.println("Double value- " + dblValue); } }
Integer value- 7 String value- Test Value Double value- 23.45
参见本文中的如何在Java中编写通用的冒泡排序。
Java泛型中的类型参数命名约定
按照约定,类型参数名称是单个大写字母。最常用的类型参数名称为:
T –类型
V –价值
E –元素
K –键
N –数字
S,U,V等–第二,第三,第四类型
通用类
完成泛型的介绍之后,让我们看看如何在Java中创建泛型类。
通用类以以下格式定义:
类名称<T1,T2,…,Tn> {
/ …/
}
类名后面有一个类型参数部分,由尖括号(<>)分隔。它指定类型参数(也称为类型变量)T1,T2,…和Tn。
通用类创建Java示例
在此示例中,我们将创建一个具有两个类型参数的通用类,并将其用于不同的数据类型。
class GenericClass<K, V> { private K key; private V value; public GenericClass(K key, V value) { this.key = key; this.value = value; } public K getKey(){ return key; } public V getValue(){ return value; } } public class GenericDemo{ public static void main(String[] args) { GenericClass<String, String> g1 = new GenericClass<>("Test", "Value"); System.out.println("Key- " + g1.getKey()); System.out.println("Value- " + g1.getValue()); GenericClass<Integer, Integer> g2 = new GenericClass<>(1, 2); System.out.println("Key- " + g2.getKey()); System.out.println("Value- " + g2.getValue()); GenericClass<Integer, String> g3 = new GenericClass<>(1, "One"); System.out.println("Key- " + g3.getKey()); System.out.println("Value- " + g3.getValue()); } }
输出:
Key- Test Value- Value Key- 1 Value- 2 Key- 1 Value- One
通用接口
通用接口的创建与通用类一样。
接口名称<T1,T2,…,Tn> {
/ …/
}
实施通用接口时应遵循的一些规则如下
- 如果接口使用通用类型参数,则实现通用接口的类必须是具有相同类型参数的通用类。
public class GenericClass<E> implements GenericInterface<E>
- 如果为接口提供数据类型,则可以使用普通类。
public class NormalClass implements GenericInterface<Integer>
- 泛型类除了必须使用的类型参数以外,还可以具有其他参数,因为如果实现了泛型接口。
public class GenericClass<K, V, E> implements GenericInterface<E>
通用方法
泛型类中的任何方法都可以指定该类的类型参数,也可以自由添加自己的类型参数。我们也可以在非泛型类中使用泛型方法。
通用方法Java示例
在这里,我们将在非泛型类中使用泛型方法。
class TestClass { // Generic method public <T> void displayArrayElements(T[] arr){ System.out.println("Elements in Array- " + Arrays.toString(arr)); } } public class GenericDemo{ public static void main(String[] args) { TestClass obj = new TestClass(); Integer[] intArray = {1, 2, 3, 4, 5, 6, 7}; Double[] doubleArray = {1.2, 2.3, 3.4, 4.5, 5.6}; String[] strArray = {"A", "B", "C", "D"}; obj.displayArrayElements(intArray); obj.displayArrayElements(doubleArray); obj.displayArrayElements(strArray); } }
输出:
Elements in Array- [1, 2, 3, 4, 5, 6, 7] Elements in Array- [1.2, 2.3, 3.4, 4.5, 5.6] Elements in Array- [A, B, C, D]
如我们所见,如果我们正在编写具有自己的类型参数的泛型方法,则需要在access修饰符之后声明类型参数。
公共<T> void displayArrayElements(T [] arr)
我们也可以在调用通用方法时在尖括号中指定实际数据类型。尽管Java可以根据方法参数的类型自动推断类型,但这不是强制性的
obj.displayArrayElements(intArray);
或者这个
obj。<Integer> displayArrayElements(intArray);
钻石操作员
从Java 7开始,并不是必须指定调用泛型类的构造函数所需的类型实参,我们可以传递空的类型实参集(<>),只要编译器可以确定或者推断出以下类型的实参即可:上下文。这对尖括号<>被非正式地称为菱形。
例如,如果我们具有如下定义的通用类
public class Test<T> { .. .. }
然后,我们可以从Java 7开始创建其实例。
Test<Integer> obj = new Test<>();
无需在右侧指定Integer,只需传递空尖括号<>,类型将自动推断。