栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

java泛型详解

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

java泛型详解

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 list = new ArrayList<>();
        list.add(2);

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

泛型擦除

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

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

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

public class GenericsDemo02 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        List 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 stringTest = new Test<>();
        stringTest.setT("类的泛型");
        System.out.println(stringTest.getT());
    }
}

class Test{
    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 {
    public T method();
}

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

class GeneratorImpl implements Generator{

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

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

class GeneratorImpl2 implements Generator{

    @Override
    public String method() {
        return "null";
    }
}
泛型方法

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

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

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

假入有三个类A、B、C,B、C继承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,也可以是List,或者List等等。

因为你不知道集合是哪种类型,所以你只能够对集合进行读操作。并且你只能把读取到的元素当成 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存在了C,这明显违背了泛型的本意。

上界通配符(? extends)

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

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

public static void forEach2(List 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的意思是List集合 list,它可以持有 A 及其父类的实例。

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

public static void forEach3(List 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可以转换为A的原因是他知道集合里的元素的类型要么是A要么是A的子类,他们都可以转换为A。这个和这里的都可以转换为Object的道理是一样的。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1021490.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号