java基础--泛型
2016-01-07 20:34
525 查看
在java 1.5之前,一般的类和方法,只能使用具体的类型(基础类型或自定义类型),如果要编写可以适应多种类型的代码,就只能使用多态这种泛化机制,然后通过强制类型转换来实现参数的“任意化”。但这种转换要求实际参数类型可以预知。且对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。
泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率。
泛型的类型参数只能是类类型(包括自定义类),不能是基本数据类型(可以使其包装类)。
泛型的参数类型可以使用extends语句,例如
泛型的参数类型还可以是通配符类型。例如
强制类型转换很麻烦,还要事先知道各个Object具体类型是什么,才能做出正确转换。否则,要是转换的类型不对,就会出现运行时错误。而使用泛型就可以暂时先不指定类型,稍后再决定具体使用什么类型。
实现泛型版本
元组(tuple)是将一组对象直接打包存储于一个单一对象中。这个对象允许读取其中的对象,不允许向其中放入新对象。这个概念也称为数据传送对象或信使。
元组可以具有任意长度,元组中的对象可以是任意不同的类型。例如:
注:对象a,b声明成final不声明成private不违反java编程的安全原则吗?
声明成private类型,a,b就只能在类内部使用,只有添加get,set方法才能通过对象来调用
声明成final的元素,当他们第一次被初始化以后就不能被修改,同样保证了编程的安全性。
可以通过继承机制实现类型更长的元组,例如:
基本原则:如果使用泛型方法可以取代整个类泛型化,就应该使用泛型方法。
如:
注意:
使用泛型方法时,不必指明参数类型,编译器会自己找出具体的类型。这称为类型参数推断(type argument inference)。泛型方法除了定义不同,调用就像普通方法一样。
如果f()传入基本类型,自动打包机制就会介入其中,将基本类型的值包装为对应的对象。
对于一个static的方法而言,无法访问泛型类的类型参数,所以,如果static方法需要使用泛型能力,就必须使其成为泛型方法。
类型推断只对赋值操作有效,如果你将一个泛型方法调用的结果作为参数,传递给另一个方法,这时编译器并不会执行类型推断。编译器认为:调用泛型方法后,其返回值被赋给一个Object类型的变量。这时必须显示指明泛型方法的类型。如:
可变参数的特点:
只能出现在参数列表的最后;
…位于变量类型和变量名之间,前后有无空格都可以;
调用可变参数的方法时,编译器为该可变参数隐含创建一个数组,在方法体中一数组的形式访问可变参数。
泛型方法与可变参数列表能够很好的共存:
在泛型方法内部,无法获得任何有关泛型参数的类型信息。
如以下代码:
虽然偶尔可以绕过这些问题来编程,但有时必须通过引入类型标签来对擦除进行补偿。这需要显式的传递类型的Class对象。
如代码:
用动态的isInstance()方法,代替instanceof,判断是否是Cat类型的对象。
实际上
注意:
限制为集合接口的类型:
实例化:
当前看到的这个写法是可以编译通过,并运行成功。可是注释掉的两行加上就出错了,因为
这里的例子仅演示了泛型方法的类型限定,对于泛型类中类型参数的限制用完全一样的规则,只是加在类声明的头部,如:
注意:
如果只指定了
通配符泛型不单可以向下限制,如
例如:
相应的对于
参考:
think in java
百度百科
泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率。
泛型的类型参数只能是类类型(包括自定义类),不能是基本数据类型(可以使其包装类)。
泛型的参数类型可以使用extends语句,例如
<T extends superclass>。习惯上称为“有界类型”。
泛型的参数类型还可以是通配符类型。例如
Class<?> classType = Class.forName("java.lang.String");
1.泛型类
在Java 5之前,为了让类有通用性,往往将参数类型、返回类型设置为Object类型,当获取这些返回类型来使用时候,必须将其“强制”转换为原有的类型或者接口,然后才可以调用对象上的方法。例如:[code]class Gen { public Object ob; // 定义一个通用类型成员 public Gen(Object ob) { this.ob = ob; } public void getTyep() { System.out.println("实际类型是: " + ob.getClass().getName()); } } public class GenDemo { public static void main(String[] args) { // Integer版本 Gen intOb = new Gen(new Integer(88)); intOb.getTyep(); System.out.println("value= " + (Integer) intOb.ob);//需要类型转换 System.out.println("---------------------------------"); // String版本 Gen strOb = new Gen("Hello Gen!"); strOb.getTyep(); System.out.println("value= " + (String) strOb.ob);//需要类型转换 } }
强制类型转换很麻烦,还要事先知道各个Object具体类型是什么,才能做出正确转换。否则,要是转换的类型不对,就会出现运行时错误。而使用泛型就可以暂时先不指定类型,稍后再决定具体使用什么类型。
实现泛型版本
[code]class Gen2<T> { public T ob; // 定义泛型成员变量 public Gen2(T ob) { this.ob = ob; } public void getType() { System.out.println("T的实际类型是: " + ob.getClass().getName()); } } public class GenDemo2 { public static void main(String[] args) { // Integer版本 Gen2<Integer> intOb = new Gen2<Integer>(88); intOb.getType(); System.out.println("value= " + intOb.ob); System.out.println("----------------------------------"); // String版本 Gen2<String> strOb = new Gen2<String>("Hello Gen!"); strOb.getType(); System.out.println("value= " + strOb.ob); } }
元组
一次方法调用返回多个对象,是编程中经常用到的功能,但是return语句只能返回一个对象。通常的解决办法是创建一个对象来持有想要返回的多个对象,通过返回一个这种对象来获取我们需要的多个对象。有了泛型就能够简化这种操作,编写出通用的代码,同时在编译器确保类型安全。元组(tuple)是将一组对象直接打包存储于一个单一对象中。这个对象允许读取其中的对象,不允许向其中放入新对象。这个概念也称为数据传送对象或信使。
元组可以具有任意长度,元组中的对象可以是任意不同的类型。例如:
[code]//2维元组,持有两个对象 class TwoTuple<A,B> { public final A a; public final B b; public TwoTuple(A a,B b) { this.a = a; this.b = b; } } public class TupleTest { //返回这个对象 public TwoTuple<String,Integer> f() { return new TwoTuple<String,Integer>("hi",10); } }
注:对象a,b声明成final不声明成private不违反java编程的安全原则吗?
声明成private类型,a,b就只能在类内部使用,只有添加get,set方法才能通过对象来调用
声明成final的元素,当他们第一次被初始化以后就不能被修改,同样保证了编程的安全性。
可以通过继承机制实现类型更长的元组,例如:
[code]//3维元组,持有三个对象 class ThreeTuple<A,B,C> extends TwoTuple<A,B> { public final C c; public ThreeTuple(A a,B b,C c) { super(a,b); this.c = c; } }
2.泛型方法
要定义泛型方法,只需将泛型参数列表置于返回值之前。是否拥有泛型方法,与其所在的类是否是泛型没有关系,泛型方法使得该方法能够独立于类而产生变化。基本原则:如果使用泛型方法可以取代整个类泛型化,就应该使用泛型方法。
如:
[code] public class GenericMethods { public <T> void f(T x) { System.out.println(x.getClass().getName()); } public static void main(String[] args) { GenericMethods gm = new GenericMethods(); gm.f(1); //输出:java.lang.Integer gm.f("a"); //输出:java.lang.String gm.f(gm); //输出:GenericMethods } }
注意:
使用泛型方法时,不必指明参数类型,编译器会自己找出具体的类型。这称为类型参数推断(type argument inference)。泛型方法除了定义不同,调用就像普通方法一样。
如果f()传入基本类型,自动打包机制就会介入其中,将基本类型的值包装为对应的对象。
对于一个static的方法而言,无法访问泛型类的类型参数,所以,如果static方法需要使用泛型能力,就必须使其成为泛型方法。
类型推断只对赋值操作有效,如果你将一个泛型方法调用的结果作为参数,传递给另一个方法,这时编译器并不会执行类型推断。编译器认为:调用泛型方法后,其返回值被赋给一个Object类型的变量。这时必须显示指明泛型方法的类型。如:
[code]public class GenericMethods { public <T> List<T> g() { return new ArrayList<T>(); } public void h(List<String> x) { System.out.println(x.getClass().getName()); } public static void main(String[] args) { GenericMethods gm = new GenericMethods(); List<String> list = gm.g(); //类型推断 //gm.h(gm.g()); //报错,不能由h中参数类型String,推断出g()中类型 gm.h(gm.<String>g()); //显示指明类型,输出:java.util.ArrayList } }
可变参数与泛型方法
可变参数:适用于参数个数不确定,类型确定的情况,java把可变参数当做数组处理。注意:可变参数必须位于最后一项。当可变参数个数多余一个时,必将有一个不是最后一项,所以只支持有一个可变参数。可变参数的特点:
只能出现在参数列表的最后;
…位于变量类型和变量名之间,前后有无空格都可以;
调用可变参数的方法时,编译器为该可变参数隐含创建一个数组,在方法体中一数组的形式访问可变参数。
泛型方法与可变参数列表能够很好的共存:
[code]public class GenericVarargs { public static <T> void print(T ...array) { for(T item : array) { System.out.print(item + " "); } System.out.println(); } public static void main(String[] args) { print("A","B","C"); //输出:A B C print(1,2,3,4); //输出:1 2 3 4 } }
3.类型擦除与补偿
使用泛型时,java会在编译时检查类型的安全性,但会在运行时擦除所有这些信息。[code]import java.util.ArrayList; import java.util.Arrays; public class ErasedType { public static void main(String[] args) { Class c1 = new ArrayList<String>().getClass(); Class c2 = new ArrayList<Integer>().getClass(); System.out.println(c1 == c2);//输出:true System.out.println(Arrays.toString(c1.getTypeParameters()));//输出:[E] } }
ArrayList<String>和
ArrayList<Integet>在运行时事实上是相同的类型,这两种形式都被擦除成他们的原生类型,即ArrayList。
class.getTypeParameters()将返回一个TypeVariable对象数组。
在泛型方法内部,无法获得任何有关泛型参数的类型信息。
如以下代码:
[code]public class ErasedType2<T> { public static void f(Object obj) { if(obj instanceof T) { } //错误 T var = new T(); //错误 T[] array = new T[10]; //错误 } }
虽然偶尔可以绕过这些问题来编程,但有时必须通过引入类型标签来对擦除进行补偿。这需要显式的传递类型的Class对象。
如代码:
[code]public class TypeOffset<T> { Class<T> obj; public TypeOffset(Class<T> obj) { this.obj = obj; } public boolean f(Object arg) { return obj.isInstance(arg); } public static void main(String[] args) { TypeOffset<Cat> to = new TypeOffset<Cat>(Cat.class); System.out.println(to.f(new Cat())); //true System.out.println(to.f(new Animal())); //false } } class Animal { } class Cat extends Animal { }
用动态的isInstance()方法,代替instanceof,判断是否是Cat类型的对象。
4.限制泛型
要限制泛型类class Generics<T>中类型T为集合接口类型,只需要这么做:
class Generics<T extends Collection>,这样类中的泛型T只能是Collection接口的实现类,传入非Collection接口编译会出错。
实际上
class Generics<T>的限定类型相当于Object,这和“Object泛型”实质是一样的。
注意:
<T extends Collection>这里的限定使用关键字extends,后面可以是类也可以是接口。
限制为集合接口的类型:
[code]public class CollectionGen<T extends Collection> { private T x; public CollectionGen(T x) { this.x = x; } public T getX() { return x; } public void setX(T x) { this.x = x; } }
实例化:
[code]public class CollectionGenDemo { public static void main(String args[]) { CollectionGen<ArrayList> listFoo = null; listFoo = new CollectionGen<ArrayList>(new ArrayList()); System.out.println("实例化成功!"); } }
当前看到的这个写法是可以编译通过,并运行成功。可是注释掉的两行加上就出错了,因为
<T extends Collection>这么定义类型的时候,就限定了构造此类实例的时候T是确定的一个类型,这个类型实现了Collection接口,但是实现 Collection接口的类很多很多,如果针对每一种都要写出具体的子类类型,那也太麻烦了,干脆还不如用Object通用一下。泛型针对这种情况还有更好的解决方案,那就是“通配符泛型”。
多接口限制
虽然Java泛型简单的用 extends 统一的表示了原有的 extends 和 implements 的概念,但仍要遵循应用的体系,Java 只能继承一个类,但可以实现多个接口,所以你的某个类型需要用 extends 限定,且有多种类型的时候,只能存在一个是类,并且类写在第一位,接口列在后面,也就是:[code]<T extends SomeClass & interface1 & interface2 & interface3>
这里的例子仅演示了泛型方法的类型限定,对于泛型类中类型参数的限制用完全一样的规则,只是加在类声明的头部,如:
[code]public class Demo<T extends Comparable & Serializable> { // T类型就可以用Comparable声明的方法和Seriablizable所拥有的特性了 }
5.通配符泛型
为了解决类型被限制死了不能动态根据实例来确定的缺点,引入了“通配符泛型”,使用通配泛型格式为[code]import java.util.*; public class CovariantGenerics { public static void main(String[] args) { List<Apple> flist = new ArrayList<Apple>(); flist.add(new Apple()); //flist.add(new Orange()); //不能编译, List<?> slist = flist; System.out.println(slist.get(0).getClass().getName()); //输出:com.generics.Apple ,说明实际存入的为Apple类型 //Apple ap = slist.get(0); //但取出后为Object实例,不能赋给ap } } class Fruit { } class Apple extends Fruit { } class Orange extends Fruit { }
注意:
如果只指定了
<?>,而没有extends,则默认为
<? extends Object>。
通配符泛型不单可以向下限制,如
<? extends Collection>,还可以向上限制,如
<? super Double>,表示类型只能接受Double及其上层父类类型,如Number、Object类型的实例。
例如:
[code]import java.util.*; public class CovariantGenerics { public static void main(String[] args) { List<Apple> flist = new ArrayList<Apple>(); flist.add(new Apple()); List<? extends Fruit> slist = flist; //slist.add(new Apple()); //任何类型都不能存入 System.out.println(slist.get(0).getClass().getName()); //输出:com.generics.Apple ,说明实际存入的为Apple类型 Fruit ap = slist.get(0); //但取出后为Fruit实例 List<? super Apple> tlist = flist; tlist.add(new RedApple()); //Apple及其子类能够存入 Object ob = tlist.get(0); //但取出后为Object } } class Fruit { } class Apple extends Fruit { } class RedApple extends Apple { }
<? extends Fruit>,通配符告诉编译器我们正在处理Fruit的子类型,但它不知道这个子类型究竟是什么。因为没法确定,为了保证类型安全,就不允许往里面加入任何这种类型的数据。另一方面,不论它是什么类型,它总是类型Fruit的子类型,当我们在读取数据时,能确保返回的数据是一个Fruit类型的实例
相应的对于
<? super Apple>,编译器知道我们正在处理Apple的父类型的数据,当我们加入一个Apple或其子类型的数据时,能够保证类型安全。另一方面,由于不知道我们处理的究竟是那种父类型,但一定能够保证是Object或其子类型,所以当读取数据时返回Object类型
参考:
think in java
百度百科
相关文章推荐
- Java线程安全和非线程安全
- Struts2权限控制
- Java 中使用 SAX 解析 XML 文档
- 关于java中的不可变类(转)
- Java多线程-实现多线程的两种方式
- IO_其他流_基本数据类型+String处理流JAVA158
- jdk环境变量的设置
- Spring MVC - 高級參數綁定,服務端校驗,數據回線,異常處理
- javase复习系列之--多线程篇
- java模板和回调机制学习总结
- Thrift学习(1)C#调用Java开发步骤详解
- Java 数组的声明方式
- Java 非线程安全
- Java多线程-synchronized深入解析及原理
- Struts中Ognl语法注意事项
- Java开发淘宝订单系统
- java基础-常用类(API)
- eclipse hashmap 发生InvocationException的解决方案
- 从零开始使用eclipse Ant脚本语言生成.h头文件:[javah] Exception in thread "main" java.lang.NullPointerException
- java-首字母大小写