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






推荐阅读
  • 在前文探讨了Spring如何为特定的bean选择合适的通知器后,本文将进一步深入分析Spring AOP框架中代理对象的生成机制。具体而言,我们将详细解析如何通过代理技术将通知器(Advisor)中包含的通知(Advice)应用到目标bean上,以实现切面编程的核心功能。 ... [详细]
  • 本文详细探讨了Java事件处理机制的核心概念与实现原理,内容浅显易懂,适合初学者逐步掌握。通过具体的示例和详细的解释,读者可以深入了解Java事件模型的工作方式及其在实际开发中的应用。 ... [详细]
  • 本指南从零开始介绍Scala编程语言的基础知识,重点讲解了Scala解释器REPL(读取-求值-打印-循环)的使用方法。REPL是Scala开发中的重要工具,能够帮助初学者快速理解和实践Scala的基本语法和特性。通过详细的示例和练习,读者将能够熟练掌握Scala的基础概念和编程技巧。 ... [详细]
  • 本文深入解析了Java 8并发编程中的`AtomicInteger`类,详细探讨了其源码实现和应用场景。`AtomicInteger`通过硬件级别的原子操作,确保了整型变量在多线程环境下的安全性和高效性,避免了传统加锁方式带来的性能开销。文章不仅剖析了`AtomicInteger`的内部机制,还结合实际案例展示了其在并发编程中的优势和使用技巧。 ... [详细]
  • 本文探讨了 Java 中 Pair 类的历史与现状。虽然 Java 标准库中没有内置的 Pair 类,但社区和第三方库提供了多种实现方式,如 Apache Commons 的 Pair 类和 JavaFX 的 javafx.util.Pair 类。这些实现为需要处理成对数据的开发者提供了便利。此外,文章还讨论了为何标准库未包含 Pair 类的原因,以及在现代 Java 开发中使用 Pair 类的最佳实践。 ... [详细]
  • 在Android开发过程中,序列化是一个重要的概念,尤其是在数据传输和存储时。本文详细解析了Parcelable序列化的原理及其应用场景,并对比了其他序列化方式,如Serializable。通过具体的实例和代码示例,帮助开发者更好地理解和掌握Parcelable的使用方法,避免在实际开发和面试中遇到相关问题。 ... [详细]
  • Spring框架的核心组件与架构解析 ... [详细]
  • SQLite数据库CRUD操作实例分析与应用
    本文通过分析和实例演示了SQLite数据库中的CRUD(创建、读取、更新和删除)操作,详细介绍了如何在Java环境中使用Person实体类进行数据库操作。文章首先阐述了SQLite数据库的基本概念及其在移动应用开发中的重要性,然后通过具体的代码示例,逐步展示了如何实现对Person实体类的增删改查功能。此外,还讨论了常见错误及其解决方法,为开发者提供了实用的参考和指导。 ... [详细]
  • 设计实战 | 10个Kotlin项目深度解析:首页模块开发详解
    设计实战 | 10个Kotlin项目深度解析:首页模块开发详解 ... [详细]
  • 面向切面编程(AOP)是Spring框架的两大核心概念之一,另一个核心概念是控制反转(IoC)。AOP通过在应用程序中分离横切关注点,如日志记录、事务管理和安全性,从而提高代码的模块化和可维护性。本文将深入探讨AOP的核心概念和术语,帮助读者更好地理解和应用这一重要技术。 ... [详细]
  • 本文深入探讨了CGLIB BeanCopier在Bean对象复制中的应用及其优化技巧。相较于Spring的BeanUtils和Apache的BeanUtils,CGLIB BeanCopier在性能上具有显著优势。通过详细分析其内部机制和使用场景,本文提供了多种优化方法,帮助开发者在实际项目中更高效地利用这一工具。此外,文章还讨论了CGLIB BeanCopier在复杂对象结构和大规模数据处理中的表现,为读者提供了实用的参考和建议。 ... [详细]
  • 技术分享:深入解析GestureDetector手势识别机制
    技术分享:深入解析GestureDetector手势识别机制 ... [详细]
  • 本文深入探讨了JavaScript中`this`关键字的多种使用方法和技巧。首先,分析了`this`作为全局变量时的行为;接着,讨论了其在对象方法调用中的表现;然后,介绍了`this`在构造函数中的作用;最后,详细解释了通过`apply`等方法改变`this`指向的机制。文章旨在帮助开发者更好地理解和应用`this`关键字,提高代码的灵活性和可维护性。 ... [详细]
  • iOS 设备唯一标识获取的高效解决方案与实践
    在iOS 7中,苹果公司再次禁止了对MAC地址的访问,使得开发者无法直接获取设备的物理地址。为了在开发过程中实现设备的唯一标识,苹果推荐使用Keychain服务来存储和管理唯一的标识符。此外,还可以结合其他技术手段,如UUID和广告标识符(IDFA),以确保设备的唯一性和安全性。这些方法不仅能够满足应用的需求,还能保护用户的隐私。 ... [详细]
  • 深入解析 Java UTC 时间处理技术与应用 ... [详细]
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社区 版权所有