泛型(Generic)
泛型是JDK1.5版本以后出现的新特性,用于解决类型安全问题,是一个安全机制。
泛型是提供给Javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带泛型说明的集合时会去除掉“类型”信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会去除掉泛型的类型信息,只要跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如使用发射得到集合,再调用其add方法即可
泛型格式:
通过<>来定义要操作的引用数据类型。
泛型术语:
整个ArrayList<E>称为泛型类型
整个ArrayList<Integer>称为参数化的类型
ArrayList<E>中的E称为类型变量或类型参数
ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
Arrayist<Integer>中的<>念typeof
ArrayList称为原始类型
参数化类型与原始类型的兼容性:
参数化类型可以引用一个原始类型的对象,编译期报告警告
例如:
Collection<String> c = new Vector();
原始类型可以引用一个参数化类型的对象,编译期报告警告
例如:
Collection c = new Vector<String>;
参数化类型不考虑类型参数的继承关系:
Vector<String> v = new Vector<Objcet>();//错误,不写<Object>没错,写了就是明知故犯
Vector<Object> v = new Vector<String>(); //错误
思考:
Vector v = new Vector<String>();//编译通过
Vector<Object> v1 = v; //编译通过 编译器是一行一行的检查语法错误,
Java中不允许创建带有类型变量的数组,即在创建数组实例时,数组的元素不能使用参数化的类型
例如: Vector<Integer> vector int[] = new Vector<Integer>[10];
Java 泛型的参数只可以代表类,不能代表个别对象。
泛型可以定义在类,方法,接口上。
使用泛型的好处:
1将运行时期出现的问题ClassCastException,转移到了编译时期,
方便于程序员解决问题,让运行时期问题减少。
2避免了强制转换的麻烦
在使用java提供的对象时,何时定义泛型:
通常在集合框架中很常见,只要见到<>就要定义泛型。
当使用集合时,将集合中要存储的数据类型作为参数传递到<>中即可。
如API1.6中的 ArrayList<E> 代表定义该ArrayList集合时需要定义泛型。
泛型示例:
ArrayList<String> al = new ArrayList<String>();
//集合中添加的是字符串,这时需要在集合定义时制定类型<String>
al.add("abcedf");
al.add(4);//相当于al.add(new Integer(4)); 指定泛型后,在编译时就失败,不会在运行时才产生问题
Iterator<String> it = al.iterator();
while(it.hasNext())
{
System.out.println(it.next().length());
//iterator将元素从集合中取出后装入了迭代器中,所以在定义迭代器时也需要制定其类型为<String>,
//否则在迭代操作元素时,将产生ClassCastException异常。
}
泛型类
当泛型定义在类上时,这个类就叫泛型类。
什么时候定义泛型类?
当类中要操作的引用数据类型不确定的时候。
早期定义Object来完成扩展,
JDK1.5后定义泛型来完成扩展。
泛型类示例:
class Worker{}
class Student{}
class Tools //JDK1.5之前做法
{
private Object obj;
public void setObject(Object obj) { this.obj = obj; }
public Object getObject() { return obj; }
}
class Utils<Mgc> //JDK1.5出现泛型之后,泛型类
{
private Mgc m;
public void setObject(Mgc m) { this.m = m; }
public Mgc getObject() { return m; }
}
class GenericDemo
{
public static void main(String[] args)
{
Utils<Student> u = new Utils<Student>();
u.setObject(new Student());
Student s = u.getObject();
}
}
泛型方法
泛型类定义的泛型,在整个类中有效,如果被方法使用,
那么泛型类的对象明确要操作的具体类型后,所有要操作的类型就已经固定了。
为了让不同方法可以操作不同类型,而且类型还不确定,可以将泛型定义在方法上。
当泛型定义在方法上时,这个方法就叫泛型方法。
泛型类示例:
class GenericDemo
{
public static void main(String[] args)
{
Demo d = new Demo();
d.print("hello");
d.print(new Integer(4));
}
}
class Demo
{
public static <T> void print(T t)
{System.out.println(t);}
}
当一个泛型类中定义了泛型方法时,该泛型方法所调用的引用数据类型由该方法的泛型参数决定。
此泛型类中未定义泛型的方法所调用的引用数据类型由该类的泛型参数决定。
泛型类中定义了泛型方法示例:
class Demo<T>
{
public void print_1(T t) { System.out.println(t); }
public <T> void print_2(T t) { System.out.println(t); }
}
class GenericDemo
{
public static void main(String[] args)
{
Demo<String> d = new Demo<String>();
d.print_1("hello");//编译成功
d.print_2("hello");//编译成功
d.print_1(new Integer(8));//编译成功
d.print_2(new Integer(8));//编译会失败
}
}
静态方法泛型
静态方法不可以访问类上定义的泛型设定,因为类上的泛型是在建立对象之后才明确的,
而静态方法是随着类的加载就已经产生,这时对象还未建立。
如果静态方法操作的引用数据类型不确定,可以将泛型定义在方法上。
这个方法就是静态方法泛型。
静态方法泛型示例:
class Demo<T>
{
public static <T> void print_1(T t)
{ System.out.println(t); }
}
class GenericDemo
{
public static void main(String[] args)
{
Demo<String> d = new Demo<String>();
d.print_1("hello");
}
}
泛型接口
当泛型定义在接口上时,这个接口就是泛型接口。
泛型接口示例:
interface Inter<T>
{
void show(T t);
}
class InterImpl<T> implements Inter<T>
{
public void show(T t){
System.out.println("show: "+t);
}
}
class GenericDemo
{
public static void main(String[] args){
InterImpl<String> i1 = new InterImpl<String>();
i1.show("hello");
InterImpl<Integer> i2 = new InterImpl<Integer>();
i2.show(new Integer(4));
}
}
泛型限定
?: 通配符,也可以理解为占位符。代表不制定类型。
? extends E: 可以接收E类型及其子类型。上限设定。
? super E: 可以接收E类型及其父类型。下限设定。
定义一个方法,该方法用于打印出任意参数化类型的集合中的所有数据,该方法该如何定义?
错误方式:
public static void printCollection(Collection<Object> cols){
for(Object obj:clos){
System.out.println(obj);
}
cols.add("String"); //错误
cols = new HashSet<Date>();//会报告错误
}
正确方式:
public static void PrintCollection(Collection<?> cols){
for(Ojbect obj:cols){
System.out.println(obj);
}
//cols.add("String");//错误,因为它不知自己未来匹配的就一定是String
cols.size();//正确,此方法与类型参数没有关系,可参看此方法的API定义
cols = new HashSet<Date>(); //这里是复制操作相当于给cols赋值。
}
总结:
使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,
可以调用与参数化无关的方法,不能调用与参数化有关的方法。
泛型限定示例:
class GenericDemoOne
{
public static void main(String[] args)
{
ArrayList<String> al1 = new ArrayList<String>();
ArrayList<Integer> al2 = new ArrayList<Integer>();
al1.add(new String("hello"));
al1.add(new String("java"));
al2.add(new Integer(5));
al2.add(new Integer(1));
al2.add(new Integer(8));
printCollection_2(al1);
}
public static <T> void printCollection_1(ArrayList<T> t)
{
Iterator<T> it = t.iterator();
while(it.hasNext())
{
T t1 = it.next();//如果定义了具体类型T,可以对T进行操作
System.out.println(t1);
}
}
public static void printCollection_2(ArrayList<?> t)
{
Iterator<?> it = t.iterator();
while(it.hasNext())
{
//因为?只是占位符,代表不确定类型,所以此处不可以对类型进行操作。
System.out.println(it.next());
//编译失败,因为类型不具体,所以不可以使用类型的特有方法,只能使用共性方法。
//System.out.println(it.next().length());
}
}
}
class GenericDemoTwo
{
public static void main(String[] args)
{
ArrayList<Person> al1 = new ArrayList<Person>();
al1.add(new Person("张三"));
al1.add(new Person("李四"));
ArrayList<Student> al2 = new ArrayList<Student>();
al2.add(new Student("小张三"));
al2.add(new Student("小李四"));
printCollection_2(al2);
}
public static void printCollection_2(ArrayList<? extends Person> t)
{
Iterator<? extends Person> it = t.iterator();
while(it.hasNext())
{
System.out.println(it.next().getName());
}
}
public static <T> void printCollection_1(ArrayList<T> t)
{
Iterator<T> it = t.iterator();
while(it.hasNext())
{
T t1 = it.next();
System.out.println(t1);
}
}
}
class Person
{
private String name;
Person(String name)
{ this.name = name; }
public String getName()
{ return name; }
}
class Student extends Person
{
private String name;
Student(String name)
{ super(name); }
}