1、泛型
1.1概念
泛型是JDK1.5的时候提出的新特性,提供了编译时的一种安全检测机制,这种机制会在编译的时候检测非法类的数据结构。
1.2泛型的实质
数据类型参数化,将数据类型作为参数,定义的的时候不做规范,当创建对象,或者使用类中的方法的时候再以参数的形式进行设定。
1.3泛型的好处
提供编译时的安全机制,消除了强制类型转换的问题。
2、泛型类
2.1泛型类的定义规则:
public class Generic<E>(){
//成员变量
//函数
可以定义普通的方法也可以定义泛型类的方法
}
2.2泛型类的继承关系:
父类声明为泛型方法子类继承后要与父类的泛型的参数列表相同,子类可以拓展自己的但要保证父类的都要有。
public class Father<T>(){...}
public class Son<T,E> exrtends Father<T>(){...}
2.3泛型通通配符:
问题:泛型类作为参数,new对象的时候只能传递相应的类的参数。
解决:上限通配符extends-----<? extends 数据类型>、下限通配符super-----<? super 数据类型>。
/*
* 这里写了一个Number的泛型类,方法就是成员变量ey的get、set方法。
*/
public class Box<Number>{
private Number key;
public void setKey(Number key){
this.key = key;
}
public Number getKey(){
return key;
}
}
@Test
public void test(){
Box<Number> box1 = new Box<>();//这里没有问题
box1.setKey(100);
showBox(box1);
Box<Integer> box2 = new Box<>();
box2.setKey(100);
showBox(box2);//这里会报红,虽然Integer是Number的子类但是泛型不可以继承,这里就可以使用泛型通配符解决。静态的方法使用泛型通配福,而不是直接规定为NUmber型
}
public static void showBox(Box<Number> box){//更改为Box<? extends Number>--表示Number,和其子类。或者<? super Integer>表示通配Integer和他的父类们。
Number num = box.getKey();
System.out.println(num);
}
2.4泛型方法:
定义规则:
/*泛型方法 泛型方法,定义 public<E> E 方法名(参数列表{}
* 当泛型类中定义泛型方法的时候,方法的泛型类型与类的无关,如果传入的也是泛型的话,可以不用传入与调用对象相同的泛型
* 比如,一个类定义为T泛型,它的泛型方法可以为E泛型,泛型类的成员方法,当创建类的对象并确定数据类型的时候后其中的方法会
* 就确定了类型。
* 泛型方法不同,泛型类的泛型为<String>,因为是泛型方法,所以定义T或E类型的时候,返回值的话应该是依据传,void类型的话
* 形参列表中可以传入相应的泛声明这些,这都是在方法被调用的时候才会确认,十分的方便,简而言之就是能任意传入数据。
* 传入的参数的泛型,参数的泛型是在方法调用的时候才会确定,如果泛型方法定义为public<T> T 方法名(T t){}这个参数的泛型也
* 要为T,不然返回的类型就会不匹配。当调用的时候类的泛型可以为Integer,方法可以为String,他们两个应该是无关的,并且这个
* 方法的方法名也可以与成员方法相同。注意的是普通的成员方法如果使用的类的泛型进行定义的话就不能声明为是static,而反省方法
* 可以,还支持泛型可变参数
*/
public <E> E Generic(E e){...}
可变参数泛型方法:
public <E> E Generic(E...e){...}
/**
* 可变参数泛型的方法,传入的数据,和数据的类型,应当当做数组来打印,测试类中进行调用。
* 数组的可变参数类似:数组是不用单个的再去定义一个数组,而是直接在小括号中传递数据,不过不同的应该是可变泛型方法可以传递任意数据进行打印
* @param e
* @param <E>
*/
public static <E> void Generic(E...e){
for(int i=0 ;i <e.length;i++){
System.out.println(e[i])
}
}
3、泛型接口
/**
*泛型接口子类实现的时候需要传递数据类型,原先的参数类表的泛型会统一进行擦除更改为Object类型,知道
*调用方法的时候才回确定参数数
*的数据类型,作为参数的类型,也必须为类类型。
*例如:集合框架中的List、Map类,可以作为泛型类当做参数,他们本身也有泛型这喝应该是重点,等同与数组吧。
*/
public interface Generic<T>{
T eat();
T drink(T t);
.....
}
4、泛型擦除
概念:
意思应该就是,我们原本的泛型为T,但是编译后的字节码文件就会更改为Object类型,T>>>Object就是泛型擦除。如果明确一个数据类型,保持不变,并在创建对象的时候,进行安全检查机制,看是否属于规定的泛型类。
-------泛型不参与运行阶段,只在编译的阶段对代码的规范进行约束。
public class Erasure<T> {
private T key;
public T getKey() {
return key;
}
public void setKey(T key) {
this.key = key;
}
}
public class MyTest {
@Test
public void test(){
//创建两个泛型不同的list
ArrayList<String> list1 = new ArrayList<>();
ArrayList<Integer> list2 = new ArrayList<>();
System.out.println(list1.getClass());
System.out.println(list2.getClass());
//这两个一样意味着他们的内存地址相同,就是一个对象
System.out.println(list1.getClass() == list2.getClass());
System.out.println("_________________________________________________");
/*
* 反射获取类的三种方式:
* 1、类.Class
* 2、类的对象.getClass
* 3、class.forName("全类名")
*
* 类型擦除:测试就是同过反射拿到本类的对象再获取,其中所有属性的数据类型
* 发现声明的泛型T,当通过反射拿到其中的属性的时候,数据类型为Object所以
* 数据的类型在转换为字节码文件的时候就会进行更改
* */
//创建对象
Erasure<Integer> erasure = new Erasure<>();
//反射拿到类
Class<? extends Erasure> clz = erasure.getClass();
//反射获取类中的所有变量,
Field[] fields = clz.getDeclaredFields();
for (Field a: fields) {
System.out.println(a.getName() + "---"+ a.getType().getSimpleName());
}
}
}