深入浅出的Java泛型

泛型

在这里插入图片描述

泛型是一种未知的数据类型,当我们不知道使用什么数据类型的时候,可以使用泛型

泛型也可以看成是一个变量,用来接收数据类型

​ E e:element 元素

​ T t:Type 类型

ArrayList集合在定义的时候,不知道集合中都会存储什么类型的数据,所以类型使用泛型,E是未知的数据类型

创建集合的时候,就会确定泛型的数据类型

在这里插入图片描述

使用泛型的好处

创建集合对象,不使用泛型
好处:集合不使用泛型,默认的类型就是object,可以存储任意类型的数据
弊端:不安全,会引发异常

ArrayList list = new ArrayList();
list.add("abc");
list.add(1);
//使用迭代器,遍历集合,获取迭代器
Iterator iterator = list.iterator();
while (iterator.hasNext()){
    Object obj = iterator.next();
    System.out.println(obj);
    //想要使用String特有的方法,获取字符串长度,多态 Object obj = "abc"
    //需要向下转型
    //当输出元素1的长度时,会抛出ClassCastException类型转换异常,不能把Integer类型转换为String类型
    String s = (String)obj;
    System.out.println(((String) obj).length());
}

创建集合对象,使用泛型
好处:1)避免了类型转换的麻烦,存储的是什么类型,取出的就是什么类型;
​ 2)把运行期异常(代码运行之后会抛出异常),提升到了编译期(编码阶段会检查)
弊端:泛型是什么类型,只能存储什么类型的数据

泛型的定义与使用

含有泛型的类
/*
泛型是一个未知的数据类型,当我们不确定什么数据类型的时候,可以使用泛型
泛型可以接收任意的数据类型,可以是Integer、String、Student...
创建对象的时候确定泛型的数据类型
*/
//泛型类
public class Use_fanxing<E>{
    public E name;
    public E getName() {
        return name;
    }
    public void setName(E name) {
        this.name = name;
    }
}
//使用泛型类
//不写泛型,默认为Object类型
Use_fanxing obj = new Use_fanxing();
obj.setName("只能是字符串");
Object obj1 = obj.getName();
System.out.println(obj1);
//使用泛型创建use_fanxing对象
Use_fanxing<Integer> in1 = new Use_fanxing<>();  //泛型使用Integer
in1.setName(789);
Integer num = in1.getName();   //由于自动拆箱,也可定义为int类型
Use_fanxing<String> str1 = new Use_fanxing<>();  //泛型使用String
str1.setName("小鸟");
String nameStr = str1.getName();

泛型类也可以派生子类

1)子类也是泛型类,子类和父类的泛型类型要一致

//父类用的T,子类也要用T作为泛型标志
class ChildGeneric<T> extends Generic<T>   
//子类可以对父类的泛型进行扩展,但必须包含父类的泛型标志
class ChildGeneric<T,E,K> extends Generic<T>   

2)子类不是泛型类,父类要明确泛型的数据类型

class ChildGeneric extends Generic<String>   
含有泛型的方法

修饰符与返回值中间非常重要,可以理解为声明此方法为泛型方法。

/*
泛型定义在方法的修饰符和返回值类型之间
格式: 修饰符 <泛型> 返回值类型 方法名(参数列表(使用泛型))
*/
public class Methodfanxing {
    //定义一个含有泛型的方法
    public <E> void methodFanxing(E e){
        System.out.println(e);
    }
    //定义一个含有泛型的静态方法
    static <E> void  jingtaiFanxing(E e){
        System.out.println(e);
    }
}
Methodfanxing methodfanxing = new Methodfanxing();
methodfanxing.methodFanxing(10);
methodfanxing.methodFanxing(true);
methodfanxing.methodFanxing("abc");
Methodfanxing.jingtaiFanxing("静态方法,不建议创建对象");
//-------------------------------------------------------------
public class Methodfanxing<E> {
    //定义一个泛型类中含有泛型的成员方法
    public void methodFanxing1(E e){
        System.out.println(e);
    }    
    //定义一个含有泛型的方法
    public <E> void methodFanxing2(E e){
        System.out.println(e);
    }    
}
Methodfanxing<Integer> methodfanxing = new Methodfanxing();
methodfanxing.methodFanxing1(10);  //10
methodfanxing.methodFanxing2("abc");   //abc

1)必须保证权限修饰符和返回值之间的泛型标识符 和 参数列表中的泛型标识符相同。

2)即使泛型类和泛型方法中的泛型标识符相同,但是两个也互不影响,独立存在。(泛型方法能使方法独立于类而产生变化)

3)如果泛型类中定义了成员方法,成员方法中使用了泛型类中的泛型标识符,那么这两个标识符必须相同,因为是由泛型类传递进来的,成员方法只是使用者。

4)泛型类中的成员方法或变量如果使用了泛型类传递进来的泛型,那么该方法和变量不能被定义为static,因为泛型是通过泛型类实例化时指定的,但是static类型的成员变量和方法在类初始化之前加载。

5)泛型方法可以定义为static类型,如果static方法要使用泛型能力,就必须使其成为泛型方法。

6)泛型方法还可以支持可变参数。

public <E> void print(E...e){
    for(E e1 : e){
        System.out.println(e);
    }
}
含有泛型的接口

含有泛型的接口,有两种使用方式

1)定义接口的实现类,实现接口,指定接口的泛型

2)接口使用什么泛型,实现类就使用什么泛型,类跟着接口走,就相当于定义一个含有泛型的类,创建对象的时候确定泛型的类型

//定义泛型接口
public interface FanxingInterface<E> {
    public abstract void method1(E e);
    public abstract void method2(int m);
}

实现含泛型的接口

//方式 1)定义实现类时,指定接口的泛型
public class FanxingInterfaceimp implements FanxingInterface<String>{
    @Override
    public void method1(String str) {
        System.out.println("定义实现类的时候,指定接口泛型"+str);
    }
    @Override
    public void method2(int m) {
        System.out.println(m);
    }
}

FanxingInterfaceimp fimp = new FanxingInterfaceimp();
fimp.method1("iosnsk");
fimp.method2(10);
//方式 2)接口使用什么泛型,实现类就使用什么泛型
public class FanxingInterfaceimp1<E> implements FanxingInterface<E> {
    @Override
    public void method1(E e) {
        System.out.println("接口使用什么泛型,实现类就使用什么泛型"+e);
    }
    @Override
    public void method2(int m) {
        System.out.println(m);
    }
}

FanxingInterfaceimp1<Integer> fimp1 = new FanxingInterfaceimp1<>();
FanxingInterfaceimp1<String> fimp2 = new FanxingInterfaceimp1<>();
fimp1.method1(100);
fimp1.method2(1000);

泛型通配符

当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符<?>表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。

通配符的使用,不知道使用什么类型来接收的时候,此时可以使用?,?表示未知通配符。

此时只能接收数据,不能往该集合中存储数据。不能创建对象使用,只能作为方法的参数使用,而且类型通配符时类型实参,而不是类型形参

/*
不能创建对象使用,只能作为方法的参数使用
*/
 //泛型通配符
ArrayList<String> str = new ArrayList<>();
str.add("a");
str.add("b");
ArrayList<Integer> num = new ArrayList<>();
num.add(1);
num.add(2);
printArray(str);
printArray(num);
//遍历ArrayList集合时,不需要知道使用了什么数据类型  
public static void printArray(ArrayList<?> list){
    Iterator<?> iterator = list.iterator();
    while (iterator.hasNext()){
        Object o = iterator.next();
        System.out.println(o);
    }
通配符的高级使用----受限泛型

泛型上限:

​ 格式:类型名称<? extends 类> 对象名称

​ 意义:只能接收该类型及其子类

泛型下限:

​ 格式:类型名称<? super 类> 对象名称

​ 意义:只能接收该类型及其父类

Collection<Integer> list1 = new ArrayList<>();
Collection<String> list2 = new ArrayList<>();
Collection<Number> list3 = new ArrayList<>();
Collection<Object> list4 = new ArrayList<>();

getElement1(list1);
getElement1(list2);//报错
getElement1(list3);
getElement1(list4);//报错

getElement2(list1);//报错
getElement2(list2);//报错
getElement2(list3);
getElement2(list4);
//泛型的上限,此时的泛型?,必须时Number类型或者其子类
public static  void getElement1(Collection<? extends Number> coll){}
//泛型的上限,此时的泛型?,必须时Number类型或者其父类
public static  void getElement2(Collection<? super Number> coll){}

类型擦除

泛型是 Java 1.5版本才引进的概念,在这之前是没有泛型的,但是,泛型代码能够很好地和之前版本的代码兼容。那是因为,泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉,我们称之为类型擦除。

ArrayList<Integer> intNum = new ArrayList<>();
ArrayList<String> str = new ArrayList<>();
System.out.println(intNum.getClass().getSimpleName()); //ArrayList
System.out.println(intNum.getClass() == str.getClass());  //true

在这里插入图片描述

Erasure<String> erasure = new Erasure<>();
erasure.setKey("hello");
System.out.println(erasure.getClass().getField("key").getType());
//class java.lang.Object

无限制类型擦除时,最终会被转换为Object类型

在这里插入图片描述

Erasure<Integer> erasure = new Erasure<>();       System.out.println(erasure.getClass().getField("key").getType());
//class java.lang.Number

有限制擦除时,最终会被转换为上限的类型

在这里插入图片描述

Class<? extends Erasure> aClass = erasure.getClass();
Method[] methods = aClass.getDeclaredMethods();
for (Method method : methods) {
  System.out.println(method.getName()+":"+method.getReturnType());
}//Number

在这里插入图片描述

InfoImpl info = new InfoImpl();
Class<? extends InfoImpl> infoClass = info.getClass();
Method[] methods = infoClass.getDeclaredMethods();
for (Method method : methods) {
	System.out.println(method.getName() + "==>" + 	     method.getReturnType());
}
/*输出
info==>class java.lang.Integer
info==>class java.lang.Object
*/

桥方法可以解决类型擦除和多态之间的冲突。

泛型深入

1)类型擦除之后,在class文件中保存的是原始类型。 原始类型就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型。无论何时定义一个泛型类型,相应的原始类型都会被自动地提供。类型变量被擦除,并使用其限定类型(无限定的变量用Object)替换。

2)类型擦除之后,可以避免类型膨胀的问题,如:每次使用一个泛型类都会给定其泛型参数,而且每次的泛型类型很大概率是不一样的,每次由于泛型参数不同使类发生改变,会导致类过大。

3) java编译器是通过先检查代码中泛型的类型,然后再进行类型擦除,在进行编译的。

4) 既然类型擦除了,如何保证我们只能使用泛型变量限定的类型呢?

//第一种情况  
ArrayList<String> arrayList1=new ArrayList(); 
arrayList1.add("1");//编译通过  
arrayList1.add(1);//编译错误  
String str1=arrayList1.get(0);//返回类型就是String  
//第二种情况
ArrayList arrayList2=new ArrayList<String>(); 
arrayList2.add("1");//编译通过  
arrayList2.add(1);//编译通过  
Object object=arrayList2.get(0);//返回类型就是Object 

new ArrayList<String>().add("11");//编译通过  
new ArrayList<String>().add(22);//编译错误  
String string=new ArrayList<String>().get(0);//返回类型就是String  

由上可知, new ArrayList()只是在内存中开辟一个存储空间,可以存储任何的类型对象。而真正涉及类型检查的是它的引用,因为我们是使用它引用arrayList1 来调用它的方法,比如说调用add()方法,所以arrayList1引用能完成泛型类型的检查。

5)泛型擦除了,但又如何获取泛型的实际类型?

//源代码
public class cachu {
    public static void main(String[] args){
        List<String> list = new ArrayList<>();
        list.add("abc");
        String str = list.get(0);
        System.out.println(list);
        System.out.println(str);
    }
}
//class文件
public class cachu {
    public cachu() {
    }
    public static void main(String[] args) {
        List<String> list = new ArrayList();
        list.add("abc");
        String str = (String)list.get(0);
        System.out.println(list);
        System.out.println(str);
    }
}

通过源代码和class文件可以知道, Java中的泛型只在程序源代码中存在, 在编译后的字节码文件中就已经替换为原来的原生类型, 并在相应的地方插入强制类型装换代码。

反射获取泛型信息

泛型擦除后,类,字段和方法的形参泛型信息是会保存到Signature中的(使用javap -verbose class文件 查看class文件的结构)。

使用 ParameterizedType ,即参数化类型

public class reflect_fanxing {
    public ArrayList<String> list;
}

try {
    reflect_fanxing fanxing = new reflect_fanxing();
    Class<? extends reflect_fanxing> aClass = fanxing.getClass();
    Field field = aClass.getDeclaredField("list");
    //获取list属性的类型
    Type type = field.getGenericType();
    if (type instanceof ParameterizedType){
        //强制转为ParameterizedType对象
   ParameterizedType parameterizedType = (ParameterizedType) type;
        //获取原始类型
        Type rawType = parameterizedType.getRawType();
        //获取list的类型的所有泛型信息
        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
        System.out.println("list的原始类型:"+rawType);
        System.out.println("list的泛型:"+ Arrays.toString(actualTypeArguments));
    }
} catch (NoSuchFieldException e) {
    e.printStackTrace();
}

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值