Java 的引用和对象

news2024/10/6 12:16:36

「是否完全掌握 Java 的引用?」是 Java 基础是否入门的重要标志,甚至没有之一。
相较于其它的 Java 基础问题,其它问题都是语法层面的熟悉度、熟练度的问题,基本是不用
动脑子思考的,直接怼就完事了。只有「Java 的引用」在概念上需要反复揣摩思考,才能彻
底掌握。
Java 的引用的难以理解和掌握,原因在于「它一脉相承与 C 语言的指针」,而 C 语言的指针
是很多 C 程序员的学习历程中的必备槽点。

1.从 C 的指针变量说起

这里我们讲 C 语言的指针变量只是为了讲清楚「引用概念」的来龙去脉,以及为大家找
到一个概念上的参照物。为了不给大家引入新的学习负担,相关的 C 语言的具体语法我
们这里少讲、不讲,以讲道理、讲概念为主。

1.1 变量

「变量」是所有的编程语言中都会涉及到的最最基本的概念。有的书为了便于零基础的人学
习,会举一个很贴切的例子:变量就像一个「盒子」,为一个变量赋值,就如同你往这个盒子
中存放东西。
之所以用盒子来类比变量,是因为一个变量意味着内存中的一块内存空间,为变量赋值,就是
为这块内存赋值,在这块内存上填入数据。而我们常说的「变量名」实际上就是这块内存的一
个「代号」 (你可以这么通俗地类比) 。

1.2 强类型语言:变量的类型

 C (以及 C++、Java 等) 语言都是「强类型」语言。所谓的强类型语言的标志就是「变量是有类
型的」,也就是说,变量的类型和变量中所存储的值必须一致。
以上面类比的「盒子」为例,这相当于说,这是一个放苹果的盒子,那么,无论未来这个盒子
中放的是一个大苹果,还是一个小苹果;放的是一个红苹果,还是一个绿苹果;放的是一个进
口苹果,还是一个本地苹果;这都是次要问题,最最基本一个问题是:是这个盒子里必须放苹
果!放橘子、橙子、梨子、香蕉都不行。

int i = 'a'; // 此处会报语法错误

以具体的代码为例,上述代码就是错误的:变量 i 的类型是 int 类型,那么变量 i 中必须存放
整型的数据,例如:10,9527,10086 等,而你将一个字符 'a' 赋值给它,那么值的类型和
变量的类型不一样,这就是一个语法错误。

1.3 特殊类型的变量:指针变量

上面说到过,变量就代表着一块内存空间,变量名就相当于是这块内存空间的代号。除了通过
变量名能让你找到那块内存,进而操作它之外,还有一样东西能让你找到这块内存,进而操作
它:内存的地址 。
道理很简单。你想,现实生活中,不考虑同名的情况,你除了用「人名」找得到一个人之外,
你如果有这个人的「身份证号」或者说「手机号」,那么,你一样也能找得到这个人,进而做
后续的操作。
上面例子中的「人名」就相当于是变量名,「身份证号」/「手机号」就相当于是变量的地
址,而「人」就相当于是变量背后的那块实实在在的用于存储数据的内存空间。
看到这里你会发现一个问题:变量背后的那块内存的地址也是数据!地址本身也就是一串数
字! 我们完全完全可以将这串数字也存储于一个变量中。
例如,现实生活中,我们完全可以将一个人的身份证号/手机号,抄在一张便签纸上!
「此处欠图一张,后续补」
结合上面所讲到的变量类型的概念,在 C 语言中,这种专门用于存放内存的地址的变量就是
「指针变量」。
简单来说,结合变量的类型和指针变量两个概念一起考虑,类似于以下情况:

  • 有的便签纸上只允许写汉字,至于是什么汉字内容都可以,但必须是汉字;
  • 有的便签纸上只允许写英语,至于是什么英语内容都可以,但必须是英语;
  • 有些便签纸上只允许写拼音,至于是什么拼音内容都可以,但必须是拼音;
  • 而有一种/一类便签纸,它上面只允许写人的手机号,至于是谁的手机号都可以,但必须是

手机号。

1.4 一个容易忽视的问题:一个地址,两个指针变量

现实中,我们完全可以做这样的一件事情:在两张「便签纸」上抄写同一个人的「手机号」。
更具体更形象的说法可以是这样,我们先将这个人的手机号抄写在一张变签纸上,让后再拿出
第二张便签纸,将第一张便签纸上的内容原样抄写一遍。
这是生活中很正常的操作,看到这里大家一定会觉得这个操作好像没什么值得说道的地方
呀?!
如果你能接受上述现实世界中的试试,那么你一定可以理解代码中的这种情况:有两个指针类
型的变量,它们俩记录的都是同一块内存的地址。
别说,两个便签纸/变量记录同一个手机号/内存地址,就是有百八十个的也是合情合理又合
法。

2. 回到 Java 语言

2.1 Java 语言中的内存地址

Java 语言中并没有「内存地址」这个说法。所有的 Java 基础语法书上都没有出现这个概念。
在 C 语言中,有一个取地址运算符: & ,通过它你可以「求得」一个变量背后的那块内存的内
存地址,而 Java 语言中并没有这个取地址运算符。
当然,有一个地方还是残留了「内存地址」的一点点痕迹:在默认的 Java 虚拟机
(HotSpot)中,调用对象的  hashCode 方法,默认的返回值就是这个对象在内存中的地址。

不过需要注意的是,如果运行你的代码的 Java 虚拟机并非 HotSpot,或者你自定义类
时自己实现了  hashCode 方法,那么上述说法就不一定成立了。

2.2 Java 语言中的引用

Java 语言并没有直接提出「内存地址」的概念,而是将它演化成了「引用」的概念。
大家都知道,Java 也是一种强类型编程语言,Java 中的变量也是有类型的。Java 也要求变量
的「值的类型」要与「变量的类型」是一致的。也就是我们上面类比的例子:苹果盒子中必然
只能放苹果,不能放橘子、橙子、梨、香蕉。
而 Java 中变量的类型又分为两大类:基本类型(boolean、byte、short、int、long、
float、double)以及「引用」。
简单来说,在 Java 中,一个变量的类型,如果不是上述 7 种基本类型之一,那么一定、一
定、一定就是「引用」类型,绝无例外。
那什么引用类型的变量?
你可以这么理解,Java 中引用类型的变量就如同 C 语言中的指针类型的变量,它是专门用来
存一个对象在内存中的地址的。

2.3 一个常识性错误

先看代码示例:

Student tom = new Student();

对于上述代码,你如果愿意,也可以写成如下两行的形式:

 Student tom;
 tom = new Student();

一个初学者常见的错误就是认为上述代码中 tom 是一个对象!无论一个 Java 程序员他工作了
多少年,他如果是这么认为的,那么他就是初学者!
以之前的类比的例子来说:我们可以在一张便签纸上去记录一个人的手机号。这里有几样东
西?
两样。便签纸 和 人 。
我通常会用另一种类比去帮助学生去理解「引用」和「对象」是分开看的两样东西。
情况一:假设你今天突然灵感来了,觉得「张三」是个好名字,决定以后你的孩子就叫这个名
字。但是问题是,你现在可能都没有孩子,甚至都没有女朋友呢。那么此时,有名字,但这个
名字背后并没有人!
情况二:一个班上有 30 个同学,自然就有 30 个人名,然后大家集体决定给「张三」同学起
个外号叫「豆豆」。从此以后,你就会发现,如果「张三」今天迟到了,那么「豆豆」今天也
会迟到;如果「张三」今天过生日,那么「豆豆」今天也会过生日。好巧喔~。巧个屁,「张
三」和「豆豆」本身就是同一个人的两个名字!
在上面两个例子中,你会发现「名字」和「人」其实是两个独立的概念,只不过大家平时没有
专门去思考这个问题。通常大家就是笼统地将「人名」和「人」画上了等号,但是细说起来并
非如此。
回到我们的 Java 代码中:我有一个对象,另外我还有一个引用类型的变量记录了这个对象在
内存中的地址,那么这里有几个东西?两个。对象和引用类型的变量。
再看上述代码, tom 是一个引用类型的变量的名字,它本身并非 Student 对象!只不过它记
录了一个 Student 对象在内存中的地址。

2.4 常识性错误的高阶版

先看代码:

Student tom = new Student();
Student jerry = tom;

上述代码中有几个对象?
标准的 错误答案 是两个:tom 和 jerry 。

回忆/复习上一章节,tom 和 jerry 是对象吗?不是,tom 和 jerry 是引用类型的变量,而非
对象。
这个代码中对象有且仅有一个(显而易见,因为 new 只有一个嘛)。而 tom 和 jerry 记录的
都是这一个对象在内存中的地址。
细说起来是 tom 变量先记录了这个 Student 对象在内存中的地址,而 jerry 变量把 tom 变量
的内容原样炒了一份,那么毫无疑问,jerry 自然也是记录这个 Student 对象在内存中的地址。
这是不是我们之前类比的,在两张便签纸上记录同一个人的手机号。

3. 基于引用的高阶知识

3.1 引用传递

Java 中有一个引用传递的概念,示例代码如下:

1.  public static void main(String[] args) {
2.  Student tom = new Student();
3.  demo(tom); // 方法调用
4.  }
5.  public static void demo(Student jerry) {
6.  ...
7.  }

在 main 方法调用 demo 方法时会有一个参数传递的行为,逻辑上就是发生了形如如下代码
的操作:

Student jerry = tom;

结合之前讲的概念,这里你应该很容易理解,main 方法里的  tom 和 demo 方法中的  jerry
实际上就是同一个对象的两个「名字」。

3.2 == 判断

讲到这里,Java 中 == 判断的作用和底层逻辑就很清晰了:它是用来判断两个引用是否指向
的是同一个对象。通俗地说,就是两个「人名」背后是否是同一个「人」。

1.  Student tom = new Student();
2.  Student jerry = tom;
3.  System.out.println( tom == jerry ? "Yes" : "No"); // Yes
4.  Student ben = new Student();
5.  System.out.println( tom == ben ? "Yes" : "No"); // No

第一个输出会是 Yes,而第二个输出会是 No。因为 ben 所指向的那个 Student 对象是第二
个 new 创建出来了,tom 和 ben 背后分别是两次 new 出来的两个 Student 对象。

3.3 引用类型的细分

「引用类型」实际上仍然是一个很宽泛的说法。例如下述错误代码:

Student tom = new Teacher();

上述代码中,tom 是一个引用类型的变量,这没错。不过,更进一步细说,tom 是一个
Student 类型的引用,也就是说,tom 这个变量只能去记录一个 Student 对象的地址。
而上述代码的错误就在于,你让 tom 去记录一个 Teacher 对象的内存地址。本质上,这就是
犯了一个在苹果盒子里装橘子的错误。

3.4 null

如果一个引用类型的变量自声明以后,从未被赋过值,那么它的值就是 null。

1.  Student tom;
2.  Student tom = null;

上述两行代码的效果是等价的。
这就相当于,你有个苹果盒子,但是在这个盒子中什么苹果都没有放。也相当于,你想到了一
个叫做「豆豆」的外号,但是你还没有把这个外号「按到」任何一个人的头上。
这种情况下就是有「人名」,但是「人名」的背后还没有「人」。此时,你喊这个「人名」,
就是喊破喉咙都没人答应,因为逻辑上这里就是有问题的。

3.5 数组与引用的关系

之前说过,在 Java 中,一个变量但凡不是 7 种基本类型之一,那它就是引用类型,无一例
外。数组变量就是引用。一个数组变量,记录了一个数组对象在内存种的地址。

 int data[] = new int[3];

借用之前的说法:此处有 2 样东西,一个是数组对象,其中可以存放三个 int 型的数据。另一
个是用于存放数组对象的内存地址的东东,也就是变量 data,它是一个引用。
再看下面这段代码:

1.  int array1[] = new int[]{1, 2, 3};
2.  int array2[] = array1;

上述的代码和我们前面章节讲到的情况本质上是一样的:array1 和 array2 这两个引用指向了
同一个对象。你动了 array1 数组,你会发现 array2 会同样发生变化。就跟今天「张三」过生
日,结果「豆豆」也过生日一样。

3.6 数组与引用的关系 2

前一章节中我们代码种的数组是基本类型的数组:

  int data[] = new int[3];

除了有基本类型的数组之外,我们还会见到对象的引用类型的数组,例如:

Student array[] = new Student[10];

那么上述代码中的 array 中装的是什么东西?array 中是装了 10 个 Student 对象吗?
我们现实中会有这样的情景:老师手里拿着学生的名单走进教室,对同学们说:「所有的同学
都在这个纸上,......」。
你想想什么叫「同学在纸上」?如果真的是人在纸上那是个什么样子?所以在纸上的并不是真
的「人」,而是代表着人的「名字」或者是「身份证号」。
在上面的例子中,「纸」就是数组,数组中存的并不是 Student 对象本身,而是存 Student
对象在内存中的地址。也就是说,内存中有 10 个 Student 对象,它们所在的地址分别是:
xxx、xxx、xxx、...,而这 10 个地址被记录在了一个数组中。而这个数组本身又有一个地址,
这个地址记录在了引用类型的变量  array 中。

3.7 清楚之后的马虎
Student tom = new Student();

现在我们都知道了 tom 本身并不是一个对象,它是一个引用,它记录了一个 Student 对象在
内存中的地址,或者说它指向了一个内存中的 Student 对象。
在你能明确区分「引用」和「对象」两个概念之后,我们日常的沟通和交流中,也不至于每次
都说的那么精确,那么严谨,不必每次都说「Student 类型的引用 tom」,这个时候,在双
方都清楚的情况下,我们还是可以「马虎」地说:对象 tom 。

数组也一样:

int data[] = new int[3];

在沟通双方都清楚的前提下,我们还是可以「马虎」地说数组 data,尽管 data 并非数组对象本身。
就好像我们可以说 136xxxxxxxx 这个人如何如何一样,它并非人本身,只是人的手机号,但是说话和听话双方还是可以相互沟通交流的。

3.8 引用类型的对象的属性

我们经常会遇到这样的情况,要给类的属性中有引用类型的属性,例如:

1.  class Student {
2.      String name;
3.      int age;
4.      Teacher teacher;
5.  }

如何理解上面的「Student 中「有」一个 Teacher」?
如果你能理解前面章节的内容一路看到这里,那么这里的情况对你而言就十分简单了。很明
显,一个 Student 对象中不可能有一个 Teacher 对象。
我们日常生活中会说这样的话:我心里有我女朋友。来来来,你给我表演一个把个活人装心里?!
Student 对象的 teacher 属性是一个引用,而非对象本身。这里和数组的情况一样,Teacher对象本身在「另一个地方」,而 Student 对象的 teacher 属性中记录的是这个 Teacher 对象
的所在内存地址。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2191863.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux】进程控制(创建、终止、等待、替换)

文章目录 1. 进程创建2. 进程终止3. 进程等待4. 进程程序替换4.1 认识进程替换4.2 认识全部接口 1. 进程创建 如何创建进程我们已经在之前学习过了,无非就是使用fork(),它有两个返回值。创建成功,给父进程返回PID,给子进程返回0&…

LLM大模型常见面试题汇总(含答案)

最近秋招正在如火如荼地进行中,看到很多人的简历上都包含大模型相关的工作,各家大厂和初创都很舍得给钱,动辄百万年包也变得不再稀奇。 因此在大模型纵横的这个时代,不仅大模型技术越来越卷,就连大模型相关的岗位和面…

零点校准说明

目录 注意,校准时电机不能带负载,否则校准容易出错。 零点校准 点击【零点校准】,显示“校准中…”, 如果是“High Current”类型电机,电机先“嘀”一声测量电阻电感(耗时约5秒),然后再正转一圈…

【实战】Nginx+Lua脚本+Redis 实现自动封禁访问频率过高IP

大家好,我是冰河~~ 自己搭建的网站刚上线,短信接口就被一直攻击,并且攻击者不停变换IP,导致阿里云短信平台上的短信被恶意刷取了几千条,加上最近工作比较忙,就直接在OpenResty上对短信接口做了一些限制&am…

Vivado viterbi decoder license

Viterbi Decoder 打卡以上链接 添加后next后, 会发送lic文件到邮件,vivado导入lic即可

Latex技巧——参考文献中加入url和doi

有的期刊要求在参考文献里加入url或者doi, 例如下图中蓝色的字体。 在bib里编辑为下图中note行,也就是利用\href命令。\href后第一个{}内为网址,第二个{}为在参考文献中显示的蓝色文字。一般来说,两个{}内的文字相同。若遇到有些网址有下划线…

不是哥们,真有人拿 AI 来面试啊?

大家好,我是八哥。如今 AI 已经渗透到了我们的生活、求职、工作等方方面面。就拿求职来说,今年的美团校招,已经用上了 AI 来当面试官,降本增效。 我自己也当过面试官,其实对 AI 面试官还是挺好奇的。毕竟每个候选人的…

UOM无人机空域快速申请技术详解

UOM无人机空域快速申请技术详解主要包括以下几个步骤: 一、准备阶段 1. 实名登记:首先,您需要在相应的民航部门进行无人机的实名登记,这是合法飞行的前提。 2. 了解规定:熟悉并遵守当地关于无人机飞行的法律法规&am…

C语言:预编译过程的剖析

目录 一.预定义符号和#define定义常量 二.#define定义宏 三.宏和函数的对比 四、#和##运算符 五、条件编译 在之前,我们已经介绍了.c文件在运行的过程图解,大的方面要经过两个方面。 一、翻译环境 1.预处理(预编译) 2.编译 3…

SpringBoot在线教育系统:从零到一的构建过程

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及,互联网成为人们查找信息的重要场所,二十一世纪是信息的时代,所以信息的管理显得特别重要。因此,使用计算机来管理微服务在线教育系统的相关信息成为必然。开…

如何禁止电脑蓝牙?分享五个有效方法,快@企业老板来看! 内行人都在用的方法哦,不妨一试!

在现代办公环境中,电脑蓝牙功能的便捷性无可否认,但随之而来的安全风险也不容忽视。 尤其对于企业而言,蓝牙的开放可能引发数据泄露、未经授权的设备连接等一系列安全问题。 因此,如何有效地禁止电脑蓝牙,成为了许多…

终于有人把多模态大模型讲这么详细了

多模态大型语言模型(Multimodal Large Language Models, MLLM)的出现是建立在大型语言模型(Large Language Models, LLM)和大型视觉模型(Large Vision Models, LVM)领域不…

付费计量系统通用功能(14)

11.18 Function_Object功能目标 11.18.1 Function_Object definition功能目标定义 A Function_Object is an abstract notation of defining the attributes of a function. 功能目标是一个抽象的功能属性的定义 Definition of a function object should stat…

Linux plt表调用汇编代码分析

linux调用共享库中的函数时通过plt表和got表实现位置无关代码,过程中涉及到lazy binding,即在第一调用外部函数时解析被调用的函数地址并将地址写入到got表,后续调用则不需要解析函数地址,具体过程如下 1.c程序如下 #include &l…

基于Node2Vec的图嵌入实现过程

目录 一、引言二、Node2Vec(原理)2.1 随机游走(Random Walk)2.2 嵌入学习2.3 Node2Vec 的优势 三、使用 Node2Vec 进行图嵌入(实践)3.1 读取和转换 JSON 文件为 Graph 对象3.2 训练 Node2Vec 模型3.3 二维嵌…

Python+Django微信小程序前后端人脸识别登录注册

程序示例精选 PythonDjango微信小程序前后端人脸识别登录注册 如需安装运行环境或远程调试,见文章底部个人QQ名片,由专业技术人员远程协助! 前言 这篇博客针对《PythonDjango微信小程序前后端人脸识别登录注册》编写代码,代码整…

2024年10月6日历史上的今天大事件早读

23年10月06日西汉“新莽政权”领袖王莽被刺身亡 1866年10月06日清政府批准筹设天津机器局 1905年10月06日俄国爆发铁路工人大罢工 1913年10月06日中、英西姆拉会商“西藏问题” 1927年10月06日阿尔-乔尔森主演第一部有声电影 1940年10月06日新四军获黄桥决战胜利 1949年1…

字节跳动员工玩转 AI 的 100 种办法

今年,字节跳动在国内上线了 AI 应用开发平台——扣子。用户可以根据自己的想法在扣子上创建 chat bot(聊天机器人),甚至能利用扣子 API 上线一个产品。 扣子上有丰富的插件工具,如果用户希望 bot 具备某个功能&#x…

sql-labs靶场第九关测试报告

目录 一、测试环境 1、系统环境 2、使用工具/软件 二、测试目的 三、操作过程 1、寻找注入点 2、注入数据库 ①寻找注入方法 ②爆库,查看数据库名称 ③爆表,查看security库的所有表 ④爆列,查看users表的所有列 ⑤成功获取用户名…

2024年,现在做全职的AI产品经理,时机对不对?

就在最近,Chatgpt又更新了,推出了新版本名字叫做canvas。 这个版本可以支持对AI的对话内容进行编辑了,也就是以前prompt一次性生成的结果总是很难控制输出结果,新版新增了可以在结果内容与用户进行交互的产品入口,最终…