美文网首页
Java泛型

Java泛型

作者: 0x70e8 | 来源:发表于2018-07-11 00:14 被阅读0次

[TOC]

什么是泛型,为什么使用

泛型是将类型作为参数,像方法一样在使用时能够传入对象的类型,这样即约束了对象的类型,也无需使用Object作为通用类型带来的强制转换和很差的可读性。

泛型的使用一方面能让人一眼看出来对象所属的类型,可读性大大提高,另外对应编译器,参数化类型的静态约束,使得对集合这些容器的元素类型检查在编译期就能判断,增加了代码的安全性。

如在泛型出现之前的ArrayList类,底层的存储元素的数组是一个Object数组,这意味着所有的java对象都可以放入此容器中,在取出时需要类型强制转换,如果在add的时候添加了不符合预期的类型,可能就会抛ClassCastException。且在运行时才会抛出。

如何使用泛型

定义简单泛型类

设计一个泛型栈,代码不完整,且非并发安全类。

public class Stack<E> {
    private E[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    @SuppressWarnings("unchecked")
    public Stack() {
        elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(E e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public E pop() {
        if (size == 0)
            throw new EmptyStackException();
        E result = elements[--size];
        elements[size] = null; // Eliminate obsolete reference
        return result;
    }
     
    public void ensureCapacity(){}
 
}

定义泛型方法

泛型方法可以定义在泛型类中,也可以定义在普通类中
上面泛型类已经有泛型方法

// 普通类定义泛型方法
class Example{
    // <T>必须
    public static <T> T get() {
        return null;
    }
}
class TestG<T> {
        // error ,静态方法不能使用类的泛型参数
    public static T get() {
        return null;
    }
}

类型变量的限定

限定类型的边界,使用extends和super关键字声明接受的类型的范围。


public <T extends Number> T find(T a){
    
}

上面使用extends表明类型T是Number类的子类。如果有多个限定,使用T extends A & B,AB为具体类或者接口;

泛型运行时擦除

使用泛型的类在编译后,泛型是被擦除的,有限定的泛型使用限定类替换,没有限定的使用Object类替换。所以泛型的主要作用就是静态类型的检查。

需要注意的是,在编译器擦除类型后,可能会使得重载方法产生冲突,因为T被擦除成Object,会生成Object返回值或者参数的方法,这可能与原本类中的实现方法重载了,如:

PS: 重载是方法名相同,入参类型不同,和返回值没有关系

class People<T> {
    private T t;

    public T get() {
        return null;
    }

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

class Man extends People<Integer> {

    @Override
    public Integer get() {
        return null;
    }

    @Override
    public void setT(Integer t) {

    }

}

擦除之后(反编译):

class People
{

    People()
    {
    }

    public Object get()
    {
        return null;
    }

    public void setT(Object obj)
    {
        t = obj;
    }

    private Object t;
}

class Man extends People
{

    Man()
    {
    }

    public Integer get()
    {
        return null;
    }

    public void setT(Integer integer)
    {
    }

    public volatile void setT(Object obj)
    {
        setT((Integer)obj);
    }

    public volatile Object get()
    {
        return get();
    }
}

方法签名由方法名和参数组成,返回值类型不参与,所以get方法重载冲突了,set方法在重载时有类型匹配,虽然不冲突。虚拟机使用了桥方法来解决重载冲突问题。

类型擦除有以下几个事实:

  • 虚拟机中没有泛型,只有普通类和方法;
  • 所有类型参数都用它们的限定类型替换,没有就是Object;
  • 桥方法被用来解决重载冲突;
  • 为保证类型安全,必要时会插入强制类型转换。

约束与局限

大部分都是因为类型擦除:

  • 不能使用基本类型作为类型参数
  • 运行时类型检查只适用于原始类型

instanceof判断对象类型,泛型类型在运行时是擦除的,所以不能使用这种方式来检查类型

  • 不能创建参数化类型的数组
    如:
People<Integer>[] p = new People<Integer>(); 

如果需要这种限定元素类型的集合,可以使用ArrayList<People<Integer>>

  • 方法的泛型可变参数警告
  • 不能实例化类型变量T t = new T();
  • 不能构造泛型数组 T[] ta = new T[1];
  • 泛型类的静态上下文中类型变量无效(不能在静态域和方法中使用类型参数)
  • 不能抛出或捕获泛型类的实例
  • 可以消除对受检查异常的检查

泛型类型的继承规则

无论S和T是什么关系,Pair<S>Pair<T>之间没有关系。

通配符类型

?代表泛型的通配符,Pair<? extends Employee>,表示任何泛型Pair类型,它的参数类型是Employee的子类。指明了类型的上边界。
Pair<? super Manager>表面任何泛型Pair类型,它的参数类型是Manager的超类,这指明了类型的下边界;

<T>与<?>的区别

  • <T>:泛型标识符,用于泛型定义(类、接口、方法等)时,可以想象成形参。 在使用时T是一个类型参数,需要传入实际的类型变量才能使用。T相当于一个方法的参数声明,也就是形参,需要传入实际值的。
  • <?>:通配符,用于泛型实例化时,可以想象成实参,这个不是一个类型变量,就是一个实在的类型,使用时不需要传入类型。可以看成就是一个固定的类型,只是这个类型不确定,并不是需要在使用的时候再传入类型参数。

<T>与<?>的区别很重要,是理解通配符限制的基础。

向上、向下转换类型

向上转型:子类型通过类型转换成为父类型(隐式的)。
向下转型:父类型通过类型转换成为子类型(显式的,有风险需谨慎)。

上边界限定通配符

利用 <? extends Employee> 形式的通配符,可以实现泛型的向上转型:
例如:

  • 定义了一个泛型类,里面含有两个泛型方法:
    class Gen<T> {
        public void add(T t) {

        }

        public T get() {
            return null;

        }
    }
  • 传类型参数的时候,传入一个通配类型的的类型参数
    @Test
    public void test3() {
        Gen<? extends Super> gen = new Gen<>();
    }

则此时对应的gen对象内的实例方法就是:

    public void add(? extends Super t) {

        }

        public ? extends Super get() {
            return null;

        }

看一下add方法,add(? extends Super t),意思是这个方法接受一个通配类型的参数,这个参数的类型是什么不知道,只知道是Super类的子类。 编译器不知道具体的类型,所以它拒绝传递任何类型的参数,所以add()都会编译时报错,只能add(null);

再看get方法,? extends Super get(),意思是这个方法返回的对象类型不确定,只知道是Super的子类,这样就可以使用Super obj = get();来接受返回值,因为不管是哪个子类,都可以安全地转换成父类。

所以上边界通配符适合用来接收,而不能传入数据。

下边界限定通配符

通配符的另一个方向是 “超类型的通配符“: ? super T,T 是类型参数的下界。使用这种形式的通配符,我们就可以 ”传递对象” 了。

        Gen<? super Sub> gen2 = new Gen<>();
        gen2.add(new Sub());
        Object o = gen2.get();//只能Object接收,因为Object是所有对象的父类

下边界类型通配符可以确定子类型,回顾向上转型与向下的概念:

在获取数据时 [ ? super Sub get(){ return null;} ]:只能知道返回值的类型的子类是Sub,不知道对象的具体类型,因为向下转型有风险,所以不能使用Sub来接收结果,需要接收只能使用安全的父类:Object。

在写入数据时 [ void add(? super Sub t){ } ],只知道参数类型的子类是Sub,所以写入Sub类或者Sub类的子类(它们能安全转为Sub类)是安全的。

所以下边界通配符适合用来写入数据,限制读取。

无边界通配符

无边界类型通配符(<?>) 等同于 上边界通配符<? extends Object>
因为可以确定父类型是Object,所以可以以Object去获取数据(向上转型)。但是不能写入数据。

总结

通配符 说明
上边界类型通配符(<? extends 父类型>) 因为可以确定父类型,所以可以以父类型去获取数据(向上转型)。但是不能写入数据。
下边界类型通配符(<? super 子类型>) 因为可以确定最小类型,所以可以以最小类型去写入数据(向上转型)。只能以Object类去获取数据,但意义不大。
无边界类型通配符(<?>) 等同于 上边界通配符<? extends Object>,所以可以以Object类去获取数据,但意义不大。

关于通配符,类型安全转换是编译器判断的依据。另外通配符表示是符合要求的任意类型,而非要传入类型,记忆时可以记成 “不确定的某个类”。


参考资料

[1] Java 泛型总结(三):通配符的使用

[2] Java泛型06 : 通配符:上边界、下边界与无界

[3] Java核心技术卷 I

相关文章

  • Java泛型教程

    Java泛型教程导航 Java 泛型概述 Java泛型环境设置 Java泛型通用类 Java泛型类型参数命名约定 ...

  • 第二十八课:泛型

    泛型出现之前 泛型出现之后 Java深度历险(五)——Java泛型

  • Kotlin 泛型

    说起 kotlin 的泛型,就离不开 java 的泛型,首先来看下 java 的泛型,当然比较熟悉 java 泛型...

  • java泛型中类型擦除的一些思考

    java泛型 java泛型介绍 java泛型的参数只可以代表类,不能代表个别对象。由于java泛型的类型参数之实际...

  • Java泛型

    参考:Java知识点总结(Java泛型) 自定义泛型类 自定义泛型接口 非泛型类中定义泛型方法 继承泛型类 通配符...

  • Java泛型—Java语法糖,只在编译有作用,编译后擦出泛型

    Java泛型—Java语法糖,只在编译有作用,编译后擦出泛型 在代码进入和离开的边界处,会处理泛型 Java泛型作...

  • JAVA 核心笔记 || [xxx] 泛型

    泛型 JAVA 的参数化类型 称为 泛型 泛型类的设计 Learn12.java 运行

  • 简单回顾Java泛型之-入门介绍

    什么时候开始有了Java泛型?什么是Java泛型?为什么要引入Java泛型?什么时候用到了泛型?可不可以给泛型下一...

  • Kotlin 泛型

    Kotlin 支持泛型, 语法和 Java 类似。例如,泛型类: 泛型函数: 类型变异 Java 的泛型中,最难理...

  • JAVA-泛型

    JAVA-泛型 sschrodinger 2018/11/15 简介 泛型是Java SE 1.5的新特性,泛型的...

网友评论

      本文标题:Java泛型

      本文链接:https://www.haomeiwen.com/subject/nbrcpftx.html