0

0

11.Java 基础 - 泛型

黄舟

黄舟

发布时间:2017-02-27 10:43:58

|

1040人浏览过

|

来源于php中文网

原创

基本概念

泛型的本质是参数化类型(Parameterized Type)的应用,也就是说所操作的数据类型被指定为一个参数,在用到的时候在指定具体的类型。

这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法


1.发展

在 JDK 1.5 之前,只能通过 Object 是所有类型的父类和类型强制转换两个特点的配合来实现类型泛化。

因此在编译期间,编译器无法检查这个 Object 的强制转型是否成功,这样容导致发生 ClassCastException (强制转换异常) 。

立即学习Java免费学习笔记(深入)”;

下面我们来看一个实例,就能知道泛型的作用:

  • 不使用泛型的情况(1.5 之前)

ArrayList arrayList = new ArrayList();
arrayList.add(100);
arrayList.add("abc");//因为不知道取出来的值的类型,类型转换的时候容易出错  String str = (String) arrayList.get(0);
  • 使用泛型的情况(1.5 之后)

ArrayList arrayList = new ArrayList();
arrayList.add("abc");//因为限定了类型,所以不能添加整形,编译器会提示出错arrayList.add(100);

2.术语

// 以 ArrayList,ArrayList 为例:ArrayList:泛型类型

E:类型变量(或者类型参数)

ArrayList :参数化的类型

Integer:类型参数的实例(或实际类型参数)

ArrayList :原始类型

3.探究

泛型类

class Demo {    private T value;

    Demo(T value) {        this.value = value;
    }    public T getValue() {        return value;
    }    public void setValue(T value) {        this.value = value;
    }
}public class Test {
    public static void main(String[] args) {
        Demo demo = new Demo("abc");
        demo.setValue("cba");
        System.out.println(demo.getValue()); // cba
    }
}

泛型接口

interface Demo {    void print(K k, V v);
}

class DemoImpl implements Demo {    @Override
    public void print(String k, Integer v) {
        System.out.println(k + "-" + v);
    }
}public class Test {
    public static void main(String[] args) {
        Demo demo = new DemoImpl();
        demo.print("abc", 100);
    }
}

泛型方法

public class Test {
    public static void main(String[] args) {        int num = get("abc", 100);
        System.out.println(num);
    }    // 关键 --> 多了  ,可以理解为声明此方法为泛型方法
    public static  V get(K k, V v) {        if (k != null) {            return v;
        }        return null;
    }
}

类型限定

类型限定在泛型类、泛型接口和泛型方法中都可以使用,不过要注意下面几点:

  • 不管该限定是类还是接口,统一都使用关键字 extends

  • 可以使用 & 符号给出多个限定

  • 如果限定既有接口也有类,那么类必须只有一个,并且放在首位置。例如:

public static  T get(T t1,T t2)

下面再来分析下类型限定的作用…


1.不对类型参数设置界限

观察下面的代码,在没有对类型参数进行类型限定时会出现编译错误。原因如下:

  • 因为在编译之前,编译器并不能确认泛型类型(T)是什么类型

  • 因此它默认 T 为原始类型(Object)。

  • 所以只能调用 Object 的方法,而不能调用 compareTo 方法。

public static  T get(T t1,T t2) {    //编译错误
    if(t1.compareTo(t2)>=0);    return t1;
}

2.对类型参数设置界限

当对类型参数 T 设置界限(bound)后,编译错误不再发生。因为此时编译器默认 T 的原始类型为 Comparable。

public static  T get(T t1,T t2) {    if(t1.compareTo(t2)>=0);    return t1;
}

类型擦除

  • Java 中的泛型基本上都是在编译器这个层次来实现的。

  • 在生成的 Java 字节码中是不包含泛型中的类型信息的。

  • 使用泛型时加上的类型参数,会在编译器在编译的时候去掉,这个过程就称为类型擦除。


来看下面的这个例子:

public class Test {
    public static void main(String[] args) {
        ArrayList arrayList1 =new ArrayList();
        ArrayList arrayList2 = new ArrayList();        // true
        System.out.println(arrayList1.getClass() == arrayList2.getClass());
    }
}

观察代码,这里定义了两个ArrayList数组:

  • 一个是ArrayList泛型类型,只能存储字符串,一个是ArrayList泛型类型,只能存储整形。

  • 通过比较它们的类对象,发现结果为 true。

  • 说明泛型类型 String 和 Integer 在编译过程中都被擦除掉了,只剩下了原始类型(即 Object)。

    猫宁Morning公益商城系统
    猫宁Morning公益商城系统

    猫宁Morning公益商城是中国公益性在线电子商城,以商城B2C模式运营的公益在线商城,是一家致力于将传统公益商城互联网化的创新公益商城。该网上商城系统分为电子商城系统、公益商城系统、后台管理系统,使用Maven对项目进行模块化管理,搭建多模块企业级项目。Morning是在Spring Framework基础上搭建的一个Java基础开发平台,以Spring MVC为模型视图控制器,MyBatis为

    下载

再来看一个例子:

public class Test {
    public static void main(String[] args) throws Exception{
        ArrayList arrayList =new ArrayList();
        arrayList.add("abc");
        arrayList.getClass().getMethod("add", Object.class).invoke(arrayList, 100);         for (int i=0;i

观察代码,这里定义了一个ArrayList 泛型类型实例化为 Integer 的对象

  • 如果直接调用 add 方法,那么只能存储整形的数据。

  • 利用反射调用 add 方法,却可以存储字符串。

  • 说明 Integer 泛型实例在编译之后被擦除了,只保留了原始类型。


1.原始类型

  • 原始类型(raw type)就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型

  • 任意一个泛型的类型参数,都存在对应的原始变量。

  • 一旦类型变量被擦除(crased),就会使用其限定类型(无限定的变量用 Object)替换。

// 此时 T 是一个无限定类型,所以原始类型就是 Objectclass Pair { } 

// 类型变量有限定,原始类型就用第一个边界的类型变量来替换,即Comparableclass Pair { }  

// 此时原始类型为 Serializable,编译器在必要的时要向 Comparable 插入强制类型转换
// 为了提高效率,应该将标签(tagging)接口(即没有方法的接口)放在边界限定列表的末尾class Pair

2.类型参数的类型

在下面的例子中,类型参数指 T,T 的类型就是所谓的【类型参数】的类型。

观察代码,可以得出如下结论:

  • 不指定【类型参数 T 】的类型,当参数的类型不一致时,原始类型取同一父类的最小级

  • 指定【类型参数 T 】的类型时,原始类型只能为其指定的类型或类型的子类

public class Test {    // 定义泛型方法
    public static  T add(T x, T y) {        return y;
    }    public static void main(String[] args) {        // 1.不指定泛型

        // 两个参数都是 Integer,所以 T 为 Integer 类型
        int i = Test.add(1, 2); 

        // 两个参数分别是 Integer,Float,取同一父类的最小级,T 为 Number 类型
        Number f = Test.add(1, 1.2);        // T 为 Object
        Object o = Test.add(1, "asd");        // 2.指定泛型

        // 指定了Integer,所以只能为 Integer 类型或者其子类
        int a = Test. add(1, 2);        //编译错误,指定了 Integer,不能为Float
        int b=Test.add(1, 2.2); 

         // 指定为Number,所以可以为 Integer,Float
        Number c = Test. add(1, 2.2);
    }

}

3.类型检查

泛型的类型检查是针对引用的,而不针对被引用的对象本身。

在下面的例子中,list 是引用对象,因此类型检查是针对它的。

// 没有进行类型检查,等价于 ArrayList list = new ArrayLis()ArrayList list = new ArrayList();
list.add(100);
list.add("hello");// 进行编译检查,等价于 ArrayList list = new ArrayList();ArrayList list = new ArrayList();
list.add("hello");
list.add(100);  // 编译错误

4.类型擦除与多态的冲突

来看下面的例子,这里定义了一个泛型类 Parent,一个实现它的子类 Son,并在子类中重写了父类的方法。

class Parent {    private T value;    public T getValue() {        return value;
    }    public void setValue(T value) {        this.value = value;
    }
}

class Son extends Parent{    @Override
    public void setValue(String value) {         super.setValue(value);
    }    @Override
    public String getValue(){        return super.getValue();}
    }

在上面提到过泛型的类型参数在编译时会被类型擦除,因此编译后的 Parent 类如下:

class Parent {    private Object value;    public Object getValue() {        return value;
    }    public void setValue(Object value) {        this.value = value;
    }
}

此时对比 Parent 与 Son 的 getValue/setValue 方法,发现方法的参数类型已经改变,从 Object -> String,这也意味着不是重写(overrride) 而是重载(overload)。

然而调用 Son 的 setValue 方法, 发现添加 Object 对象时编译错误。说明也不是重载。

public class Test {
    public static void main(String[] args) {
        Son son = new Son();
        son.setValue("hello");        // 关键 -->编译错误
        son.setValue(new Object());
    }
}

那么问题来了,通过上面的分析?Son 中定义的方法到底是重写还是重载?答案是:重写。这里 JVM 采用了桥方法(Brige)来解决类型擦除和多态引起的冲突。

我们对 Son 进行反编译(”Javap -c 类名.class”),得到如下内容:

Compiled from "Test.java"class Son extends Parent {
  Son();
    Code:       0: aload_0       
       1: invokespecial #8                  // Method Parent."":()V
       4: return        

  public void setValue(java.lang.String);
    Code:       0: aload_0       
       1: aload_1       
       2: invokespecial #16                 // Method Parent.setValue:(Ljava/lang/Object;)V
       5: return        

  public java.lang.String getValue();
    Code:       0: aload_0       
       1: invokespecial #23                 // Method Parent.getValue:()Ljava/lang/Object;
       4: checkcast     #26                 // class java/lang/String
       7: areturn       

  public java.lang.Object getValue();
    Code:       0: aload_0       
       1: invokevirtual #28                 // Method getValue:()Ljava/lang/String;
       4: areturn       

  public void setValue(java.lang.Object);
    Code:       0: aload_0       
       1: aload_1       
       2: checkcast     #26                 // class java/lang/String
       5: invokevirtual #30                 // Method setValue:(Ljava/lang/String;)V
       8: return        }

发现这里共有 4 个 setValue/getValue 方法,除了 Son 表面上重写的 String 类型,编译器又自己生成了 Object 类型的方法,也称为桥方法。结果就是,编译器通过桥方法真正实现了重写,只是在访问时又去调用表面的定义的方法。


注意事项

  • 不能用基本类型实例化类型参数,可以用对应的包装类来实例化类型参数

// 编译错误ArrayList list = new ArrayList();// 正确写法ArrayList list = new ArrayList();
  • 参数化类型的数组不合法

Demo{
}public class Test {
    public static void main(String[] args) {        // 编译错误 --> 类型擦除导致数组变成 Object [],因此没有意义
        Demo[ ]  demo =new Demo[10];
    }
}
  • 不能实例化类型变量

// 编译错误,需要类型参数需要确定类型Demo demo = new Demo
  • 泛型类的静态上下文中不能使用类型变量

public class Demo {
    public static T name;    public static T getName() {
        ...
    }
}
  • 不能抛出也不能捕获泛型类的对象

//异常都是在运行时捕获和抛出的,而在编译的时候,泛型信息全都会被擦除掉。会导致这里捕获的类型一致try{  
}catch(Problem e1){  
    //do Something... }catch(Problem e2){  
    // do Something ...}

 以上就是11.Java 基础 - 泛型的内容,更多相关内容请关注PHP中文网(www.php.cn)!


相关文章

java速学教程(入门到精通)
java速学教程(入门到精通)

java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
AO3官网入口与中文阅读设置 AO3网页版使用与访问
AO3官网入口与中文阅读设置 AO3网页版使用与访问

本专题围绕 Archive of Our Own(AO3)官网入口展开,系统整理 AO3 最新可用官网地址、网页版访问方式、正确打开链接的方法,并详细讲解 AO3 中文界面设置、阅读语言切换及基础使用流程,帮助用户稳定访问 AO3 官网,高效完成中文阅读与作品浏览。

38

2026.02.02

主流快递单号查询入口 实时物流进度一站式追踪专题
主流快递单号查询入口 实时物流进度一站式追踪专题

本专题聚合极兔快递、京东快递、中通快递、圆通快递、韵达快递等主流物流平台的单号查询与运单追踪内容,重点解决单号查询、手机号查物流、官网入口直达、包裹进度实时追踪等高频问题,帮助用户快速获取最新物流状态,提升查件效率与使用体验。

7

2026.02.02

Golang WebAssembly(WASM)开发入门
Golang WebAssembly(WASM)开发入门

本专题系统讲解 Golang 在 WebAssembly(WASM)开发中的实践方法,涵盖 WASM 基础原理、Go 编译到 WASM 的流程、与 JavaScript 的交互方式、性能与体积优化,以及典型应用场景(如前端计算、跨平台模块)。帮助开发者掌握 Go 在新一代 Web 技术栈中的应用能力。

4

2026.02.02

PHP Swoole 高性能服务开发
PHP Swoole 高性能服务开发

本专题聚焦 PHP Swoole 扩展在高性能服务端开发中的应用,系统讲解协程模型、异步IO、TCP/HTTP/WebSocket服务器、进程与任务管理、常驻内存架构设计。通过实战案例,帮助开发者掌握 使用 PHP 构建高并发、低延迟服务端应用的工程化能力。

3

2026.02.02

Java JNI 与本地代码交互实战
Java JNI 与本地代码交互实战

本专题系统讲解 Java 通过 JNI 调用 C/C++ 本地代码的核心机制,涵盖 JNI 基本原理、数据类型映射、内存管理、异常处理、性能优化策略以及典型应用场景(如高性能计算、底层库封装)。通过实战示例,帮助开发者掌握 Java 与本地代码混合开发的完整流程。

2

2026.02.02

go语言 注释编码
go语言 注释编码

本专题整合了go语言注释、注释规范等等内容,阅读专题下面的文章了解更多详细内容。

62

2026.01.31

go语言 math包
go语言 math包

本专题整合了go语言math包相关内容,阅读专题下面的文章了解更多详细内容。

55

2026.01.31

go语言输入函数
go语言输入函数

本专题整合了go语言输入相关教程内容,阅读专题下面的文章了解更多详细内容。

27

2026.01.31

golang 循环遍历
golang 循环遍历

本专题整合了golang循环遍历相关教程,阅读专题下面的文章了解更多详细内容。

33

2026.01.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 3.1万人学习

C# 教程
C# 教程

共94课时 | 8.3万人学习

Java 教程
Java 教程

共578课时 | 55.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号