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

Java基础知识_泛型

2013-07-15 10:16 295 查看

一、引入

一般的类和方法,只能使用具体的类型,要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。

在面向对象编程语言中,多态是一种泛化机制。可有时候,拘泥于单继承体系,也会使程序受限太多。这时我们想到,如果参数是一个接口,而不是一个类,这种限制就放松了许多,因为任何实现了该接口的类都能都满足该方法。

可是有时候,即便使用了接口,对程序的约束也还是太强,因为一旦指明了接口,他就要求你的代码必须使用特定的接口。而我们希望达到的目的是编写更通用的代码,要使代码能够应用于“某种不具体的类型”,而不是一个具体的接口或类。

这就是JAVA SE5的重大变化之一:泛型的概念。泛型实现了参数化类型的概念,使代码可以应用于多种类型。“泛型”这个术语的意思是:“适用于许多许多的类型”。泛型在编程语言中出现时,其最初的目的是希望类或方法能够具备最广泛的表达能力。

二、泛型的好处

Java 语言中引入泛型是一个较大的功能增强。不仅语言、类型系统和编译器有了较大的变化,以支持泛型,而且类库也进行了大翻修,所以许多重要的类,比如集合框架,都已经成为泛型化的了。这带来了很多好处:类型安全。 泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中)。

Java 程序中的一种流行技术是定义这样的集合,即它的元素或键是公共类型的,比如“String 列表”或者“String 到 String 的映射”。通过在变量声明中捕获这一附加的类型信息,泛型允许编译器实施这些附加的类型约束。类型错误现在就可以在编译时被捕获了,而不是在运行时当作 ClassCastException 展示出来。将类型检查从运行时挪到编译时有助于您更容易找到错误,并可提高程序的可靠性。

消除强制类型转换。 泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。

尽管减少强制类型转换可以降低使用泛型类的代码的罗嗦程度,但是声明泛型变量会带来相应的罗嗦。比较下面两个代码例子。

//该代码不使用泛型:
List list = new ArrayList();
list.put(new Integer(3));
Integer i = (Integer) list.get(0);
//该代码使用泛型:
List<Integer> li = new ArrayList<Integer>();
li.put(new Integer(3));
Integer i = li.get(0);


在简单的程序中使用一次泛型变量不会降低罗嗦程度。但是对于多次使用泛型变量的大型程序来说,则可以累积起来降低罗嗦程度。

潜在的性能收益。 泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,程序员会指定这些强制类型转换)插入生成的字节码中。但是更多类型信息可用于编译器这一事实,为未来版本的 JVM 的优化带来可能。

由于泛型的实现方式,支持泛型(几乎)不需要 JVM 或类文件更改。所有工作都在编译器中完成,编译器生成类似于没有泛型(和强
制类型转换)时所写的代码,只是更能确保类型安全而已。

三、泛型的基础知识

泛型是提供给javac编译器使用的,它可以限定集合中输入的类型,让编译器挡住源程序中的非法输入,编译器编译带类型的说明的集合时会去除掉"类型"信息,是程序运行效率不受影响。对应参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉泛型的类型信息(这就是泛型擦除),只要能跳过编译器,就可以往某个泛型集合中加入其他类型的数据。例如用反射得到的集合,再调用add方法即可。

泛型是编译器看的,所以通过反射可以避过编译器,继续把不同的类型的值放进已经被泛型限定了的集合里面。

以ArrayList<E>为例

ArrayList称为原始类型,<>译为"typeof",ArrayList<E>称为泛型类型,E称为类型变量或者类型参数;

ArrayList<String>称为参数化的类型,String称为类型参数的实例或实际类型参数

参数化类型与原始类型全部兼容Vector v = new Vector<String>();或者:Vector<String> v = new Vector();都可以。

参数化类型不考虑类型参数的继承问题。也就是说泛型类型中的类型参数,等号两边只要不是同一种,那么编译就一定不会通过。

为什么在创建数组实例时,数组的元素不能使用参数化的类型?

编译器是严格按照步骤走的。它检测每一行代码的信息是否有误,但是不会用执行时候的答案来检测代码的正确性。

四、泛型限定

泛型的通配符(?)应用:?表示任意类型。比如当要定义一个任意参数化类型的集合(Collection<?> col),就用得到了通配符。

使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。比如集合中的add()方法,使用了?的集合就不能在调用这个方法。

public void printCollection(Collection<?> col){}

也就是说Collection<?> col = new ArrayList<String>();编译器让这个表达式被编译通过。

泛型的限定:

?extends E:可以接收E类型或者E的子类。这是上限限定

?super E:可以接收E类型或者E的父类。这是下限限定

五、自定义泛型

要定义一个泛型,必须在函数的返回值之前用尖括号括起来

1、//这样声明的范型可以代替任意类型数据

我们平常用到的键值对Map.Entry<K,V>不就是给予范型的吗

  KV都可以代替任意类型的值,但是在java中范型的实际类型必须是引用类型

<K,V> void get(K k,V v)
{
}


2、Java中的范型不能像C++那么灵活
<T>  T  add(T a,T b)
{
//return  a+b   ;//很多人以为java也想C++一样可以这样 ,但是不可以 。
return  null;
}


  这个返回的null也是有类型限制的,比如上面的ab分别是Integer和String那么就会取他们共同的基类Object做为返回值类型,其他的同理

3、实现任意类型的数组的成员值的交换,注意在自定义范型中范型的实际类型只能是引用数据类型不能是基本数据类型

public  static <T> void  swap(T[]a,int x,int y)
{
T  tem  =a[x]  ;
a[x]=a[y]  ;
a[y]=tem ;
}

上面这个方法如果我 

 swap(new Integer[]{1,2,3,4,5},1,2);       //这样就会自动交换下标12的值

但是这样调用就错了  

swap(new int[]{1,2,3,5,6},2,3) ;  //所以说Java的范型的实际类型 只能是引用数据类型

4、<T  extends  String>     表示类型只能是String或者String的派生类

  <T super  String >   表示范型类型只能是String或者String的父类

   用法同3

5、下面这个函数利用范型来实现类型自动转换的功能

public static  <T> T autoConvert(Object obj)  //因为返回值是 T标识任意类型 所哟可以 将返回结果赋值给任意类型对象
{
return (T)obj;
}

Object  obj==”";

String str=autoConvert(obj);


可以完成自动转换,因为范型T代表任意类型,因此他可以赋值给String类型的对象

6、将任意类型的对象填充到任意类型的数组中,与是fillArray(newInteger[]{2,3,4},”ddd”);这样调用是正确的,这样做忽略类型限制

public  static <T> void  fillArray(T[] a,T b)  //将任意一个对象填充到任意类型的数组
{
for(int i =0;i<a.length;i++)
{
a[i] =b ;
}
}


7、以自定义范型的形式显示一个集合的数据,下面一个是利用自定义范型一个是利用通配符来实现,但是不同的是利用通配符操作的集合不能向集合中插入元素。

 但是自定义范型却可以。原因是通配符代表的集合我们不知道集合内部具体元素是什么类型所以不能对集合进行add操作。

public static  <T> void showCollection(Collection<T> col,T  obj)  //利用范型来输出任意类型集合
{
col.add(obj) ;
for(T a:col)
{
System.out.println(a);
}
}
public static void showCollection(Collection<?> col)  //利用范型来输出任意类型集合
{
for(Object obj:col)
{
System.out.println(obj);
}
}


8、如果一个类中多个方法都需要范型那么就是用类级别的范型。例如

class  A<E>
{
public void  add(E obj){}
public  E  get(){}
private E data;
}
这样声明范型和在函数前面声明其实是一样的只不过是在类的级别上作用于整个类而已

9、要注意范型只是给编译器看的。

  也就是说Vector<Integer>Vector<String>他们用到的都是同一份字节码,字节码只有class文件加载到内存中的时候才有

  所以在一个类中下面2个方法不能同时存在

void show(Vector<Integer>) {}

void show(Vector<String>){}

五、通过反射获得泛型的参数化类型

jdk1.5开始,一个方法可以通过反射获取到他的参数的参数化类型比如:put(Vector<Date> v),通过反射可以获取Date的类型

1.Method里面的方法:

Type[] getGenericParameterTypes()

按照声明顺序返回描述了此 Method 对象所表示的方法的形参类型的 Type 对象的数组。

Type getGenericReturnType()

返回表示由此 Method 对象所表示方法的正式返回类型的 Type 对象。

2.Type是接口,是ParameterizedType的父类

3.ParameterizedType 参数化类型 它放的是泛型的参数化类型实例

Type[] getActualTypeArguments()
返回表示参数的类的的类型参数的Type对象数组。

Type getOwnerType()
返回 Type 对象,返回此类的顶层类的类型。比如O<T>.I<S>,则返回 O<T> 的表示形式

Type getRawType()
返回 Type 对象,表示声明此类型的类或接口。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: