您的位置:首页 > 编程语言 > Java开发

java泛型是如何工作的,为什么泛型这么重要

2015-11-26 15:13 1076 查看
在javaSE8中有很多值得我们兴奋的地方,在新的版本中新的或者更新的特征允许开发者以更有效的、更简洁的方式写代码。为了完全了解一些新特征的实现,比如lambdas,理解java的核心概念就变得十分重要了,在javaSE8中扮演这个核心角色之一的就是泛型。

这篇文章一开始对泛型进行了一个简单的介绍,并介绍了一些基本的概念。在介绍完基本概念之后我们将深入泛型的一些具体应用场景,最后我们看一下为什么泛型对于一些构造是非常重要的组成部分。

这篇文章用到的所有代码可以在这里下载。

什么是泛型

考虑下边这个场景:你希望开发一个容器用来传递你应用程序中的对象。但是对象的类型并不是每次都相同的,因此,你需要开发一个能够处理不同类型对象的容器。考虑这个场景,达到这个目标最显然的方式是容器可以存储和恢复对象的类型,然后强制转换这个对象,下边这段代码就有这样的实现:

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 (which is autoboxed to an Integer object)
myObj.setObj(3);
System.out.println("Value of myObj:" + myObj.getObj());

List objectList = new ArrayList();
objectList.add(myObj);
// We have to cast and must cast the correct type to avoid ClassCastException!
String myStr = (String) ((ObjectContainer)objectList.get(0)).getObj();
System.out.println("myStr: " + myStr);


在开发这个容器时,泛型是一个更好的选择,可以有一个类型用来实例化,或者指定为泛型类型,允许创建的对象存储指定的类型。泛型类型是一个类或者是一个接口,通过类型参数化,这意味着类型可以通过执行泛型类型调用指定,会用具体的类型替代泛型类型。指定的类型就可以用来限制在容器中使用的值,这个值是消除了强制转化的,同时在编译时提供了更强的类型检查

下边这段代码展示了怎么创建一个容器,但是这次使用了泛型类型作为参数而不是Object类型

public class GenericContainer<T> {
private T obj;

public GenericContainer(){
}

// Pass type in as parameter to constructor
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;
}
}


最显著的区别是类的定义中包含<T>,并且类的字段 obj 不再是Object类型了,而是泛型类型T。类的定义包含了类型参数区,引入在本类中将要用到的类型参数。T是一个和泛型类型关联的参数。

为了使用这个泛型容器,你必须指定容器的类型。因此下边这段代码将会用类型Integer来指定泛型容器的类型。

GenericContainer<Integer> myInt =  new GenericContainer<Integer>();


如果我们试着在上边实例化的这个容器中存入其他类型,这段代码便不会编译通过:

myInt.setObj(3);  // OK
myInt.setObj("Int"); // Won't Compile


使用泛型的好处

我们在上边的例子中已经看到了使用泛型的一些好处。更强的类型检查是最重要的特征之一,因为它省去了检查运行时类型错误的异常处理。另一个好处就是消除了强制转换的存在,这意味着你可以写更少的代码,因为编译器清楚的知道在集合中存储的类型。例如下边这段代码,我们来看一下在集合中存储对象实例和使用GenericContainer存储对象实例的区别
List myObjList = new ArrayList();

// Store instances of ObjectContainer
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<GenericContainer> genericList = new ArrayList<GenericContainer>();

// Store instances of GenericContainer
for(int x=0; x <=10; x++){
GenericContainer<String> myGeneric = new GenericContainer<String>();
myGeneric.setObj(" Generic Test" + x);
genericList.add(myGeneric);
}
// Get the objects; no need to cast to String

for(GenericContainer<String> obj:genericList){
String objectString = obj.getObj();
// Do something with the string...here we will print it
System.out.println(objectString);
}


注意但我们使用ArrayList的时候,我们可以通过使用(<GenericContainer>) 指定集合的类型来表明我们存储的是GenericContainer类型实例,集合仅会存储GenericContainer实例(或者他的子类),当在集合中取数据的时候没有必要再进行强制转换了。在集合中使用泛型揭示了泛型的另一个好处,他们允许我们利用泛型算法来定制适合我们任务的泛型,集合的API使用了泛型,不适用泛型,集合API永远也不会适应参数化的类型

深入泛型

下边这些会更深入的探索泛型的一些特征

你怎么使用泛型

泛型的用法有非常多的种类,这篇文章的第2个例子说明了产生泛型对象类型的一个用例。在类或者接口级别上学习泛型的语法是一个很好的开始,看看这个代码,类的签名包含类型参数区,它被包含在名字后的<>中,例如:
public class GenericContainer<T> {
...


类型参数,也叫作类型变量,用来作为指定在运行时将要分配给类的类型的占位符,可以有一个或者是多个类型参数,也可以通过类被使用。习惯上讲,类型参数是一个大写字母组成的,用来指定被定义的参数类型,下边的列表包含的标准的一些用法:
E: Element
K: Key
N: Number
T: Type
V: Value
S, U, V, and so on: Second, third, and fourth types in a multiparameter situation


在上边的例子中,T指定了将要配分配的类型,那么GenericContainer在实例化的时候可以被指定为任何类型。注意T参数通过类使用表明在实例化的时候指定类型,当使用下边这段代码的时候,每一个T参数都会被替代为String类型
GenericContainer<String> stringContainer = new GenericContainer<String>();


泛型在构造函数中同样也是非常有用的,用来为类字段初始化传递参数,GenericContainer有一个构造函数允许在实例化的时候传递任何参数:
GenericContainer gc1 = new GenericContainer(3);
GenericContainer gc2 = new GenericContainer("Hello");


注意没有指定类型的泛型叫做原生类型,例如,创建GenericContainer一个原生类型,请看下边这段代码:
GenericContainer rawContainer = new GenericContainer();


原生类型有时对向后兼容是很有用的,但是在每一段代码中使用却不是一个很好的习惯,原生类型消除了在编译时期的类型检查,是代码更容易出错。

泛型的多类型

有时在接口或者类中使用多于一个泛型类型是很有好处的,多种类型的参数可以被用在类或者接口中通过替换在尖括号中的参数。下边的代码展示了这个概念,它使用了两种类型:T和S。
如果我们看看之前列出的参数列表,T是第一个类型,S是第二个类型,这两个类型用来利用泛型存储多种类型的值
public class MultiGenericContainer<T, S> {
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<String, String> mondayWeather =
new MultiGenericContainer<String, String>("Monday", "Sunny");
MultiGenericContainer<Integer, Double> dayOfWeekDegrees =
new MultiGenericContainer<Integer, Double>(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();


说明:本文有选择性的翻译了文章中的一部分,关于全文的内容,读者可以访问下边的链接查看原文。

原文地址:http://www.oracle.com/technetwork/articles/java/juneau-generics-2255374.html

其他资料:https://dzone.com/articles/5-things-you-should-know-about-java-generics
http://www.journaldev.com/1663/java-generics-tutorial-example-class-interface-methods-wildcards-and-much-more http://blog.csdn.net/wangjian223344/article/details/16846165 http://www.imooc.com/article/1935
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: