Java-泛型机制详解

这篇具有很好参考价值的文章主要介绍了Java-泛型机制详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

泛型作用

  • 第一是泛化

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

  • 第二是类型安全

    ​ 泛型的一个主要目标就是提高Java程序的类型安全,使用泛型可以使编译器知道变量的类型限制,进而可以在更高程度上验证类型假设。如果不用泛型,则必须使用强制类型转换,而强制类型转换不安全,在运行期可能发生ClassCast Exception异常,如果使用泛型,则会在编译期就能发现该错误。

  • 第三是消除强制类型转换

    泛型可以消除源代码中的许多强制类型转换,这样可以使代码更加可读,并减少出错的机会。

  • 第四是向后兼容

    ​ 支持泛型的Java编译器(例如JDK1.5中的Javac)可以用来编译经过泛型扩充的Java程序(Generics Java程序),但是现有的没有使用泛型扩充的Java程序仍然可以用这些编译器来编译

3:泛型的基本使用

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

泛型类

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

例子:

  • 简单泛型类
/**
 * 
 * @param <T> 此处是标识符号,T是type简称
 */
public class MyBox<T> {
    /**
     * 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<String> stringMyBox = new MyBox<>();
        stringMyBox.add("dddd");
        System.out.println(stringMyBox.get());

        MyBox<Integer> integerMyBox = new MyBox<>();
        integerMyBox.add(23);
        System.out.println(integerMyBox.get());
    }
}
  • 多元泛型类
/**
 * 
 * @param <K> 变量key的类型,由外部指定
 * @param <V> 变量value的类型,由外部指定
 */
public class MyMap<K,V> {
    
    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<String, String> stringStringMyMap = new MyMap<>();
        stringStringMyMap.setKey("key1");
        stringStringMyMap.setValue("key2");
        System.out.println(stringStringMyMap.getKey()+"="+ stringStringMyMap.getValue());

        MyMap<String, Integer> stringIntegerMyMap = new MyMap<>();
        stringIntegerMyMap.setKey("key1");
        stringIntegerMyMap.setValue(1234);
        System.out.println(stringIntegerMyMap.getKey()+"="+ stringIntegerMyMap.getValue());
    }
}

泛型接口

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

例子:

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

/**
 * 定义一个泛型接口
 * @param <T>
 */
public interface Generator<T> {
    
    public T getNext();
    
}

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

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

/**
 * 传入泛型实参后,那么这个实现类里面的所有的参数都指定的类型,不能更改
 * @param <Params>
 */
public class MyGoodsGenerator<Params> implements  Generator<Params> {

    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;
    }
}

泛型方法

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

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

泛型方法规则

  • 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前(在下面例子中的
  • 每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符
  • 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符
  • 泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像 int、double、char 等)

Java中泛型标记符

  • E - Element (在集合中使用,因为集合中存放的是元素)
  • T - Type(Java 类)
  • K - Key(键)
  • V - Value(值)
  • N - Number(数值类型)
  • - 表示不确定的 java 类型

下面我们看一个例子:

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

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

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

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

好处

​ 因为泛型类要在实例化的时候就指明类型,如果想换一种类型,不得不重新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<Entity> Entitys) {
        // ...
    }

    public static void funD(List<BaseEntity> BaseEntitys) {
        funC(BaseEntitys); 
        // ...
    }

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

   public static void funC(List<? extends Entity> Entitys) {
       // ...
   }

    public static void funD(List<BaseEntity> BaseEntitys) {
        funC(BaseEntitys);
        // ...
    }

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

  • 上限

    public class MyEntity <T extends Number>{
    
        private T value ;
    
        public void setVar(T var){
            this.value = var ;
        }
        public T getVar(){
            return this.value ;
        }
    }
    
    public class MainTest {
        public static void main(String[] args) {
            MyEntity<Long> longMyEntity = new MyEntity<>();
            longMyEntity.setValue(10000000000000l);
    
            MyEntity<Integer> integerMyEntity = new MyEntity<>();
            integerMyEntity.setValue(1111);
        }
    }
    
  • 下限

//前面举例的方法
public static void funC(List<? extends Entity> Entitys) {
       // ...
}

总结:

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

 <?> 无限制通配符 

<? extends E> extends 关键字声明了类型的上界, 表示参数化的类型可能是所指定的类型,或者是此类型的子类 

<? super E> super 关键字声明了类型的下界,表示参数化的类型可能是指定的类型,或者是此类型的父类

4: 泛型的好处

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

  1. 类型安全

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

  2. 消除强制类型转换

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

  3. 更高的运行效率

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

  4. 潜在的性能收益

    泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,Java系统开发人员会指定这些强制类型转换)插入生成的字节码中。文章来源地址https://www.toymoban.com/news/detail-409449.html

到了这里,关于Java-泛型机制详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • java的泛型【详解】

    定义类、接口、方法时,同时声明了一个或者多个类型变量(如:E) ,称为泛型类、泛型接口,泛型方法、它们统称为泛型。 作用:泛型提供了在编译阶段约束所能操作的数据类型,并自动进行检查的能力!这样可以避免强制类型转换,及其可能出现的异常。 1.泛型类:

    2024年02月19日
    浏览(43)
  • Java中泛型详解,非常详细

    在前面的几篇文章中,详细地给大家介绍了Java里的集合。但在介绍集合时,我们涉及到了泛型的概念却并没有详细学习, 所以今天我们要花点时间给大家专门讲解什么是泛型、泛型的作用、用法、特点等内容。 有些粉丝朋友,在之前就一直很好奇,比如List String 中的 Strin

    2024年02月07日
    浏览(53)
  • Java 泛型中的通配符详解

    目录 1、如何定义和使用上界通配符? 2、如何定义和使用无界通配符? 3、如何定义和使用下界通配符? 4、如何使用通配符定义泛型类或接口之间的子类型关系? 5、通配符的捕获和辅助方法 6、通配符使用指南         在泛型代码中,问号(?)称为通配符,用来表示未知

    2024年02月10日
    浏览(37)
  • Java 中的泛型(两万字超全详解)

    博主将用 CSDN 记录 Java 后端开发学习之路上的经验,并将自己整理的编程经验和知识分享出来,希望能帮助到有需要的小伙伴。 博主也希望和一直在坚持努力学习的小伙伴们共勉!唯有努力钻研,多思考勤动手,方能在编程道路上行至所向。 由于博主技术知识有限,博文中

    2024年02月04日
    浏览(49)
  • Java反射机制深入详解

    一.概念 反射就是把Java的各种成分映射成相应的Java类。 Class类的构造方法是private,由JVM创建。 反射是java语言的一个特性,它允程序在运行时(注意不是编译的时候)来进行自我检查并且对内部的成员进行操作。例如它允许一个java的类获取他所有的成员变量和方法并且显示

    2024年02月06日
    浏览(45)
  • Java类加载机制详解

    类加载器 加载类 备注 启动类加载器(Bootstrap ClassLoader) JAVA_HOME/jre/lib 无上级,无法直接访问 由jvm加载 拓展类加载器(Extension ClassLoader) JAVA_HOME/jre/lib/ext 父加载器为 Bootstrap,显示为 null 。该类由Bootstrap加载 应用类加载器(Application ClassLoader) classpath 父加载器上级为 Exten

    2024年02月08日
    浏览(35)
  • Java的垃圾回收机制详解

    目录 1、C语言与Java语言垃圾回收区别 2、System.gc() 3、面试题引入Java垃圾回收 3.1 jvm怎么确定哪些对象应该进行回收 3.1.1 引用计数法 3.1.2 可达性分析算法  3.2 jvm会在什么时候进行垃圾回收的动作 3.2 jvm到底是怎么回收垃圾对象的 4、垃圾回收算法 4.1 标记-清除算法 4. 2 复制算

    2024年02月09日
    浏览(48)
  • 【详解】Java反射机制(打穿封装)

    目录 定义: 用途: 反射基本信息: 反射相关的类(important): Class类(反射机制的起源 ) Class类中的相关方法: 常用获得类相关的方法(重要) 常用获得类中属性相关的方法(重要) 获得类中注解相关的方法(了解) 获得类中构造器相关的方法(重要) 获得该类所有构造

    2024年04月16日
    浏览(61)
  • Java-SPI机制详解

    SPI 全称是 Service Provider Interface ,是一种 JDK 内置的动态加载实现扩展点的机制,通过 SPI 技术我们可以动态获取接口的实现类,不用自己来创建。这个不是什么特别的技术,只是 一种 设计理念 。 Java SPI 实际上是 基于接口的编程+策略模式+配置文件 组合实现的动态加载机

    2023年04月08日
    浏览(30)
  • Java JVM中的GC机制详解

    垃圾回收(Garbage Collection,简称GC)机制是JVM中最重要的部分之一。在Java程序运行的过程中,运行时数据区域(包括堆和栈等内存区域)一直都需要使用和回收内存空间。由于Java中的内存分配方式是动态的,所以在程序运行期间,其内存空间的占用量会不断变化。 如果Java程

    2024年02月14日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包