二、泛型
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;
}
public static void main(String[] args) {
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);
return 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);
return 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<?>
当你需要一个既能读又能写的对象时, 就不要使用通配符了.