深入理解Java引用类型和值调用-变量在内存里存在哪里
引言
什么是数据类型?在计算机科学和计算机编程中,数据类型或简单的类型是数据的一个属性,它告诉编译器或解释器程序员打算如何使用数据。
每一种编程语言都有属于自己的数据类型,可能不尽相同,但万变不离其宗。Java中当然也有一套属于自己的数据类型,分为两大类:基础数据类型和引用数据类型,它们各自又有不同的分类。
在C语言中,关于函数的参数传递有两个专业术语:按值调用(call by value)和按引用调用(call by reference),它们和数据类型关系密切,但是,Java中只有按值调用。小编楼兰胡杨的这篇文章就和老铁们探索一下Java中的各种数据类型的分类、用途以及之间的关系等,探索java热门基础面试题按值调用(值传递)。
数据类型分类
在Java里面,整体上把数据类型分为两大类——四类八种基础类型(primitive types) 和 3种引用类型(reference types) ,分类图如下图所示:

接下来,分别介绍基本类型和引用类型。
基本类型
基本类型是Java语言预定义的类型,用相应的保留关键字来表示,具有明确的取值范围和数学行为,表示了真实的数字、字符和整数。基本类型的数据都是单个值,而不是复杂的对象,所以基本类型并不是面向对象的,这主要是出于效率方面的考虑。
基本类型包括布尔类型和数值类型,数值类型又分为整型和浮点型。八种基本数据类型分类如下:
1、整型:byte、short、int、long
2、字符型:char(本质上是一种特殊的int)
3、浮点型:float、double
4、布尔型:boolean
与此同时,Java语言也为基本类型提供了对应的对象版本,即基本类型的包装类(wrapper),具体信息如下表所示:

如果类的成员变量(字段)是基本类型,那么在类初始化时,每个成员变量将会被赋予一个如上表中默认值列所述的默认值。若为了防止因默认值引起不必要的麻烦,推荐用基本类型的包装类。
引用类型
引用类型(The value of reference types are references to objects)中的引用,一般是指某个对象的内存地址,其中对象是动态创建的类实例或者数组等。简单的说,引用其实就像是一个对象的名字或者别名 (alias),一个对象在内存中会请求一块空间来保存数据,根据对象的大小占用不相等的内存空间。
另外,Java语言本身不支持C++中的结构体(struct) 或联合体(union) 等数据类型,这种复合数据类型一般都是通过类或接口进行构造。Java中,除了基础类型外,其余的类型都是引用类型,诸如Java自带的String、BigDecimal、BigInteger以及自定义的业务类等,如下所示:
User user; //类引用类型
List<Object> list; //接口引用类型
Integer[] array; //数组引用类型
和基础数据类型变量不同的是,引用类型的变量可以被赋值为null,如下所示:
User user = null; //类引用类型
List<Object> list = null; //接口引用类型
Integer[] array = null; //数组引用类型
Java是一门面向对象的编程语言,除了基本数据类型以外,它要求每种数据类型都必须是一个类。面向对象的编程思想力图使在计算机语言中对事物的刻画与现实世界中该事物的本来面目尽可能地一致,类(class)和对象(object)就是面向对象方法的核心概念。
类是对某一类事物的描述,是抽象的、概念上的定义;对象是实际存在的该类事物的个体,因而也称为实例(Instance)。类和对象就如同概念和实物之间的关系一样,类就好比是一个模板,而对象就是该模板下的一个实例。
引用分类
引用有哪些分类?【划重点】在Java中引用包括Final reference 强引用、Soft reference 软引用、Weak reference 弱引用 和 Phantom reference 虚引用。那么,为什么会提供这四种引用?主要原因如下:
利于 JVM 进行垃圾回收;
利于开发人员灵活的决定某些对象的生命周期。
如果我们定义了不止一个引用指向同一个对象,那么这些引用是不相同的,因为引用也是一种数据类型,需要一定的内存空间(stack,栈空间)来保存。但是它们的值是相同的,都指向同一个对象在堆内存(heap,堆空间)中的位置,比如:
String a="Word";
String b="Word Two";
String b=a;

如图,b = "Word Two"; b = a; 不是改变了 "Word" 这一对象的值,初始化时,b 的值为绿线所指向的“Word Two”,然后 b=a使 b 的引用变更为红线所指向的”Word“。但要注意,String 对象的值本身是不可更改的。
数据存在哪
Java是面向对象语言,其概念为一切皆为对象,但基本数据类型特立独行。基本数据类型大多是面向机器底层的类型,它是 “值” 而不是一个对象,存放于“栈”中而非“堆”中,但Java一切皆为对象的概念绝非说说而已,它为每一个基本数据类型都提供了相应的包装类(封装器类)。包装类就是一个对象,它存放于“堆”中,下面介绍一下栈内存和堆内存的区别。
程序是运行在内存中的,也就是我们常说的电脑16g还是8g的内存。而内存空间又划分为栈内存和堆内存。
栈内存分配速度快,内存空间小。Java的基本类型和引用类型的对象引用存在栈内存中。堆内存分配速度稍慢,内存空间大。Java的引用类型指向的具体对象存在堆内存中。
基本类型使用频繁而且占用空间小,放在栈内存中。引用类型的对象引用就是堆内存中对象存放的地址。打个比喻:藏宝图里面记录着宝藏的位置,可以根据藏宝图上面标记的位置找到这些宝藏,这里藏宝图就是栈内存,存放着对象引用,而具体埋放宝藏的地方就是堆内存。
基本类型与引用类型的区别
不论是基本类型还是引用类型,都会先在栈中分配一块内存。对于基本类型来说,这块内存区域中包含的是基本类型的具体数据内容;对于引用类型来说,这块内存区域中包含的是指向真正内容的引用,而真正的内容则被分配在堆上。
所有的类型在内存中都会分配一定的存储空间,基本类型只有一块存储空间(分配在stack中), 而引用类型有两块存储空间(一块在stack中,一块在heap中);形参在使用的时候也会分配存储空间,方法调用完成之后进行垃圾回收。
值传递
首先要强调的是java中只存在值传递,只存在值传递!!! 然而我们经常看到对象(数组,类,接口)的传递似乎有点像引用传递,可以改变对象中某个属性的值。但是这样理解是一叶障目,希望老铁们不要被此假象所蒙蔽,实际上传入函数的值是对象引用的拷贝,即传递的是引用的地址值,故依然是按值传递。
函数的形参类型如果是引用类型,则调用函数时传过来的就是实参的副本,这个副本存放的是实参的引用地址。如果是基本类型,那么传过来的也是实参的副本,但此时副本存放的是实参的值,如果在函数中改变了副本的值不会改变原始的值。
为什么Java中只有按值调用,没有按引用调用?Java不支持指针,所以Java不支持引用调用。但是C/C++支持指针,故这些语言支持引用调用。
Java 方法的参数是简单类型的时候,是值传递的。这一点我们可以通过一个简单的例子来说明:
package test;
public class Test {
//交换两个形参的值
public static void swap(int a,int b){
int c=a;
a=b;
b=c;
System.out.println("a: "+a);
System.out.println("b: "+b);
}
public static void main(String[] args){
int c=10; // 实参
int d=20; // 实参
swap(c,d);
System.out.println("After swap:");
System.out.println("c: "+d);
System.out.println("d: "+c);
}
}
运行结果:
a: 20
b: 10
After swap:
c: 20
d: 10
不难看出,虽然在 swap (a,b) 方法中改变了形参的值,但对实参本身并没有影响,即对 main(String[]) 方法里的 a,b 变量没有影响。故参数类型是基本类型的时候,是按值传递的,实际上是将参数的值作了一个拷贝传进函数的,无论在函数里怎么改变其值,其结果都是只改变了拷贝的值,而不影响源值。
引用对象传递之例外null
众所周知,java中除基本类型外,参数传递的都是引用的地址。但是,有一个例外,就是当为引用类型的实参赋值null时,它依然是值传递。也就是说,传参为null的时候退化为值传递,不管函数体内用这个参数做了什么,跳出函数体后该参数依然是null。
这里澄清一些误解,null既不是对象也不是一种类型,它仅仅是一个特殊的值;你可以将其赋予任何引用类型变量,即可以将其转化成任何类型。再进一步剖析,其实是基本类型和指向null的非基本类型的引用都存在栈而非堆中,而引用类型传递的是堆内存地址。还有一种常见的、引用失效的场景是引用被改变,示例如下:
User wiener = new User();
wiener.setUserName("Wiener");
wiener.setAge(19);
wiener = new User(); // 覆盖了原来的引用
wiener.setAge(18);
log.info(wiener); // 此时打印出来时,控制台没有姓名只有年龄为18
Reference
- https://blog.csdn.net/JYKgo/article/details/116997179
- https://www.cnblogs.com/ZhangKunDeBoKe/p/16359835.html
- https://www.jianshu.com/p/26ed65e9600b
- https://www.jianshu.com/p/9a37a05a8845
深入理解Java引用类型和值调用-变量在内存里存在哪里的更多相关文章
- 深入理解Java引用类型
深入理解Java引用类型 在Java中类型可分为两大类:值类型与引用类型.值类型就是基本数据类型(如int ,double 等),而引用类型,是指除了基本的变量类型之外的所有类型(如通过 class ...
- 深入理解Java中配置环境变量
深入理解Java中配置环境变量 配置的目的: 本来只在安装JDK的bin目下能运行java.exe,javac.exe,jar.exe,javadoc.exe等Java开发工具包命令,我们现在想让在所 ...
- 深入理解Java虚拟机:垃圾收集器与内存分配策略
目录 3.2 对象已死吗 判断一个对象是否可被回收 引用类型 finalize() 回收方法区 3.3. 垃圾收集算法 1.Mark-Sweep(标记-清除)算法 2.Copying(复制)算法 3. ...
- 深入理解java:1.3.1 JVM内存区域的划分(运行时数据区)
学习Java GC机制,可以帮助我们在日常工作中 排查各种内存溢出或泄露问题,解决性能瓶颈,达到更高的并发量,写出更高效的程序. 我们将从4个方面学习Java GC机制, 1,内存是如何分配的: 2, ...
- 《深入理解Java虚拟机》学习笔记之内存分配
JVM在执行Java程序的过程中会把它所管理的内存划分若干个不同的数据区域,如下图: 大致可以分为两类:线程私有区域和线程共享区域. 线程私有区域 程序计数器(Program Counter Regi ...
- 深入理解java虚拟机(一)虚拟机内存划分
Java虚拟机在执行Java程序时,会把它管理的内存划分为若干个不同的数据区.这些区域有不同的特性,起不同的作用.它们有各自的创建时间,销毁时间.有的区域随着进程的启动而创建,随着进程结束而销毁,有的 ...
- 《深入理解java虚拟机》读书笔记1--java内存区域
Java内存管理 本文主要介绍Java虚拟机运行时的内存区域是如何划分的.Java对象的创建过程.Java对象的内存布局.Java对象的访问定位 一:运行时区域划分 主要可以分为以下 几个: 程序计数 ...
- 深入理解Java虚拟机二:垃圾收集与内存分配
垃圾收集:垃圾收集要完成三件事,包括哪些内存需要回收,什么时候回收及如何回收. 1.需要回收的内存判定:没有引用指向原先分配给某个对象的内存时,则该内存是需要回收的垃圾 Java垃圾收集器在对内存进行 ...
- 《深入理解Java虚拟机》学习笔记之内存回收
垃圾收集(Garbage Collection,GC)并不是Java语言的半生产物,事实上GC历史远比Java久远,真正使用内存动态分配和垃圾收集技术的语言是诞生于1960年的Lisp语言.经过半个世 ...
- 《深入理解Java虚拟机》——垃圾收集器与内存分配策略
GC需要完成: 哪些内存需要回收 什么时候回收 如何回收 如何确定对象不再使用 引用计数算法 给对象添加一个引用计数器,当有一个地方引用它时,计数器值进行加1操作:当引用失效时,计数器值进行减1操作: ...
随机推荐
- linux安装lspci
点击查看代码 `lspci` 是一个用于在Linux系统中显示所有PCI总线以及已连接设备信息的命令.这个工具通常包含在 `pciutils` 包里.如果你需要在你的Linux系统上安装 `lspci ...
- 【 Python 】补全fibersim 导出的xml语法
fibersim导出的xml文件中,node 和mesh部分的标签会缺失.即<R></R>变成了<R/>. 以下python脚本可以自动修正 # ********* ...
- directory 用于数据泵 导入、导出创建的目录。
1.查询directory目录 select * from dba_directories; 2.创建或者修改 directory目录 create or replace directory 目录名称 ...
- 我最常用的 Visual Studio 2022 扩展插件推荐:生产力必备工具
Visual Studio 2022作为微软推出的一款功能强大的IDE,业界称之为"宇宙第一IDE".它以出色的性能.丰富的内置功能和对多种编程语言的支持,深受开发者喜爱.然而,随 ...
- Netty源码—4.客户端接入流程
大纲 1.关于Netty客户端连接接入问题整理 2.Reactor线程模型和服务端启动流程 3.Netty新连接接入的整体处理逻辑 4.新连接接入之检测新连接 5.新连接接入之创建NioSocketC ...
- BUUCTF---传感器
题目 5555555595555A65556AA696AA6666666955 这是某压力传感器无线数据包解调后但未解码的报文(hex) 已知其ID为0xFED31F,请继续将报文完整解码,提交hex ...
- 新装的 MySQL 不允许远程连接
新装的 MySQL 通常会出现这样的情况:无法远程连接,但是本地连接是正常的. 问题原因 新装的 MySQL 通常默认的用户是 root, 而为了安全起见, root 用户是不允许远程连接. mysq ...
- 从零创建npm依赖,只需执行一条命令
由来 最近在弄新的npm依赖,但是发现没有都从头创建项目实属有点儿麻烦,然后我找了之前开发的依赖,将多余代码删除了作为初始化的项目.于是~为什么不弄个模版,每次只需要初始化模版即可,所以就有了这个模版 ...
- C#/.NET/.NET Core技术前沿周刊 | 第 32 期(2025年3.24-3.31)
前言 C#/.NET/.NET Core技术前沿周刊,你的每周技术指南针!记录.追踪C#/.NET/.NET Core领域.生态的每周最新.最实用.最有价值的技术文章.社区动态.优质项目和学习资源等. ...
- Chrome谷歌浏览器常用快捷键、开发技巧
谷歌浏览器作为常用的开发工具,熟悉常用的快捷键,不仅方便快捷,也能间接提高不少工作效率.以下是谷歌浏览器常用快捷键和开发技巧. 标签页和窗口快捷键 1. Ctrl + n 打开新窗口 2. Ctrl ...