1.泛型的作用
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
try {
Class<? extends List> aClass = list.getClass();
Method add = aClass.getDeclaredMethod("add", Object.class);
add.invoke(list,"kl");
}catch (Exception ex){
}
System.out.println(list);
}
运行结果: [1, kl]
为什么泛型为Integer类型的List能存String 其实list添加的时候类型本身就是object,jdk1.5推出泛型这只是为了更严格的代码编写,更安全,也降低了异常:如强制转化带来的麻烦。但是泛型还有其他作用,如框架封装提供的基类等都是使用泛型。
2.泛型主要应用在接口,类,方法上,分为4种。
接口泛型:大致的写法。
package com.JavaTest1;
class InterfaceClass1 <T,M,N>{
public T test(M m,N n){
T t = null;
return t;
}
}
public class InterfaceClass <T>{
public T test(T t){
return t;
}
}
interface InterfactClass2<T extends Number> {
default T test() {
T t = null;
return t;
}
T test2();
}
当实现接口的类不传入定义的泛型实参,编译报错,上述的T,M,N称为通配符,可以任意命名。
形参:一般方法上的参数就叫形参
实参:传给方法的参数叫实参。
泛型方法:大致写法
public <T> T test1(T... args){
T t = null;
return t;
}
public void test2(InterfaceClass<T> interfaceClass){
}
public void test3(InterfaceClass<?> interfaceClass) {
}
这里的?号是类型实参你可以把它看做是所以参数的父类,比如object类一样。
泛型类:大致写法
class Plate<T> {
private T item;
public Plate(T t) {
this.item = t;
}
public T get() {
return item;
}
public void set(T item) {
this.item = item;
}
}
泛型静态方法:
public static <T> void show(T t){ }
<T> 需要额外增加这个申明,因为不依赖泛型类,静态方法本身属于类,不依赖实例对象。
3.泛型上下边界
查看下面的两个例子:
public class Abrasion { public static void main(String[] args) { Class a = new ArrayList<Integer>().getClass(); Class b = new ArrayList<String>().getClass(); System.out.println(a == b); } }
运行结果: true
为什么运行结果一样呢,因为在泛型代码内部,无法获取任何有关泛型参数类型的任何信息!,Java的泛型就是使用擦除来实现的,当你在使用泛型的时候,任何信息都被擦除,你所知道的就是你在使用一个对象。
擦除的理解:
class Hello{ public void hello(){ System.out.println("hello"); } } class TestHello<T>{ private T t; public TestHello(T t){ this.t = t; } public void callHello(){ t.hello();//这里不能编译 } } public class Demo { public static void main(String[] args) { Hello hello = new Hello(); TestHello<Hello> testHello = new TestHello<>(hello); testHello.callHello(); } }
我们可以看到,t.hello()这里是不能够编译的。因为擦除的存在,main中传入的泛型,在TestHello中编程了Object,因此并不能跟Hello这个类绑定,也就调用不了hello方法,泛型擦除了所以的信息,解决这个问题只需要把TestHello定义为TestHello<T extends Hello> 。这个边界申明了必须具有类型Hello或者从Hello导出的类型。
再看下面的例子
class redApple extends Apple{};
class Fruit {}
class Apple extends Fruit {}
class Plate<T> {
private T item;
public Plate(T t) {
this.item = t;
}
public T get() {
return item;
}
public void set(T item) {
this.item = item;
}
}
public void test3(){
Plate<? extends Apple> fruitPlate=new Plate<Apple>(new Apple());
fruitPlate.get();
fruitPlate.set(new Apple()); // 报错,因为不知道我的子类是哪个
Plate<? super Apple> plate =new Plate<Fruit>(new Fruit());
plate.set(new redApple());
}
这里讲述了通配符和泛型的上界和下界,上界< ? extends Class> 下界下界< ? super Class>
PECS原则
如果要从集合中读取类型T的数据,并且不能写入,可以使用 ? extends 通配符;(Producer Extends)
如果要从集合中写入类型T的数据,并且不需要读取,可以使用 ? super 通配符;(Consumer Super)
如果既要存又要取,那么就不要使用任何通配符。
总结:
无论何时,如果你能做到,你就该尽量使用泛型方法。也就是说,如果使用泛型方法将整个类泛型化,那么就应该使用泛型方法。另外对于一个static的方法而已,无法访问泛型类型的参数。所以如果static方法要使用泛型能力,就必须使其成为泛型方法。