java泛型详解

本文详细介绍了Java泛型的概念,包括类型安全、泛型擦除以及通配符的使用。通过示例代码展示了泛型如何避免运行时类型错误,解释了编译期类型擦除的现象,并探讨了泛型通配符的上界和下界的用法,帮助读者深入理解Java泛型的运用。
摘要由CSDN通过智能技术生成

java泛型

泛型

Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

public class GenericsDemo00 {
    public static void main(String[] args) {
        List arrayList = new ArrayList();
        arrayList.add("String类型");
        arrayList.add(2);
        arrayList.add(5);
        arrayList.add(3);
        Integer count = 0;
        for (Object o : arrayList) {
            if (o instanceof String){
                System.out.println((String) o);
            }
            if (o instanceof Integer){
                count += (Integer) o;
            }
        }
        System.out.println(count);
    }
}

没有泛型之前集合中可能会装有多个类型的元素,需要进行类型的检查和类型的强转,一旦犯错就是一个运行时崩溃

List<String> list = new ArrayList<>();
        list.add(2);

有了泛型之后,编译器在编译时期即可完成 类型检查 工作,并提出错误(其实IDE在代码编辑过程中已经报红了);

泛型擦除

泛型擦除:Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除。Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。
例如:List<String>List<Integer> 在编译后都变成 List

public class GenericsDemo01 {
    public static void main(String[] args) {
        ArrayList<Integer> list1 = new ArrayList<>();
        ArrayList<String> list2 = new ArrayList<>();
        list1.add(5);
        list2.add("证明泛型擦除");
        System.out.println(list1.getClass()==list2.getClass());//true
    }
}

还可用反射向List<Strig>中添加其他类型数据来更进一步证实泛型擦除

public class GenericsDemo02 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        List<String> list = new ArrayList<>();
        Class c1 = list.getClass();
        list.add("证明泛型除");
//        list.add(5);//这一步ide会爆红,但是可以通过反射的方式加入到list
        Method method = c1.getMethod("add", Object.class);
        method.invoke(list,5);
        System.out.println(list);
    }
}

泛型通配符

常用的 T,E,K,V,?

本质上这些个都是通配符,没啥区别,只不过是编码时的一种约定俗成的东西。比如上述代码中的 T ,我们可以换成 A-Z 之间的任何一个 字母都可以,并不会影响程序的正常运行,但是如果换成其他的字母代替 T ,在可读性上可能会弱一些。通常情况下,T,E,K,V,? 是这样约定的:

  • ? 表示不确定的 java 类型
  • T (type) 表示具体的一个java类型
  • K V (key value) 分别代表java键值中的Key Value
  • E (element) 代表Element

泛型的使用方式

泛型类

泛型一般有三种使用方式:泛型类、泛型接口、泛型方法。

类的泛型就是把泛型定义在类上,用户在使用该类时才确定下来类的类型;

public class GenericsTest03 {
    public static void main(String[] args) {
        Test<String> stringTest = new Test<>();
        stringTest.setT("类的泛型");
        System.out.println(stringTest.getT());
    }
}

class Test<T>{
    private int id;

    private int age;

    private T t;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public T getT() {
        return t;
    }

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

    public Test(int id, int age, T t) {
        this.id = id;
        this.age = age;
        this.t = t;
    }

    public Test() {
    }

    public Test(T t) {
        this.t = t;
    }
}

泛型接口

public interface Generator<T> {
    public T method();
}

实现泛型接口,不指定类型:

class GeneratorImpl<T> implements Generator<T>{

    @Override
    public T method() {
        return null;
    }
}

实现泛型接口,指定类型:

class GeneratorImpl2<T> implements Generator<String>{

    @Override
    public String method() {
        return "null";
    }
}

泛型方法

判断泛型方法:判断一个方法是否是泛型方法关键看方法返回值前面有没有使用 <> 标记的类型,有就是,没有就不是。

public class GenericsDemo03 {
    public <T> String getString(T t){
        return t.toString();
    }

    public static  <T> void printString(T t){
        System.out.println(t);
    }

    public static void main(String[] args) {
        String string = new GenericsDemo03().getString(555);
        System.out.println(string);
        GenericsDemo03.printString("dasd");
    }
}

// 泛型类中的泛型方法
 class GenericsDemo04<T> {
    // 成员泛型方法
    public <E> String getString(E e) {
        return e.toString();
    }
    // 静态泛型方法
    public static <V> void printString(V v) {
        System.out.println(v.toString());
    }
}
  • 方法返回值前面的T,声明此方法持有一个类型T,也可以理解为声明此方法为泛型方法
  • 后面的T作用是指明泛型的类型

通配符 ?

假入有三个类ABC,BC继承A

public class A{
    public int id;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}
class B extends A{
    public int age;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
class C extends A {
    public String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

无限定通配符?

List<?> 的意思是这个集合是一个可以持有任意类型的集合,它可以是List<A>,也可以是List<B>,或者List<C>等等。

因为你不知道集合是哪种类型,所以你只能够对集合进行读操作。并且你只能把读取到的元素当成 Object 实例来对待。

public class GenericsDemo05 {
    //可以读取 并且是以object类型来读取
    public static void forEach(List<?> list){
        for (Object o : list) {
            System.out.println(o);
        }
        //不能添加 报错
//        list.add(new A());
//        list.add(new B());
//        list.add(new C());
    }
    
}

不知道集合是哪种类型,那集合所持有的元素类型也就不确定,所以不可以随便往集合里写入东西,不然会出现List<B>存在了C,这明显违背了泛型的本意。

上界通配符(? extends)

List<? extends A> 代表的是一个可以持有 A及其子类(如B和C)的实例的List集合。

当集合所持有的实例是A或者A的子类的时候,此时从集合里读出元素并把它强制转换为A是安全的(也可以转成object类型读取,它们是object的子类)。

public static void forEach2(List<? extends A> list){
        for (A a : list) {
            System.out.println(a);
        }
    	for(Object o :list){
            System.out.println(o);
        }
          //不能添加 报错
//        list.add(new A());
//        list.add(new B());
//        list.add(new C());
    }

forEach()方法仍然是不能给传入的list插入元素的(比如进行*list.add()*操作),因为你不知道list集合里面的元素是什么类型(A、B还是C等等)。比如你传进来的list是List,那插入C或者A就不行。

这里涉及到了一个多态里向上转型的题,这里有一篇向上、向下转型的文章推荐向上转型和向下转型,如果有不理解的可以去参考一下。

下界通配符(? super)

List<? super A>的意思是List集合 list,它可以持有 A 及其父类的实例。

当你知道集合里所持有的元素类型都是A及其父类的时候,此时往list集合里面插入A及其子类(B或C)是安全的。

public static void forEach3(List<? super A> list){
     //list读取只能时object类型读取  因为你不知到集合里的类型是什么,所以你不能够把他们读出来并转换为某一特定类型
        for (Object o : list) {
            System.out.println(o);
        }
        //可以添加
        list.add(new A());
        list.add(new B());
        list.add(new C());
    }

传入的List集合里的元素要么是A的实例,要么是A父类的实例,因为B和C都继承于A,如果A有一个父类,那么这个父类同时也是B和C的父类。

因为你不知到集合里的类型是什么,所以你不能够把他们读出来并转换为某一特定类型(除非你可以找出集合里元素的共同父类,比如这里的Object类)。

list<? extends A>可以转换为A的原因是他知道集合里的元素的类型要么是A要么是A的子类,他们都可以转换为A。这个和这里的都可以转换为Object的道理是一样的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值