泛型的使用

一.为什么需要泛型

泛型最大的好处是实现类型安全,也就是说在编译期就可以检查出类型错误,而不需要等到运行时才发现。主要有以下几个原因:

1. 避免类型转换

没有泛型之前,我们在使用集合的时候往往要显式的进行类型转换,否则会出现ClassCastException。例如:

java 复制代码
List list = new ArrayList();

list.add("Hello");

String str = (String) list.get(0);

有了泛型之后,我们在声明集合时就指定了集合元素的类型,这样在使用时就避免了类型转换:

java 复制代码
List<String> list = new ArrayList<>();

list.add("Hello");

String str = list.get(0);

泛型能够减少不必要的类型转换(装箱拆箱),从而提高程序的性能

2. 编译时类型检查

有了泛型之后,由于在声明集合时已指定类型,所以如果试图添加其他类型的元素,编译器会报错。这样可以在编译期就发现错误,而不需要等到运行时再通过ClassCastException来发现。例如:

java 复制代码
List<String> list = new ArrayList<>();

list.add(1); // 编译错误

3. 消除警告

没有泛型之前,如果把元素添加到集合中,编译器会发出警告,因为编译器不知道你要添加的元素类型。有了泛型之后,由于在声明时已指定类型,就不会有这种警告了。

4. 允许库的设计者在实现时限制类型

通过使用泛型,实现集合类的设计者可以指定可以使用的类型。这样可以在设计时就避免类被误用。例如ArrayList<T>中T必须是引用类型,如果试图用基本类型会编译报错。

泛型的最大好处就是在编译期实现类型安全检查,这个好处远远大于泛型可能引入的复杂性。合理使用泛型可以在大幅度提高代码质量和健壮性的同时,减少运行时出现的ClassCastException。

二.泛型类和泛型接口

泛型类:

java 复制代码
public class NormalGeneric<K> {
    private K data;

    public NormalGeneric() {
    }

    public NormalGeneric(K data) {
        this.data = data;
    }

    public K getData() {
        return data;
    }

    public void setData(K data) {
        this.data = data;
    }

    public static void main(String[] args) {
        NormalGeneric<String> normalGeneric = new NormalGeneric<>();
        normalGeneric.setData("OK");
        //normalGeneric.setData(1);
        System.out.println(normalGeneric.getData());
        NormalGeneric normalGeneric1 = new NormalGeneric();
        normalGeneric1.setData(1);
        normalGeneric1.setData("dsf");
    }
}

泛型接口:

java 复制代码
public interface Genertor<T> {
    public T next();
}


public class ImplGenertor<T> implements Genertor<T> {
    @Override
    public T next() {
        return null;
    }
}

public class ImplGenertor2 implements Genertor<String> {
    @Override
    public String next() {
        return null;
    }
}

我们的泛型接口有两种实现类,第一种我并不给他设置一个实际的类型,这个实现类本身还是一个泛型类;第二种在实现接口的时候直接规定类型,如果规定了类型的花,实现类后面就不要需要尖括号了。

三.泛型方法

java 复制代码
public class GenericMethod {

    public <T> T genericMethod(T...a){
        return a[a.length/2];
    }

    //会报错,K无法识别
    public <T> T genericMethod(K...a){
        return a[a.length/2];
    }

    //正常,T和K都声明了
    public <T,K> T genericMethod(K...a){
        return a[a.length/2];
    }

    public void test(int x,int y){
        System.out.println(x+y);
    }

    public static void main(String[] args) {
        GenericMethod genericMethod = new GenericMethod();
        genericMethod.test(23,343);
        System.out.println(genericMethod.<String>genericMethod("mark","av","lance"));
        System.out.println(genericMethod.genericMethod(12,34));
    }
}

genericMethod就是一个泛型方法,泛型方法再普通类里面就可以声明,但是相较于普通的方法多了个<T>,如果没这个<T>的话,那就不是泛型方法。

特别需要注意的是在普通类里面使用泛型方法的时候,方法后面()里面的T或者K必须在<>里面先声明。

泛型方法的调用也很简单,可以直接调用,也可以在方法前增加<>标明具体的参数类型。

java 复制代码
public class GenericMethod3 {
    static class Fruit{
        @Override
        public String toString() {
            return "fruit";
        }
    }

    static class Apple extends Fruit{
        @Override
        public String toString() {
            return "apple";
        }
    }

    static class Person{
        @Override
        public String toString() {
            return "Person";
        }
    }

    static class GenerateTest<T>{
        //普通方法
        public void show_1(T t){
            System.out.println(t.toString());
        }

        //在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。
        // 可以类型与T相同,也可以不同。
        //由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,
        // 编译器也能够正确识别泛型方法中识别的泛型。
        public <E> void show_3(E t){
            System.out.println(t.toString());
        }

        //在泛型类中声明了一个泛型方法,使用泛型T,
        // 注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
        public <T> void show_2(T t){
            System.out.println(t.toString());
        }
    }

    public static void main(String[] args) {
        Apple apple = new Apple();
        Person person = new Person();

        GenerateTest<Fruit> generateTest = new GenerateTest<>();
        generateTest.show_1(apple);
        //会报错
        generateTest.show_1(person);

        generateTest.show_2(apple);
        generateTest.show_2(person);

        generateTest.show_3(apple);
        generateTest.show_3(person);
    }
}

再来看到这个实例,泛型类里面的泛型只影响普通方法,泛型类的泛型方法里面的泛型,以泛型方法里面的泛型为准;泛型类里面的泛型方法的泛型可以和泛型类里面的泛型一致,也可以不一致。

show_2方法和show_3方法可以传入apple,也可以传入person类型。

四.限定类型变量

我们都知道extends叫做派生,A extends B说明A继承自B类。那么为什么泛型需要用extends呢?

java 复制代码
public class ArrayAlg {

    //这个方法会报错,因为无法确定泛型T中存在compareTo方法
    public static <T> T min1(T a,T b){
        if(a.compareTo(b)>0) return a; else return b;
    }

    //通过extends 给T增加限制,T是接口Comparable的派生对象就行了,一定会有compareTo方法
    public static <T extends ArrayList&Comparable> T min(T a, T b){
        if(a.compareTo(b)>0) return a; else return b;
    }

    static class Test{}

    public static void main(String[] args) {
        //报错,因为Test满足extends的限制
        ArrayAlg.min(new Test(),new Test());


    }
}

如果我们需要泛型满足某一个条件的时候,泛型需要存在什么方法的时候,需要使用extends来对泛型做限制。注意extends后面只能存在一个对象,但是可以有多个接口,因为java是单继承多现实的。并且那个对象需要写在最前面,之间使用&关联。

五.泛型的约束性和局限性

java 复制代码
public class Restrict<T> {
    private T data;

    //不能实例化类型变量
    public Restrict() {
        this.data = new T();
    }

    public Restrict(T data) {
        this.data = data;
    }


    //静态域或者方法里不能引用类型变量
    private static T instance;
    //静态方法 本身是泛型方法就行
    private static <T> T getInstance(){}


    public static void main(String[] args) {
        
        //泛型不能传递基础类型,只能传递它们的包装类
        Restrict<double> restrict = new Restrict<>();
        Restrict<Double> restrict = new Restrict<>();

        //判定具体的泛型类型,不支持使用instanceof
        if(restrict instanceof  Restrict<Double>)
        if(restrict instanceof  Restrict<T>)

        //getClass方法获得的是原生类型,都是Restrict,与传入的参数无关
        Restrict<String> restrictString= new Restrict<>();
        System.out.println(restrict.getClass()==restrictString.getClass());
        System.out.println(restrict.getClass().getName());
        System.out.println(restrictString.getClass().getName());

        //可以定义数组但是不能初始化数组
        Restrict<Double>[] restrictArray;
        //报错
        Restrict<Double>[] restricts = new Restrict<Double>[10];

        //ArrayList<String>[] list1 = new ArrayList<String>[10];
        //ArrayList<String>[] list2 = new ArrayList[10];

    }


public class ExceptionRestrict {

    /*泛型类不能extends Exception/Throwable*/
    //private class Problem<T> extends Exception;

    /*不能捕获泛型类对象*/
//    public <T extends Throwable> void doWork(T x){
//        try{
//
//        }catch(T x){
//            //do sth;
//        }
//    }


    public <T extends Throwable> void doWorkSuccess(T x) throws T{
        try{

        }catch(Throwable e){
            throw x;
        }
    }
}

限制1:泛型不能实例化类型变量,不能直接new一个泛型。

限制2:静态域或者方法里面不能引用类型变量,泛型是在对象创建的时候才会知道具体的类型是什么。在虚拟机里面,创建一个对象最先执行的是static的代码,然后再执行类的构造函数,执行static的时候还不知道泛型的具体类型,编译器会直接报错。如果这个static方法是一个泛型方法则没问题,这个时候的泛型是调用这个方法的时候确定的。

限制3:泛型不能传递基础类型,只能传递它们的包装类。因为基础类型不是对象。

整型**:**byte,short,int,long

浮点型:float,double

字符型**:**char

布尔型**:**boolean

限制4:判定具体的泛型类型不支持使用instanceof

限制5:可以定义泛型数组,但是不能初始化数组

限制6:泛型类不能extends Exception/Throwable

限制7:不能捕获泛型类对象

六.泛型类型的继承规则

java 复制代码
public class Employee {
    private String firstName;
    private String secondName;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getSecondName() {
        return secondName;
    }

    public void setSecondName(String secondName) {
        this.secondName = secondName;
    }
}

public class Worker extends Employee {
}


public class Pair<T> {

    private T one;
    private T two;

    public T getOne() {
        return one;
    }

    public void setOne(T one) {
        this.one = one;
    }

    public T getTwo() {
        return two;
    }

    public void setTwo(T two) {
        this.two = two;
    }

    private static <T> void set(Pair<Employee> p){
    }

    public static void main(String[] args) {
        //Pair<Employee>和Pair<Worker>没有任何继承关系
        Pair<Employee> employeePair = new Pair<>();
        Pair<Worker> workerPair = new Pair<>();

        //Worker是Employee的子类,这样new出来继承关系成立
        Employee employee = new Worker();
        //会报错,证明继承关系不存在
        Pair<Employee> employeePair2 = new Pair<Worker>();


        Pair<Employee> pair = new ExtendPair<>();

        
        //正常
        set(employeePair);
        //报错
        set(workerPair);
    }

    /*泛型类可以继承或者扩展其他泛型类,比如List和ArrayList*/
    private static class ExtendPair<T> extends Pair<T>{

    }
}

1.Worker继承自类Employee,创建两个泛型类

Pair<Employee> employeePair = new Pair<>();

Pair<Worker> workerPair = new Pair<>();

这两个泛型对象没有任何继承关系。

2.泛型类可以继承或者扩展其他泛型类

3.调用静态泛型方法set,传入的是Pair<Employee>就正常,传入Pair<Worker>就报错。因为Worker是Employee的子类,但是Pair<Worker>却不是Pair<Employee>的子类。

七.通配符

存在一些类的继承关系如下:

存在一个泛型类GenericType

java 复制代码
public class GenericType<T> {
    private T data;

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

然后我们来看具体的使用:

java 复制代码
public class WildChar{

    public static void print(GenericType<Fruit> p){
        System.out.println(p.getData().getColor());
    }

    public static void use(){
       GenericType<Fruit> a = new GenericType<>();
        print(a);
       GenericType<Orange> b = new GenericType<>();
        //报错
        print(b);
    }


    public static void print2(GenericType<? extends Fruit> p){
        System.out.println(p.getData().getColor());
    }

    public static void use2(){
        GenericType<Fruit> a = new GenericType<>();
        print2(a);
        GenericType<Orange> b = new GenericType<>();
        print2(b);
        //报错
        print2(new GenericType<Food>());
        GenericType<? extends Fruit> c =  new GenericType<>();

        Apple apple =  new Apple();
        Fruit fruit = new Fruit();
        //报错
        c.setData(apple);
        //报错
        c.setData(fruit);
        Fruit x = c.getData();
    }

    public static void printSuper(GenericType<? super Apple> p){
        System.out.println(p.getData());
    }

    public static void useSuper(){
        GenericType<Fruit> fruitGenericType = new GenericType<>();
        GenericType<Apple> appleGenericType = new GenericType<>();
        GenericType<HongFuShi> hongFuShiGenericType = new GenericType<>();
        GenericType<Orange> orangeGenericType = new GenericType<>();
        printSuper(fruitGenericType);
        printSuper(appleGenericType);
//        printSuper(hongFuShiGenericType);
//        printSuper(orangeGenericType);


        //表示GenericType的类型参数的下界是Apple
        GenericType<? super Apple> x = new GenericType<>();
        x.setData(new Apple());
        x.setData(new HongFuShi());
        //x.setData(new Fruit());
        Object data = x.getData();

    }

}

在use方法中直接调用print(b)会报错,因为Orange是Fruit的子类但是GenericType<Orange> b并不是GenericType<Fruit>的子类,这时候怎么办呢?

我们看到use2方法里面的print使用了? extend Fruit,然后print(b)就正常不报错了。GenericType<? extends Fruit> 标识传入的泛型类里面的泛型需要时Fruit的子类,也包括Fruit。

可以看到我们对 GenericType<? extends Fruit> c = new GenericType<>()这个对象,调用setData方法塞入一个Apple和Fruit会报错。而通过getData方法拿出来的对象必须是Fruit。

因为c里面的泛型必须是Fruit的子类,子类是包含父类全部特征的,c里面所有的对象都可以看做是Fruit,所以get出来的对象就是Fruit。同理,我只知道c里面是Fruit的子类,但是不知道具体是哪个子类,所以set方法会报错。所以? extend A通常用来安全的访问数据。

可以看到useSuper方法,printSuper方法里面调用 printSuper(hongFuShiGenericType)和printSuper(orangeGenericType)都会报错。这是因为GenericType<? super Apple>规定了这个泛型类里面的泛型必须是Apple的父类,也包括Fruit。

定义了一个x对象GenericType<? super Apple> x = new GenericType<>(),使用set方法传入Apple的子类不会报错,get出来的是Object对象。

因为x里面的泛型必须是Apple的父类,具体是哪个父类我不清楚,但是不管是哪个父类,对于Apple的子类来说,都有父类的所有特征,所以我可以随便set进去Apple的子类。同理,我不知道具体是哪个父类,但是所有类的终极父类就是Object,那么就可以使用Object去承接get出的对象。

八.虚拟机是怎么实现泛型的

Java泛型的实现是靠类型擦除技术实现的,类型擦除是在编译期完成的,也就是在编译期,编译器会将泛型的类型参数都擦除成它指定的原始限定类型,如果没有指定的原始限定类型则擦除为Object类型,之后在获取的时候再强制类型转换为对应的类型,因此生成的Java字节码中是不包含泛型中的类型信息的,即运行期间并没有泛型的任何信息。

在使用泛型的时候,虽然传入了不同的泛型实参,但并没有真正意义上生成不同的类型,传入不同泛型实参的泛型类在内存中只有一个,即还是原来的最基本的类型;泛型只在编译阶段有效,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦除,并且在对象进入和离开方法的边界处添加类型检查和类型转化的方法,也就是说,成功编译后的class文件是不包含任何泛型信息的。总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同类型。

相关推荐
_.Switch16 分钟前
高级Python自动化运维:容器安全与网络策略的深度解析
运维·网络·python·安全·自动化·devops
2401_8504108316 分钟前
文件系统和日志管理
linux·运维·服务器
JokerSZ.20 分钟前
【基于LSM的ELF文件安全模块设计】参考
运维·网络·安全
老猿讲编程21 分钟前
一个例子来说明Ada语言的实时性支持
开发语言·ada
芯盾时代1 小时前
数字身份发展趋势前瞻:身份韧性与安全
运维·安全·网络安全·密码学·信息与通信
Chrikk1 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*1 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue1 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man1 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
测开小菜鸟1 小时前
使用python向钉钉群聊发送消息
java·python·钉钉