首页 > 编程语言 >Java难绷知识03——包装器类及其自动装箱和拆箱

Java难绷知识03——包装器类及其自动装箱和拆箱

时间:2024-12-30 13:53:25浏览次数:6  
标签:03 Java 器类 包装 数据类型 自动 类型 Integer 装箱

Java难绷知识03——包装器类及其自动装箱和拆箱

本篇文章和之前的倾向稍微有些不同,这篇文章我不仅要讨论一些容易头疼的细节,而且我打算尝试讨论一下如何理解Java中的包装类以及自动拆箱和自动装箱

自动装箱(Autoboxing)和自动拆箱(Unboxing)是在基本数据类型和它们对应的包装类之间“转换”的一个包装过程,其中

装箱:基本数据类型包装成对应的包装类

拆箱:包装类拆包装成基本数据类型

自动拆装箱下,上述转换在代码中是隐式的,由编译器自动完成。

为什么Java要引入包装类,来包装起来数据类型

其实很简单,原因就是因为:Java的面向对象语言,一切面向对象

为了让基本类型也具有对象的特征,Java引入了包装器类,使得它具有了对象的性质

统一数据类型处理

基本数据类型不是对象,无法使用对象的特性,包装类将基本数据类型包装成对象,使其能够融入面向对象的编程体系

在集合框架中,如ArrayList、HashMap等,它们只能存储对象类型。如果要将基本数据类型存储到这些集合中,就需要使用对应的包装类。

使其支持多态

包装类使得基本数据类型也能参与多态的实现。通过向上转型,不同的包装类对象可以被统一处理。
例如,所有的包装类都继承自Number类(Boolean除外),可以在需要Number类型的地方使用Integer、Double等包装类对象,来满足Number的特别支持,也就是满足多态

使其支持泛型和反射机制

支持泛型
在泛型代码中,类型参数必须是引用类型,不能是基本数据类型。这与泛型的实现原理有关,在编译后,泛型类型信息会被擦除,替换为其限定的类型,所以基本数据类型无法直接参与这种类型擦除机制。引入了包装器类

用ArrayList存储整数时使用泛型作为例子

import java.util.ArrayList;
import java.util.List;

public class GenericWithWrapper {
    public static void main(String[] args) {
        // 使用Integer包装类在泛型中存储整数
        List<Integer> intList = new ArrayList<>();
        intList.add(10);
        int num = intList.get(0); // 自动拆箱
    }
}

有兴趣可以试试String丢进去会咋样瞬间爆炸

支持反射

反射允许程序在运行时获取和操作类的信息
那你基本数据类型就没法支持了,众所周知反射在java里面有多有用,所以引入了包装器类

包装类为基本数据类型提供了对应的类对象,使得可以通过反射操作基本数据类型
包装类在反射机制中为基本数据类型提供对象层面的操作能力

通过Class.forName("java.lang.Integer")获取Integer包装类的Class对象,然后利用反射机制调用其构造函数创建Integer对象,同样,也可以通过反射调用包装类的方法。

public class ReflectionInWrapper {
    public static void main(String[] args) {
        try {
            // 获取Integer类的Class对象
            Class<?> wrapperClass = Class.forName("java.lang.Integer");
            // 通过反射调用构造函数创建对象
            Object instance = wrapperClass.getConstructor(int.class).newInstance(10);
            System.out.println(instance);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

基本数据类型及其细节

为什么还要重新讲一下8 种基本数据类型,很简单,因为他们是Java语言的基础,并且在自动装箱(autoboxing)和自动拆箱(unboxing)机制中扮演着关键角色(毕竟进行的是基本数据类型和引用数据类型的“转换”)

不厌其烦的八种基本数据类型

Java是一种强类型语言,第一次变量赋值称为变量的初始化
8 种基本数据类型可以分类为如下三类:
字符类型 char
布尔类型 boolean
数值类型 byte、short、int、long、float、double

基本数据类型 所占字节数(大小) 备注
byte 1字节 表示范围 -128 到 127
short 2字节 表示范围 -32,768 到 32,767
int 4字节 范围是-2,147,483,648 (-2^31)到2,147,483,647 (2^31-1)
long 8字节 范围为-9,223,372,036,854,775,808 (-2^63)到9,223,372,036, 854,775,807 (2^63-1)
float 4字节 大约 7 位有效数字
double 8字节 大约 15 - 17 位有效数字
char 2字节 采用 Unicode 编码
boolean 通常占用 1 位

类型转换问题(向上和向下取型)

为什么上面我还要列个表格,就是要注意,在进行自动拆装箱和类型转换时,要注意数据的范围和精度问题,可能会隐藏一些类型转换错误。

在 Java 的基本数据类型中,类型转换分为自动类型转换(向上转型)和强制类型转换(向下转型)。
自动类型转换(向上转型):当把一个取值范围小的类型赋值给取值范围大的类型时,会自动进行转换。
强制类型转换(向下转型):当把一个取值范围大的类型赋值给取值范围小的类型时,需要进行强制类型转换,这可能会导致数据丢失。

当基本数据类型自动装箱为包装器类时,也遵循自动类型转换的规则,转换的是包装器类所继承的类,例如,byte 装箱为 Byte,Byte 可以自动向上转型为 Number(因为 Byte 继承自 Number)。

当从包装器类自动拆箱为基本数据类型时,如果要进行向下转型,同样需要强制类型转换。

跨类型的包装器转换:对于数值类型的包装器类,有时需要进行跨类型的转换。例如,将 Integer 转换为 Double。这需要先拆箱再装箱。

Integer intValue = 10;
// 先拆箱为int,再装箱为Double
Double doubleValueFromInt = new Double(intValue); 

boolean 类型及其包装类 Boolean 与其他基本数据类型和包装类之间不存在类型转换关系。boolean 类型只有 true 和 false 两个值,不能转换为数值类型或其他类型。

char 类型及其包装类 Character 可以与数值类型进行一些转换。
char 本质上是一个无符号的 16 位整数,所以 char 可以自动转换为 int 类型。

Character charValue = 'A';
int intValueFromChar = charValue; // 自动装箱后,Character可自动转换为int

有关溢出

在基本数据类型下,进行同类型数值运算的时候溢出并不会抛异常,也没有任何提示,需要注意
包装器类下溢出的情况代码

public class WrapperOverflowExample {
    public static void main(String[] args) {
        Integer maxInt = Integer.MAX_VALUE;
        // 尝试增加1
        Integer result = maxInt + 1;
        System.out.println("运算结果: " + result);
    }
}

以上例而言,Integer.MAX_VALUE 是 int 类型能表示的最大值。当对 maxInt 加 1 时,会发生溢出,结果变为 Integer.MIN_VALUE,这和直接使用 int 基本数据类型进行运算溢出的情况一致。
所以处理极大数的时候,我们偏向使用 BigInteger 和 BigDecimal 类

Java中的数值类型不存在无符号的,它们的取值范围是固定的

伏笔
实际上,Java中还存在另一种基本类型void,它也有对应的包装类java.lang.Void,不过他很特殊,我们无法直接对它们进行操作,这个在下面我会特意说

基本数据类型及其包装类

八种基本数据类型都分别都有对应的包装类,如下表

基本数据类型 包装类 缓存值范围
boolean java.lang.Boolean true和false
byte java.lang.Byte -128~127
char java.lang.Character 0 ~ 127
float java.lang.Float 没有缓存
int java.lang.Integer -128~127
long java.lang.Long -128~127
short java.lang.Short -128~127
double java.lang.Double 没有缓存

有关记忆:在这八个类名中,除了Integer和Character类以后,其它六个类的类名和基本数据类型一致,只是类名的第一个字母大写即可。

包装类的方法与常量

首先,使用和声明包装器类需要实例化,因为包装器类对象,需要进行实例化,才能对变量数据进行处理。
包装类提供了丰富的方法和常量方便对基本数据类型进行操作

方法

构造方法(在 Java 9 及之后不推荐使用)

Integer(int value)
Integer i = new Integer(1000);

因为我们更多使用静态工厂方法:(也就是valueOf进行装箱)
例如

valueOf(byte b):返回一个表示指定 byte 值的 Byte 实例。例如:Byte byteObj = Byte.valueOf((byte)5);

valueOf(String s):返回表示字符串指定值的相应包装类实例,其值由字符串参数解析得到

例如:Integer intFromString = Integer.valueOf("123");,但字符串必须是合法的数值表示,否则会抛出 NumberFormatException。

解析方法:

parseXxx()

该方法用于将字符串解析为对应的基本数据类型。字符串必须是合法的数值表示形式,否则会抛出 NumberFormatException

int num = Integer.parseInt("123");
double d = Double.parseDouble("3.14");

特殊的一点,在parseBoolean(String s),将字符串参数解析为 boolean 值时候

如果输入的字符串不是 "true"(不区分大小写),该方法将返回 false。

这种设计使得 Boolean.parseBoolean 方法在处理非标准布尔字符串输入时,有一个明确且一致的返回值,不会抛出异常,而是统一返回 false。


转换方法:

XxxValue()

该方法以Xxx类型返回输入的Byte、Short、Integer、Long、Float、Double 的值

例如:

shortValue()以 short 类型返回此 Short、Integer、Long、Float、Double 的值。Long l = 20L; short s = l.shortValue();

其中在Character中,还有一些字符判断方法和字符转换方法,看一下就会用,也没啥特殊之处需要注意,就不在这里说了。


常量

MIN_VALUE 和 MAX_VALUE

每个数值型包装类和Character都有这两个常量,分别表示该类型能够表示的最大值和最小值。

例如,Integer.MAX_VALUE 表示 int 类型能表示的最大整数值,Double.MIN_VALUE 表示 double 类型能表示的最小正非零值(接近零)。

在Character中,Character.MIN_VALUE 表示 char 类型能表示的最小 Unicode 代码点('\u0000'),Character.MAX_VALUE 表示 char 类型能表示的最大 Unicode 代码点('\uffff')。
在Character中,Character.MIN_VALUE和Character.MAX_VALUE分别表示所缓存的最大值


TRUE 和 FALSE

两个常量分别表示布尔值 true 和 false。它们是 Boolean 类的静态成员,用于获取对应的 Boolean 对象。

在使用 Boolean 对象时,推荐使用这两个常量,而不是通过 new Boolean(true) 或 new Boolean(false) 创建对象,因为后者会创建新的对象实例,可能会引起问题

基本数据类型和包装类需要注意的问题

缓存机制:部分包装类(如 Integer、Byte、Short、Long、Character)在一定范围内会缓存对象。就拿Integer来说,Integer缓存了 -128 到 127 之间的整数。这意味着在这个范围内,相同值的对象是共享的。

Integer a = 100; 
Integer b = 100; 
System.out.println(a == b); 
// 输出 true,因为 a 和 b 引用的是缓存中的同一个对象

Integer c = 200; 
Integer d = 200; 
System.out.println(c == d); 
// 输出 false,因为 200 超出缓存范围,c 和 d 是不同的对象

自动拆装箱

如何理解自动拆装箱

从用途上理解其实就是下述这样,自动装箱就是将基本数据类型自动转换为封装类型,自动拆箱是将封装类型自动转换为基本数据类型。

但是其实编译器的自动执行情况如下:
自动装箱,相当于Java编译器替我们执行了 Integer.valueOf(XXX);
自动拆箱,相当于Java编译器替我们执行了Integer.intValue(XXX)

自动拆装箱需要注意的问题和细节


空指针异常:

当对一个 null 值的包装类对象进行自动拆箱时,会抛出 NullPointerException;
因为自动拆箱实际是调用包装类对象的 xxxValue 方法,null 对象无法调用该方法。


装箱拆箱有开销:

自动装箱和拆箱过程涉及对象的创建与销毁,相较于直接操作基本数据类型,会带来额外的性能开销。在性能敏感的场景(如频繁的循环操作)中,应尽量减少自动拆装箱的使用。

可以用如下代码了解自动拆装箱的性能开销

long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
    Integer wrapper = i; 
    int primitive = wrapper; 
}
long endTime = System.currentTimeMillis();
System.out.println("Time taken: " + (endTime - startTime) + " ms");

方法重载与自动拆装箱:

在方法重载的情况下,自动拆装箱可能导致选择错误的方法。

例如,当有一个方法接受 int 参数,另一个方法接受 Integer 参数时,传递一个 Integer 对象可能会调用接受 Integer 参数的方法,而不是自动拆箱后调用接受 int 参数的方法。

public class AutoBoxingOverload {
    public static void print(Object obj) {
        System.out.println("Object method: " + obj);
    }

    public static void print(int num) {
        System.out.println("int method: " + num);
    }

    public static void main(String[] args) {
        Integer i = 10;
        print(i); 
        // 调用 print(Object obj) 方法,可能与预期不符
    }
}

有关void和Void

void 是 Java 中的一种特殊数据类型,它表示 “无类型” 或 “空类型”

Java不能声明 void 类型的变量,void 不能作为数组元素类型。


Void包装类:

Void 是 void 对应的包装类,它是一个不可实例化的类(其构造函数是私有的)。Void 类主要用于与 Java 反射机制和泛型等特性交互。


特殊之处如下:

Void 类没有公共的构造函数,所以无法创建 Void 类的实例。
因为 void 本身表示无值,创建 Void 实例没有实际意义。

唯一常量 TYPE:Void 类包含一个公共的静态成员 TYPE
它是一个 Class 类型的对象,用于表示 void 类型本身。

在反射中获取一个返回 void 的方法的返回类型

import java.lang.reflect.Method;
public class VoidExample {
    public void voidMethod() {}
    public static void main(String[] args) throws NoSuchMethodException {
        Method method = VoidExample.class.getMethod("voidMethod");
        if (method.getReturnType() == Void.TYPE) {
            System.out.println("The method returns void.");
        }
    }
}

不要混淆 Void 与 void:虽然 Void 是 void 的包装类,但它们的使用场景和语义有很大区别。void 用于声明方法返回类型或在特定语义中表示无值,而 Void 主要用于在需要对象表示 void 类型的场景


上一篇: Java难绷知识02——抽象类中只能有或者必须有抽象方法吗以及有关抽象类的细节探讨

下一篇:Java难绷知识04——异常处理中的 finally 块


文章个人编辑较为匆忙,或许存在各种缺陷之处,需要大家积极反馈来帮助这篇文章和我的技术知识的更进一步,感谢每一位读者
QQ:1746928194,是喜欢画画的coder,欢迎来玩!

标签:03,Java,器类,包装,数据类型,自动,类型,Integer,装箱
From: https://www.cnblogs.com/ErgouTree/p/18640849

相关文章

  • 使用 httputils + sbe (Simple Binary Encoding) 实现金融级 java rpc
    1、认识SimpleBinaryEncoding(sbe)高性能Java库Agrona的主要目标是减少性能瓶颈,通过提供线程安全的直接和原子缓冲区、无装箱操作的原始类型列表、开散列映射和集合以及锁-free队列等,为开发者在处理并发和低延迟场景时提供强大工具。SimpleBinaryEncoding(sbe)是Agr......
  • 课程思政元素收集系统|Java|SSM|JSP| 
                  【技术栈】1⃣️:架构:B/S、MVC2⃣️:系统环境:Windowsh/Mac3⃣️:开发环境:IDEA、JDK1.8、Maven、Mysql5.7+4⃣️:技术栈:Java、Mysql、SSM、Mybatis-Plus、JSP、jquery,html5⃣️数据库可视化工具:navicat6⃣️服务器:SpringBoot自带apachetom......
  • 列车票务信息系统|Java|SSM|JSP| 
                  【技术栈】1⃣️:架构:B/S、MVC2⃣️:系统环境:Windowsh/Mac3⃣️:开发环境:IDEA、JDK1.8、Maven、Mysql5.7+4⃣️:技术栈:Java、Mysql、SSM、Mybatis-Plus、JSP、jquery,html5⃣️数据库可视化工具:navicat6⃣️服务器:SpringBoot自带apachetom......
  • 基于 Java 大数据的旅游推荐系统的设计与实现
    标题:基于Java大数据的旅游推荐系统的设计与实现内容:1.摘要随着人们生活水平的提高和旅游行业的快速发展,越来越多的人选择旅游作为休闲和放松的方式。然而,在旅游过程中,人们常常面临着信息过载和选择困难的问题,不知道如何选择适合自己的旅游景点和旅游路线。为了解决这个问......
  • Java List 分片工具类
    JavaList分片工具类为了将一个大的List分组为多个小的List,每个小List的大小为50,我们可以使用Java中的subList​方法来实现。以下是详细的实现步骤和代码示例:实现步骤确定原List的大小:获取原List的大小,以便确定需要分成多少个小组。使用循环分组:使用一个循环,每次取50个元素,......
  • java容器及其并发容器的演进
    staticList<String>arrayList=newArrayList();static{for(inti=0;i<10000;i++){arrayList.add("编号:"+i);}}//会出现多线线程处理同一个元素for(inti=0;i<10;i++){......
  • java.sql.SQLException: CLI-specific condition, message from server: "Host '10.1
    您遇到的错误信息表明,MySQL服务器由于检测到来自主机'10.11.xxx.xx'的多次连接错误而自动封锁了该主机的连接请求。这是一种数据库安全机制,旨在防止潜在的恶意攻击或配置不当导致的资源滥用。要解决这个问题,您可以采取以下步骤:检查网络连接:确保客户端和服务器之间的网络稳定,并......
  • Android 兼容 Java 8 语法特性的原理分析4
       本文主要阐述了Lambda表达式及其底层实现(invokedynamic指令)的原理、Android第三方插件RetroLambda对其的支持过程、Android官方最新的dex编译器D8对其的编译支持。通过对这三个方面的跟踪分析,以Java8的代表性特性——Lambda表达式为着眼点,将Android如何兼容Java8的过程......
  • java期末总结第二章
    2.java编程基础1.标识符与命名规范标识符是给Java中的类、方法、变量、包命名的符号:只能由字母、数字、下划线、美元符号组成,并且不能以数字开头。Java标识符大小写敏感,长度无限制标识符不可以是Java关键字和保留字2.变量的定义和赋值变量赋值语法如下:inta,b,c;//声......
  • Java多线程实战避坑指南:从入门到生产实践
    在微服务架构下,多线程编程已经成为Java开发者的必备技能。本文将帮助你掌握多线程开发的核心知识,避开常见陷阱。一、为什么要深入理解多线程?1.1现实问题接口响应慢CPU利用率低内存泄漏频发死锁难以排查并发BUG难复现1.2业务场景批量数据处理并行任务执行......