Java基础-泛型机制

news2024/12/27 10:14:39

文章目录

    • 为什么引入泛型
    • 泛型的基本使用
      • 泛型类
      • 泛型接口
      • 泛型方法
      • 泛型数组
        • 正确的数组声明
        • 使用场景
        • 如何理解Java中的泛型是伪泛型?泛型中类型擦除
      • 泛型数组:如何正确的初始化泛型数组实例?

为什么引入泛型

引入泛型的意义在于:

  • 适用于多种数据类型执行相同的代码(代码复用)

我们通过一个例子来阐述,先看下下面的代码:

private static int add(int a, int b) {
    System.out.println(a + "+" + b + "=" + (a + b));
    return a + b;
}

private static float add(float a, float b) {
    System.out.println(a + "+" + b + "=" + (a + b));
    return a + b;
}

private static double add(double a, double b) {
    System.out.println(a + "+" + b + "=" + (a + b));
    return a + b;
}

如果没有泛型,要实现不同类型的加法,每种类型都需要重载一个add方法;通过泛型,我们可以复用为一个方法:

private static <T extends Number> double add(T a, T b) {
    System.out.println(a + "+" + b + "=" + (a.doubleValue() + b.doubleValue()));
    return a.doubleValue() + b.doubleValue();
}
  • 泛型中的类型在使用时指定,不需要强制类型转换(类型安全,编译器会检查类型

看下这个例子:

List list = new ArrayList();
list.add("xxString");
list.add(100d);
list.add(new Person());

我们在使用上述list中,list中的元素都是Object类型(无法约束其中的类型),所以在取出集合元素时需要人为的强制类型转化到具体的目标类型,且很容易出现java.lang.ClassCastException异常。

引入泛型,它将提供类型的约束,提供编译前的检查:

List<String> list = new ArrayList<String>();

// list中只能放String, 不能放其它类型的元素

泛型的基本使用

泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法。

泛型类

  • 从一个简单的泛型类看起:
class Point<T>{         // 此处可以随便写标识符号,T是type的简称  
    private T var ;     // var的类型由T指定,即:由外部指定  
    public T getVar(){  // 返回值的类型由外部决定  
        return var ;  
    }  
    public void setVar(T var){  // 设置的类型也由外部决定  
        this.var = var ;  
    }  
}  
public class GenericsDemo06{  
    public static void main(String args[]){  
        Point<String> p = new Point<String>() ;     // 里面的var类型为String类型  
        p.setVar("it") ;                            // 设置字符串  
        System.out.println(p.getVar().length()) ;   // 取得字符串的长度  
    }  
}
  • 多元泛型
class Notepad<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 GenericsDemo09{  
    public static void main(String args[]){  
        Notepad<String,Integer> t = null ;        // 定义两个泛型类型的对象  
        t = new Notepad<String,Integer>() ;       // 里面的key为String,value为Integer  
        t.setKey("汤姆") ;        // 设置第一个内容  
        t.setValue(20) ;            // 设置第二个内容  
        System.out.print("姓名;" + t.getKey()) ;      // 取得信息  
        System.out.print(",年龄;" + t.getValue()) ;       // 取得信息  
  
    }  
}

泛型接口

  • 简单的泛型接口
interface Info<T>{        // 在接口上定义泛型  
    public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型  
}  
class InfoImpl<T> implements Info<T>{   // 定义泛型接口的子类  
    private T var ;             // 定义属性  
    public InfoImpl(T var){     // 通过构造方法设置属性内容  
        this.setVar(var) ;    
    }  
    public void setVar(T var){  
        this.var = var ;  
    }  
    public T getVar(){  
        return this.var ;  
    }  
} 
public class GenericsDemo24{  
    public static void main(String arsg[]){  
        Info<String> i = null;        // 声明接口对象  
        i = new InfoImpl<String>("汤姆") ;  // 通过子类实例化对象  
        System.out.println("内容:" + i.getVar()) ;  
    }  
}  

泛型方法

泛型方法,是在调用方法的时候指明泛型的具体类型。重点看下泛型的方法。

  • 定义泛型方法语法格式

image-20241010114434131

  • 调用泛型方法语法格式

img

说明一下,定义泛型方法时,必须在返回值前边加一个<T>,来声明这是一个泛型方法,持有一个泛型T,然后才可以用泛型T作为方法的返回值。

Class<T>的作用就是指明泛型的具体类型,而Class<T>类型的变量c,可以用来创建泛型类的对象。

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

泛型方法要求的参数是Class<T>类型,而Class.forName()方法的返回值也是Class<T>,因此可以用Class.forName()作为参数。其中,forName()方法中的参数是何种类型,返回的Class<T>就是何种类型。在本例中,forName()方法中传入的是User类的完整路径,因此返回的是Class<User>类型的对象,因此调用泛型方法时,变量c的类型就是Class<User>,因此泛型方法中的泛型T就被指明为User,因此变量obj的类型为User。

当然,泛型方法不是仅仅可以有一个参数Class<T>,可以根据需要添加其他参数。

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

泛型数组

正确的数组声明
List<String>[] list11 = new ArrayList<String>[10]; //编译错误,非法创建 
List<String>[] list12 = new ArrayList<?>[10]; //编译错误,需要强转类型 
List<String>[] list13 = (List<String>[]) new ArrayList<?>[10]; //OK,但是会有警告 
List<?>[] list14 = new ArrayList<String>[10]; //编译错误,非法创建 
List<?>[] list15 = new ArrayList<?>[10]; //OK 
List<String>[] list6 = new ArrayList[10]; //OK,但是会有警告
使用场景
public class GenericsDemo30{  
    public static void main(String args[]){  
        Integer i[] = fun1(1,2,3,4,5,6) ;   // 返回泛型数组  
        fun2(i) ;  
    }  
    public static <T> T[] fun1(T...arg){  // 接收可变参数  
        return arg ;            // 返回泛型数组  
    }  
    public static <T> void fun2(T param[]){   // 输出  
        System.out.print("接收泛型数组:") ;  
        for(T t:param){  
            System.out.print(t + "、") ;  
        }  
    }  
}
如何理解Java中的泛型是伪泛型?泛型中类型擦除

Java泛型这个特性是从JDK 1.5才开始加入的,因此为了兼容之前的版本,Java泛型的实现采取了“伪泛型”的策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型),就像完全没有泛型一样。理解类型擦除对于用好泛型是很有帮助的,尤其是一些看起来“疑难杂症”的问题,弄明白了类型擦除也就迎刃而解了。

泛型数组:如何正确的初始化泛型数组实例?

这个无论我们通过new ArrayList[10] 的形式还是通过泛型通配符的形式初始化泛型数组实例都是存在警告的,也就是说仅仅语法合格,运行时潜在的风险需要我们自己来承担,因此那些方式初始化泛型数组都不是最优雅的方式。

我们在使用到泛型数组的场景下应该尽量使用列表集合替换,此外也可以通过使用 java.lang.reflect.Array.newInstance(Class<T> componentType, int length) 方法来创建一个具有指定类型和维度的数组,如下:

public class ArrayWithTypeToken<T> {
    private T[] array;

    public ArrayWithTypeToken(Class<T> type, int size) {
        array = (T[]) Array.newInstance(type, size);
    }

    public void put(int index, T item) {
        array[index] = item;
    }

    public T get(int index) {
        return array[index];
    }

    public T[] create() {
        return array;
    }
}
//...

ArrayWithTypeToken<Integer> arrayToken = new ArrayWithTypeToken<Integer>(Integer.class, 100);
Integer[] array = arrayToken.create();

所以使用反射来初始化泛型数组算是优雅实现,因为泛型类型 T在运行时才能被确定下来,我们能创建泛型数组也必然是在 Java 运行时想办法,而运行时能起作用的技术最好的就是反射了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2202688.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

KEYSIGHT B1500A 半导体器件参数分析仪

新利通 B1500A 半导体器件参数分析仪 ——一体化器件表征分析仪—— 简述 Keysight B1500A 半导体参数分析仪是一款一体化器件表征分析仪&#xff0c;能够测量 IV、CV、脉冲/动态 IV 等参数。 主机和插入式模块能够表征大多数电子器件、材料、半导体和有源/无源元器件。 B…

关于相机的一些零碎知识点

热成像&#xff0c;英文为Thermal Imaging&#xff0c;例如型号500T&#xff0c;其实指的就是热成像500分辨率。 相机的CMOS&#xff0c;英文为Complementary Metal Oxide Semiconductor&#xff0c;是数码相机的核心成像部件&#xff0c;是一种互补金属氧化物导体器件。 DPI…

PVC刻字膜高精度模切应用

PVC刻字膜是一种由聚氯乙烯&#xff08;PVC&#xff09;为主要成分制成的薄膜材料&#xff0c;具有耐磨、耐刮、耐水、耐油以及良好的化学稳定性等特点。这种薄膜在多个行业中得到广泛应用&#xff0c;特别是在服装、鞋业、箱包、汽车内饰等领域&#xff0c;用于制作各种标识、…

NDC美国药品编码目录数据库查询方法

NDC&#xff08;National Drug Code&#xff09;翻译为“国家药品代码”&#xff0c;是美国食品药品监督管理局&#xff08;FDA&#xff09;制定的一种药品标识系统&#xff0c;用于唯一标识药品。这个编码系统主要目的是为精准识别和追踪不同药品而建设&#xff0c;行业人员和…

2024最新【Pycharm】史上最全PyCharm安装教程,图文教程(超详细)

1. PyCharm下载安装 完整安装包下载&#xff08;包含Python和Pycharm专业版注册码&#xff09;&#xff1a;点击这里 1&#xff09;访问官网 https://www.jetbrains.com/pycharm/download/#sectionwindows 下载「社区版 Community」 安装包。 2&#xff09;下载完成后&#…

【斯坦福CS144】Lab7

一、实验目的 在本课程中&#xff0c;你已经实现了互联网基础设施的重要部分。这个检查点不是关于实现&#xff0c;而是关于测量实际的互联网并报告特定路径的长期统计数据。 二、实验内容 1.收集数据 选择一个远程主机&#xff0c;其往返时间&#xff08;RTT&#xff09;从…

Unity3D相关知识点总结

Unity3D使用的是笛卡尔三维坐标系&#xff0c;并且是以左手坐标系进行展示的。 1.全局坐标系&#xff08;global&#xff09; 全局坐标系描述的是游戏对象在整个世界&#xff08;场景&#xff09;中的相对于坐标原点&#xff08;0&#xff0c;0&#xff0c;0&#xff09;的位置…

处理 Vue3 中隐藏元素刷新闪烁问题

一、问题说明 页面刷新&#xff0c;原本隐藏的元素会一闪而过。 效果展示&#xff1a; 页面的导航栏通过路由跳转中携带的 meta 参数控制导航栏的 显示/隐藏&#xff0c;但在实践过程中发现&#xff0c;虽然元素隐藏了&#xff0c;但是刷新页面会出现闪烁的问题。 项目源码&…

MLP优化KAN

一&#xff1a;spline概念介绍 在数学学科数值分析中&#xff0c;样条&#xff08;spline&#xff09;是一种特殊的函数&#xff0c;由多项式分段定义。样条的英语单词spline来源于可变形的样条工具&#xff0c;那是一种在造船和工程制图时用来画出光滑形状的工具 样条有两个特…

Adversarial and Adaptive Tone Mapping Operatorfor High Dynamic Range Images

Abstract 这项工作涉及色调映射&#xff0c;这是一种将高动态范围 (HDR) 图像转换为低动态范围 (LDR) 图像的常用方法。 我们通过使用自适应色调映射来解决这个问题。 我们建议部署条件生成对抗网络来构建对抗性和自适应色调映射算子&#xff08;adTMO&#xff09;&#xff0c…

游戏盾是如何解决游戏行业攻击问题

随着游戏行业的迅猛发展&#xff0c;其高额的利润和激烈的市场竞争吸引了众多企业和创业者的目光。然而&#xff0c;这一行业也面临着前所未有的业务和安全挑战&#xff0c;尤其是DDoS&#xff08;分布式拒绝服务&#xff09;攻击&#xff0c;已经成为游戏行业的一大威胁。今天…

Metasploit渗透测试之MSFvenom

简介 到目前为止&#xff0c;你应该已经对MSFvenom不陌生了&#xff0c;因为在之前的文章中已经介绍多次了。MSFvenom是用于生成有效攻击载荷和编码的工具。它由msfpayload和msfencode演变而来。并于2015年6月8日取代了这两者。 在本文中&#xff0c;我们将更深入地研究可用的…

MySQL进阶 - 索引

01 索引概述 【1】概念&#xff1a;索引就是一种有序的数据结构&#xff0c;可用于高效查询数据。在数据库表中除了要保存原始数据外&#xff0c;数据库还需要去维护索引这种数据结构&#xff0c;通过这种数据结构来指向原始数据&#xff0c;这样就可以根据这些数据结构实现高…

如何高效开发一套医院绩效核算系统

医院绩效核算系统是一种专为医疗机构设计的软件系统&#xff0c;旨在通过科学、系统的方法评估和核算医院内各科室及员工的绩效。该系统与医院的信息化系统紧密集成&#xff0c;特别是与医院信息系统&#xff08;HIS&#xff09;对接&#xff0c;以确保数据的准确性和实时性。 …

nginx配置多域名共用服务器80端口

nginx配置多域名共用服务器80端口 多个域名&#xff0c;比如两个域名&#xff0c;这两个域名其实共用一台服务器&#xff08;意味着域名解析到同一个IP&#xff09;&#xff0c;一个域名为abc.com&#xff08;可以是http://abc.com或者www.abc.com&#xff09;,另外一个域名为x…

腾讯地图接口报错此key每日调用量已达到上限

需要在 配额管理 的 账户额度 中进行配额的分配

安捷伦Agilent N9918A,N9918B Fieldfox手持式微波分析仪

Agilent N9918B、Keysight N9918B、HP N9918B FieldFox 手持式微波分析仪&#xff0c;26.5 GHz 配置详情&#xff1a; 233 - 频谱分析仪 350 - 实时频谱分析仪 (RTSA) 010 - 矢量网络分析仪时域 235 - 前置放大器 238 - 频谱分析仪时间选通 210 - 矢量网络分析仪传输/反射…

照片在线转成二维码展示,更方便分享图片的好办法

怎么能把照片生成二维码后&#xff0c;分享给其他人展示呢&#xff1f;现在很多人为了能够更方便的将自己的图片展现给其他人会使用生成二维码的方式&#xff0c;将图片存储到云空间&#xff0c;通过扫码调取图片查看内容。与其他方式相比&#xff0c;这样会更加的方便&#xf…

BI 在运营管理中的案例实操与策略交流会开始报名啦!

BI 在运营管理中的案例实操与策略交流会开始报名啦 点击链接报名https://www.dataondemand.cn/activity20241029

【C++差分数组】3229. 使数组等于目标数组所需的最少操作次数|2066

本文涉及知识点 C差分数组 LeetCode3229. 使数组等于目标数组所需的最少操作次数 给你两个长度相同的正整数数组 nums 和 target。 在一次操作中&#xff0c;你可以选择 nums 的任何子数组&#xff0c;并将该子数组内的每个元素的值增加或减少 1。 返回使 nums 数组变为 tar…