Java私房菜:探索泛型之妙用与深意

       “泛型”(generics)作为Java特性之一,相信大家也耳熟能详了,就算没听说过,也肯定看过或偶然间使用过泛型,在这里,我们一起重新温习一下泛型,通过一些案例引发些新的思考。Java中的泛型作为V1.5后新增的特性,在JDK源码、中间件源码中有大量的使用,如果掌握了泛型将更容易理解源码,也提升代码抽象能力,封装通用性更强的组件。

什么是泛型

        泛型是在定义类、接口和方法时,可以在声明时通过一定的格式指定其参数类型,在使用时再指定具体的类型,从而使得类、接口和方法可以被多种类型的数据所实例化或调用,这种可以在编译时进行参数类型检查的技术被称为泛型,是 JDK 5 中引入的一个新特性。本质是参数化类型,给类型指定一个参数,在使用时再指定此参数具体的值,那这个类型就可以在使用时决定。    

        其优点在于把运行时的错误,提前到编译时,这样就可以在编译时把错误提示出来,避免了运行时出现错误,使得在使用泛型可以提高代码的复用性,因为它可以支持多种类型的数据。

为什么要用

        在没有泛型之前,从集合中读取到的每一个对象都必须进行类型转换,如果插入了错误的类型对象,在运行时的转换处理就会出错。集合容器里面如果没指定类型,默认都是Object类型,那什么都可以插入。泛型减少了源代码中的强制类型转换,代码更加可读。

 泛型的分类

        它可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。泛型字母通常类型参数都使用大写的单个字母,例如:

          T:任意类型 type
          E:集合中元素的类型 element
          K:key-value形式 key
          V: key-value形式 value

泛型类

public class 类名 <泛型类型1,...> {
   
}

泛型接口  

public interface 接口名称 <泛型类型1,...> {
   泛型类型 方法名();
   ....
}

  泛型方法

public 修饰符 <T,E,...> 返回值类型 方法名( ){
 
 ....
}

泛型类

        泛型类型必须是引用类型,即类类型(不能使用基本数据类型),在类名后添加一对尖括号,并在尖括号中填写类型参数,如果参数可以有多个,多个参数使用逗号分隔。

定义

public class 类名 <泛型类型,...> {
   
   private 泛型类型 变量名

   public 泛型类型 方法名(){ }
   
   public 返回值 方法名(泛型类型 t){ }
   
   ....
}

使用(JDK1.7后,结尾的具体类型不用写 )

类名<具体数据类型> 对象名 = new 类名< >();

        泛型类创建的使用没有指定类型,则默认是object类型,泛型类型从逻辑上看是多个类型,实际都是相同类型,Java 可以创建对应的泛型对象和泛型数组引用,但不能直接创建泛型对象和泛型数组。 

        因为Java 有类型擦除,任何泛型类型在擦除之后就变成了 Object 类型,因此创建泛型对象就相当于创建了一个 Object 类型的对象,所以直接创建泛型对象和泛型数组也的行为被编译器禁止

public class CustomArrayStack<T> {
    private Object[]  arr;
    private int top;

    public CustomArrayStack(int capacity) {
        arr = new Object[capacity];
        top = -1;
    }

    // 入栈
    public void push(T value) {
        if (top == arr.length - 1) {
            throw new RuntimeException("栈已满,无法入栈");
        }
        arr[++top] = value;
    }

    // 出栈
    public T pop() {
        if (top == -1) {
            throw new RuntimeException("栈为空,无法出栈");
        }
        return (T)arr[top--];
    }

    // 查看栈顶元素
    public T peek() {
        if (top == -1) {
            throw new RuntimeException("栈为空,无法查看栈顶元素");
        }
        return (T)arr[top];
    }

    public static void main(String[] args) {
        CustomArrayStack<String> stringStack = new CustomArrayStack(2);
        stringStack.push("hello");
        stringStack.push("springboot");
        String stringValue = stringStack.pop();
        System.out.println(stringValue);

        CustomArrayStack<Integer> integerStack = new CustomArrayStack(2);
        integerStack.push(12);
        integerStack.push(88);
        Integer integerValue = integerStack.pop();
        System.out.println(integerValue);


        System.out.println(stringStack.getClass());
        System.out.println(integerStack.getClass());

    }
}

泛型派生类

        如果泛型类的子类也是泛型类,那父类和子类的类型要一致,如果子类泛型有多个,那需要包括父类的泛型类型。

class Child <T,E,F> extends Parent<T> {
....
}
//定义父类
public class Parent <T>{
    private T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}


//父子类需要同个类型
public class Child<T> extends Parent<T> {
    @Override
    public T getValue() {
        return super.getValue();
    }
}

//没指定则是默认Object
public class Child extends Parent {
    @Override
    public Object getValue() {
        return super.getValue();
    }
}

//如果子类不是泛型类,那父类要明确泛型类型
public class Child extends Parent<String> {
    ....
}

泛型接口

        规则和泛型类一样,如果实现类是泛型类,那接口和实现类的泛型类型要一致;如果实现类泛型有多个,那需要包括接口的泛型类型。如果实现类不是泛型类,那接口要明确泛型类的类型

interface 接口名称 <泛型类型1,...> {
   泛型类型 方法名();
   ....
}

泛型方法

        调用方法的时候指定泛型的具体类型

修饰符 <T,E,...> 返回值类型 方法名( 参数列表 ){ 
 
    方法体
    ....
}

修复符和返回值中间的有<T,E ...> 才是泛型方法 泛型类里面的普通返回值类型不是泛型方法

        泛型类的类型和泛型方法的类型是互相独立的,同名也不影响,声明了【泛型方法】在参数列表和方法体里面才可以用对应的泛型。

public class CustomArrayStack<T> {
    public <E> E getRandomElement(List<E> list){
        Random random = new Random();
        return list.get(random.nextInt(list.size()));
    }
}


public class CustomArrayStack<T> {
    public <T> T getRandomElement(List<T> list){
        Random random = new Random();
        return list.get(random.nextInt(list.size()));
    }
}


//上述两种方式定义泛型方法都可以
List<String> list = Arrays.asList("springcloud","springboot","alibabacloud");
CustomArrayStack<Integer> integerStack = new CustomArrayStack(2);

String randomElement = integerStack.getRandomElement(list);
System.out.println(randomElement);

        使用了类泛型的成员方法,不能定义为静态方法;使用了泛型方法的才可以定义为静态方法

 

 可变参数的泛型方法

  /**
     * 支持多个泛型类型的,可变参数的泛型方法
     * @param f
     * @param arr
     * @param <E>
     * @param <F>
     */
    public static  <E,F,K>  void print(F f,K k,E...arr){
        System.out.println(f.getClass());
        System.out.println(k.getClass());
        for(E e:arr){
            System.out.println(e);
        }
    }
    
    
    //验证
    print(1,"hello word",1,2,3,4,5,6);

泛型通配符

        Java泛型的通配符是用于解决泛型之间引用传递问题的特殊语法

//表示类型参数可以是任何类型
public class CustomCollection<?>{}
 
//表示类型参数必须是A或者是A的子类
public class CustomCollection<T extends A>{}
 
//表示类型参数必须是A或者是A的超类型
public class CustomCollection<T supers A>{}

分类

通配符

        通用类型通配符  < ? >,如List < ? >,主要作用就是让泛型能够接受未知类型的数据,可以把 ?看成所有泛型类型的父类,是一种真实的类型,类型通配符是实参,不是形参。

public class NumberCollection<T> {

    private T value;

    public NumberCollection(T t){
        this.value = t;
    }

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }

    public static void main(String[] args) {
        NumberCollection<Integer> integerNumberCollection = new NumberCollection<>(1);
        NumberCollection<Long> longNumberCollection = new NumberCollection<>(23L);
        printInteger(integerNumberCollection);
        printLong(longNumberCollection);

        //通用性更强
        print(integerNumberCollection);
        print(longNumberCollection);

    }

    //需要写个long类型的
    public static void printLong(NumberCollection<Long> collection){
        Number number = collection.getValue();
        System.out.println(number);
    }

    //需要写个integer类型的
    public static void printInteger(NumberCollection<Integer> collection){
        Number number = collection.getValue();
        System.out.println(number);
    }

    //使用泛型通配符,复用性更强
    public static void print(NumberCollection<?> collection){
        Object collectionValue = collection.getValue();
        System.out.println(collectionValue);
    }


}

通配符的上边界

        固定上边界的通配符 采用<? extends E>的形式,使用固定上边界的通配符的泛型,  只能够接受指定类及其子类类型的数据。采用<? extends E>的形式, 这里的E就是该泛型的上边界

这里虽然用的是extends关键字, 却不仅限于继承了父类E的子类, 也可以代指显现了接口E的类 

    //使用泛型通配符,什么类型都适合,里面具体类型需要用到Object
    public static void print(NumberCollection<?> collection){
        Object collectionValue = collection.getValue();
        System.out.println(collectionValue);
    }

    //使用泛型通配符, 固定上边界的通配符,不能任意元素,只能是Number的子类
    public static void printUp(NumberCollection<? extends Number> collection){
        Number collectionValue = collection.getValue();
        System.out.println(collectionValue);
    }


     //字符串类型,测试报错
     NumberCollection<String> stringNumberCollection = new NumberCollection<>("springboot");
     printUp(stringNumberCollection);

通配符的下边界

        固定下边界的通配符,采用<? super E>的形式,使用固定下边界的通配符的泛型, 只能够接受指定类及其父类类型的数据。采用<? super E>的形式, 这里的E就是该泛型的下边界,可以为一个泛型指定上边界或下边界, 但是不能同时指定上下边界 

//只能是integer或者integer的父类
public static void printDown(NumberCollection<? super Integer> collection){
    Object object = collection.getValue();
    System.out.println(object);
}
    
    
//测试
NumberCollection<Long> longNumberCollection = new NumberCollection<>(23L);
//报错,类型不支持,需要ineter或其父类
// printDown(longNumberCollection);
NumberCollection<Integer> integerNumberCollection = new NumberCollection<>(1);
printDown(integerNumberCollection);
NumberCollection<Number> numberCollection = new NumberCollection<>(55L);
printDown(numberCollection);

泛型类型擦除

        泛型是JDK1.5后出现的,但泛型代码和常规版本代码可以兼容,主要原因是泛型是在代码编译阶段,代码编译完成后进入JVM运行前,相关的泛型类型信息会被删除,这个即泛型类型擦除。
对于类泛型、接口泛型、方法泛型都有影响。

无限制类型擦除,擦除后都是Object类型

//没指定类型则擦除后是Object最顶级父类
public class Generic<T,K>{
    private T age;
    private K name;
    public static void main(String[] args) {
        Generic generic = new Generic<Ingeger,String>();

        //反射获取字节码文件class对象
        Class<? extends Generic> aClass = generic.getClass();

        //获取所有成员变量
        Field[] declaredFields = aClass.getDeclaredFields();
        for(Field field : declaredFields){
            //获取每个属性名称和类型
            System.out.println(field.getName() +",类型="+field.getType().getSimpleName());
        }
    }
}

//打印输出
age,类型=Object
name,类型=Object

有限制类型擦除

//T 需要是 Number的子类,所以擦除后就是Number类型,没指定类型则擦除后是Object最顶级父类
public class Generic<T extends Number ,K >{
    private T age;

    private K name;

    public static void main(String[] args) {
        Generic<Integer,String> generic = new Generic<>();

        //反射获取字节码文件class对象
        Class<? extends Generic> aClass = generic.getClass();

        //获取所有成员变量
        Field[] declaredFields = aClass.getDeclaredFields();
        for(Field field : declaredFields){
            //获取每个属性名称和类型
            System.out.println(field.getName() +",类型="+field.getType().getSimpleName());
        }

    }
}

//打印输出
age,类型=Number
name,类型=Object

泛型应用

如何创建泛型数组?

        在 Java 中是不能直接创建泛型对象和泛型数组的,主要原因是 Java 有类型擦除,任何泛型类型在擦除之后就变成了 Object 类型或者对应的上限类型,那定义的类中如果需要用到泛型数组,如何解决这个问题?

方法一:可以通过反射下的 Array.newInstance 创建泛型数组,告知指定的类型字节码即可,使得可以创建实际类型的数组。

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

    public GenericsArray(Class<T> clz , int capacity) {
        array = (T[]) Array.newInstance(clz,capacity);
    }

    public T[] getAll() {
        return array;
    }

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

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


    public static void main(String[] args) {
        GenericsArray<String> genericsArray = new GenericsArray(String.class,3);
        genericsArray.put(0,"springcloud");
        genericsArray.put(1,"springboot");
        String value = genericsArray.get(0);
        System.out.println(value);

        //下面代代码运行不报错,虽然有泛型的擦除,但在构造器中传递了类型标记Class,从擦除中恢复,使得可以创建实际类型的数组
        String[] all = genericsArray.getAll();
        System.out.println(all);

    }
}

 方法二:直接使用创建Object类型的数组,然后获取的时候,根据类型返回进行强转即可

public class GenricsArray<T> {
    private Object[] arr;

    public GenricsArray(int size) {
        arr = new Object[size];
    }

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

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

    public T[]  getArr() {
         return (T[]) arr;
     }
    public static void main(String[] args) {
        GenricsArray<String> ga = new GenricsArray<>(3);
        ga.put(0, "Hello");
        ga.put(1, "World");
        ga.put(2, "!");

        // 自动类型转换
        String[] strArr = ga.getArr();
        for (String str : strArr) {
            System.out.println(str);
        }}

}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

保研复习数据结构-图(10)

一.图的定义和基本术语 1.什么是图&#xff1f; 图(Graph)是由顶点的有穷非空集合V(G)和顶点之间边的集合E(G)组成&#xff0c;通常表示为:G(V,E)&#xff0c;其中&#xff0c;G表示图&#xff0c;V是图G中顶点的集合&#xff0c;E是图G中边的集合。 2.什么是完全图&#xf…

MySQL基础练习题:创建数据库

这部分主要是为了帮助大家回忆回忆MySQL的基本语法&#xff0c;数据库来自于MySQL的官方简化版&#xff0c;题目也是网上非常流行的35题。这些基础习题基本可以涵盖面试中需要现场写SQL的问题。 创建数据库 在开始练习之前&#xff0c;我默认你的电脑上是没有本系列练习题需要…

题目:【序列中删除指定数字】【变种水仙花数】【数组串联】【交换奇偶位】【offsetof宏的实现】

题目一:序列中删除指定数字 #include <stdio.h>int main(){int a0;int arr[50]{0};int c0;scanf("%d",&a);for(int i0;i<a;i){scanf("%d",&arr[i]);//输入a个值}scanf("%d",&c);//输入要删除的数据int i0;int j0;for(i0;i&…

【科东软件】鸿道Intewell-Win_V2.1.1_release版本正式发布

Intewell-Win_V2.1.1_release版本 版本号&#xff1a;V2.1.1 版本发布类型&#xff1a;release正式版本 版本特点 修复此前版本中的问题 运行环境推荐 Intewell developer可以运行在windows7及windows10 64位 支持硬件列表

【40分钟速成智能风控2】互联网金融信用风险

目录 ​编辑 信用风险 白名单准入 贷前识别 贷中管理 贷后催收 信用风险 白名单准入 白名单是信用风险管理的第一道门槛&#xff0c;与整个平台贷款产品的设计和定位有紧密的联系。白名单设立的初衷是圈定目标客户&#xff0c;有了目标客群之后才能更好地进行精准营销&…

非关系型数据库——三万字Redis数据库详解

目录 前言 一、Redis概述 1.主要特点 2.Redis优缺点 3.Redis为什么这么快 4.Redis那么快&#xff0c;为什么不用它做主数据库&#xff0c;只用它做缓存 5.线程模型 5.1单线程架构 5.2多线程IO处理&#xff08;Redis 6及以上&#xff09; 5.3线程模型的优化 6.作用 …

基于SpringBoot+微信小程序的点餐系统

一、项目背景介绍&#xff1a; 小程序外卖扫码点餐为客户提供的是最方便的饮食方式,以快速、便捷的点餐业务送货上门为 -客户服务,这省去了客户很多不必要的时间和麻烦,给商家带来更多利益。同时,小程序外卖扫码点餐可以辅助餐饮企业营销,通过信息管理,可以记录餐饮企业方方面面…

Linux--进程(2)

目录 前言 1. 进程的状态 1.1 进程排队 1.2 运行&#xff0c;阻塞&#xff0c;挂起 2.Linux下具体的进程状态 2.1僵尸和孤儿 3.进程的优先级 4.Linux的调度与切换 前言 这篇继续来学习进程的其它知识 上篇文章&#xff1a;Linux--进程&#xff08;1&#xff09;-CS…

挑战30天C++基本入门(DAY8--树)[part 3](速通哦~)

#上一章我们把搜索二叉树的知识给传授完毕&#xff0c;如果认真的看下去并且手打了几遍&#xff0c;基本上内部的逻辑还是可以理解的&#xff0c;那我们现在就截至继续学习树的一些重要知识啦~~ 树高怎么求呀&#xff1f;如果用上一次学的层次遍历来求树高&#xff0c;有点小题…

笔记 | 软件工程:需求分析

1 需求分析 #需求分析 1.1 需求分析概述 初步软件需求存在的问题&#xff1a;不具体&#xff0c;不清晰&#xff0c;关系不明朗&#xff0c;存在潜在缺陷&#xff0c;没有区分不同软件需求&#xff08;有必要鉴别不同软件需求项的重要性差别&#xff0c;区分不同软件需求的开…

图数据库技术:知识图谱的存储与查询

图数据库技术&#xff1a;知识图谱的存储与查询 一、引言 在探索知识的宇宙中&#xff0c;知识图谱是组织和理解海量信息的星系图。在这张图中&#xff0c;每一个概念、实体与事物不再是孤立的点&#xff0c;而是通过关系与边相互连接&#xff0c;形成一个复杂而有机的网络。图…

Kubesphere在创建服务的添加容器步骤搜索镜像步骤找不到镜像

Kubesphere在创建服务的添加容器步骤搜索镜像步骤找不到镜像 {"status": "failed","message": "invalid character p after top-level value" }添加了标签也没用&#xff08;如&#xff1a;mysql:5.7&#xff09; 可以看到 dockerhu…

再聊一聊AUC指标

关于模型评估的指标&#xff0c;之前已经写过不少这方面的文章&#xff0c;最近在实践中又有了一点新的思考&#xff0c;本文对模型评估中的AUC指标再进行一些简单的探讨。 情况一&#xff0c;以下图中的数据为例&#xff0c;1代表用户发生逾期&#xff0c;标记为坏样本&#x…

定时器测试:用定时器监控定时器

using System; using System.Timers;namespace TestTimer {internal class Program{private static int usingResource 0;static int m 0;static Timer timerTask new Timer();static Timer timerMonitor new Timer();static void Main(string[] args){//任务 定时器timerT…

金三银四面试题(十六):MySQL面试都问什么(1)

在开发岗位面试中&#xff0c;MySQL基本是必考环节。所以接下来我们就进入MySQL八股文环节&#xff0c;看看都有哪些高频考题。 MySQL 中有哪些不同的表格&#xff1f; 在MySQL中&#xff0c;可以创建多种不同类型的表格&#xff0c;其中一些常见的类型包括&#xff1a; InnoD…

js笔记(学习存档)

JS的调用方式与执行顺序 使用方式 HTML页面中的任意位置加上<script type"module"></script>标签即可。 常见使用方式有以下几种&#xff1a; 直接在<script type"module"></script>标签内写JS代码。直接引入文件&#xff1a;…

GPT-5将在6月发布前进行「红队进攻测试」

“GPT-5将在6月发布”的消息刷屏了AI朋友圈。这则消息之所以被无数人相信并转发&#xff0c;是因为已经有不少技术人员在社交平台上晒出了「红队进攻测试」邀请。 基于 GPT系列庞大的用户体量和影响力&#xff0c;OpenAI 将更加重视GPT-5 的安全性&#xff0c;作为GPT-5上市前的…

2024年C语言最新经典面试题汇总(21-30)

C语言文章更新目录 C语言学习资源汇总&#xff0c;史上最全面总结&#xff0c;没有之一 C/C学习资源&#xff08;百度云盘链接&#xff09; 计算机二级资料&#xff08;过级专用&#xff09; C语言学习路线&#xff08;从入门到实战&#xff09; 编写C语言程序的7个步骤和编程…

OpenAI 推出新网络爬虫GPTBot,为GPT-5做准备

目录 一、GPTBot是什么&#xff1f;它是如何工作的&#xff1f;二、GPTBot 与 Google Bot 等搜索引擎网络爬虫有何不同&#xff1f;三、GPTBot 与 Perplexity AI 的网络爬虫有何不同&#xff1f;四、允许 GPTBot 爬取有哪些风险和好处&#xff1f;4.1 允许 GPTBot 的好处4.2 允…

PostgreSQL:所有支持的数据类型及建表语句实例

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 一、引言 在当今这个数据驱动的时代&#xff0c;数据库已经成为了企业和个人不可或缺的工具。而在众多数据库产品中&#xff0c;PostgreSQL以其强大的功能和高度的可扩展性&#xff0c;受到了越来越多开发者的青睐。…