什么是泛型?
在实例化时为所使用的容器分配一个类型,也称泛型类型
使用泛型的好处更强的类型检查,因为避开运行时可能引发的ClassCastException可以节省时间
消除了类型转换,这意味着可以用更少的代码,因为编译器确切知道集合中存储的是何种类型
使用场景
考虑以下场景:您希望开发一个用于在应用中传递对象的容器。但对象类型并不总是相同。因此,需要开发一个能够存储各种类型对象的容器。
你的代码可能是这样的
/*** Object Container* @author Juneau*/
public class ObjectContainer {
private Object obj;
/*** @return the obj*/
public Object getObj() {
return obj;
}
/*** @param obj the obj to set*/
public void setObj(Object obj) {
this.obj = obj;
}
}
演示如何使用
ObjectContainer myObj = new ObjectContainer();
// store a string myObj.setObj("Test");
System.out.println("Value of myObj:" + myObj.getObj());
// store an int myObj.setObj(3);
System.out.println("Value of myObj:" + myObj.getObj());
List objectList = new ArrayList();
objectList.add(myObj);
它并不是最合适的解决方案。它不是类型安全的,并且要求在检索封装对象时使用显式类型转换,因此有可能引发异常。
可以使用泛型开发一个更好的解决方案
在实例化时为所使用的容器分配一个类型,也称泛型类型,这样就可以创建一个对象来存储所分配类型的对象。泛型类型是一种类型参数化的类或接口,这意味着可以通过执行泛型类型调用分配一个类型,将用分配的具体类型替换泛型类型。然后,所分配的类型将用于限制容器内使用的值,这样就无需进行类型转换,还可以在编译时提供更强的类型检查。
以下演示了如何创建与先前创建的容器相同的容器,但这次使用泛型类型参数,而不是Object类型。
public class GenericContainer {
private T obj;
public GenericContainer(){
}
public GenericContainer(T t){
obj = t;
}
/*** @return the obj*/
public T getObj() {
return obj;
}
/*** @param obj the obj to set*/
public void setObj(T t) {
obj = t;
}
}
最显著的差异是类定义包含 ,类字段 obj 不再是 Object 类型,而是泛型类型 T。类定义中的尖括号之间是类型参数部分,介绍类中将要使用的类型参数(或多个参数)。T 是与此类中定义的泛型类型关联的参数。
要使用泛型容器,必须在实例化时使用尖括号表示法指定容器类型。因此,以下代码将实例化一个 Integer 类型的 GenericContainer,并将其分配给 myInt 字段。
GenericContainer intContainer = new GenericContainer();
intContainer.setObj(3);
intContainer.setObj(5);
//intContainer.setObj("Int"); // will not compile System.out.println("Value of intContainer: " + intContainer.getObj());
来看看将Object容器实例存储到集合中与存储GenericContainer实例之间的差异。
List myObjList = new ArrayList();
for(int x=0; x <=10; x++){
ObjectContainer myObj = new ObjectContainer();
myObj.setObj("Test" + x);
myObjList.add(myObj);
}
// Get the objects, we need to cast for(int x=0; x <= myObjList.size()-1; x++){
ObjectContainer obj = (ObjectContainer) myObjList.get(x);
System.out.println("Object Value: " + obj.getObj());
}
List genericList = new ArrayList();
for(int x=0; x <=10; x++){
GenericContainer myGeneric = new GenericContainer();
myGeneric.setObj(" Generic Test" + x);
genericList.add(myGeneric);
}
// Get the objects, no need to cast for(int x=0; x <= myObjList.size()-1; x++){
GenericContainer obj = genericList.get(x);
String objectString = obj.getObj();
// Do something with the string...here we will print it System.out.println(objectString);
}
使用ArrayList时,我们可以使用括号表示法 () 在创建时指定集合类型,指明我们将存储GenericContainer实例。该集合将只能存储GenericContainer实例(或GenericContainer的子类),无需在从集合检索对象时使用显式类型转换。
将泛型与 Collections API 结合使用的概念让我们能获得泛型提供的另外一个好处:允许开发可根据手头的任务定制的泛型算法。Collections API 本身是使用泛型开发的,如果不使用,Collections API 将永远无法容纳参数化类型。
多种泛型类型
有时,能够在类或接口中使用多种泛型类型很有帮助。通过在尖括号之间放置一个逗号分隔的类型列表,可在类或接口中使用多个类型参数。清单 5 中的类使用一个接受以下两种类型的类演示了此概念:T 和 S。
如果我们回顾上一节中列出的标准类型命名约定,T 是第一种类型的标准标识符,S 是第二种类型的标准标识符。使用这两种类型生成一个使用泛型存储多个值的容器。
public class MultiGenericContainer {
private T firstPosition;
private S secondPosition;
public MultiGenericContainer(T firstPosition, S secondPosition){
this.firstPosition = firstPosition;
this.secondPosition = secondPosition;
}
public T getFirstPosition(){
return firstPosition;
}
public void setFirstPosition(T firstPosition){
this.firstPosition = firstPosition;
}
public S getSecondPosition(){
return secondPosition;
}
public void setSecondPosition(S secondPosition){
this.secondPosition = secondPosition;
}
}
MultiGenericContainer类可用于存储两个不同对象,每个对象的类型可在实例化时指定。容器的用法如下所示
MultiGenericContainer mondayWeather =
new MultiGenericContainer("Monday", "Sunny");
MultiGenericContainer dayOfWeekDegrees =
new MultiGenericContainer(1, 78.0);
String mondayForecast = mondayWeather.getFirstPosition();
// The Double type is unboxed--to double, in this case. More on this in next section!double sundayDegrees = dayOfWeekDegrees.getSecondPosition();
由于getSecondPosition()的结果存储到double类型的字段中,因此无需进行类型转换。MultiGenericContainer是用MultiGenericContainer实例化的,这怎么可能呢?借助将引用类型自动转换为原始类型的拆箱操作,即可实现。同样,通过构造函数存储值时,使用自动装箱操作将原始类型的double值存储为Double引用类型。注:无法将原始类型用于泛型;只能使用引用类型。自动装箱和拆箱操作能够在使用泛型对象时将值存储为原始类型并检索原始类型的值。
看看MuliGenericContainer的实例化,也可以使用类型引用避免重复类型声明。不必指定对象类型两次,只要编译器可以从上下文推断类型,即可以指定尖括号运算符<>。因此,可以在实例化对象时使用尖括号运算符,如下可见。
MultiGenericContainer mondayWeather =
new MultiGenericContainer<>("Monday", "Sunny");
MultiGenericContainer dayOfWeekDegrees =
new MultiGenericContainer<>(1, 78.0);
有界类型
我们经常会遇到这种情况,需要指定泛型类型,但希望控制可以指定的类型,而非不加限制。有界类型 在类型参数部分指定 extends 或 super 关键字,分别用上限或下限限制类型,从而限制泛型类型的边界。例如,如果希望将某类型限制为特定类型或特定类型的子类型,请使用以下表示法:
同样,如果希望将某个类型限制为特定类型或特定类型的超类型,请使用以下表示法:
我们用先前使用的GenericContainer类,通过指定一个上限,将其泛型类型限制为Number或Number的子类。注意,GenericNumberContainer这个新类指定泛型类型必须扩展Number类型
public class GenericNumberContainer {
private T obj;
public GenericNumberContainer(){
}
public GenericNumberContainer(T t){
obj = t;
}
/*** @return the obj*/
public T getObj() {
return obj;
}
/*** @param obj the obj to set*/
public void setObj(T t) {
obj = t;
}
}
该类可以很好地将其字段类型限制为Number,但如果您尝试指定一个不在边界内的类型(如下 所示),将引发编译器错误。
GenericNumberContainer gn = new GenericNumberContainer();
gn.setObj(3);
// Type argument String is not within the upper bounds of type variable TGenericNumberContainer gn2 = new GenericNumberContainer();
泛型方法
有时,我们可能不知道传入方法的参数类型。在方法级别应用泛型可以解决此类问题。方法参数可以包含泛型类型,方法也可以包含泛型返回类型。
假设我们要开发一个接受 Number 类型的计算器类。泛型可用于确保可将任何 Number 类型作为参数传递给此类的计算方法。例如 add() 方法演示了如何使用泛型限制两个参数的类型,确保其包含 Number 的上限:
通过将类型限制为Number,您可以将Number子类的任何对象作为参数传递。此外,通过将类型限制为Number,我们还可以确保传递给该方法的任何参数将包含doubleValue()方法。要查看实际效果,如果您想添加一个Integer和一个Float,可以按如下所示调用该方法:
double genericValue1 = Calculator.add(3, 3f);
System.out.println("The int + float result: " + genericValue1);
通配符
些情况下,编写指定未知类型的代码很有用。问号 (?) 通配符可用于使用泛型代码表示未知类型。通配符可用于参数、字段、局部变量和返回类型。但最好不要在返回类型中使用通配符,因为确切知道方法返回的类型更安全。
假设我们想编写一个方法来验证指定的 List 中是否存在指定的对象。我们希望该方法接受两个参数:一个是未知类型的 List,另一个是任意类型的对象。
public static void checkList(List> myList, T obj){
if(myList.contains(obj)){
System.out.println("The list " + myList + " contains the element: " + obj);
} else {
System.out.println("The list " + myList + " does not contain the element: " + obj);
}
}
演示如何调用改方法
List intList = new ArrayList();
intList.add(2);
intList.add(4);
intList.add(6);
List strList = new ArrayList();
strList.add("two");
strList.add("four");
strList.add("six");
List objList = new ArrayList();
objList.add("two");
objList.add("four");
objList.add(strList);
有时要使用上限或下限限制通配符。与指定带边界的泛型类型极其相似,指定extends或super关键字加上通配符,后面跟用于上限或下限的类型,即可声明带边界的通配符类型。例如,如果我们要更改checkList方法使其只接受扩展Number类型的List
public static void checkNumber(List extends Number> myList, T obj){
if(myList.contains(obj)){
System.out.println("The list " + myList + " contains the element: " + obj);
} else {
System.out.println("The list " + myList + " does not contain the element: " + obj);
}
}
由于泛型方法应引入自己的类型参数,该参数的范围限于该方法的主体。类型参数必须出现在方法的返回类型之前。在 countTypes 的情况下,只用一个 表示泛型类型。
如前所述,可以使用有界类型限制可为泛型类型指定的类型。如果您查看 GitHub 上的代码中的 JavaHouse 类中的 addToPurchase() 方法,将看到它接受一个泛型 List。
这种情况下,List 必须包含扩展 CoffeeSaleType 的元素,因此 CoffeeSaleType 是上限。换句话说,只能使用扩展 CoffeeSaleType 的对象列表作为此方法参数。参见以下。
public void addToPurchase(List saleList) {
for (CoffeeSaleType sale : saleList) {
purchase.add(sale);
}
}
Oracle官方的泛型文档和demo下载地址泛型:工作原理及其重要性www.oracle.comhttps://github.com/juneau001/GenericsExamplesgithub.com