泛型的概念

原文链接:泛型的介绍 – 编程屋 目录 1 前言 2 泛型类 3 泛型擦除 4 泛型通配符 1 前言 大家平时在编程的过程中,可能都看过泛型。我目前对于泛型的了解也不是很深刻,所以这里先简单介绍下 。 泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参

原文链接:泛型的介绍 – 编程屋

目录

1 前言

2 泛型类

3 泛型擦除

4 泛型通配符


1 前言

大家平时在编程的过程中,可能都看过泛型。我目前对于泛型的了解也不是很深刻,所以这里先简单介绍下 。

泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
 

那么在介绍泛型之前:先讲解两个小例子,让大家更好的理解泛型。

例子1:

    public static void main(String[] args) {
        List list  = new ArrayList<>();

        list.add("llall");
        list.add(21254);

        for (int i = 0; i < list.size(); i++) {
            String o = (String)list.get(i);
            System.out.println("o:"+o);
        }

    }

大家肯定都知道,这样写运行时肯定会报错,因为lis集合中,一个放了String类型,一个放了数值类型。在循环中都用String类型强行转换时就会报转换异常。

但是要是将List集合刚开始声明时就加上类型,那么就会在编译阶段报错。如下图:

例子2:

泛型只在编译阶段有效:

    public static void main(String[] args) {
        List<String> strings = new ArrayList<>();
        List<Integer> integers = new ArrayList<>();

        Class aClass = strings.getClass();
        Class aClass1 = integers.getClass();

        System.out.println(aClass.equals(aClass1));


    }

 大家看如上代码:认为他会输出什么,我的第一感觉是会输出false,但是事实恰恰相反。它会输出true。

这说明:在编译之后程序会采取去泛型化的措施。也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果时,会将泛型相关信息擦除,并且在对象进入和离开的方法的边界处添加类型检查和类型转化都是方法。也就是说,泛型信息不会进入到运行时阶段。

总结:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。

2 泛型类

那么如何声明一个泛型类呢?

class 类名称 <泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{
  private 泛型标识 var; 
  .....

  }
}

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;
    }
}

但出于规范的目的,Java 还是建议我们用单个大写字母来代表类型参数。常见的如:

  1. T 代表一般的任何类。
  2. E 代表 Element 的意思,或者 Exception 异常的意思。
  3. K 代表 Key 的意思。
  4. V 代表 Value 的意思,通常与 K 一起配合使用。

注意:

1)泛型类上的T是任意标识,也可以用T、E、K、V等形式的参数表示泛型

public class Generic<V> {
    
}

2)在实例化泛型类时,必须指定T的具体类型

Generic<Integer> genericInteger = new Generic<Integer>(123456);

3)传入的实参类型需与泛型的类型参数类型相同

Generic<Integer> genericInteger = new Generic<Integer>(123456); //正确
Generic<Integer> genericInteger = new Generic<Integer>("123456");//错误

4)定义泛型类,并不一定需要传入实参

Generic generic = new Generic("111111");
Generic generic1 = new Generic(4444);

5)泛型的类型参数只能是类类型,不能是简单类型

 Generic<int> genericInteger = new Generic<int>(123456); //错误

6)不能对确切的泛型类型使用instanceof操作。如下面的操作是非法的,编译时会出错。

if(ex_num instanceof Generic<Number>){   
} 

3 泛型擦除

通俗的说:泛型信息只存在于代码编译阶段,在进入jvm之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除

讲泛型擦除,先说一个经典的例子:

    public static void main(String[] args) {
        List<String> l1 = new ArrayList<String>();
        List<Integer> l2 = new ArrayList<Integer>();

        System.out.println(l1.getClass() == l2.getClass()); //true
    }

正是由于泛型擦除,上述的结果才会输出true。

换句话说泛型类和虚拟机类在java虚拟机中没有什么区别,因为在进入虚拟机之前,泛型信息会被擦除掉。拿上面的例子来说:类型String和Integer怎么办呢?答案是泛型转译。

public class Generic<T> {

    private T object;

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

}

Generic是一个泛型类,查看它的运行状态可以通过反射。

    public static void main(String[] args) {
        Generic<String> hello = new Generic<>("hello");
        Class<? extends Generic> aClass = hello.getClass();
        System.out.println("Generic class is:"+aClass.getName());
    }

运行结果: 

 可以看到,class的类型依然是它本身并不是带泛型的形式。同理我们也能看看泛型T在jvm中是什么类型。

        Generic<String> hello = new Generic<>("hello");
        Class<? extends Generic> aClass = hello.getClass();
        Field[] declaredFields = aClass.getDeclaredFields();
        for (Field f : declaredFields) {
            System.out.println("Field name "+f.getName()+" type:"+f.getType().getName());
        }

运行结果:

Field name object type:java.lang.Object

可以看到,泛型类被类型擦除之后,相应的类型被替换成Object类型。那么所有的都是这样的吗?不一定。

我们更改下代码在试一下:

public class Generic<T extends String> {

    private T object;

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

    public static void main(String[] args) {
        Generic<String> hello = new Generic<>("hello");
        Class<? extends Generic> aClass = hello.getClass();
        Field[] declaredFields = aClass.getDeclaredFields();
        for (Field f : declaredFields) {
            System.out.println("Field name "+f.getName()+" type:"+f.getType().getName());
        }
    }
}

打印结果:

Field name object type:java.lang.String

所以:在泛型类被泛型擦除的时候,之前被擦除中的类型参数如果没有指定上限,如<T>会被转译成普通的Object类型,如果加了上限就会限如 <T extends String>则类型参数就被替换成类型上限。

利用泛型擦除可以做的一些事情:

当我们对于List集合已经声明泛型的时候,那么它就只能放入指定类型的元素。那么能不能借助泛型擦除将其它类型的元素放入其中呢?大家都知道在进入jvm中有泛型的类和没有泛型的类是一样的。那么我们就可以利用反射去完成它。

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(23);
//        list.add("fbsjc");
        Method method = list.getClass().getDeclaredMethod("add", Object.class);
        method.invoke(list,"845120");
        method.invoke(list,30.f);

        for (Object o : list) {
            System.out.println(o);
        }
    }

 输出结果:

4 泛型通配符

泛型通配符通常用?表示,代表任意的数据类型,不能创建对象使用,只能作为方法的参数使用

泛型通配符的上限限定与下限限定

上限限定:?extends E代表使用泛型只能是E类型的子类/本身

    public static void main(String[] args) {
        List<Integer> list1 = new ArrayList<>();
        List<Number> list2 = new ArrayList<>();
        List<String> list3 = new ArrayList<>();
        List<Object> list4 = new ArrayList<>();

        upperLimit(list1); //ok
        upperLimit(list2); //ok
        upperLimit(list3); //不ok
        upperLimit(list4); //不OK

    }



    // 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
    public static void upperLimit(List<? extends Number> obj) {
    }

因为String和Object并不是Number的类型或Number类型,泛型的上限限制了它的范围,所以会编译报错。

下限限定:?super E代表使用的泛型只能是E类型的父类/本身

    public static void main(String[] args) {
        List<Integer> list1 = new ArrayList<>();
        List<Number> list2 = new ArrayList<>();
        List<String> list3 = new ArrayList<>();
        List<Object> list4 = new ArrayList<>();

        lowerLimit(list1); //不ok
        lowerLimit(list2); //ok
        lowerLimit(list3); //不ok
        lowerLimit(list4); //OK

    }
    


    // 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
    public static void lowerLimit(List<? super Number> list) {
    }

因为泛型的下限的泛型只能是其本身或其父类,而Integer类型和String类型不满足,所以会编译报错。

可以通过上下限的限定,限定方法参数数据类型的范围。

以上只是部分内容,为了维护方便,本文已迁移到新地址:泛型的介绍 – 编程屋

知秋君
上一篇 2024-08-13 13:48
下一篇 2024-08-13 13:12

相关推荐