热门标签 | 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






推荐阅读
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 本文详细介绍了GetModuleFileName函数的用法,该函数可以用于获取当前模块所在的路径,方便进行文件操作和读取配置信息。文章通过示例代码和详细的解释,帮助读者理解和使用该函数。同时,还提供了相关的API函数声明和说明。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • 本文探讨了C语言中指针的应用与价值,指针在C语言中具有灵活性和可变性,通过指针可以操作系统内存和控制外部I/O端口。文章介绍了指针变量和指针的指向变量的含义和用法,以及判断变量数据类型和指向变量或成员变量的类型的方法。还讨论了指针访问数组元素和下标法数组元素的等价关系,以及指针作为函数参数可以改变主调函数变量的值的特点。此外,文章还提到了指针在动态存储分配、链表创建和相关操作中的应用,以及类成员指针与外部变量的区分方法。通过本文的阐述,读者可以更好地理解和应用C语言中的指针。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • 标题: ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
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社区 版权所有