Java-泛型机制详解

博客园   2023-04-10 12:19:56

Java-泛型机制详解

1: 提出背景

Java集合(Collection)中元素的类型是多种多样的。例如,有些集合中的元素是Byte类型的,而有些则可能是String类型的,等等。Java允许程序员构建一个元素类型为Object的Collection,其中的元素可以是任何类型在[Java SE](https://baike.baidu.com/item/Java SE/4662159?fromModule=lemma_inlink) 1.5之前,没有泛型(Generics)的情况下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要作显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以在预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。因此,为了解决这一问题,J2SE 1.5引入泛型也是自然而然的了。


(资料图片仅供参考)

2: 泛型简介

泛型概念

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

泛型作用

3:泛型的基本使用

泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法。下面一部分例子参考与菜鸟教程网站(https://www.runoob.com/java/java-generics.html)

泛型类

泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。

例子:

/** *  * @param  此处是标识符号,T是type简称 */public class MyBox {    /**     * t的类型有T指定,创建该对象的时候,指定的类型     */    private T t;        public void add(T t) {        this.t = t;    }    public T get() {        return t;    }}public class MainTest {    public static void main(String[] args) {        MyBox stringMyBox = new MyBox<>();        stringMyBox.add("dddd");        System.out.println(stringMyBox.get());        MyBox integerMyBox = new MyBox<>();        integerMyBox.add(23);        System.out.println(integerMyBox.get());    }}
/** *  * @param  变量key的类型,由外部指定 * @param  变量value的类型,由外部指定 */public class MyMap {        private K key;    private V value;    public K getKey() {        return this.key;    }    public V getValue() {        return this.value;    }    public void setKey(K key) {        this.key = key;    }    public void setValue(V value ){        this.value = value;    }}public class MainTest {    public static void main(String[] args) {        MyMap stringStringMyMap = new MyMap<>();        stringStringMyMap.setKey("key1");        stringStringMyMap.setValue("key2");        System.out.println(stringStringMyMap.getKey()+"="+ stringStringMyMap.getValue());        MyMap stringIntegerMyMap = new MyMap<>();        stringIntegerMyMap.setKey("key1");        stringIntegerMyMap.setValue(1234);        System.out.println(stringIntegerMyMap.getKey()+"="+ stringIntegerMyMap.getValue());    }}

泛型接口

泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中

例子:

当实现泛型接口的类,没有传入泛型实参的时候:

/** * 定义一个泛型接口 * @param  */public interface Generator {        public T getNext();    }/** * 没有传入参数实参时,与泛型类的定义相同,在声明类的时候,需要将泛型的声明也一起加到类中 * @param  */class GoodsGenerator implements Generator {        @Override    public T getNext() {        return null;    }}

当实现泛型接口的类,传入泛型实参时候:

/** * 传入泛型实参后,那么这个实现类里面的所有的参数都指定的类型,不能更改 * @param  */public class MyGoodsGenerator implements  Generator {    private Params params = null;    @Override    public Params getNext() {        return params;    }}class Params {    private String name;    private String type;    Params() {}    Params(String name,String type) {        this.name = name;        this.type = type;    }}

泛型方法

相对于泛型类,泛型方法就比较复杂了。

泛型类 是在实例化类的时候指明泛型的具体类型。而泛型方法,是在调用的时候指明泛型的具体类型

泛型方法规则

Java中泛型标记符

下面我们看一个例子:

/**     * 泛型方法     *  首先在public与返回值之间的必不可少,这表明这是一个泛型方法,并且声明了一个泛型T。     *  这个T可以出现在这个泛型方法的任意位置     * @param container 用来创建泛型对象     * @param  指明泛型T的具体类型     * @return     * @throws IllegalAccessException     * @throws InstantiationExceptio     */    public  T showKeyName(Class c) throws IllegalAccessException, InstantiationException {        //创建对象        T t = c.newInstance();        return t;    }

1: 为什么要用变量c来创建对象呢?

​既然是泛型方法,就代表着我们不知道具体的类型是什么,也不知道构造方法如何,因此没有办法去new一个对象,但可以利用变量c的newInstance方法去创建对象,也就是利用反射创建对象。

​泛型方法要求的参数是Class类型,而Class.forName()方法的返回值也是Class,因此可以用Class.forName()作为参数。当然,泛型方法不是仅仅可以有一个参数Class,可以根据需要添加其他参数。

好处

​因为泛型类要在实例化的时候就指明类型,如果想换一种类型,不得不重新new一次,可能不够灵活;而泛型方法可以在调用的时候指明类型,更加灵活。

泛型的上下限

先看下下面的例子

public class Entity {    }    class BaseEntity extends Entity {    }
//这两个方法编译不报错    public static void funA(Entity a) {    // ...    }    public static void funB(BaseEntity b) {    funA(b);    // ...    }    //下面的funC(ListB)就会编译报错    public static void funC(List Entitys) {        // ...    }    public static void funD(List BaseEntitys) {        funC(BaseEntitys);         // ...    }

如何解决这个问题: 看下面的方法

public static void funC(List Entitys) {       // ...   }    public static void funD(List BaseEntitys) {        funC(BaseEntitys);        // ...    }

​为了解决泛型中隐含的转换问题,Java泛型加入了类型参数的上下边界机制。表示该类型参数可以是Entity(上边界)或者Entity的子类类型。编译时擦除到类型Entity,即用Entity类型代替类型参数。这种方法可以解决开始遇到的问题,编译器知道类型参数的范围,如果传入的实例类型BaseEntity是在这个范围内的话允许转换,这时只要一次类型转换就可以了,运行时会把对象当做Entity的实例看待。

//前面举例的方法public static void funC(List Entitys) {       // ...}

总结:

在使用泛型时,可以对传入的泛型类型实参进行上下边界的限制,如:类型实参只准传入某种类型的父类或某种类型的子类。

 无限制通配符  extends 关键字声明了类型的上界, 表示参数化的类型可能是所指定的类型,或者是此类型的子类  super 关键字声明了类型的下界,表示参数化的类型可能是指定的类型,或者是此类型的父类

4: 泛型的好处

Java语言中引入泛型是一个较大的功能增强。不仅语言、类型系统和编译器有了较大的变化,以支持泛型,而且类库也进行了很大的改动,许多重要的类,比如集合框架,都已经成为泛型化的了。这带来了很多好处:

  1. 类型安全

    泛型的主要目标是提高Java程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在非常高的层次上验证类型假设

  2. 消除强制类型转换

    泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会

  3. 更高的运行效率

    在非泛型编程中,将筒单类型作为Object传递时会引起Boxing(装箱)和Unboxing(拆箱)操作,这两个过程都是具有很大开销的。引入泛型后,就不必进行Boxing和Unboxing操作了,所以运行效率相对较高,特别在对集合操作非常频繁的系统中,这个特点带来的性能提升更加明显

  4. 潜在的性能收益

    泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,Java系统开发人员会指定这些强制类型转换)插入生成的字节码中。

相关新闻