泛型相关知识点

目录

泛型相关知识点

1.泛型的基本用法

泛型类

泛型接口

泛型方法

2.类型擦除

补充:无限定通配符--->  ?

泛型的一个基本限制和其背后的实现机制

自动装箱

自动拆箱


泛型相关知识点

在java中 泛型是一个强大的特性 它允许在类、接口和方法中指定参数的类型  这样 类、接口和方法就可以在不指定具体类类型的情况下进行编写 直到在实例化时才指定具体的类型  这带来了几个重要的好处 包括类型安全、代码复用和清晰的代码结构

1.泛型的基本用法

泛型类

泛型类是在类定义时通过参数的类型 指定类中某些属性的类型

例如 一个简单的泛型类Box可能看起来像这样:

在这个例子中 T是一个类型参数(为了便于自己理解 我认为是参数的类型)  它可以在创建Box对象时指定

泛型接口

泛型接口与泛型类类似 但在接口中定义

例如:

泛型方法

泛型方法允许你在方法级别(返回值 参数类型....)上指定 参数的类型

例如:

2.类型擦除

Java的泛型是通过类型擦除来实现的 这意味着泛型信息只存在于编译时   而在运行时 Java虚拟机并不保留泛型参数的类型信息  因此 泛型在运行时并不提供额外的类型安全检查或类型信息  但是 你可以通过泛型来编写类型安全的代码 并在编译时检查类型错误

实例:

尽管上面的Box类使用了泛型T,但当你查看编译后的字节码时,你会发现T的类型信息并不存在。这是因为Java编译器在编译时将泛型类型信息擦除了,JVM将Box<T>视为Box类型(除非你有显式的类型边界,比如Box<T extends SomeClass>,那么T会被替换成SomeClass)。但是,由于Java的自动类型转换和类型推断机制,你仍然可以在不丢失类型安全的情况下使用泛型。

然而,需要注意的是,printClassOfT方法能够打印出t的实际类型(Integer或String),这是因为它是在运行时通过t.getClass()获取的,而不是通过泛型类型参数获取的。这证明了尽管泛型类型信息在编译期间被擦除了,但对象本身仍然保留了自己的类型信息。

类型擦除还意味着你不能在运行时检查泛型类型参数,例如你不能编写一个方法来检查Box对象是否是一个Box<Integer>类型的实例,因为所有的Box对象在运行时都被视为Box的实例  而没有任何关于T的具体信息。这是Java泛型设计的一个限制

示例

以Java为例 如果你有一个List<String>的实例 在编译后 这个实例的类型信息(即<String>部分)会被擦除  运行时JVM只会将其视为List类型  这意味着 在运行时 我们无法判断这个列表是否是存储字符串的

总结一下,在运行时,JVM将List<String>视为List类型,但它不是Object类型。类型安全主要是通过编译时的类型检查和运行时对元素的实际处理(如类型转换)来维护的。泛型信息在编译后被擦除,但编译器生成的代码和运行时检查确保了类型安全

类型擦除的详细解释

类型擦除主要指的是在编译期间 将泛型信息从代码中擦除的过程

含义

编译时处理:泛型信息(如参数的类型)只在编译阶段存在 用于编译器进行类型检查

运行时无泛型信息:在Java虚拟机运行时 泛型信息被擦除 泛型类型被当作它们的原始类型或无限定通配符(?)类型来处理

在Java的泛型实现中 无论是使用无限定通配符?还是具体的类型参数(如String)  在编译后的字节码中都会被擦除为它们的原始类型或边界类型(如果有的话)  对于无限定通配符  由于它代表任意类型  因此在类型擦除后 其行为更像是Object类型 但实际上并没有明确声明为Object

补充:无限定通配符--->  ?

使用无限定通配符 ? 的泛型类型(如List<?>)可以被视为持有任何类型的对象,这与持有Object类型对象的集合(如List<Object>)在类型兼容性上有很大的相似性。但是,它们之间有一个关键的区别:List<?>类型的集合不能添加除了null之外的任何元素,因为它是一个未知类型的列表;而List<Object>则可以添加任何类型的对象,因为所有类型都是Object的子类型

用途和限制:

无限定通配符?主要用于表示未知的类型 这在处理泛型集合时非常有用  尤其是当我们只需要从集合中读取元素而不需要添加元素时   使用?可以避免类型安全问题  因为它强制我们以类型安全的方式使用集合

相比之下 List<Object>明确声明了集合可以持有任何类型的对象 但它也允许你向集合中添加任何类型的对象 这可能会导致类型安全问题

反射

关于反射  我们接下来会出一篇文章来讲解  敬请稍后

在使用反射时  List<?>和List<Object>之间的区别变得更加明显  由于List<?>在运行时被擦除为原始类型List  因此通过反射获取的元素类型信息将是Object(或者更准确地说  是擦除后类型的元素类型  但在这个情况下是Object)  但这不意味着List<?>就是List<Object>   List<Object>在反射中会有更明确的类型信息 即Object

无限定通配符 ? 的使用案例1

在这个例子中,尽管我们知道list实际上是一个ArrayList<String>(但这只是为了说明,实际上编译器不知道),但我们不能向List<?>类型的list中添加任何除了null之外的元素。这是因为编译器在编译时无法验证这种添加操作是否类型安全。

因此,List<?>类型的集合被设计为只能读取元素(通过迭代器等),而不能添加除了null之外的任何元素。这种设计有助于防止类型错误,并确保集合的类型安全

案例2

在遍历List<?>类型的集合时 由于?代表未知类型  我们通常只能将元素作为Object类型来处理  如果我们需要将元素转换为特定类型 则应该使用instanceof检查来确保类型安全  

但在大多数情况下 当我们使用List<?>时 我们应该只是读取元素而不进行修改

泛型的一个基本限制和其背后的实现机制

在Java中 泛型的实现机制是通过类型擦除来完成的 这一机制限制了 泛型只能用于对象类型(即引用类型)  而不能直接用于基本数据类型

下面详细解释这一机制及其背后的原因

泛型与类型安全

泛型是Java SE 5中引入的一个特性 它提供了一种编译时类型安全检测机制  允许程序员在编译时期就检查到非法的类型  例如 我们可以创建一个List<String>来确保这个列表只能包含字符串类型的元素  尝试向其中添加非字符串类型的元素会导致编译失败

类型擦除

然后 泛型信息在运行时是不可用的  这是因为Java的泛型是通过类型擦除来实现的 类型擦除意味着在编译时 编译器会将泛型代码中的类型参数替换为它们的界限类型(如果有的话 通常为Object)  这个过程会移除所有的泛型类型信息  例如 List<String>在运行时会被当作List来处理 JVM不知道这个列表原本是只包含字符串的

为什么不能用于基本数据类型

由于类型擦除的存在  泛型在运行时失去了其类型信息  而基本数据类型并不是对象  它们无法继承自Object  也没有null值的概念  如果Java允许泛型使用基本数据类型  那么在类型擦除后  编译器无法将泛型类型参数替换为有效的运行时类型  因为基本数据类型无法被当作对象来处理

解决方案:自动装箱和拆箱

为了解决这个问题  Java为每一个基本数据类型都提供了对应的包装类(如Integer包装int  Double包装double等)  这些包装类都是对象  可以继承自Object  也可以拥有null值  因此  当需要使用泛型与基本数据类型一起工作时 可以使用它们的包装类来替代  这就是自动装箱和拆箱发挥作用的地方:自动装箱是将基本数据类型转换为对应的包装类对象 而自动拆箱则执行相反的操作

自动装箱

自动装箱是指将基本数据类型自动转换为对应的包装类对象  这个过程是由编译器自动完成的 无需显式调用包装类的构造方法

示例:

在上面的例子中  int类型的变量i被自动装箱成了Integer类型的对象integer  需要注意的是  自动装箱并不是简单地调用new Integer(i)  因为这样做每次都会创建一个新的对象  而自动装箱会尝试重用现有的对象(通过调用Integer.valueOf(int)方法)  这称为缓存或”池化”

对于Integer  范围在 -128到127之间的值会被缓存

自动拆箱

自动拆箱是指将包装类对象自动转换为对应的基本数据类型  这个过程同样是由编译器自动完成的  无需显式调用包装类的方法(如intValue()方法)

示例:

在上面的例子中 Integer类型的对象integer被自动拆箱成了int类型的变量i

注意事项

  1. 性能问题:虽然自动装箱和拆箱提供了方便  但它们可能会引入性能问题  因为每次装箱和拆箱操作都可能涉及到对象的创建和销毁  特别是当这些操作发生在循环或频繁调用的方法中时  性能问题可能会更加明显
  2. 空指针异常:自动拆箱可能导致NullPointerException  如果尝试拆箱一个null的包装类对象
  3. 缓存机制:对于Integer、Boolean、Byte、Character、Short和Long(对于Long和Integer  只有一定范围内的值会被缓存)  Java提供了缓存机制以减少对象创建的开销  但是  对于其他包装类(比如Double和Float)  则没有这样的缓存机制
  4. 比较:使用包装类对象进行比较时  应该注意equals()方法和 == 操作符的区别  

Equals()方法会比较两个对象的值是否相等  而==操作符会比较两个对象的引用是否相同

因此  在自动装箱的上下文中 使用==来比较两个Integer对象可能会得到意外的结果  特别是当它们位于缓存范围内时

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值