600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > java基础知识(七)-- 泛型(Generics )

java基础知识(七)-- 泛型(Generics )

时间:2023-04-08 23:25:50

相关推荐

java基础知识(七)-- 泛型(Generics )

介绍

用法:

List list = new ArrayList();// 1list .add(new Integer(12));// 2Integer x = (Integer) list .iterator().next();// 3// 第3 行的类型转换有些烦人,为了保证对Integer 类型变量赋值的类型安全,必须进行类型转换。//当然,因为程序员可能不清楚他们的类型,导致这个类型转换有可能产生一个运行时错误。//而如何把一个list(集合) 中的内容限制为一个特定的数据类型呢?//这就是generics背后的核心思想。这是上面程序片断的一个泛型版本:List<Integer> list = new ArrayList<Integer>(); // 1list.add(new Integer(12)); // 2Integer x = list.iterator().next(); // 3//注意第1行变量list的类型声明。//它指定这不是一个任意的List,而是一个Integer 的List。//我们说List是一个带一个类型参数的泛型接口,我们在创建这个List 对象的时候指定了一个Integer类型参数是。//另一个需要注意的是第3行没了类型转换。

现在,我们用第1行的类型参数取代了第3 行的类型转换。然而,这里还有个很大的不同。编译器现在能够在编译时检查程序的正确性。当我们说list 被声明为ist<Integer>类型,这告诉我们无论何时何地使用list 变量,编译器保证其中的元素的正确的类型。实际结果是,这可以增加可读性和稳定性,尤其在大型的程序中。

泛型的设计背景

Java中的泛型是什么 ?

所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。

集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,在集合中存储对象并在使用前进行类型转换是很不方便。

JDK1.5之后使用泛型来解决。这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection<E>,List<E>,ArrayList<E>这个<E>就是类型参数,即泛型。允许我们在创建集合时再指定集合元素的类型,正如:List<String>,这表明该List只能保存字符串类型的对象。

JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。

使用泛型的好处是什么?

它提供了编译期的类型安全,确保你只能把正确类型的对象放入集合中,避免了在运行时出现ClassCastException。

在集合中没有泛型时任何类型都能够加入集合中,类型不安全,读出来的时候还需要强转。

在集合中有泛型时只有指定类型才能添加到集合中,类型是安全的,读出来的时候不需要强转,很便捷。

自定义泛型结构

泛型的声明

Interface List<T> 和class GenTest<K,V>//其中,T,K,V,E不代表值,而是表示类型。这里使用任意字母都可以。//常用T表示,是Type的缩写。

泛型的实例化:

一定要在类名后面指定类型参数的值(类型)。如:

List<String> strList= new ArrayList<String>();Iterator<Customer> iterator = customers.iterator();

T只能是类,不能用基本数据类型填充。但可以使用包装类填充,把一个集合中的内容限制为一个特定的数据类型,这就是generics背后的核心思想

使用泛型的主要优点是能够在编译时而不是在运行时检测错误。

自定义泛型类, 泛型接口

泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>

实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。

泛型不同的引用不能相互赋值。泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。经验:泛型要使用一路都用。要不用,一路都不要用。

如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。泛型的指定中不能使用基本数据类型,可以使用包装类替换。

在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。

异常类不能是泛型的

父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:

子类不保留父类的泛型:按需实现

没有类型擦除

具体类型

子类保留父类的泛型:泛型子类

全部保留

部分保留

子类除了指定或保留父类的泛型,还可以增加自己的泛型

自定义泛型方法

方法,也可以被泛型化,不管此时定义在其中的类是不是泛型类。在泛型方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型。

/*** 泛型方法的格式* [访问权限] <泛型> 返回类型 方法名([泛型标识 参数名称])抛出的异常* 泛型方法声明泛型时也可以指定上限**/public class DAO {public<E> E get(intid, E e) {E result= null;return result;}}

泛型和子类继承

让我们测试一下我们对泛型的理解。下面的代码片断合法么?

List<String> ls = new ArrayList<String>(); //1List<Object> lo = ls; //2

第1 行当然合法,但是这个问题处在于第2 行。这产生一个问题:一个String 的List 是一个Object 的List 么?大多数人的直觉是回答: “当然!”。因为乍看起来String是一种Object,所以List<String>应当可以用在需要List<Object>的地方,但是事实并非如此。真这样做的话会导致编译错误。

好,在看下面的几行

lo.add(new Object()); // 3String s = ls.get(0); // 4: 试图把Object 赋值给String

这里,我们使用lo 指向ls。我们通过lo 来访问ls,一个String 的list。我们可以插入任意对象进去。结果是ls 中保存的不再是String。当我们试图从中取出元素的时候,会得到意外的结果。java编译器当然会阻止这种情况的发生。第2 行会导致一个编译错误。总之,如果Foo 是Bar 的一个子类型(子类或者子接口),而G 是某种泛型声明,那么G<Foo>是G<Bar>的子类型并不成立!!

如果你再深一步考虑,你会发现Java这样做是有意义的,因为List<Object>可以存储任何类型的对象包括String, Integer等等,而List<String>却只能用来存储Strings。

通配符(Wildcards)

考虑写一个例程来打印一个集合(Collection)中的所有元素。下面是在老的语言中你可能写的代码:

void printCollection(Collection c) {Iterator i = c.iterator();for (int k = 0; k < c.size(); k++) {System.out.println(i.next());}}下面是一个使用泛型的幼稚的尝试(使用了新的循环语法):void printCollection(Collection<Object> c) {for (Object e : c) {System.out.println(e);}}

一个集合,它的元素类型可以匹配任何类型。显然,它被称为通配符。我们可以写:

void printCollection(Collection<?> c) {for (Object e : c) {System.out.println(e);}}

现在,我们可以使用任何类型的collection 来调用它。注意,我们仍然可以读取c 中的元素,其类型是Object。这永远是安全的,因为不管collection 的真实类型是什么,它包含的都是Object。但是将任意元素加入到其中不是类型安全的

Collection<?> c = new ArrayList<String>();c.add(new Object()); // 编译时错误

因为我们不知道c 的元素类型,不能添加对象。add 方法有类型参数E 作为集合的元素类型。我们传给add 的任何参数都必须是一个未知类型的子类。因为我们不知道那是什么类型,所以我们无法传任何东西进去。唯一的例外是null,它是所有类型的成员。另一方面,我们可以调用get()方法并使用其返回值。返回值是一个未知的类型,但是我们知道,它总是一个Object

有限制的通配符(Bounded Wildcards)

限定通配符和非限定通配符。List<? extends T>和List <? super T>这两个List的声明都是限定通配符的例子:

List<? extends T>可以接受任何继承自T的类型的List.

List<? super T>可以接受任何T的父类构成的List。例如List<? extends Number>可以接受List<Integer>或List<Float>

泛型方法

考虑写一个方法,它用一个Object 的数组和一个collection 作为参数,完成把数组中所有object 放入collection 中的功能。下面是第一次尝试:

static void fromArrayToCollection(Object[] a, Collection<?> c) {for (Object o : a) {c.add(o); // 编译期错误}}

把对象放进一个未知类型的集合中。办法是使用generic methods就像类型声明,方法的声明也可以被泛型化——就是说,带有一个或者多个类型参数

static <T> void fromArrayToCollection(T[] a, Collection<T> c){for (T o : a) {c.add(o); // correct}}

我们可以使用任意集合来调用这个方法,只要其元素的类型是数组的元素类型的父类。

Object[] oa = new Object[100];Collection<Object> co = new ArrayList<Object>();fromArrayToCollection(oa, co);// T 指ObjectString[] sa = new String[100];Collection<String> cs = new ArrayList<String>();fromArrayToCollection(sa, cs);// T inferred to be StringfromArrayToCollection(sa, co);// T inferred to be ObjectInteger[] ia = new Integer[100];Float[] fa = new Float[100];Number[] na = new Number[100];Collection<Number> cn = new ArrayList<Number>();fromArrayToCollection(ia, cn);// T inferred to be NumberfromArrayToCollection(fa, cn);// T inferred to be NumberfromArrayToCollection(na, cn);// T inferred to be NumberfromArrayToCollection(na, co);// T inferred to be ObjectfromArrayToCollection(na, cs);// compile-time error}static <T> void fromArrayToCollection (T[] a, Collection<T> c) {for (T o : a) {c.add(o); // correct}}

注意,我们并没有传送真实类型参数(actual type argument)给一个泛型方法。编译器根据实参为我们推断类型参数的值。它通常推断出能使调用类型正确的最明确的类型参数。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。