什么是泛型

一、泛型

什么是泛型

泛型是Java SE 1.5的新特性,它是一种不确定的数据类型,这种不确定的数据类型需要我们在使用这个类的时候它才能够确定出来,早期的Object类型可以接收任意的对象类型,但是在实际的使用过程当中,会出现类型转换的问题,会报出一个异常,使用泛型就可以避免这种问题,因为泛型可以使编译器在编译期间对类型进行检查以次来提高类型安全,减少运行时由于对象类型不匹配引发的异常。

  • 如果不确定使用什么类型的数据就使用泛型;

  • 从概念上讲就是一个预定义数据类型(占位符)

泛型的好处:

  • 代码更加简洁【不用强制转换,在编写集合的时候,就限定了类型】

  • 程序更加健壮【只要编译时期没有警告,那么运行时期就不会出现ClassCastException异常】

非泛型集合:

非泛型集合,存储元素用Object类型,add接收的类型可以是基本数据类型(装箱),引用数据类型,布尔类型

public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        arrayList.add("zzz");
        arrayList.add(111);
        for (int i = 0; i < arrayList.size(); i++) {
            String o = (String) arrayList.get(i);
            System.out.println(o);
        }
    }
}

在使用该集合的过程中,需要明确知道存储每个元素的数据类型,否则引发ClassCastException异常

ClassCastException异常:是JVM在检测到两个类型转换不兼容时引发的运行时异常

泛型集合:

默认为Object类型,接受的类型可以是引用数据类型,如果指定泛型类型为String类型,存储的类型只能是String类型,可以存储多个元素

泛型必须是包装类,只能代表引用数据类型,在程序中,有些数据会返回空值,用基本数据类型int会发生异常,因为int是没有null值可言的,但是基本数据类型对应的Integer包装类型不会,因为对象可以为Null。

public static void main(String[] args) {
        ArrayList<Object> list = new ArrayList<>();
        list.add(123);
        list.add("asd");
        list.add(true);
        for (int i = 0; i < list.size(); i++) {
            Object s =list.get(i);
            System.out.println(s);
        }
    }
}
public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("aa");
        for (int i = 0; i < list.size(); i++) {
            String s = list.get(i);
            System.out.println(s);
        }
    }
}
public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(123);
        for (int i = 0; i < list.size(); i++) {
            Integer s = list.get(i);
            System.out.println(s);
        }
    }
}

二、泛型类

泛型标识:

泛型标识:当我们定义一个数据的时候,不知道到底是什么数据类型,那我们就用一个特殊的标识代替,本质就是一个占位符,当我们使用的时候,再用确定的数据类型替换我们的标识。

T:通用泛型类型,通常作为第一个泛型类型 E:集合元素 泛型类型,主要用于定义集合泛型类型 List<E> K:映射-键 泛型类型,主要用于定义映射泛型类型 V:映射-值 泛型类型,主要用于定义映射泛型类型

  • 泛型类在创建对象的时候,如果没有指定类型,将按照Object类型来操作

//下面的T仅仅表示的是一种参数类型,这个参数类型是一个变量,可以指代任意一种引用数据类型。T可以换成 A-Z 之间的任何一个字母都可以,并不会影响程序的正常运行,但是如果换成其他的字母代替  ,在可读性上可能会弱一些
public class Generic<T> {
    private T key;

    public Generic(T key) {
        this.key = key;
    }

    public T getKey() {
        return key;
    }

    public void setKey(T key) {
        this.key = key;
    }
    
    @Override
    public String toString() {
        return "Generic{" +
                "key=" + key +
                '}';
    }
}
public class Text {
    public static void main(String[] args) {
        Generic<Integer> generic = new Generic<Integer>(100);
        Integer key = generic.getKey();
        System.out.println(key);
    }
}

public class Text {
    public static void main(String[] args) {
        Generic<String> generic = new Generic<String>("aaa");
        String key = generic.getKey();
        System.out.println(key);
    }
}

从泛型类派生子类:

子类:也叫派生类

父类: 超类 基类

  • 如果子类也是泛型类,子类和父类的泛型类型要一致,如果子类泛型标识和父类不一样,就无法得到具体的数据类型,会报红。

public class Parent<T> {
    private T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

public class ChildFirst<T> extends Parent<T>{
        @Override
        public T getValue() {
            return super.getValue();
        }
}

public class Text {
    public static void main(String[] args) {
        ChildFirst<String> first = new ChildFirst<>();
        first.setValue("zxc");
        String s = first.getValue();
        System.out.println(s);
    }
}

如果子类不是泛型类,父类要明确泛型的数据类型

public class Parent<T> {
    private T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

public class ChildFirst extends Parent<String>{
    @Override
    public String getValue() {
        return super.getValue();
    }
}

public class Text {
    public static void main(String[] args) {
        ChildFirst first = new ChildFirst();
        first.setValue("aa");
        String value = first.getValue();
        System.out.println(value);
    }
}

如果父类是泛型类,子类不是泛型类,继承父类后,父类没有声明,默认是Object。

public class Parent<T> {
    private T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

public class ChildFirst extends Parent{
    @Override
    public Object getValue() {
        return super.getValue();
    }
}

public class Text {
    public static void main(String[] args) {
        ChildFirst first = new ChildFirst();
        first.setValue(11);
        Object o = first.getValue();
        System.out.println(o);
    }
}

子类的泛型标识可以添加多个,但必须有一个和父类的泛型标识一样

public class ChildFirst<T, E, V> extends Parent<T> {
    @Override
    public T getValue() {
        return super.getValue();
    }	
}

子类的泛型标识可以添加多个,创建对象的时候必须指定扩展的类型

ChildFirst<String,Integer,Boolean> childFirst = new ChildFirst<>();

总结:

  • 泛型类是定义的时候用的,但是正真使用的时候,我们必须确定的类型。

  • 定义了泛型类,使用的时候没有指定,那么就是Object

  • 子父关系中,定义的时候,子类的泛型类必须要和父类的泛型类一致,否则报错。

  • 如果子类不是泛型类,父类要明确泛型的数据类型

  • 如果父类是泛型类,子类不是泛型类,继承父类后,父类没有声明,默认是Object。

  • 子类的泛型标识可以添加多个,但必须有一个和父类的泛型标识一样。

三、泛型接口:

  • 泛型接口的定义语法:

声明泛型接口和声明普通接口语法相似,在接口名称后面多了泛型标识,可以拿泛型标识作为方法的返回值类型或参数类型

public interface Generator<T> {
    T getkey();
}
  • 泛型接口的使用:

    1、如果实现类不是泛型类,接口要明确数据类型

    实现接口类指定泛型接口类型为String类型,那么接口类里的泛型标识T就代表的String类型,返回值也是String类型,

    public class Apple implements Generator<String> {
        @Override
        public String getkey() {
            return "hello";
        }
    }
    
    public class Text {
        public static void main(String[] args) {
            Apple apple = new Apple();
            String s = apple.getkey();
            System.out.println(s);
        }
    }
    

    2、如果实现类也是泛型类,实现类和接口的泛型类型要一致,否则无法接收具体的数据类型

public class Apple<T,E> implements Generator<T>{
    private T key;
    private E value;

    public Apple(T key, E value) {
        this.key = key;
        this.value = value;
    }

    @Override
    public T getkey() {
        return key;
    }

    public E getValue() {
        return value;
    }
}

public class Text {
    public static void main(String[] args) {
        Apple<String,Integer> apple = new Apple<>("age",20);
        String s = apple.getkey();
        Integer v = apple.getValue();
        System.out.println(s+"="+v);
    }
}

四、泛型方法

  • 泛型方法和泛型类的区别:

    泛型类:是在实例化类的时候指明泛型的具体类型

    泛型方法:是在调用方法的时候指明泛型的具体类型

定义语法:

1、public与返回值类型中间的<T>非常重要,可以理解为声明此方法为泛型方法。

2、只有在泛型列表中声明了泛型标识的方法才是泛型方法

3、<T>表明该方法将使用泛型类型T

4、与泛型类的定义一样,此处T可以写为任意标识

泛型方法:就是将方法参数类型中的泛型,提前在前面声明,必须用<>包起来。

修饰符 <泛型标识> 返回值类型 方法名(形参){

方法体

}

public class Produtc<T> {
    //创建一个新的随机数生成器
    Random random=new Random();
    //参数里的泛型标识必须和泛型列表里的泛型标识一样
    public <T> T getProdutc(ArrayList<T> list){
        //随机数最大不能超过数组长度
        return list.get(random.nextInt(list.size()));
    }
}
public class Text {
    public static void main(String[] args) {
        //把Produtc类定义成Integer类型的
        Produtc<Integer> produtc = new Produtc<>();
        //把参数里的泛型标识T定义成Integer类型
        ArrayList<Integer> list = new ArrayList<>();
        //添加元素
        list.add(11);
        list.add(111);
        list.add(1111);
        //把添加的元素传递进去并返回Integer类型
        Integer integer = produtc.getProdutc(list);
        //打印随机数和返回值类型
        System.out.println(integer+"\t"+integer.getClass().getSimpleName());
    }
}

静态泛型方法:

public class Produtc<T> {
	public static <T, K, E> void aVoid(T t, K k, E e) {
    	
        System.out.println(t + "\t" + t.getClass().getSimpleName());
        System.out.println(k + "\t\t" + k.getClass().getSimpleName());
        System.out.println(e + "\t" + e.getClass().getSimpleName());
    }
}
public static void main(String[] args) {
        Produtc.aVoid(1000, "zzz", true);
    }

泛型可变参数:

可变参数的定义就是在形参泛型标识后面加"...",...背后的本质就是一个T[] 数组。

public class Produtc {
	public static <T> void aVoid(T... t){
    for (int i = 0; i < t.length; i++) {
        System.out.println(t[i]);
    	}
    }
}
public class Text {
    public static void main(String[] args) {
        Produtc.aVoid(1,2,5,"aaa",true);
    }
}

五、泛型通配符

泛型通配符一般是使用""?"代替具体的类型实参(此处是类型实参,而不是类型形参)

public class Box<E> {
    //声明成员变量
    private E first;
    
    public E getFirst() {
        return first;
    }

    public void setFirst(E first) {
        this.first = first;
    }
}
public class Text {
    public static void show(Box<Integer>box){
        Integer first = box.getFirst();
        System.out.println(first);
    }

    public static void main(String[] args) {
        //调用Box类
        Box<Integer> box2 = new Box<>();
        //给box2赋值
        box2.setFirst(111);
        show(box2);
        
        
        
        Box<String> box1 = new Box<>();
        box1.setFirst("aaa");
        show(box1);(报错,因为把Box泛型类里的泛型标识E定义成Integer类型,所以不能用String类型)       
    }
}

不知道使用什么类型来接收的时候,此时可以使用?, ?表示未知通配符。此时只能接受数据,不能往该集合中存储数据

泛型通配符"?"就是实参,代表可以传递任意类型

不能使用泛型标识A-Z,因为泛型标识代表形参,这里需要实参

public class Text {

    public static void show(Box<?>box){
        Object first = box.getFirst();
        System.out.println(first);
    }


    public static void main(String[] args) {
        Box<String> box1 = new Box<>();
        box1.setFirst("aaa");
        show(box1);


        Box<Integer> box2 = new Box<>();
        box2.setFirst(111);
        show(box2);
    }
}

泛型通配符上限:

  • 定义语法

类/接口<? extends 实参类型>

要求该泛型的类型,只能是实参类型,或实参类型的子类类型

? extends Fu:类型通配最大只能到Fu,只能传Fu或者Fu的子类

泛型通配符下限:

  • 定义语法

类/接口<? super 实参类型>

要求该泛型的类型,只能是实参类型,或实参类型的父类类型

? super Fu :类型通配最小只能到Fu,只能传Fu或者Fu的父类

public class Zi extends Fu{
}
public class Fu extends Ye{
}
public class Ye {
}
package com.xinzhi.t2;

public class Test1 {

    public static void show1(Box<? extends Fu>box){
        Fu first = box.getFirst();
        System.out.println(first);
    }

    public static void show2(Box<? super Fu>box){
        Ye first = (Ye) box.getFirst();
        System.out.println(first);
    }

    public static void main(String[] args) {
        Zi zi1 = new Zi();
        Ye ye1 = new Ye();
        Fu fu1 = new Fu();
        Box<Zi> zi = new Box<>(zi1);
        Box<Fu> fu = new Box<>(fu1);
        Box<Ye> ye = new Box<>(ye1);

        show1(zi);
        show2(ye);
        show1(fu);
        show2(fu);
    }
}

六、泛型擦除

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

通俗地讲,泛型类和普通类在 java 虚拟机内是没有什么特别的地方。

List<String> l1 = new ArrayList<String>();

List<Integer> l2 = new ArrayList<Integer>();

System.out.println(l1.getClass() == l2.getClass());

打印的结果: true 。

是因为 List<String>和 List<Integer>在 jvm 中的 Class 都是 List.class。

泛型信息被擦除了。

可能同学会问,那么类型 String 和 Integer 怎么办?答案是泛型转译。

无限制泛型擦除:

把泛型标识擦除,转换成Object类型

定义的时候是泛型标识E,在使用的时候给泛型标识定为String类型,编译结束后,进行泛型擦除,生成了class字节码文件,通过反射,此时成员变量key的数据类型就成了Object类型。

就如上图一样:虽然你在编译阶段给定了T泛型,但是当你的程序运行的时候,jvm就会将T用Object代替,jvm泛型编译的结果代码如下。

public class Erasure<E> {
    //定义成员变量
    private E key;
    private Integer a;
    private String b;

    public E getKey() {
        return key;
    }

    public void setKey(E key) {
        this.key = key;
    }

    public Integer getA() {
        return a;
    }

    public void setA(Integer a) {
        this.a = a;
    }

    public String getB() {
        return b;
    }

    public void setB(String b) {
        this.b = b;
    }
}
public class Text {
    public static void main(String[] args) {
        //创建类的对象
        Erasure<String> erasure = new Erasure<>();
        //利用反射获取Erasure类的字节码文件
        Class<? extends Erasure> aClass = erasure.getClass();
        //通过反射获取所有成员变量
        Field[] declaredFields = aClass.getDeclaredFields();
        //对成员变量进行遍历
        for (Field declaredField : declaredFields) {
            //打印成员变量的名称和类型
            System.out.println(declaredField.getName()+":"+declaredField.getType().getSimpleName());
        }
    }
}
key:Object
a:Integer
b:String

有限制泛型擦除:

指定了上限,上限是Number,泛型标识T在做泛型擦除的时候转换成了上限类型

定义的时候是泛型标识E,在使用的时候给泛型标识定为Integer类型,编译结束后,进行泛型擦除,生成了class字节码文件,通过反射,此时成员变量key的数据类型就成了Number类型

public class Erasure<E extends Number> {
    private E key;
    private Integer a;
    private String  b;

    public E getKey() {
        return key;
    }

    public void setKey(E key) {
        this.key = key;
    }

    public Integer getA() {
        return a;
    }

    public void setA(Integer a) {
        this.a = a;
    }

    public String getB() {
        return b;
    }

    public void setB(String b) {
        this.b = b;
    }
}
public class Text {
    public static void main(String[] args) {
        //创建类的对象
        Erasure<Integer> erasure = new Erasure<>();
        //利用反射获取Erasure类的字节码文件
        Class<? extends Erasure> aClass = erasure.getClass();
        //通过反射获取所有成员变量
        Field[] declaredFields = aClass.getDeclaredFields();
        //对成员变量进行遍历
        for (Field declaredField : declaredFields) {
            //打印成员变量的名称和类型
            System.out.println(declaredField.getName() + ":" + declaredField.getType().getSimpleName());
        }
    }
}
key:Number
a:Integer
b:String

擦除方法中类型定义的参数:

在泛型列表中指定上限是Number,进行泛型擦除以后转换成了上限类型

如果不是上限类型,是Object类型,进行泛型擦除以后转换成Object类型

public class Erasure<E extends Number> {
    private E key;
    
    public E getKey() {
        return key;
    }

    public void setKey(E key) {
        this.key = key;
    }
}
    
    //返回值类型是E
	public <E extends List> E show(E e){
        return e;
	}
}
 	public static void main(String[] args) {
  	//创建类的对象
    Erasure<Integer> erasure = new Erasure<>();       
    //利用反射获取Erasure类的字节码文件
    Class<? extends Erasure> aClass = erasure.getClass();    
	//通过反射获取所有的方法
	Method[] declaredMethods = aClass.getDeclaredMethods();
	//对方法进行遍历
	for (Method declaredMethod : declaredMethods) {
    //打印方法名和方法的返回值类型
    System.out.println(declaredMethod.getName()+":"+declaredMethod.getReturnType().getSimpleName());

}
//因为E是类中的泛型标识,最大上限是Number,所以E转换成了Number
getKey:Number
//show方法进行类型擦除以后,类型变成上限类型
show:List
//setKey的返回值就是void    
setKey:void

List的三个子类:

ArrayList: 底层数据结构是数组,查询快,增删慢。 线程不安全,效率高。

Vector: 底层数据结构是数组,查询快,增删慢。 线程安全,效率低。

LinkedList: 底层数据结构是链表,查询慢,增删快。 线程不安全,效率高。

桥接方法:

在接口后面定义了泛型标识,实现接口类重写接口方法,把泛型标识T转换成了Integer。

进行泛型擦除以后,接口里的T将转换成Object类型,实现类会保持原有的Integer类型,另外还要生成一个桥接方法,桥接方法是Object类型,因为要保证接口编译完以后接口返回值类型是Object类型,实现接口类要实现接口时,必须要对接口里的方法进行重写,这时桥接方法就是保证接口和类的实现关系

//泛型接口
public interface Info<T> {
    T info(T t);
}
//实现接口类
public class InfoImpl implements Info<Integer>{
    @Override
    public Integer info(Integer integer) {
        return integer;
    }
}
public class Text {
    public static void main(String[] args) {
        //InfoImpl的字节码文件
        Class<InfoImpl> infoClass = InfoImpl.class;
        //通过字节码文件获取所有方法
        Method[] declaredMethods = infoClass.getDeclaredMethods();
        //遍历成员方法
        for (Method declaredMethod : declaredMethods) {
            //打印方法名和返回值类型
            System.out.println(declaredMethod.getName()+":"+declaredMethod.getReturnType().getSimpleName());
        }
    }
}
//保持实现接口类中的Info方法,所以返回值就是Integer类型
info:Integer
//编译完以后,字节码文件多了一个Info方法,返回值是Object类型,因为接口进行类型擦除以后,info方法就是一个Object,实现类为了保持对接口实现的规范和约束,必然有一个方法返回值类型是Object类型,达到对接口方法的重写
info:Object

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值