包装类型和基本类型

article/2025/8/19 1:36:02

包装类型和基本类型

  1. Java中有八种基本数据类型,分别对应着八大包装类型,因为包装类型的实例都存在于堆中,所以包装类型也称为引用类型。
    在这里插入图片描述

  2. 基本类型属于原始数据类型,变量中存储的就是原始值。包装类型属于引用数据类型,变量中存储的是存储原始值的地址的引用。

    • 基本类型中,局部变量存在方法虚拟机栈的局部变量表中,而类中声明的的变量存在堆里。
    • 包装类型中,无论局部变量还是类中声明的变量均存在堆中,而方法内的局部包装类型变量,其也存在局部变量表中,不过期值为该变量在堆中的地址。
  3. 手动装箱 / 手动拆箱

 	Integer b = valueOf(1);int a = b.intValue();
  1. 自动装箱 / 自动拆箱
   Integer b = 1;int a = b;

Java SE5 为了减少开发人员的工作,提供了自动装箱与自动拆箱的功能,3等价4。
5. == and equals

  • == 比较的是内存地址
  • equals 比较的是值
  1. 代码实例
 int a=0;int b=0;System.out.println(a==b);

按照==的分析,此处应该输出false,然而结果是true,原因在于:

当定义b时,JVM会先检查局部变量表中是否已经有了0这个值,如果没有,则创建,如果有(如之前已经执行过int a= 0),则不会再创建,而是直接将变量b指向变量a所在的局部变量表的地址,就好像执行的语句是int b = a。换句话说,a和b最终指向的内存空间,其实还是一致的

   int a = 0;int b = 0;b = b + 1;System.out.printlt(a == 1)

首先jvm会先创建一个常量 0,然后把a的引用指向0,再把b的引用指向0,当执行b=b+1的时候,运算出结果为1,jvm先不创建1这个量,而是在局部变量表中去查找是否有这个值,有就返回,无就创建,此时b的值就被指向了1,所以输出结果自然是false,当我们看下面代码

  	int a = 0;int b = 0;int c=1;b = b + 1;System.out.println(b==c);

我们已经声明了1,并把c指向这个地址,然后运行b=b+1时,jvm查找有1这个值,就把这个值的局部变量表地址赋值给了b,所以这里判断b==c应该是true;

  1. 装箱拆箱详解
    • 装箱:装箱是通过 valueOf()方法来实现自动装箱的,我们来看看源码:
public static Integer valueOf(int i) {if (i >= IntegerCache.low && i <= IntegerCache.high)return IntegerCache.cache[i + (-IntegerCache.low)];return new Integer(i);}

这里面有个IntegerChche即Integer类型的缓存,来看看内部类IntegerCache:

private static class IntegerCache {static final int low = -128;static final int high;static final Integer cache[];static {...high = h;cache = new Integer[(high - low) + 1];int j = low;for(int k = 0; k < cache.length; k++)cache[k] = new Integer(j++);// range [-128, 127] must be interned (JLS7 5.1.7)assert IntegerCache.high >= 127;}private IntegerCache() {}}

这里省略了部分内容,这个类存在的意义是将较为常用的数字存到缓存中,int类型的是 -128 - +127,我们可以看到有一个final类型、Integer类型的cache数组,然后在static块中分别对这个数组赋值,即把-128 - +127这256个数据存到cache数组里,且索引是0-255,注意,这里的cache数组是在类加载过程的初始化阶段确定的,因为在static块中,并且由于是常量,会存在元空间内 ,又由于cache这实际上是一个对象数组,所以常量池中有cache这一项引用,其指向了再堆中的数组,但是又因为是对象数组,其每一项都指向了堆中的具体的Integer对象.

然后再看valueOf,如果要装箱的值不在-128-+127之间,那么它会返回一个新的对象,这个对象是存在堆里,如果是方法内调用,那么对象的引用会存在方法的局部变量表内。

即:当我们开始运行程序的时候,-128-+127这256个数字就已经存在了堆中,用cache进行管理,如果新建一个Integer对象,其值是在此范围内,就会直接返回cache中的相对于的信息即堆中的信息,如果不在此范围内,就会新建立一个Integer对象,放在堆中,然后将其引用存在局部变量表里。

  • 拆箱:拆箱是通过intValue来实现的
  	  Integer a = 0;int b = a.intValue();

其中intValue:

   public int intValue() {return value;}

从JVM来看,先创建了一个Integer类型的变量a,其引用指向堆,当我们实现拆箱的时候,JVM会先判断
在局部变量表中是否有这个值,如果有,直接放回在局部变量表中的引用,如果没有则新建再返回。

  1. 包装类型代码详解:
   Integer a = 1;Integer b = 1;System.out.println(a==b);

由于 == 是比较地址,而Integer属于引用类型,分别建立了两个实例分别存在了堆中,所以直觉来看应该两个地址应该不同,但是结果是true,原因是cache的存在,再代码正式运行前,堆中就已经存有缓存,这里的1属于-128-+127之间,所以直接返回缓存中的值也就是说,在赋值a的时候,其实它指向了缓存中的1的引用,也就是指向了堆中,而赋值b的时候也是直接指向了缓存,所以他们两个地址是一致的。
给一个草图
在这里插入图片描述

 Integer a = 128;Integer b = 128;System.out.println(a==b);

这里由于128不属于缓存范围,所以两个语句分别建立了两个不同的对象,所以他们的内存地址也是不一样的,所以返回false,同样也给出一个草图

在这里插入图片描述

 public class test2 {Integer b=0;public static void main(String[] args) {test2 test2 = new test2();Integer a = 0;System.out.println(test2.b==a);}}

我们来看这个代码,我们再类中定义了一个Integer对象b,赋值为0,在main方法中也定义了一个Integer对象,也赋值为0,比较这两个地址,会输出什么?答案是true,原因很简单:
在我们在类中定义b时,实际上这个b存于堆中,然后指向常量池中的cache,然后cache又指向在堆中的数组,而数组的每一项均指向了堆中的Integer对象,所以可以直接说是b直接指向了0这个Integer对象,在类中定义的a,其引用存在于局部变量表中,引用指向了Integer的cache,cache指向了堆,所以它们两个的实际地址引用是一致的,都是cache在堆中的缓存.

所以我们得出了这个结论:在一个程序运行期间,无论是方法内还是方法外,其所定义的Integer,且其值是在-128-+127之间,那么他们的引用是一致的。

  int a = 1;Integer b = 1;System.out.println(a==b);

但是如果我们来比较包装类型和基本类型的地址的时候会输出什么?这里输出了true,为什么呢?
单看代码看不出来啥,我们来看看字节码:

 public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=3, locals=3, args_size=10: iconst_11: istore_12: iconst_13: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;6: astore_27: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;10: iload_111: aload_212: invokevirtual #4                  // Method java/lang/Integer.intValue:()I15: if_icmpne     2218: iconst_119: goto          2322: iconst_023: invokevirtual #5                  // Method java/io/PrintStream.println:(Z)V26: return}

这里是main方法的主要字节码,省略了部分,我们不做细讲,主要讲跟问题相关的,我们通过行号指示器来看,
首先第3行,invokestatic 表示调用了一个static方法,后面是注释,表示调用了Integer.valueOf,这里对应着代码里的定义包装类,因为定义包装类就必须进行装箱,而Java5后直接支持自动拆装箱,自动不代表不用,所以这里需要调用静态方法Integer.valueOF进行装箱,

看第7行,调用了输出流的PrintSteam方法,再来看第12行,看后面的注释,表示调用了Integer.intValue方法,这是拆箱的方法,但是我们代码中并没有拆箱,那么这段代码是怎么来的呢?

如果要使用拆箱,就必须有包装类,看代码,输出流里面只有一个包装类即b,那就说明了b调用了拆箱的方法,其主要过程是,把b的值取出,判断局部变量表中是否存在,如果存在,则返回局部变量表中的地址,如果不存在则创建再返回。

所以我们能很好的解释为什么上面的输出是true,因为,当我们使用==来比较基本类型和包装类型时,包装类型自动进行拆箱,并返回局部变量表中的引用,由于之前局部变量表中已经存在0这个值,并把a的引用执行它,当进行拆箱的时候,b的引用也指向它,如此一比较,他们的地址当然相等。

  1. equals 详解
 Integer a=128;Integer b=128;System.out.println(a.equals(b));

为什么用equals能比较值呢?来看看equals源码:

  public boolean equals(Object obj) {if (obj instanceof Integer) {return value == ((Integer)obj).intValue();}return false;}

非常简单粗暴,先判断是否属于Integer,如果是转换再进行拆箱直接判断值是否相等即可。
这个equals方法属于Object类,如果要分别实现比较不同的值就必须进行重写,所以上面的equals是Integer类重写的equals,其实现会根据不同的引用类型给予不同实现。例如 Character-char类型包装类,其equals是这样的:

  public boolean equals(Object obj) {if (obj instanceof Character) {return value == ((Character)obj).charValue();}return false;}

和Integer的equals是不一样的。


几个问题:

  1. b=b+1;1存在哪?
    当我们执行b=b+1时,b的值和1均存在于局部变量表中,不过b是以变量形式存在,即b的引用可以根据需要指向不同的值,而1是常量,直接存在局部变量表中,当我们执行加法操作时,JVM会先把b这个变量所代表的数(有可能是1、2、3等)压入操作数栈,然后再把1压入操作数栈,然后一个个出栈进行加法操作,再把结果入栈,这就完成了一个加法操作,看下面的实例:
public class test6 {public static void main(String[] args) {int a=1;a = a+1;}}

很简单一个例子,要分析就要通过字节码进行分析,字节码如下:

public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=2, args_size=10: iconst_11: istore_12: iload_13: iconst_14: iadd5: istore_16: return

省略了部分,我们来分析这个字节码的内容

第一行代表这个字节码是main方法的字节码

第二行是这个方法的描述信息,说明这个方法的参数是String类型的一维数组,其返回值是void

第三行表示控制修饰符:说明这个方法的控制描述符(AccessFlag)是public、static

第五行表示,操作数栈深度为2,局部变量表数为2,参数大小是1

剩下的就是这个main方法的具体实现过程

  • 0:iconst_1:把int类型的常量1压入到操作数栈中,对应着是代码中的 int a=1;
  • 1:istore_1:把int类型的值从操作数栈中弹出,将其放到位置为1的局部变量中;
  • 2: iload_1:将位置为1的int类型的局部变量压入栈;
  • 3: iconst_1:把int类型的常量1压入到操作数栈中,对应着是代码中的 a+1的1;
  • 4:iadd: 从操作数栈栈顶弹出两个元素然后做加法,把结果压入栈。对应着代码中的a=a+1;
  • 5:istore_1:把int类型的值从操作数栈中弹出,将其放到位置为1的局部变量中;
  • 6:return表示结束。

很容易看出:一个简单的Java程序,其原理无非就是数据再内存中入栈出栈并进行计算的过程,其常量在编译期均已经确定并存于局部变量表中,因为字节码是根据class文件反编译而成,

  1. 基本类型的内存模型是什么
    在这里插入图片描述
    方法中的基本类型如int、float等类型是直接存在局部变量表中的,类中的基本类型数据是存在堆中的。

参考资料
java中的基本数据类型和引用类型在JVM中存储在哪?
包装类和基本类型
操作数详解一
操作数详解二
自动拆箱装箱
Java基本类型详解
基本类型和包装类型的区别


http://chatgpt.dhexx.cn/article/INTZIABX.shtml

相关文章

Java 的八大基本类型及其包装类型(超级详细)

Java 中有八种内置的基本数据类型&#xff0c;他们分别是 byte、short、int、long、float、double、char 和 boolean&#xff0c;其中&#xff0c;byte、short、int 和 long 都是用来表示整数&#xff0c;float 和 double 是用来表示浮点数的&#xff0c;那它们之间有什么区别和…

Java 八大包装类(超详细!)

一、8种基本数据类型对应的包装类型名 基本数据类型包装类型bytejava.lang.Byte&#xff08;父类Number&#xff09;shortjava.lang.Short&#xff08;父类Number&#xff09;intjava.lang.Integer&#xff08;父类Number&#xff09;longjava.lang.Long&#xff08;父类Numbe…

关于InnoDB的读写锁类型以及加锁方式

&#xff08;本文为了方便&#xff0c;英文关键词都都采用小写方式&#xff0c;相关知识点会简单介绍&#xff0c;争取做到可以独立阅读&#xff09; 文章开始我会先介绍本文需要的知识点如下&#xff1a; innodb的主键索引&#xff08;一级索引&#xff09;和二级索引&#x…

C++线程中的几类锁

C线程中的几类锁 多线程中的锁主要有五类&#xff1a;互斥锁、条件锁、自旋锁、读写锁、递归锁。一般而言&#xff0c;所得功能与性能成反比。而且我们一般不使用递归锁&#xff08;C提供std::recursive_mutex&#xff09;&#xff0c;这里不做介绍。 互斥锁 互斥锁用于控制多…

Oracle - 锁

锁概念 锁出现在数据共享的环境中&#xff0c;它是一种机制&#xff0c;在访问相同资源时&#xff0c;可以防止事务之间的破坏性交互。例如&#xff0c;在多个会话同时操作某表时&#xff0c;优先操作的会话需要对其锁定。 事务的分离性要求当前事务不能影响其他的事务&#…

用友数据库错误“未能读取并闩锁页(1:3355)(用闩锁类型SH)”修复

客户硬盘无法识别&#xff0c;检测后&#xff0c;硬盘有坏道&#xff0c;由于数据库正在坏道上&#xff0c;所以恢复出来的用友数据库无法附加。 通过无日志附加后&#xff0c;做DBCC检测数据库出现以下错误&#xff1a; “消息8966&#xff0c;级别16&#xff0c;状态1&#x…

Mysql中锁的类型有哪些?

Mysql中锁的类型有哪些&#xff1f; 1. 基于锁的属性分类&#xff1a;共享锁、排他锁2. 基于锁的粒度分类&#xff1a;行级锁&#xff08;INNODB&#xff09;、表级锁&#xff08;INNODB、MYISAM&#xff09;、页级锁&#xff08;BDB引擎&#xff09;、记录锁、间隙锁、临键锁。…

mysql 常见锁的类型(一)

文章目录 一、锁的分类1.1 加锁的目的1.2 锁的类别 二、乐观锁和悲观锁2.1. 乐观锁2.2. 悲观锁&#xff1a; 三、共享锁与排他锁四、表锁五、意向锁六、行级锁七、记录锁&#xff08;Record Locks&#xff09;八、间隙锁&#xff08;Gap Locks&#xff09;九、临键锁&#xff0…

MySQL-InnoDB常用锁类型解析

Shared&#xff08;乐观锁&#xff09; and Exclusive Locks&#xff08;互斥锁&#xff09;&#xff1a; InnoDB有两种锁类型&#xff0c;Shared&#xff08;s&#xff09; and Exclusive&#xff08;x&#xff09; Locks&#xff08;乐观锁和互斥锁&#xff09;。 Shared&…

MySql InnoDB锁类型

MySql InnoDB锁类型 从类型上来分类&#xff0c;InnoDB存储引擎实现了两种标准的锁 共享锁(S-Lock)&#xff1a;允许事务读一行数据 排它锁(X-Lock)&#xff1a;允许事务删除或者更新一行数据 如果一个事务获取了S锁&#xff0c;那么其他事务也可以立即获得S锁&#xff0c;…

锁的分类总结

锁的分类是从不同角度去看的。同一个锁也可以同时属于多种类型。 一、乐观锁与悲观锁 1. 互斥同步锁的劣势 阻塞和唤醒会带来性能的劣势 用户态和核心态切换上下文切换检查是否有被阻塞线程需要被唤醒等等 可能出现永久阻塞的问题&#xff1a;持有锁的线程永久阻塞了&#…

锁的介绍和分类(轻量级锁 重量级锁 偏向锁 自旋锁 互斥锁)

目录 公平锁 非公平锁 非公平锁 公平锁 可重入锁 不可重入锁 可重入锁&#xff08;递归锁&#xff09; 不可重入锁 轻量级锁 重量级锁 偏向锁 重量级锁 自旋锁(循环上锁) 轻量级锁 轻量级锁的释放 偏向锁 自旋锁和互斥锁 自旋锁 互斥锁 为何要使用自旋锁 自旋…

最全锁种类

你可能听说过很多锁&#xff0c;也看到过很多文章讲解锁&#xff0c;这篇我在这里将对锁的不同分类进行描述锁的设计 互斥锁–共享锁 互斥锁&#xff1a;顾名思义&#xff0c;就是互斥的&#xff0c;意思就是当前同步代码块只能被一个线程访问&#xff0c;sync、reentrantlock、…

锁的类型有哪些

锁的类型有哪些 基于锁的属性分类&#xff1a;共享锁、排他锁。 基于锁的粒度分类&#xff1a;行级锁(INNODB)、表级锁(INNODB、MYISAM)、页级锁(BDB引擎 )、记录锁、间隙锁、临键锁。 基于锁的状态分类&#xff1a;意向共享锁、意向排它锁 共享锁(Share Lock) 共享锁又称读锁&…

Kettle使用教程之Job使用

1、Kettle的Job使用十分简单&#xff0c;这里也只是演示比较简单的操作&#xff0c;创建Job 2、点击转换&#xff0c;然后点击浏览&#xff0c;选择转换对象 3、执行按钮&#xff0c;运行该转换 4、如果需要长期的进行定时转换&#xff0c;可以在Job中的start控件进行配置 转载…

Kettle使用教程之数据同步

Kettle使用教程之数据同步 数据模型原型如下&#xff1a; 1、表输入&#xff0c;针对最新的数据输入的表 2、目标表&#xff0c;需要更新的表 3、两个表都需要进行排序操作 4、合并&#xff0c;根据id进行合并 5、数据同步(包括更新、插入、删除) 6、点击运行&#xff0c;就可…

ETL开发工具KETTLE使用教程

Kettle的建立数据库连接、使用kettle进行简单的全量对比插入更新&#xff1a;kettle会自动对比用户设置的对比字段&#xff0c;若目标表不存在该字段&#xff0c;则新插入该条记录。若存在&#xff0c;则更新。 Kettle简介&#xff1a;Kettle是一款国外开源的ETL工具&#xff0…

ETL工具Kettle使用教程

Kettle使用教程之数据同步 数据同步标识字段 标志字段的值有4种&#xff0c;分别是&#xff1a; “Identical” : 关键字段在新旧数据源中都存在&#xff0c;且域值相同 “changed” : 关键字段在新旧数据源中都存在&#xff0c;但域值不同 “new” : 旧数据源中没有找到该…

Kettle使用教程(一)—— 在MacOS系统中安装 Kettle

Kettle使用教程&#xff08;一&#xff09;—— 在MacOS系统中安装 Kettle 一、环境准备二、下载并启动Kettle二、初始化资源库 一、环境准备 Kettle 9.2JDK 1.8 &#xff08;安装指引&#xff09;Mysql&#xff08;安装指引&#xff09; 二、下载并启动Kettle 首先到官网下…

Kettle使用教程(问题)

关于kettle的介绍此文不做介绍 笔者电脑环境 winoraclejdk1.8kettle7.1 1. 考虑到在在官网下载速度比较慢&#xff0c;在这里可以使用国内的镜像 国内镜像 2. 配置java环境 (1) kettle需要以来java环境&#xff0c;因为没有安装java环境的朋友请移步配置java环境&#xff…