Java泛型思想

Java 泛型中的通配符详解 可以使用上界通配符来放宽对变量的限制。例如,你想编写一个方法,该方法适用于List, List 和 List;就可以通过使用上界通配符来实现这一点。声明一个上界通配符,需要使用通配符 ('?'),跟上 extends 关键字,然后再跟它的上界。比如,编写用于 Number 列表和 Number 子类型(如 Integer、Double 和 Float)的方法,可以指定 List 阅读详情

这篇文章是对java泛型思想的一个总结,原文地址Java总结篇系列:Java泛型

一. 泛型概念的提出(为什么需要泛型)?

首先,我们看下下面这段简短的代码:

public class GenericTest {

    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("qqyumidi");
        list.add("corn");
        list.add(100);

        for (int i = 0; i < list.size(); i++) {
            String name = (String) list.get(i); // 1
            System.out.println("name:" + name);
        }
    }
}

定义了一个List类型的集合,先向其中加入了两个字符串类型的值,随后加入一个Integer类型的值。这是完全允许的,因为此时list默认的类型为Object类型。在之后的循环中,由于忘记了之前在list中也加入了Integer类型的值或其他编码原因,很容易出现类似于//1中的错误。因为编译阶段正常,而运行时会出现“java.lang.ClassCastException”异常。因此,导致此类错误编码过程中不易发现。

在如上的编码过程中,我们发现主要存在两个问题:

1.当我们将一个对象放入集合中,集合不会记住此对象的类型,当再次从集合中取出此对象时,改对象的编译类型变成了Object类型,但其运行时类型任然为其本身类型。

2.因此,//1处取出集合元素时需要人为的强制类型转化到具体的目标类型,且很容易出现“java.lang.ClassCastException”异常。

那么有没有什么办法可以使集合能够记住集合内元素各类型,且能够达到只要编译时不出现问题,运行时就不会出现“java.lang.ClassCastException”异常呢?答案就是使用泛型。

二.什么是泛型?

泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

看着好像有点复杂,首先我们看下上面那个例子采用泛型的写法。

public class GenericTest {

    public static void main(String[] args) {
        /*
        List list = new ArrayList();
        list.add("qqyumidi");
        list.add("corn");
        list.add(100);
        */

        List<String> list = new ArrayList<String>();
        list.add("qqyumidi");
        list.add("corn");
        //list.add(100);   // 1  提示编译错误

        for (int i = 0; i < list.size(); i++) {
            String name = list.get(i); // 2
            System.out.println("name:" + name);
        }
    }
}

采用泛型写法后,在//1处想加入一个Integer类型的对象时会出现编译错误,通过List,直接限定了list集合中只能含有String类型的元素,从而在//2处无须进行强制类型转换,因为此时,集合能够记住元素的类型信息,编译器已经能够确认它是String类型了。

结合上面的泛型定义,我们知道在List中,String是类型实参,也就是说,相应的List接口中肯定含有类型形参。且get()方法的返回结果也直接是此形参类型(也就是对应的传入的类型实参)。下面就来看看List接口的的具体定义:

public interface List<E> extends Collection<E> {

    int size();

    boolean isEmpty();

    boolean contains(Object o);

    Iterator<E> iterator();

    Object[] toArray();

    <T> T[] toArray(T[] a);

    boolean add(E e);

    boolean remove(Object o);

    boolean containsAll(Collection<?> c);

    boolean addAll(Collection<? extends E> c);

    boolean addAll(int index, Collection<? extends E> c);

    boolean removeAll(Collection<?> c);

    boolean retainAll(Collection<?> c);

    void clear();

    boolean equals(Object o);

    int hashCode();

    E get(int index);

    E set(int index, E element);

    void add(int index, E element);

    E remove(int index);

    int indexOf(Object o);

    int lastIndexOf(Object o);

    ListIterator<E> listIterator();

    ListIterator<E> listIterator(int index);

    List<E> subList(int fromIndex, int toIndex);
}

我们可以看到,在List接口中采用泛型化定义之后,中的E表示类型形参,可以接收具体的类型实参,并且此接口定义中,凡是出现E的地方均表示相同的接受自外部的类型实参。

自然的,ArrayList作为List接口的实现类,其定义形式是:

public class ArrayList<E> extends AbstractList<E> 
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    public E get(int index) {
        rangeCheck(index);
        checkForComodification();
        return ArrayList.this.elementData(offset + index);
    }

    //...省略掉其他具体的定义过程

}

由此,我们从源代码角度明白了为什么//1处加入Integer类型对象编译错误,且//2处get()到的类型直接就是String类型了。

三.自定义泛型接口、泛型类和泛型方法

从上面的内容中,大家已经明白了泛型的具体运作过程。也知道了接口、类和方法也都可以使用泛型去定义,以及相应的使用。是的,在具体使用时,可以分为泛型接口、泛型类和泛型方法。

自定义泛型接口、泛型类和泛型方法与上述Java源码中的List、ArrayList类似。如下,我们看一个最简单的泛型类和方法定义:

public class GenericTest {

    public static void main(String[] args) {

        Box<String> name = new Box<String>("corn");
        System.out.println("name:" + name.getData());
    }

}

class Box<T> {

    private T data;

    public Box() {

    }

    public Box(T data) {
        this.data = data;
    }

    public T getData() {
        return data;
    }

}

在泛型接口、泛型类和泛型方法的定义过程中,我们常见的如T、E、K、V等形式的参数常用于表示泛型形参,由于接收来自外部使用时候传入的类型实参。那么对于不同传入的类型实参,生成的相应对象实例的类型是不是一样的呢?

public class GenericTest {

    public static void main(String[] args) {

        Box<String> name = new Box<String>("corn");
        Box<Integer> age = new Box<Integer>(712);

        System.out.println("name class:" + name.getClass());      // com.qqyumidi.Box
        System.out.println("age class:" + age.getClass());        // com.qqyumidi.Box
        System.out.println(name.getClass() == age.getClass());    // true

    }

}

由此,我们发现,在使用泛型类时,虽然传入了不同的泛型实参,但并没有真正意义上生成不同的类型,传入不同泛型实参的泛型类在内存上只有一个,即还是原来的最基本的类型(本实例中为Box),当然,在逻辑上我们可以理解成多个不同的泛型类型。

究其原因,在于Java中的泛型这一概念提出的目的,导致其只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。

对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。

四.类型通配符

接着上面的结论,我们知道,Box和Box实际上都是Box类型,现在需要继续探讨一个问题,那么在逻辑上,类似于Box和Box是否可以看成具有父子关系的泛型类型呢?

为了弄清这个问题,我们继续看下下面这个例子:

public class GenericTest {

    public static void main(String[] args) {

        Box<Number> name = new Box<Number>(99);
        Box<Integer> age = new Box<Integer>(712);

        getData(name);

        //The method getData(Box<Number>) in the type GenericTest is 
        //not applicable for the arguments (Box<Integer>)
        getData(age);   // 1

    }

    public static void getData(Box<Number> data){
        System.out.println("data :" + data.getData());
    }

}

我们发现,在代码//1处出现了错误提示信息:The method getData(Box) in the t ype GenericTest is not applicable for the arguments (Box)。显然,通过提示信息,我们知道Box在逻辑上不能视为Box的父类。那么,原因何在呢?

public class GenericTest {

    public static void main(String[] args) {

        Box<Integer> a = new Box<Integer>(712);
        Box<Number> b = a;  // 1
        Box<Float> f = new Box<Float>(3.14f);
        b.setData(f);        // 2

    }

    public static void getData(Box<Number> data) {
        System.out.println("data :" + data.getData());
    }

}

class Box<T> {

    private T data;

    public Box() {

    }

    public Box(T data) {
        setData(data);
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

}

这个例子中,显然//1和//2处肯定会出现错误提示的。在此我们可以使用反证法来进行说明。

假设Box在逻辑上可以视为Box的父类,那么//1和//2处将不会有错误提示了,那么问题就出来了,通过getData()方法取出数据时到底是什么类型呢?Integer? Float? 还是Number?且由于在编程过程中的顺序不可控性,导致在必要的时候必须要进行类型判断,且进行强制类型转换。显然,这与泛型的理念矛盾,因此,在逻辑上Box不能视为Box的父类。

好,那我们回过头来继续看“类型通配符”中的第一个例子,我们知道其具体的错误提示的深层次原因了。那么如何解决呢?总部能再定义一个新的函数吧。这和Java中的多态理念显然是违背的,因此,我们需要一个在逻辑上可以用来表示同时是Box和Box的父类的一个引用类型,由此,类型通配符应运而生。

类型通配符一般是使用 ? 代替具体的类型实参。注意了,此处是类型实参,而不是类型形参!且Box

public class GenericTest {

    public static void main(String[] args) {

        Box<String> name = new Box<String>("corn");
        Box<Integer> age = new Box<Integer>(712);
        Box<Number> number = new Box<Number>(314);

        getData(name);
        getData(age);
        getData(number);
    }

    public static void getData(Box<?> data) {
        System.out.println("data :" + data.getData());
    }

}

有时候,我们还可能听到类型通配符上限和类型通配符下限。具体有是怎么样的呢?

在上面的例子中,如果需要定义一个功能类似于getData()的方法,但对类型实参又有进一步的限制:只能是Number类及其子类。此时,需要用到类型通配符上限。

public class GenericTest {

    public static void main(String[] args) {

        Box<String> name = new Box<String>("corn");
        Box<Integer> age = new Box<Integer>(712);
        Box<Number> number = new Box<Number>(314);

        getData(name);
        getData(age);
        getData(number);

        //getUpperNumberData(name); // 1
        getUpperNumberData(age);    // 2
        getUpperNumberData(number); // 3
    }

    public static void getData(Box<?> data) {
        System.out.println("data :" + data.getData());
    }

    public static void getUpperNumberData(Box<? extends Number> data){
        System.out.println("data :" + data.getData());
    }

}

此时,显然,在代码//1处调用将出现错误提示,而//2 //3处调用正常。

类型通配符上限通过形如Box

Java编程思想-泛型 package com.jzg.thinkingInJava.test.T; public interface Performs { void speak(); void sit(); } package com.jzg.thinkingInJava.test.T; import com.jzg.tingkingInJava.test.RTTI.typeinfo.Dog... 阅读详情

相关推荐

微信小程序论坛开发全栈实战:从零构建社区平台

社区论坛作为互联网经典应用形态,其核心在于用户生成内容与互动关系的构建。技术实现上,通常涉及用户系统、内容发布、实时互动与数据管理等模块。采用前后端分离架构,结合数据库进行结构化存储,是实现高性能、可扩展社区平台的基础。随着移动互联网发展,轻量化、即用即走的微信小程序成为承载社区产品的理想载体,其零安装、强社交属性与完整生态能力,为论坛类应用带来了全新的增长路径与用户触达效率。本实践基于微信原生开发框架与云开发技术栈,通过设计用户登录、Markdown编辑器集成、楼中楼评论等核心功能,并融入**云函数性能优

weixin_33924220的博客 372

Java数据结构基础——泛型、通配符

6000字速学泛型及通配符

chenchenchencl的博客 4757

FANUC机器人电弧跟踪功能深度解析:从电流采样原理到ROBOGUIDE仿真避坑指南

本文深度解析FANUC机器人电弧跟踪技术,从电流采样原理到ROBOGUIDE仿真避坑指南,详细介绍了电流波形分析、实时补偿机制及现场调试技巧。针对工业焊接中的常见问题,提供从底层原理到高阶应用的解决方案,帮助工程师优化自动化焊接质量。

weixin_31785177的博客 551

Java泛型(通配符)

Java泛型通配符 在了解通配符之前,如下知识点的理解对于为何设置通配符有着很好的解释作用。 协变: 不变: 逆变: 更加详细的解释可参考文章:《几个搞不太懂的术语:逆变、协变、不变》 ...

weixin_44298385的博客 4373

TCN+iTransformer时间序列预测实战:从风电功率预测到源码解析(附完整代码)

本文深入探讨了TCN与iTransformer融合模型在时间序列预测,特别是风电功率预测中的应用。通过解析iTransformer‘倒置’视角的创新架构与TCN强大的局部时序捕捉能力,文章提供了完整的融合方案设计、源码实现及实战调优经验,旨在构建更强大的下一代预测引擎。

weixin_29062671的博客 265

Arduino+NRF24L01 无线遥控舵机与无刷电机系统搭建指南

本文详细介绍了使用Arduino和NRF24L01模块搭建无线遥控系统的完整指南,涵盖舵机和无刷电机的无线控制。从硬件选型、电路连接到代码编写与调试,提供了实用技巧和常见问题解决方案,帮助DIY爱好者低成本实现稳定可靠的无线遥控应用。

sre5engineer的博客 588

Java学习笔记(十二):泛型的使用

来源:http://www.cnblogs.com/lwbqqyumidi/p/3837629.htmlJava总结篇系列:Java泛型 一. 泛型概念的提出(为什么需要泛型)? 首先,我们看下下面这段简短的代码:public class GenericTest { public static void main(String[] args) { List list = ne

汤圆学Java 982

第12篇: COZE 实操【自动生成每日英语短视频】详细制作流程

通过以上详细流程,你可以利用 Coze 智能体高效生成每日英语短视频。在实际操作中,若遇到问题或有新的创意需求,欢迎随时与我交流,进一步优化视频制作流程。从左侧节点库中拖拽 “开始” 节点到画布中央,设置主题输入框(如输入 “日常购物英语”),方便后续根据不同主题生成视频。2. 选题策划节点​。3. 内容生成节点​。4. 画面生成节点​。5. 配音生成节点​。6. 音频匹配节点​。7. 视频合成节点​。

AI实操部署-深耕AI领域老菜鸟! 2072

JAVA 学习·泛型(二)——通配泛型

介绍了Java泛型中的重要知识点——通配泛型及其应用。

wr114514的博客 1490

超大规模AI集群跨数据中心互联:Scale-Across技术实现与部署实践

分布式系统通过将计算任务分散到多个节点协同执行,实现算力的横向扩展。其核心原理在于网络通信、数据同步和资源调度机制的技术协同。在AI基础设施领域,分布式架构能有效突破单数据中心算力瓶颈,支撑万亿参数大模型训练。特别是面对长距离传输时延和带宽稳定性挑战,需要采用RDMA、确定性网络等关键技术。Scale-Across作为分布式系统的高级形态,通过跨地域多数据中心互联,构建逻辑统一的AI算力池,为超大规模AI训练和全球推理服务提供核心支撑。本文聚焦AI集群跨数据中心互联的技术路径,涵盖全栈优化、网络自治等实践方

chouxuyi9789的博客 400

【数据结构】考点二十八 : 二叉树的概念:二叉树的度、完全二叉树、满二叉数结点数、哈夫曼结点数、完全二叉树的深度

二叉树(Binary Tree)是一种常见的数据结构,它或者为空(null),或者由一个根节点(root node)和两个子树(left subtree 和 right subtree)组成,这两个子树又分别是一棵二叉树。二叉树的每个节点最多有两个子节点,通常称为左子节点(left child)和右子节点(right child)。

超越的博客 1738

java泛型的设计思想

java泛型的设计思想1. 什么是泛型?为什么要使用泛型?1.1 使用泛型前1.2 使用泛型后2. 泛型的使用2.1 泛型类 1. 什么是泛型?为什么要使用泛型? 泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢? 顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参)...

狂风的博客 508

无迹卡尔曼滤波 (UKF, Unscented Kalman Filter) 算法详解及案例分析

无迹卡尔曼滤波是一种用于估计非线性系统状态的递归算法。其目标是通过结合系统模型和观测数据,估计系统的最优状态。

闲人编程的博客 2628

Java泛型编程思想

每天进步一点点什么是泛型自定义泛型类通配符泛型方法泛型的嵌套使用 什么是泛型 JDK1.5之后出现的新技术泛型(Generic),此技术的最大特点是类中的属性的类型可以由外部决定,泛型即“参数化类型”,一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参,那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式,然后在调用时传入具体的类型(类型实参) 自定义泛型类 在泛型接口中、泛型类和泛型方法的定义过程中,我们常见的如T、E

来了点个赞再走... 924

Cursor使用教程:用 Cursor 创建第一个 Java 项目

Cursor是一款基于VS Code的AI编程编辑器,专为开发者设计。安装前需确保JDK 8+、Maven 3.8+等环境配置,支持Windows系统。编辑器提供中文界面选项,可通过扩展市场安装语言包。创建Java项目支持手动和AI提示词两种方式:手动需创建目录结构并编写代码;AI方式则通过自然语言描述快速生成项目框架和代码。标准Java项目包含src/main/java主代码目录、src/test/java测试目录及pom.xml等配置文件。Maven项目中,pom.xml是核心配置文件,管理项目依赖与构

博客 3547

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

Java编程思想之【泛型擦除】

文章目录前言一、简单泛型1.定义2.作用3.泛型二、泛型擦除1.擦除的神秘之处迁移兼容性擦除的问题擦除补偿 前言 还记得几年去一间公司面试的时候,面试官问的技术方面的问题,其中一个就是关于擦除的问题,当时的我第一次接触面试有点紧张而且对擦除这个术语还不太了解(说白了当时我就是一个技术小白,现在也差不多啦 ╮( ̄▽  ̄)╭ ),所以支支吾吾没怎么回答上来,幸好本人人品爆表,没回答上来也顺利入职了\( ^ ▽ ^ )/。 由于当时没回答上来的尴尬情景依然历历在目,所以这次写这篇博文的目的以弥补技术上的模糊点

qq_21609191的博客 4653

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

Java编程思想 泛型总结

一般的类和方法 只能使用具体的类型 要么是基本类型 要么是自定义的类 如果要编写可以应用于多种类型的代码 这种刻板的限制对代码的束缚就会很大 与C++的比较 Java中的泛型就需要与C++进行一番比较 理由有二 首先 了解C++模板的某些方面 有助于你理解泛型的基础 同时 非常重要的一点是 你可以了解Java泛型的局限是什么 以及为什么会有这些限制 最终的目的是帮助你理解 Java泛型的边界在哪里...

勤劳的搬运工 501

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

Java编程思想之泛型

1:一般的类和方法,只能使用具体的类型,要么是自定义的类要么是基本类型,这种刻板的限制对代码的束缚就会很大。所以我们要使代码能够应用于某种不确定的具体类型,于是有了泛型的概念。 2:有很多原因促成了泛型的出现,其中一个就是容器类,定义了泛型如果加入了不符合的类型就会报编译期的错误,而不是之前可能会出现的运行时错误。 3:return语句只能返回单个对象,如果某个对象持有一堆对象呢,变相的达到返...

zxjy666的博客 274

Java中的泛型思想

一. 泛型概念的提出(为什么需要泛型)? 首先,我们看下下面这段简短的代码: public class GenericTest { public static void main(String[] args) { List list = new ArrayList(); list.add("qqyumidi"); list.ad

Miracle.Zhao的博客 348

java编程思想之list泛型

可不可以有一个需求:构造一个泛型容器,可以容纳两种数据类型未知的对象,实现添加和随机取元素 首先就想到了元组,实现多种类型的组合,随机数生成函数为Random,多的不说,贴代码 package com; import java.util.ArrayList; import java.util.Random; public class test { private ArrayList st

wugegg的专栏 561

《Java编程思想》第15章 泛型

确实能够具备更好的灵活性。但是,考虑到除了final类不能扩展,其他任何类都可以被扩展,所以这种灵活性大多数时候也会有一些性能损耗。而我们希望达到的目的是编写更通用的代码,要使代码能够应用于“某种不具体的类型”,而不是一个具体的接口或类。这样的方法更加通用一些,可应用的地方也多一些。在类的内部也是如此,因为任何实现了该接口的类都能够满足该方法,这也包括暂时还不存在的类。这就是JavaSES的重大变化之一泛型的概念。泛型实现了参数化类型的概念,使代码可以。要求你的代码必须使用特定的接口。...........

qq_43783527的博客 754

Java编程思想——泛型

一般的类和方法,只能使用具体的类型;要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。       Java面向对象的思想中多态算是一种泛化机制。如你可以将方法的参数类型设为基类,那么该方法就可以接受从这个基类中导出的任何类作为参数。这样的方法更加通用一些,可应用的地方也多一些。范式需要说明类型的地方,如果都使用基类,确实能够具备更好的灵活

sam 1023

《Java编程思想》学习笔记8——泛型编程高级

1.泛型边界: Java泛型编程时,编译器忽略泛型参数的具体类型,认为使用泛型的类、方法对Object都适用,这在泛型编程中称为类型信息檫除。 例如: class GenericType{ public static void main(String[] args){ System.out.println(new ArrayList().getClass()); System.ou

Tony Chen的专栏 1万+

java编程思想之泛型

1.泛型边界: Java泛型编程时,编译器忽略泛型参数的具体类型,认为使用泛型的类、方法对Object都适用,这在泛型编程中称为类型信息檫除。 例如: [java] view plain copy print? class GenericType{      public static void main(String[] args){          Sys

何成俭的博客 714

java 泛型思想及其应用

泛型编程:容易地编写可以应用于多种类型的代码,使得代码能够应用“某种不具体的类型”,而不是一个具体的接口或类。“泛型”就是参数化类型的意思,实现了参数化类型的概念,适用于许多的类型。 泛型的初衷是希望类或方法能够具备最广泛的表达能力。 了解某个技术可以做到什么,不能做到什么。 泛型应用之一——泛型类 public class Holder {      private T a;

chongtian_zhiying的专栏 353
上一篇: 一张图让你搞懂JavaScript的继承与原型链
下一篇: java接口思想
the__apollo
博客等级 码龄10年 90粉丝 86原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值