泛型
泛型,即类型参数化,就是将原来具体的类型参数化,类似于方法中的变量参数,此时类型也被定义成了参数形式,然后在调用的时候,传入具体类型。
泛型案例:
public static void demo1(){
ArrayList list=new ArrayList();
list.add("kobe");
list.add(24);
list.add(new Object());
for (int i=0;i<list.size();i++){
System.out.println(list.get(i).getClass());
}
}
通过demo1,可以看到,list可以存任意类型。但如果创建list的时候,用泛型进行类型限定就不一样了
public static void demo2(){
ArrayList<String> list=new ArrayList<>(); //限定String类型
list.add(23); //int类型,编译错误
list.add("jordan");
list.add("james");
list.add(new Object()); //Object类型,编译错误
for (String s:list) {
System.out.println(s);
}
}
通过demo2,可以看到,在创建ArrayList时,指定了存储类型是String类型,所以在编译阶段存入其他类型,就会报错,只能存储String类型,由于指定了String类型,所以就可以增强for遍历。这就是泛型的本质意义所在,指定参数类型
泛型的好处
通过上面的案例,不难总结出泛型会带来的好处:
- 编译期间就指定了类型,保证类型安全
- 避免了强制类型转换异常
- 编码利于重用,增加了代码的通用性
上面的好处提到了编译期间,那么运行期间呢?我通过下面的内容给大家分析。
泛型的类型擦除
泛型只在编译阶段有效,编译后程序会采取去泛型化的措施。见下面案例:
public static void demo3(){
ArrayList list=new ArrayList();
ArrayList<String> stringArrayList=new ArrayList<>();
System.out.println(list.getClass()==stringArrayList.getClass()); //输出true
}
无论有没有指定类型,都是由同一个class创建出来的,所以泛型类型在逻辑上可以看成是多个不同的类型,但实际上都是同一个数据类型。
再看一个案例:
public static void demo4() throws Exception {
ArrayList<String> list=new ArrayList<>(); //限定String类型
list.add("jordan");
list.add("james");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
Class clazz=list.getClass();
Method add = clazz.getDeclaredMethod("add", Object.class);
add.invoke(list,23);
System.out.println(list.size());
}
限定泛型类型后,通过反射进行元素新增,增加的是int类型,打印list的长度,发现+1了。因为反射是在程序运行期间,所以可以证明泛型在运行阶段被去除。
由此可见,在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦除,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。
泛型的表达
无边界通配符 <?>
如果我们只有在使用的时候,才知道存入的类型时,可以使用无边界通配符来表示通用的类型
public static void demo5(ArrayList<?> arrayList){
for (int i = 0; i < arrayList.size(); i++) {
System.out.println(arrayList.get(i));
}
}
main方法中:
//demo5
ArrayList<String> arrayList=new ArrayList<>();
arrayList.add("kobe");
arrayList.add("bryant");
ArrayList<Integer> arrayList1=new ArrayList<>();
arrayList1.add(8);
arrayList1.add(24);
demo5(arrayList);
demo5(arrayList1);
案例中的 ? 代表着限定通用类型. 或者这么理解,代表可变的参数类型。
上界通配符
上界通配符例如:<? extends Number>,代表限制类型为Number的子类或子孙类对象
public static void demo6(ArrayList<? extends Number> arrayList){
for (int i = 0; i <arrayList.size(); i++) {
System.out.println(arrayList.get(i));
}
}
下界通配符
下界通配符例如<? super Integer>,代表从Integer到Object所有对象都是可以的
泛型的使用
泛型跟我们的成员属性一样,使用之前要先声明,不过泛型用<>进行声明。声明一般约定采用单个大写字母表示,常用的有:
- E - Element (在集合中使用,因为集合中存放的是元素)
- T - Type(Java 类)
- K - Key(键)
- V - Value(值)
- N - Number(数值类型)
- ? - 表示不确定的java类型
- S、U、V - 2nd、3rd、4th types
错误的使用案例:
public E get(E e){
return null;
}
编译器报错,为什么呢,因为泛型只有使用,没有声明
正确的案例:
public <KOBE> KOBE get(KOBE kobe){
return null;
}
这里声明的泛型是KOBE,故而在方法中可以使用KOBE泛型
泛型类:
泛型类一般指泛型的定义与类名一起.在创建实体对象时,指定泛型的类型
普通Car类:
public class Car {
private String brand;
public Car(String brand) {
this.brand = brand;
}
public static void drive(){
System.out.println("The car is driving");
}
}
实例化过程:
Car car=new Car("benz");
加入新需求,不仅要记录品牌名称,还要记录汽车的颜色,型号等信息,这时候我们需要创建一个实体Brand进行信息的保存,此时得需求改变带来的代码代表使得我们的灵活度并不够。我们可以使用泛型类进行解决..
public class Car2<T> {
private T brand;
public void drive(String brand){
System.out.println("the "+brand+" is driving");
}
public Car2(T brand) {
this.brand = brand;
}
}
Car2 实例化过程:
Brand brand=new Brand();
brand.setColor("black");
brand.setModel("大G");
Car2<Brand> car2=new Car2<Brand>(brand);
通过泛型类,我们可以提升我们程序固定逻辑的灵活度
泛型方法
方法的泛型有两种:
- 实体方法:实体方法中可以使用类中定义的泛型或者方法中定义的泛型
- 静态方法:方法中不可用类中定义的泛型,只能用静态方法上定义的泛型
public static class demo<T,K>{
public T method1(T t,K k){
return (T)null;
}
public <S,Y>K method2(S s,Y y){
return (K)null;
}
public static <J> void method3(J j){
}
}
泛型接口
指在接口的定义时进行泛型的申明.
接口是标准的指定者,指实现该接口的类必须实现其标准定义(即抽象方法).
所以在接口上进行泛型的申明,或者说使用泛型接口,可以让我们的程序代码更加简洁,更加多变.
网友评论