热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【Java数据结构】泛型详解+图文,通配符上界、下界

0.泛型的本质0.泛型的目的1.泛型的语法1.1泛型的使用2.包装类2.1装箱和拆箱2.2.1练习题3.泛型如何编译4.泛型的上界5.通配符5.1通配符上界5.2通配符下界有坑填坑





  • 0. 泛型的本质
  • 0. 泛型的目的
  • 1. 泛型的语法
    • 1.1 泛型的使用

  • 2. 包装类
    • 2.1 装箱和拆箱
      • 2.2.1练习题


  • 3 .泛型如何编译
  • 4.泛型的上界
  • 5. 通配符
    • 5.1通配符上界
    • 5.2通配符下界

  • 有坑填坑



0. 泛型的本质

泛型的本质:泛型是在JDK1.5引入的新的语法,通俗讲,泛型:就是适用于许多许多类型。从代码上讲,就是对类型实现了参数
化。
总的来说,泛型就是把类型参数化了,即给类型一个指定的参数,在使用时指定参数具体的值,这个类型就可以在使用的时候决定了,这样就可以编写应用于多种类型的代码。


泛型代码注释&#xff1a;List<>给String类型&#xff0c;代表List这个集合操作的是String类型的数据&#xff0c;且&#xff0c;&#61;后面的ArrayList<>&#xff0c;这里的<>里的值可以省略。

在这里插入图片描述


0. 泛型的目的

泛型的目的&#xff1a;是指定当前容器&#xff0c;要持有什么类型的对象&#xff0c;让编译器去做检查&#xff0c;检查类型的安全性&#xff0c;并且所有的强制转换都是隐式转换的。此时&#xff0c;就需要把类型&#xff0c;作为参数传递&#xff0c;提高了代码的复用性。


作用
1.安全性 2.消除转换 3. 提高性能 4. 复用性

1. 泛型的语法

class 泛型类的名称<类型形参列表>{
//代码块内使用类型参数
}
class Student<T1,T2....Tn>{
}

  • 泛型的继承

class 泛型类型名称<类型形参列表> extends 继承类<类型形参列表>
//示例
class Mike<Integer> extends Student<Integer>{

}
//可以只是用部分类型参数
class Rose<T1,T2..Tn> extends Student<T1>{

}

1.1 泛型的使用


泛型类public class Student {...;}
泛型方法public T func(T[] arr,T root){....;}
泛型接口public calss Student implements Comparable{...;}

  • 泛型类代码实例&#xff1a;
    在这里插入图片描述

T代表占位符&#xff0c;表示这个类或者是变量、方法是一个泛型&#xff0c;需要传类型参数&#xff0c;就好比一个函数有一个参数&#xff0c;你调用它需要给它传参&#xff0c;否则就会报错&#xff0c;这个T其实就是形参名&#xff0c;可以改成E、K...V、T都可以

了解&#xff1a;【命名规范】类型形参一般使用一个大写字母表示&#xff0c;常用的名称有&#xff1a;


E表示Element(元素)
K表示Key(钥匙、键)
V表示Value(值)
N表示Number(数)
T表示Type(类型)
S,U,V等第二&#xff0c;第三&#xff0c;第四个类型

  1. 注释1处&#xff1a;不能直接new泛型的数组
    我是先new一个Object数组&#xff0c;然后强制转换为T类型

T[] arr &#61; new T[10]//error

  1. 注释2处&#xff1a;类型(Student)后加入是指定当前类型,new Student<>&#xff0c;这里的<>内可加也可不加Integer
  2. 注释3处&#xff1a;不需要强制类型转换&#xff0c;Integer是包装类&#xff0c;可以自动拆箱和装箱。这也就提高了效率。
  3. 注释4处&#xff1a;代码编译报错&#xff0c;因为在注释2处指定类的当前类型是Integer&#xff0c;此时在注释4处&#xff0c;存放元素时&#xff0c;编译器就会帮助我们进行类型检查。

2. 包装类

在JAVA中&#xff0c;由于基本类型不是继承于Object&#xff0c;为了在泛型代码中可以支持基本类型&#xff0c;JAVA给每个基本类型都对应了一个包装类。


基本数据类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

  • 需要特别注意的是&#xff1a;int和char的包装类是Integer和Character&#xff0c;其他的基本类型的包装类都是基本类型首字母大写。

2.1 装箱和拆箱

int a &#61; 10;
/*
装箱操作
*/

Integer b &#61; Integer.value(a); //调用value方法
Integer c new Integer(a);//调用构造方法
/*
拆箱操作
*/

int d &#61; c.intValue();


在下面几行行代码&#xff0c;编译器自动调用了Integer.valueof()&#xff0c;属于自动装包和拆包。


在这里插入图片描述


2.2.1练习题


  • 【挖坑&#xff1a;】下列代码输出什么&#xff0c;为什么&#xff1f;

public static void main(String[] args) {
Integer a &#61; 100;
Integer b &#61; 100;
Integer c &#61; 200;
Integer d &#61; 200;
System.out.println(a &#61;&#61; b);
System.out.println(c &#61;&#61; d);
}

3 .泛型如何编译

  • 擦除机制

在编译的过程中&#xff0c;将所有的T都替换为Object的这种机制&#xff0c;称为&#xff1a;擦除机制。JAVA的泛型机制是在编译时实现的&#xff0c;编译器生成的字节码在运行期间并不包含泛型的类型信息(运行时没有泛型这么一说了)。


泛型擦除机制的文章介绍&#xff1a;
链接

在这里插入图片描述


  • 思考&#xff1a;

  1. Why&#xff1f; T[] T &#61; new T[10];是不对的。编译的时候替换为Object&#xff0c;不是相当于&#xff1a;Object[] T &#61; new Object[10];
  2. 类型擦除&#xff1a;一定把T擦除给Object吗&#xff1f;

  • 解惑&#xff1a;

解答 1.
泛型数组不能实例化

T[]擦除成了Object&#xff0c;Object是不能用其他类型接收的&#xff0c;因为Object可以存各种类型&#xff0c;把Object里的元素取出来转换成某个具体元素&#xff0c;是不安全的&#xff0c;JAVA不支持这么做。
数组和泛型之间有一个重要的区别就是&#xff1a;它们是如何强制执行类型检查。 数组在运行时存储和检查类型信息&#xff0c;而泛型是在编译时检查类型错误。
所以&#xff1a;泛型是不能实例化泛型类型数组。前面我写的T[] t &#61; (T[]) new Obeject[10];其实也是错的。
在这里插入图片描述


解答2.
一定是都擦除成了Object&#xff0c;前面我已经说过了。

4.泛型的上界

class 泛型类名称 <类型形参> extends<类型形参> {
//....
}

  • 示例&#xff1a;

    class Myarray{
    //..
    }
    class Int extends Myarray{
    //..
    }
    class Long extends Int{
    //...
    }
    public class Maths<E extends Myarray>{
    //...

    }

    解释&#xff1a;MathsE只能是Myarray的子类&#xff0c;也就是说**E接受的类型实参只能是Myarray的子类**。

  • 再看一个例子&#xff1a;

    class Student<E extends Number>{
    E[] arr;
    E Name;
    }
    public class Test {
    public static void main(String[] args) {
    Student<Integer> in;//1
    Student<String> str;//2
    }
    }

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6YVc6XOJ-1665319877790)(C:\Users\COFFEEWEN\AppData\Roaming\Typora\typora-user-images\image-20221009194623454.png)]

    **解释&#xff1a;**注释1处&#xff0c;编译正确&#xff0c;因为IntegerNumber的子类型&#xff0c;注释2处&#xff0c;编译错误&#xff0c;因为String不是Number的子类型。


    • 了解&#xff1a;Number是 java.lang包下的一个抽象类&#xff0c;提供了将包装类型拆箱成基本类型的方法&#xff0c;所有基本类型的包装类型都继承了该抽象类&#xff0c;并且是final声明不可继承改变&#xff1b;

    • 了解&#xff1a;没有指定类型边界 E&#xff0c;可以视为 E extends Object


5. 通配符
通配符&#xff1a;
?用于在泛型的使用&#xff0c;即为通配符
无边界通配符
上界的通配符&#xff0c;E为上界
下界的通配符&#xff0c;E为下界

通配符是用于解决泛型之间引用传递问题的特殊语法&#xff0c;更为灵活&#xff0c;多用于扩大参数的范围。


  • 示例&#xff1a;

class Fruits<T>{
private T fruits;

public T getFruits(){
return this.fruits;
}

public void setFruits(T fruits){
this.fruits &#61; fruits;
}
}
public class Demo {
public static void main(String[] args) {
Fruits<String> fruits1 &#61; new Fruits<>();
fruits1.setFruits("苹果");
put(fruits1);//输出

Fruits<Integer> fruits2 &#61; new Fruits<>();
fruits2.setFruits(100);
put(fruits2);
}
public static void put(Fruits<?> tmp){//1.
System.out.println("这个水果是&#xff1a;"&#43;tmp.getFruits());
tmp.setFruits("banana");//2.无法修改
tmp.setFruits(66);//3.无法修改
Fruits ret &#61; tmp.getName();//4.无法接收
}
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cXPRUoxj-1665319877791)(C:\Users\COFFEEWEN\AppData\Roaming\Typora\typora-user-images\image-20221009200552338.png)]

解释&#xff1a;


  1. 注释1&#xff1a;处 put(Fruits tmp)使用无边界通配符&#xff0c;说明它是可以接受任意类型&#xff0c;所以当不同类型的fruits1和fruits2调用put进行传参时&#xff0c;并不会报错。

  2. 注释2处、3处&#xff1a;由于是可以接受任意类型的&#xff0c;tmp是不确定类型的&#xff0c;不允许进行修改操作。比如&#xff0c;你此时传入了一个String类型的(传参时不会报错)&#xff0c;可是你代码里写的是tmp.setFruits(66)设置的是int型&#xff0c;这样是很不安全的&#xff0c;所以编译器直接不允许你这么写。

  3. 注释4处&#xff1a;传入的参数类型是不确定的&#xff0c;不能用Fruits来接收。


5.1通配符上界

class Apple extends Fruits{

}
class Banana extends Fruits{

}
class Fruits extends Meat{

}
class Meat{

}
class Food<T>{
private T name;
public T getName(){
return this.name;
}
public void setName(T name){
this.name &#61; name;
}
}
public class Demo {
public static void main(String[] args) {
Food<Apple> appleFood &#61; new Food<>();
//因为Food 给的类型是Apple&#xff0c;所以你只能给相同类型的数据
appleFood.setName(new Apple());
fun(appleFood);

Food<Banana> bananaFood &#61; new Food<>();
bananaFood.setName(new Banana());
fun(bananaFood);
}
public static void fun(Food<? extends Fruits> tmp){//1.
System.out.println(tmp.getName());
tmp.setName(new Banana());//2.无法修改
tmp.setName(new Apple());//3.无法修改
Fruits ret &#61; tmp.getName();//读取数据
}

解释&#xff1a;


  1. 注释1处&#xff1a;使用了上界通配符&#xff0c;表示可以传入的实参类型是Fruits和Fruits的子类型
  2. 注释2、3处&#xff1a;由于是不确定类型的&#xff0c;所以无法修改&#xff0c;因为tmp接收的是Fruits和它的子类&#xff0c;此时存储的元素类型应该是哪个子类&#xff0c;是无法确定的&#xff0c;所以设置会报错&#xff01;但是可以获取元素&#xff0c;因为ret的类型的Fruits&#xff0c;是所有能传入参数的父类。
  3. 通配符上界&#xff1a;可以读取数据&#xff0c;不能写入数据

5.2通配符下界

public static void main(String[] args) {
Food f1 &#61; new Food<>();
f1.setName(new Fruits());
fun(f1);

Food f2 &#61; new Food<>();
f2.setName(new Meat());
fun(f2);
}
public static void fun(Food tmp){//1.
System.out.println(tmp.getName());//只能直接输出
tmp.setName(new Banana());//2.可以修改
tmp.setName(new Fruits());//3.可以修改
Fruits ret &#61; tmp.getName();//4.不能读取数据
}

解释&#xff1a;


  1. 注释1处&#xff1a;使用的是通配符下界&#xff0c;表示接受的参数只能是Fruits或者是Fruits的父类。
  2. 注释2、3处&#xff1a;可以修改&#xff0c;因为tmp的类型是Fruits或者它的父类&#xff0c;只能设置Fruits的子类或者它本身。
  3. 注释4处&#xff1a;如果此时传入的参数是Fruits的父类&#xff0c;用Fruits类型来接收是不安全的&#xff0c;换句话说&#xff0c;传入的参数是不确定是哪个父类&#xff0c;不能接收。
  4. 通配符下界&#xff1a;不能读取数据&#xff0c;可以写入数据。

总结
无边界通配符可以传入任意实参类型&#xff0c;不能写入&#xff0c;不能读取&#xff0c;只能输出。
可以传入实参类型是上界或者上界的子类&#xff0c;不能写入&#xff0c;可以读取。
可以传入实参类型是下界或者下界的父类&#xff0c;不能读取&#xff0c;可以写入数据。

配符下界&#xff1a;不能读取数据&#xff0c;可以写入数据。


总结
无边界通配符可以传入任意实参类型&#xff0c;不能写入&#xff0c;不能读取&#xff0c;只能输出。
可以传入实参类型是上界或者上界的子类&#xff0c;不能写入&#xff0c;可以读取。
可以传入实参类型是下界或者下界的父类&#xff0c;不能读取&#xff0c;可以写入数据。

有坑填坑
  • 填坑&#xff1a;

public static void main(String[] args) {
Integer a &#61; 100;
Integer b &#61; 100;
Integer c &#61; 200;
Integer d &#61; 200;
System.out.println(a &#61;&#61; b); //1.
System.out.println(c &#61;&#61; d); //2.
}

解释&#xff1a;


  • 注释1处&#xff1a;输出true&#xff1b;
  • 注释2处&#xff1a;输出false&#xff1b;
  • Why&#xff1f;
    Integer源码有一个cache数组事先存储了【-128,127】之间的数据&#xff0c;总共256个数据&#xff0c;这些数据被static final修饰&#xff0c;是不能改变的。当赋的值是在这个区间里&#xff0c;会直接在这个数组里读取&#xff0c;也就是变量a和b&#xff0c;指向了同一个对象&#xff0c;所以输出true。当超过了这个区间&#xff0c;每次都会重新new一个对象&#xff0c;所以输出false






推荐阅读
  • 从 .NET 转 Java 的自学之路:IO 流基础篇
    本文详细介绍了 Java 中的 IO 流,包括字节流和字符流的基本概念及其操作方式。探讨了如何处理不同类型的文件数据,并结合编码机制确保字符数据的正确读写。同时,文中还涵盖了装饰设计模式的应用,以及多种常见的 IO 操作实例。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 深入理解Redis的数据结构与对象系统
    本文详细探讨了Redis中的数据结构和对象系统的实现,包括字符串、列表、集合、哈希表和有序集合等五种核心对象类型,以及它们所使用的底层数据结构。通过分析源码和相关文献,帮助读者更好地理解Redis的设计原理。 ... [详细]
  • 本文介绍如何使用Objective-C结合dispatch库进行并发编程,以提高素数计数任务的效率。通过对比纯C代码与引入并发机制后的代码,展示dispatch库的强大功能。 ... [详细]
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • 本文详细介绍了如何在Linux系统上安装和配置Smokeping,以实现对网络链路质量的实时监控。通过详细的步骤和必要的依赖包安装,确保用户能够顺利完成部署并优化其网络性能监控。 ... [详细]
  • 主要用了2个类来实现的,话不多说,直接看运行结果,然后在奉上源代码1.Index.javaimportjava.awt.Color;im ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • UNP 第9章:主机名与地址转换
    本章探讨了用于在主机名和数值地址之间进行转换的函数,如gethostbyname和gethostbyaddr。此外,还介绍了getservbyname和getservbyport函数,用于在服务器名和端口号之间进行转换。 ... [详细]
  • 本文详细介绍了 Apache Jena 库中的 Txn.executeWrite 方法,通过多个实际代码示例展示了其在不同场景下的应用,帮助开发者更好地理解和使用该方法。 ... [详细]
  • 2023年京东Android面试真题解析与经验分享
    本文由一位拥有6年Android开发经验的工程师撰写,详细解析了京东面试中常见的技术问题。涵盖引用传递、Handler机制、ListView优化、多线程控制及ANR处理等核心知识点。 ... [详细]
  • 本文详细探讨了VxWorks操作系统中双向链表和环形缓冲区的实现原理及使用方法,通过具体示例代码加深理解。 ... [详细]
  • 本教程涵盖OpenGL基础操作及直线光栅化技术,包括点的绘制、简单图形绘制、直线绘制以及DDA和中点画线算法。通过逐步实践,帮助读者掌握OpenGL的基本使用方法。 ... [详细]
  • 实体映射最强工具类:MapStruct真香 ... [详细]
  • 深入解析 Apache Shiro 安全框架架构
    本文详细介绍了 Apache Shiro,一个强大且灵活的开源安全框架。Shiro 专注于简化身份验证、授权、会话管理和加密等复杂的安全操作,使开发者能够更轻松地保护应用程序。其核心目标是提供易于使用和理解的API,同时确保高度的安全性和灵活性。 ... [详细]
author-avatar
mobiledu2502926403
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有