`
huntfor
  • 浏览: 195241 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

java泛型(原创+转载整理)

    博客分类:
  • java
 
阅读更多

在看这篇博文之前,大家先来问自己几个问题:

1.什么是泛型?

2.为什么要使用泛型?他的作用&优点是什么?

3.什么时候该使用泛型?

 

先来回答第一个问题:

百科上给出的定义是:

写道
泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。

 佶屈聱牙,晦涩难懂有木有?下面给大家简单做一下带入:

首先来看一下大家都熟悉的容器:诸如List、Map、Set之类的容器,在有的教材上被译成持有对象。其实个人觉得后一种译法更直观一些,持有对象 顾名思义,表明该类是持有其他对象的类。

正如大家熟悉的List<String>表明这是一个持有String对象的list。Set<Integer>表示这是一个持有Integer对象的集合等等。这里的<>中的类型就是该容器所持有的类型的参数。当然,<>里面的类型可以是各种基础类型或者自定义类型,但是无论是什么类型,容器对各种类型进行的方法操作都是一样的,因此容器类所持有的对象并不是唯一的,而是广泛适用的。古曰:泛型。没错,容器就是最简单的泛型,但是容器只做到的泛型类最最单一的行为:持有(对象)。如果在容器类源码的基础上,加上各种各样的方法,那么我们就得到了本篇博文的主旨:泛型类。

再次解读泛型的定义:泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。联系一下容器类——将所操作的String类型指定成容器的一个参数。即所谓的类型参数。

 2.为什么要使用泛型?他的作用&优点是什么?

(本小节代码摘自百度百科)

看下面的代码:

2.1

public class StringFoo {
private String x;
public StringFoo(String x) {
this.x = x;
}
public String getX() {
return x;
}
public void setX(String x) {
this.x = x;
}
}
public class DoubleFoo {
private Double x;
public DoubleFoo(Double x) {
this.x = x;
}
public Double getX() {
return x;
}
public void setX(Double x) {
this.x = x;
}
}

 这两段代码的逻辑是完全一样的,只有类型不一样,因此考虑重构,Object是所有类的父类,因此可以考虑用Object做为成员类型,这样就可以实现通用了,实际上就是“Object泛型”,暂时这么称呼。

2.2

 

public class ObjectFoo {
private Object x;
public ObjectFoo(Object x) {
this.x = x;
}
public Object getX() {
return x;
}
public void setX(Object x) {
this.x = x;
}
}

调用demo方法如下:

2.3

public class ObjectFooDemo {
public static void main(String args[]) {
ObjectFoo strFoo = new ObjectFoo(new StringFoo("Hello Generics!"));
ObjectFoo douFoo = new ObjectFoo(new DoubleFoo(Double("33")));
ObjectFoo objFoo = new ObjectFoo(new Object());
System.out.println("strFoo.getX="+(StringFoo)strFoo.getX());
System.out.println("douFoo.getX="+(DoubleFoo)douFoo.getX());
System.out.println("objFoo.getX="+objFoo.getX());
}
}

 解说:在Java 5之前,为了让类有通用性,往往将参数类型、返回类型设置为Object类型,当获取这些返回类型来使用时候,必须将其“强制”转换为原有的类型或者接口,然后才可以调用对象上的方法。强转很麻烦,必须要事先知道各个Object具体类型是什么,才能做出正确转换。否则,要是转换的类型不对,比如将“Hello Generics!”字符串强转为Double,那么编译的时候不会报错,可是运行的时候就挂了。泛型的出现,解决了这样的隐患。

再联系一下容器类,一来,你不需要再像2.1那样为每一种类型写一个方法。二来,你只要在定义的时候指定要类型参数,也不需要像2.3那样取对象的时候进行强转了。泛型的作用不言而喻。

 

3.什么时候使用泛型?

个人觉得,泛型提高了代码的重用率,当代码重用度比较高的时候,可以考虑泛型。

 

 下面进入正题,介绍一下泛型的基础以及应用实例:

(以后内容为转载内容,感谢原作者的分享)

 

 泛型基础

 

类型参数

在定义泛型类或声明泛型类的变量时,使用尖括号来指定形式类型参数。形式类型参数与实际类型参数之间的关系类似于形式方法参数与实际方法参数之间的关系,只是类型参数表示类型,而不是表示值。

泛型类中的类型参数几乎可以用于任何可以使用类名的地方。例如,下面是 java.util.Map 接口的定义的摘录:

public interface Map<K, V> {
public void put(K key, V value);
public V get(K key);
}

Map 接口是由两个类型参数化的,这两个类型是键类型 K 和值类型 V。(不使用泛型)将会接受或返回 Object 的方法现在在它们的方法签名中使用 K 或 V,指示附加的类型约束位于 Map 的规格说明之下。

当声明或者实例化一个泛型的对象时,必须指定类型参数的值:

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

注意,在本例中,必须指定两次类型参数。一次是在声明变量 map 的类型时,另一次是在选择 HashMap 类的参数化以便可以实例化正确类型的一个实例时。

编译器在遇到一个 Map<String, String> 类型的变量时,知道 K 和 V 现在被绑定为 String,因此它知道在这样的变量上调用 Map.get() 将会得到 String 类型。

除了异常类型、枚举或匿名内部类以外,任何类都可以具有类型参数。

 

命名类型参数

推荐的命名约定是使用大写的单个字母名称作为类型参数。反映了大多数泛型类将具有少量类型参数的假定。对于常见的泛型模式,推荐的名称是:

K —— 键,比如映射的键。
V —— 值,比如 List 和 Set 的内容,或者 Map 中的值。
E —— 异常类。
T —— 泛型

 

泛型不是协变的

关于泛型的混淆,一个常见的来源就是假设它们像数组一样是协变的。其实它们不是协变的。List<Object> 不是 List<String> 的父类型。

如果 A 扩展 B,那么 A 的数组也是 B 的数组,并且完全可以在需要 B[] 的地方使用 A[]:

Integer[] intArray = new Integer[10];
Number[] numberArray = intArray;

上面的代码是有效的,因为一个 Integer 是 一个 Number,因而一个 Integer 数组是 一个 Number 数组。但是对于泛型来说则不然。下面的代码是无效的:

List<Integer> intList = new ArrayList<Integer>();
List<Number> numberList = intList; // invalid

最初,大多数 java程序员觉得这缺少协变很烦人,或者甚至是“坏的(broken)”,但是之所以这样有一个很好的原因。如果可以将 List<Integer> 赋给 List<Number>,下面的代码就会违背泛型应该提供的类型安全:

List<Integer> intList = new ArrayList<Integer>();
List<Number> numberList = intList; // invalid
numberList.add(new Float(3.1415));

因为 intList 和 numberList 都是有别名的,如果允许的话,上面的代码就会让您将不是 Integers 的东西放进 intList 中。但是,正如下一屏将会看到的,您有一个更加灵活的方式来定义泛型

 

高级应用

限制泛型:

如果没有限制class ObjectFoo <T>类型持有者T的范围,实际上这里的限定类型相当于Object,这和“Object泛型”实质是一样的。限制比如我们要限制T为集合接口类型。只需要这么做:
class GenericsFoo<T extends Collection>,这样类中的泛型T只能是Collection接口的实现类,传入非Collection接口编译会出错。
注意:<T extends Collection>这里的限定使用extends关键字,后面可以是类也可以是接口。但这里的extends已经不是继承的含义了,应该理解为T类型是实现Collection接口的类型,或者T是继承了XX类的类型。
比如下面的例子,我们类型参数进行了限定:
public class CollectionGenFoo<T extends Collection> {
private T x;
public CollectionGenFoo(T x) {
this.x = x;
}
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
}
 实例化的时候可以这样写:
public class CollectionGenFooDemo {
public static void main(String args[]) {
CollectionGenFoo<ArrayList> listFoo = null;
listFoo = new CollectionGenFoo<ArrayList>(new ArrayList());
// CollectionGenFoo<Collection> listFoo1 = null;
// listFoo1=new CollectionGenFoo<ArrayList>(new ArrayList());
System.out.println("实例化成功!");
}
}
 当前看到的这个写法是可以编译通过,并运行成功。可是注释掉的两行加上就出错了,因为<T extends Collection>这么定义类型的时候,就限定了构造此类实例的时候T是确定的一个类型,这个类型实现了Collection接口,但是实现 Collection接口的类很多很多,如果针对每一种都要写出具体的子类类型,那也太麻烦了,我干脆还不如用Object通用一下。别急,泛型针对这种情况还有更好的解决方案,那就是“通配符泛型”。
通配符泛型
为了解决类型被限制死了不能动态根据实例来确定的缺点,引入了“通配符泛型”,针对上面的例子,使用通配泛型格式为<? extends Collection>,“?”代表未知类型,这个类型是实现Collection接口。那么上面实现的方式可以写为:
public class CollectionGenFooDemo {
public static void main(String args[]) {
CollectionGenFoo<ArrayList> listFoo = null;
listFoo = new CollectionGenFoo<ArrayList>(new ArrayList());
CollectionGenFoo<? extends Collection> listFoo1 = null;
listFoo1=new CollectionGenFoo<ArrayList>(new ArrayList());
System.out.println("实例化成功!");
}
}
 
注意:
1、如果只指定了<?>,而没有extends,则默认是允许Object及其下的任何Java类了。也就是任意类。
2、通配符泛型不单可以向下限制,如<? extends Collection>,还可以向上限制,如<? super Double>,表示类型只能接受Double及其上层父类类型,如Number、Object类型的实例。
3、泛型类定义可以有多个泛型参数,中间用逗号隔开,还可以定义泛型接口,泛型方法。这些都与泛型类中泛型的使用规则类似。
 
泛型方法
是否拥有泛型方法,与其所在的类是否泛型没有关系。要定义泛型方法,只需将泛型参数列表置于返回值前。如:
public class ExampleA {
public <T> void f(T x) {
System.out.println(x.getClass().getName());
}
public static void main(String[] args) {
ExampleA ea = new ExampleA();
ea.f(" ");
ea.f(10);
ea.f('a');
ea.f(ea);
}
}
 
使用泛型方法时,不必指明参数类型,编译器会自己找出具体的类型。泛型方法除了定义不同,调用就像普通方法一样。
需要注意,一个static方法,无法访问泛型类的类型参数,所以,若要static方法需要使用泛型能力,必须使其成为泛型方法。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics