01.java基础-java泛型

200 阅读3分钟

(记录一下一些知识概念,用于自己遗忘时候的查缺补漏)

1.泛型概念

泛型的出现是为了使用在集合类里面,能够针对不同的数据类型,执行相同的代码。并且在编译期间就知道数据类型,便于编译器更好的提供帮助。同时为了兼容以前的版本,泛型有类型擦除的问题。

2. 泛型的分类

一般常用的泛型有泛型类,泛型接口,泛型方法。

2.1 泛型类

在一个类声明时候,就申明泛型类型。

public class Test1<T> {
    private T t;

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }
}

2.2 泛型接口

在一个接口声明时候,就申明泛型类型。

public interface Test2<T> {
    T getData(T t);
}

2.3 泛型方法

方法申明时候,就声明泛型,并且在方法体中使用的方法,泛型方法,与是不是在泛型类中毫无关系。

public class Test3 {
    public <T> T getData(T t) {
        return t;
    }
}

3.通配符?和变换

通配符一般有三种,无界通配符,上界通配符和下界通配符。

3.1 无界通配符

采用 的形式,比如 List,无边界的通配符的主要作用就是让泛型能够接受未知类型的数据。 与T使用不同的是,T 是表示一个 确定的 类型,通常用于泛型类和泛型方法的定义, T 可以进行多重限定。使用& 符号设定多重边界。 ?是一个 不确定 的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法,使用有边界限定符可以定义泛型方法。 但是在下面这种情况下使用情况是一样的:

    // 使用T,U两种的类型
    public <T,U> void test1(List<T> dest, List<U> src){
    }
    // 通过? 来 表示两种不相关的数据类型
    public void test2(List<?> dest, List<?> src){
    }
    //通配符是 不确定的,所以这个方法不能保证两个 List 具有相同的元素类型
    public void  test3(List<? extends Number> dest, List<? extends Number> src){
    }
    // 通过 T 来 确保 泛型参数的一致性
    public <T extends Number> void test4(List<T> dest, List<T> src){
        }

3.2 上下界限的通配符

例如,表示上限<? extends Number>,接受Number的本身和子类,<? super Int>表示下界,接受Int 的本身和上层所有父类。

         List<? extends  Number> numbers = new ArrayList<Integer>();//协变
        List<? super Integer> numbers2 = new ArrayList<Number>();//逆变

        numbers.add(Integer.valueOf("5"));//error
        Object o = numbers2.get(3);//取数据时候只能取出Object 顶层父类的数据,因为不确定此时的数据是哪个层级的父类。

逆变和协变描述了具有继承关系的类型,通过类型构造器映射到另一范畴时所具有的继承关系。

保持原继承关系的为协变,继承关系反转的为逆变。 Java 中数组是支持协变的,所以数组并不支持泛型。

 	Number[] numbers = new Number[10];
        Integer[] integers = new Integer[10];
        numbers = integers;

应该怎么理解编程语言中的协变逆变?

4. 注解的获取

4.1 Java 获取泛型

虽然java中存在泛型的擦除机制,但是我们还是可以用一些策略在运行时获取到泛型的信息的。 具体是使用子类继承泛型类,这样在编译期就确定了子类的类型,在编译的字节码中就会保存singnature的字段,指向了泛型的类型。(AS 可以安装ASM插件)


import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public class Test1228 {
    static class Innerclass<T> {

    }

    public static void main(String[] args) {
        Innerclass<Integer> innerclass = new Innerclass<Integer>() {

        };
        Type superclass = innerclass.getClass().getGenericSuperclass();
        System.out.println(superclass);
        if (superclass instanceof ParameterizedType) {
            Type[] actualTypeArguments = ((ParameterizedType) superclass).getActualTypeArguments();
            for (Type type : actualTypeArguments) {
                System.out.println(type);
            }
        }
    }
}

打印结果:

com.company.Test1228$Innerclass<java.lang.Integer>
class java.lang.Integer

4.2 kotlin 获取泛型

由于Kotlin也有泛型擦除机制,我们无法获得一个参数的类型。然而,由于内联函数直接在字节码生成相应函数体现,我们又可以获得具体类型。我们可以用reifield修饰符实现。 例如kotlin 数组。

    val array1 = arrayOf<Int>(1, 2, 3, 4)
    val array2 = arrayOf<String>("1", "2", "3")
    println(array1.javaClass)
    println(array2.javaClass)

打印

class [Ljava.lang.Integer;
class [Ljava.lang.String;