天天看点

泛型

二、泛型

Java5开始提供的新特性,表示不确定的类型。

注意:泛型是提供的javac编译器使用的,它用于限定集合的输入类型,让

编译器在源代码级别上,挡住向集合中插入的非法数据。但编译器编译完之后,生产

的.class字节码文件中不再代用泛型的信息,依此不影响程序的运行效率,这个

过程被称为“擦除”。

另外泛型还被用在方法或类上。

-------------------------------------

public class Demo2 {

public static List method1(){

//.....

return new ArrayList();

}

public static void method2(List list){

//...

public static void main(String[] args) {

List list1 = new ArrayList();//正确的

List<String> list2=new ArrayList<String>();//正确的

List<String> list3 = new ArrayList();//正确的

List<String> list3x = method1();//兼容当前情况

List list4 = new ArrayList<String>();//正确的

List<String> list4x = new ArrayList<String>();

method2(list4x);//兼容当前情况。

List<Object> list5 = new ArrayList<String>();//错误的

List<String> list6 = new ArrayList<Object>();//错误的

}

---------------------------

总结:可以两边都没有;可以一边有一边没有;

也可以两边都用,一旦两边都有的话,两边必须保持相同(不考虑边界)。

2.2自定义泛型

分两类:方法上的泛型和类上的泛型。

2.2.1方法上的泛型。

-------------------------

class Person{}

class Dog{}

public class God {

public Object kill(Object obj){

System.out.println("神弄死了:"+obj);

return obj;

public Person kill(Person p){

System.out.println("神弄死了的人是:"+p);

return p;

public Dog kill(Dog d){

System.out.println("神弄死了的狗是:"+d);

return d;

God god = new God();

Person p = god.kill(new Person());

Dog d = god.kill(new Dog());

------------------------------

神可以kill各种类型的对象,我们不可能为每一个类型都编写一个方法,所以

需要使用定义在方法上的泛型。

注意:

(1).泛型需要先定义,再使用,方法上泛型需要定义在方法返回值的前面,通常使用

一个大写的英文字母(sun公司推荐使用T),也可以是任意的字母,但是不要使用

java中的关键字和常用的类(String)。

(2).定义在方法上的泛型的作用范围是当前方法的内部。

-----------------

public <T> T kill(T t){

System.out.println("上帝弄死了"+t);

return t;

-------------------------

public <T> T save(T t){

System.out.println("上帝救活了:"+t);

如果把save方法上的<T>去掉,发现报错,再次证明定义在方法

上的泛型只用在当前方法的内部有效。

两个方法上"T"不是同一个"T"。

-----------------------

God god = new God();

Person p = god.kill(new Person());

Dog d = god.save(new Dog());

如果想让两个方法上的T表示同一个,怎么办?

2.2.2.类上的泛型

定义在类上的泛型作用范围是当前类的内部;

需要先定义再使用,类上的泛型定义在类名的后面。通常使用大写的英文字母。

创建具有泛型的类(God)的对象时,通常需要指定泛型的具体类型。

如果在创建对象时,不明确指定泛型的具体类型,则默认为泛型的“上边界”。

在类上定义的泛型不能用在静态方法上,如果想在静态方法上使用泛型,需要当该静态

方法上重新定义。

另外查看List接口源码发现,它其实就是使用了定义在类上的泛型

public interface List<E> extends Collection<E>{

boolean add(E e);

void add(int index, E element);

E set(int index, E element);

<T> T[] toArray(T[] a);

}

-----------------------

想让所有的神的对象之间公用泛型T:

public static <T> T sleep(T t){

System.out.println("神催眠了:"+t);

注意:在sleep方法上的T,和该方法所在类God<T>上的T不是同一个。

补充:在泛型使用中可以同时定义多个。

public class HashMap<K,V> extends AbstractMap<K,V>

implements Map<K,V>, Cloneable, Serializable

{

public V put(K key, V value){...}

...

}

神马是“上边界”?

一、泛型进阶

!!通配符 ?

因为泛型没有继承的概念,所以当需要用一个“泛型引用”引用不同的泛型实现时,泛型写

他们的共同父类是不行的,这时该怎么做?引入一个新的概念,叫做泛型的通配符“?”,注意

通配符只能用在声明处,不能用在实现实例的过程中。

void print(Collection<?> c){//Collection of unknown

for(Object obj:c){

System.out.pritnln(obj);

}

new ArrayList<Stdudent>();

new ArrayList<Person>();

注意:由于参数c类型为Collection<?>,表示一种不确定的类型,因此在方法内部

不能调用与类型相关的方法。

如果没有指定泛型默认类型,可以接收任意类型,有时希望进一步限制范围,需要使用泛型的边界:

!!上边界:限定通配符的上边界

extends-用来指定泛型的上边界,和泛型通配符配合使用(List<? extends Person> list),指定具体的泛型实现必须是指定类或它子孙类。

List<? extends Number> list1 = new Vector<Number>();//正确的

List<? extends Number> list2 = new Vector<Integer>();//正确的

List<? extends Number> list3 = new Vector<String>();//错误的

List<? extends Person> list1 = null;

list1 = new Vector<Teacher>();

Teacher thr = new Teahcer();

list1.add(thr);

SomeOne<T extends Person>

演示和相关总结见:cn.tedu.v1.Demo2

!下边界:限定通配符的下边界

super:用来指定泛型的下边界,和通配符一起使用。指定具体的泛型实现必须是指定类或指定的“超类”

? super Person

这个原因也是很简单的, 因为我们所传入的类都是Integer的类或其父类, 所传入的数据类型可能是Integer到Object之间的任何类型, 这是无法预料的, 也就无法接收. 唯一能确定的就是Object, 因为所有类型都是其子类型.

使用? super E还有个常见的场景就是TreeSet有这么一个构造方法:

TreeSet(Comparator<? super E> comparator)

就是使用Comparator来创建TreeSet, 那么请看下面的代码:

public class Person {

private String name;

private int age;

public Person(){}

public Person(String name,int age){

this.name = name;

this.age = age;

}//省略getters和setters

public class Student extends Person {

public Student() {}

public Student(String name,int age) {

super(name,age);

class comparatorTest implements Comparator<Person>{

@Override

public int compare(Person p1, Person p2) {

int num = p1.getAge() - p2.getAge();

return num == 0 ? p1.getName().compareTo(p2.getName()):num;

}

public class Demo {

public static void main(String[] args) {

TreeSet<? super Person> ts1 = new TreeSet<Person>(new comparatorTest());

ts1.add(new Person("Tom", 20));

ts1.add(new Person("Jack", 25));

ts1.add(new Person("John", 22));

System.out.println(ts1);

TreeSet<? super Student> ts2 = new TreeSet<Student>(new comparatorTest());

ts2.add(new Student("Susan", 23));

ts2.add(new Student("Rose", 27));

ts2.add(new Student("Jane", 19));

System.out.println(ts2);

}

三、总结:

"in out"原则, 总的来说就是:

in就是你要读取出数据以供随后使用(想象一下List的get), 这时使用extends关键字, 固

定上边界的通配符. 你可以将该对象当做一个只读对象;

out就是你要将已有的数据写入对象(想象一下List的add), 这时使用super关键字, 固定下

边界的通配符. 你可以将该对象当做一个只能写入的对象;

当你希望in的数据能够使用Object类中的方法访问时, 使用无边界通配符;List<?>

当你需要一个既能读又能写的对象时, 就不要使用通配符了.