Java 包装类、泛型与类型擦除

文章目录

    • [1. 包装类](#1. 包装类)
      • [1.1 基本数据类型和其对应的包装类](#1.1 基本数据类型和其对应的包装类)
      • [1.2 装箱与拆箱](#1.2 装箱与拆箱)
      • [1.3 Integer 缓存机制](#1.3 Integer 缓存机制)
    • 2.泛型
      • [2.1 使用语法](#2.1 使用语法)
      • [2.2 泛型的"本质机制"](#2.2 泛型的”本质机制“)
        • [2.2.1 泛型只存在编译期](#2.2.1 泛型只存在编译期)
        • [2.2.2 类型擦除](#2.2.2 类型擦除)
      • [2.3 泛型的上界](#2.3 泛型的上界)

1. 包装类

1.1 基本数据类型和其对应的包装类

Java 为了把基本数据类型 当作"对象"来使用,专门提供的一组

基本类型 包装类
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

1.2 装箱与拆箱

装箱(装包):把基本数据类型变成包装类类型的过程叫做装箱(装包)。

装箱又分为自动装箱和手动装箱

实际自动装箱底层逻辑还是手动装箱

java 复制代码
public class Demo{
    public static void main(String[] args) {
        int a = 100;
        //手动装箱
        Integer ia = Integer.valueOf(a);
        Double da = Double.valueOf(a);
        System.out.println(ia);
        System.out.println(da);

        //自动装箱
        Integer iia = a;
        Double dda = (double) a;
        System.out.println(iia);
        System.out.println(dda);
    }
}

拆箱(拆包):把包装类类型变成基本数据类型的过程就叫做拆箱(拆包)。

拆箱也分为自动拆箱和手动拆箱

实际自动拆箱底层逻辑还是手动拆箱

java 复制代码
public class Demo{
    public static void main(String[] args) {
        Integer ia = 100;
        //手动拆箱
        int a = ia.intValue();
        double d = ia.doubleValue();
        System.out.println(a);
        System.out.println(d);

        //自动拆箱
        int iia = ia;
        double da = ia.doubleValue();
        System.out.println(iia);
        System.out.println(da);
    }
}

1.3 Integer 缓存机制

java 复制代码
public class Demo {
    public static void main(String[] args) {
        Integer a = 100;
        Integer b = 100;
        System.out.println(a == b);//true
        Integer c = 200;
        Integer d = 200;
        System.out.println(c == d);//false
    }
}

以上代码中涉及的是自动装包,结果的原因:

Integer.valueOf()会缓存 [-128,127]

在这个范围内:返回同一个对象

超出范围:新对象

我们可以查看 Integer 源码中的valueOf()方法

我们可以看出自动装包输入的 i 在其中是个数组的范围,我们可以查看到 IntegerCache 这样一个类中设定了 low = -128,而high最终被h = 127所赋予,因此得到该缓存数组范围 [-128,127]

我们再思考返回的数组内容,IntegerCache.cache[i + (-IntegerCache.low)],我们就拿 i = 100代入

而我们通常比较"值"的方法是使用a.equals(b)

2.泛型

泛型就是把"类型"当作参数,在编译期确定下来的一种机制

其作用是 1、解决"类型不安全"问题 ,2、消除强制类型转换(向下转型) ,3、提高代码复用能力

2.1 使用语法

泛型类

列举样例:

java 复制代码
class 泛型类名称<>{
//可使用类型参数
}

class ClassName<T1,T2,...,Tn>{
}

class 泛型类名称<类型形参列表>extends 继承类/*此处可使用类型参数*/{
}

class ClassName<T1,T2,...,Tn> extends ParentClass <T1>{
}

举例:

java 复制代码
class Box<T> {
    private T value;

    public void set(T value) {
        this.value = value;
    }

    public T get() {
        return value;
    }
}

使用如下:

java 复制代码
Box<String> box = new Box<>();
box.set("hello");
String s = box.get();

说明:T在定义类时只是占位;在创建对象时才是真正确定类型

泛型方法

java 复制代码
方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }
java 复制代码
public static <T> T getFirst(T[] arr) {
    return arr[0];
}

解释:

<T>:位于返回值之前,表示该方法定义了一个泛型类型参数 T

T:表示这个方法返回值的类型是T

使用如下:

java 复制代码
Integer i = getFirst(new Integer[]{1, 2});
String s = getFirst(new String[]{"a", "b"});

泛型接口

java 复制代码
interface Mapper<T> {
    T map(T input);
}

实现方式一(实现时确定类型):

java 复制代码
class StringMapper implements Mapper<String> {
    public String map(String input) {
        return input.toUpperCase();
    }
}

实现方式二(实现时仍保留泛型):

java 复制代码
class DefaultMapper<T> implements Mapper<T> {
    public T map(T input) {
        return input;
    }
}

2.2 泛型的"本质机制"

2.2.1 泛型只存在编译期

Java 的泛型是"伪泛型"

比如:

java 复制代码
ArrayList<String>
ArrayList<Integer>

在 JVM 看来都是:

java 复制代码
ArrayList
2.2.2 类型擦除

编译过程中,将所有的 T 替换为 Object 这种机制称为:擦除机制

java 的泛型机制是在编译级别实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息。

java 复制代码
ArrayList<String> list = new ArrayList<>();

编译后等价于:

java 复制代码
ArrayList list = new ArrayList();

只是编译器偷偷加了强制类型检查和转换

因此对于两个问题:

1、那为什么,T[] ts = new T[5]; 是不对的,编译的时候,替换为Object,不是相当于:Object[] ts = new Object[5]吗?

2、类型擦除,一定是把T变成Object吗?

问题 一:

首先 java 编译器不允许这样写,原因不是语法,而是在于类型安全。

因为数组是运行期类型检查的,数组在运行期知道自己真实的元素类型。

另外,泛型是编译期类型检查的,在运行期就已经被删除了,所以 JVM 运行期并不知道元素类型

举例:

假如T[] ts = new T[5];允许

java 复制代码
class Box<T> {
    T[] ts = new T[5]; // 假设合法
}

Box<String> box = new Box<>();
Object[] arr = box.ts; // 数组协变,合法
arr[0] = 100;          // 编译通过

首先 arrObject[],放Integer是合理的

box.ts逻辑上应该是String[],而且编译器也完全无法阻止这个错误

对于问题二:

不一定。无边界泛型 -> 擦除成 Object,因为没有任何约束,最安全的上界就是Object

如果有上界泛型 -> 擦除成"上界类型",方法级的泛型同理

举例如下:

java 复制代码
public <T extends Comparable<T>> T max(T a, T b) {
    return a.compareTo(b) > 0 ? a : b;
}

擦除后:

java 复制代码
public Comparable max(Comparable a, Comparable b) {
    return a.compareTo(b) > 0 ? a : b;
}

另外,对于类型擦除还有许多可以了解的地方

1.泛型信息不会进入 JVM 的方法签名中,也就是说不存在重载

2.数组协变+泛型不变

2.3 泛型的上界

泛型上界:

限制泛型参数"最多能是什么类型",并向编译器承诺:他至少具备某个父类或接口的能力。

使用语法

java 复制代码
class 泛型类名称<类型参数 extends 类型边界> {
}

这里的extends表示继承,也表示实现接口

举个例子:

java 复制代码
class Box<T extends Number> {
    T value;

    void print() {
        value.intValue();
    }
}

如果没有extends Number,那么编译器只知道TObjectObject没有intValue().

但是加上之后,就相当于上界告诉了编译器:T至少是NumberNumber定义了intValue()

以上是我关于Java的笔记分享,也可以关注关注我的静态网站🥰

感谢你读到这里,这也是我学习路上的一个小小记录。希望以后回头看时,能看到自己的成长~

相关推荐
小光学长2 小时前
基于ssm的膳食健康管理系统e6whl4q7(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
java·开发语言·数据库·学习·ssm
java1234_小锋2 小时前
Java高频面试题:Redis到底支不支持事务啊?
java·redis·面试
无心水2 小时前
【常见错误】2、Java并发编程避坑指南:从加锁失效到死锁,10个案例教你正确使用锁
java·开发语言·python
我爱学习好爱好爱2 小时前
Kubernetes 1.29集群上部署Java网站项目
java·容器·kubernetes
青衫码上行2 小时前
【项目开发日记 | Java架构】第一天
java·开发语言·spring cloud
至为芯2 小时前
IP2075_34S至为芯支持C口快充的30W功率AC/DC芯片
c语言·开发语言
DJ斯特拉2 小时前
自定义jar包导入maven&&注册第三方bean
java·maven·jar
AI_56782 小时前
基于智优达平台的Python教学实践:从环境搭建到自动评测
开发语言·前端·人工智能·后端·python
j_xxx404_2 小时前
力扣困难算法精解:串联所有单词的子串与最小覆盖子串
java·开发语言·c++·算法·leetcode·哈希算法