泛型总结(擦除机制+泛型上界+通配符的上下界)


泛型


一、 什么是泛型

1.能用于多种类型,把类型当做参数

JDK1.5后引入的

1.1 作用

在编译的时候

  • 存储数据的时候,进行自动的类型检查
  • 获取元素的时候,帮助进行类型转换
  • 泛型是编译时期的一种机制,在运行的时候没有泛型的概念

1.2 语法
class MyArray <T>{//当前类是一个泛型类

    //public Object[]  obj = new Object[5];//实现一个类,类中包含一个数组成员,\
    public T[] obj = (T[]) new Object[5];//

    // 使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个
    //下标的值?
    public T getPos(int pos) {
        return this.obj[pos];//回数组中某个
        //下标的值?
    }
    public void setObj(int pos,T val){
        this.obj[pos] = val;
    }
}

public class Test2 {
    public static void main(String[] args) {
        
        MyArray<Integer> myArray = new MyArray<>();//后面的<>可以省略
        //实例化对象的同时,指定当前的泛型类 的 指定参数类型为Integer
        //把类型进行了传递,只能存指定的数据类型
        //MyArray<Integer> myArray = new MyArray<Integer>();
        //指定的参数类型,必须是引用类型
        myArray.setObj(0,3);
        myArray.setObj(1,4);//这时就不能传String类型
        myArray.setObj(2,5);
        Object pos = myArray.getPos(1);
     //  double d = myArray.getPos(2);//不知道是什么类型的,取出的时候很麻烦
        //什么数据都能存储
        //传进去后,都向上转型常量object,获取数据的时候要强转
        //需要指定类型,才能不进行强转
        System.out.println(pos);
        System.out.println("-------------");
        MyArray<String> myArray1 = new MyArray<>();
        myArray1.setObj(1,"hello");
        //获取数据的时候不用强转,里面都是指定的数据类型,直接拿指定的类型接收
        myArray1.setObj(0,"Hello");
        String pos1 = myArray1.getPos(0);
        String pos2 = myArray1.getPos(1);
        //在编译的时候
        //存储数据的时候,进行自动的类型检查
        //获取元素的时候,帮助进行类型转换

    }
}
  • 类名后的 代表占位符,表示当前类是一个泛型类
  • 前面<>指定类型,后面的<>可以省略
  • 把类型进行了传递,只能存指定的数据类型
  • 指定的参数类型,必须是引用类型
  • 不能实例化一个泛型类型的数组
  • 裸类型:不加<>,兼容老版本的机制

E 表示 Element K 表示 Key V 表示 Value N 表示 Number


二、擦除机制

1. 为什么采用擦除机制实现泛型?

为什么Sun公司选择采用擦除机制实现泛型?

向后兼容性
  • 这种设计是为向后兼容性提供源代码和目标代码两方面的支持,希望现有的代码和类文件在新版本的Java中继续使用,而不发生冲突,在不破坏语言的同时,更改语言
移植兼容性
  • 移植兼容性要求API的泛型版本要和老的版本兼容,就是说用户能继续编译(源文件)和运行(编译后的程序)。这种要求在很大程度上限制了Java泛型设计的空间

2. 为什么不能使用“newT()”?

是因为,并不是每个类型都有一个空的构造器

  • Java是静态类型语言,不能简单的调用某一类型的空构造器,因为这个类型自身并不静态地知道有这样的构造器存在。由于擦除机制,所以没有办法来生成这种代码。

3. 创建类型T的数组

3.1 不安全的写法
T[] a = (T[]) new Object[N];

不安全,但可以在多数情况下运行,在编译时会警告
因为Object数组不是具体T类型的数组

如果方法返回T[],并且指定的类型是,将返回值存入String[]类型的变量中,在运行时将得到ClssCastException错误。

		 public T[] copyArr(T[] obj){
      		  return obj;
  	 	 }
        
        public static void main(String[] args) {
        Student<String> student = new Student<>();
        student.setObj(0,"小明");
        student.setObj(1,"小王");
        student.setObj(2,"小红");

        String[] copyArr = student.copyArr(student.obj);
        System.out.println(Arrays.toString(copyArr));
    }
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
	at learn.Test3.main(Test3.java:35)

返回的Object数组里面,可能存放的是任何的数据类型,可能是String,可能是Person,可能是Integer.运行的时候,直接转给String类型的数组,编译器认为是不安全的。

3.2 官方的写法
public Object[] obj =  new Object[5];//正确写法

  return (E) elementData[index];

在Java源码中,使用的是Object类型的数组,在方法中,返回时进行泛型的转换

3. 3 正确的写法

通过反射创建,实现类型的数组

/**
     * 通过反射创建,实现类型的数组
     * @param clazz
     * @param capacity
     */
    public Student(Class<T>clazz, int capacity) {
        this.obj = (T[])Array.newInstance(clazz,capacity);
    }
    Student<String> student = new Student<>(String.class,10);

4. 反编译后,对比方法的参数

在这里插入图片描述

  • 在运行的时候,没有泛型的概念
  • 泛型只是编译时期的一种机制
  • 编译完成后,会将泛类型擦除成了Object类型,叫做擦除机制
  • Java的泛型机制是在编译级别实现的。
  • 编译器生成的字节码在运行期间并不包含泛型的类型信息。

三、泛型的上界

  • 在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。
  • 泛型是没有下界的

public class MyArray<E extends Number>

E是number的子类 或者 E是Number本身

class Alg<E extends Comparable<E>> {}

代表将来指定的数据类型,一定实现了这个接口

四、泛型方法

  • 静态的泛型方法,要在static后面声明泛型的参数

  •     public static void main(String[] args) {
            List<Integer>list = new ArrayList<>();
            Integer [] arr = {1,2,3,4,5};
           Test2.<Integer>swap(arr,0,2);
            for (int x:arr) {
                System.out.println(x);
            }
        }
        public static<E> void swap(E[]array, int i, int j){
            E t = array[i];
            array[i] = array[j];
            array[j] = t;
        }
    }
    

五、通配符

  • 通配符: “ ?”
class Message<T> {//泛型类
	private T message ;
	public T getMessage() {
	return message;
	}
	public void setMessage(T message) {
	this.message = message;
	}
}
    public static void main(String[] args) {
        Message<String> message = new Message<>() ;
        message.setMessage("Hello world");
        fun(message);//Hello world

        Message<Integer> message2 = new Message<>() ;
        message2.setMessage(888);
       // fun(message2);//fun方法,只能接受<String>类型的对象
        fun(message2);

    }
/*    public static void fun(Message<String> temp){
        System.out.println(temp.getMessage());
    }*/
    public static void fun(Message<?> temp){
        //用通配符来规范传进的参数
        System.out.println(temp.getMessage());
    }

  • 通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改

通配符的上界和下界

? extends 类:设置通配符上限

  • 实例化的只能是参数类型本身或者它的子类
  • 不能进行写入数据(无法确定是哪个子类),只能进行读取数据(向上转型)

? super 类:设置通配符下限

  • 实例化的只能是参数类型本身、或者它的父类

  • 不能进行读取数据(无法确定是哪个父类),只能写入数据(传进去子类对象)

点击移步博客主页,欢迎光临~

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值