对象与this

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

最近想再聊聊Java的对象与this关键字。

self与this

我们先来看一段Python代码:

# 括号里的object,表示Student类继承自object。在Java里默认继承Object。当然,Python里不写也可以
class Student(object):# 构造函数,变量前面下划线,是访问修饰符,表示私有def __init__(self, name, age):self.__name = nameself.__age = age# get方法,可不写。你会发现Python里方法形参都有self,其实就相当于Java里的this,只不过Java通常是隐式的def get_name(self):return self.__namedef get_age(self):return self.__agedef print_info(self):print("姓名:" + self.__name, "年龄:" + str(self.__age))# 你可以将下面代码理解为Java的main方法,这里用来测试
if __name__ == '__main__':# 调用构造函数得到对象,Python不需要new关键字student = Student("bravo1988", 18)# 实际调用方法时并不需要传self,会默认传递print(student.get_age())print(student.get_name())student.print_info()

你会发现,有了Java基础后,上手Python其实很简单,你可以用Java的思维去写Python,尽管写出来的代码不那么Pythonic。Python中的self非常有意思,个人认为比Java友好些,因为是显式的,初学者可以很清楚的知道调用方法时到底发生了什么。

我之前知乎的那篇文章里,把对象的本质理解为“多个相关数据的统一载体”,现在依然这么认为。比如一个人,有name、age、height等社会或生理体征,而这些数据是属于一个个体的,如果用数组去存,表现力有所欠缺,无法表达“它们属于同一个个体”的含义。

但我们知道,在Java中对象是在堆空间中生成的,数据会在堆空间占据一定内存开销。而方法只有一份。

那么,方法为什么被设计成只有一份呢?

因为多个个体,属性可能不同,比如我身高180,你身高150,我18岁,你30了。但我们都能跑、能跳、能吃饭,这些技能(method)都是共通的,没必要和属性数据一样单独在堆空间各存一份,所以被抽取出来存放。

此时,方法相当于一套指令模板,谁都可以传入数据交给它执行,然后得到执行后的结果返回。

但此时会存在一个问题:张三这个对象调用了eat()方法,你应该把饭送到他嘴里,而不是送到李四嘴里。那么方法如何知道把饭送到哪里呢?

换句话说:共性的方法如何处理特定的数据?

Python的self、Java的this其实就是解决这个问题的。你可以理解为对象内部持有一个引用,当你调用某个方法时,必须传递这个对象引用,然后方法根据这个引用就知道当前这套指令是对哪个对象的数据进行操作了。

static与this

我们都知道,static修饰的属性或方法其实都是属于类的,是所有对象共享的。但接触Python后我多了一层理解。之所以一个变量或者方法要声明为static,是因为

  • static变量:大家共有的,大家都一样,不是特定的差异化数据
  • static方法:这个方法不处理差异化数据

也就是说,static注定与差异化数据无关,即与具体对象的数据无关。

以静态方法为例,当你确定一个方法只提供通用的操作流程,而不会在内部引用具体对象的数据时,你就可以把它定为静态方法。

这个其实和我们之前听到的解释不一样。网络上一贯的解释都是上来就告诉你静态方法不能访问实例变量,再解释为什么,是倒着解释的。而上面这段话的出发点是,当你满足什么条件时,你就可以把一个方法定为静态方法。

为什么我会想到反着解释呢?

写Python时获取的灵感。

我们还是来看看Python中的方法。比如,我在Student里新定义了一个方法:

def simple_print(self):print("方法中不涉及具体的对象数据,啦啦啦啦~")

IDE发现你并没有操作具体的对象数据,是一个通用的操作,于是提醒你这个方法可以用static。

要解决这个警告,有两种方式:

  • 在方法中引用对象的数据,变成实例方法

  • 坚持不在方法内使用对象引用,但把它变成静态方法

你会发现,抽取成静态方法后,形参不需要self了,Python在调用这个方法时也不再传递当前对象,反正静态方法是不处理特定对象数据的

这或许可以反过来解释,为什么Java中静态方法无法访问非静态数据(实例字段)和非静态方法(实例方法)。因为Java不会在调用静态方法时传递this,静态方法内没有this当然无法处理实例相关的一切。

我们在一个实例方法中调用另一个实例方法或者实例变量时,其实都是通过this调用的,比如

public void test(this){

    System.out.println(this.name);

    this.show();

}

只不过Java允许我们不显示书写。

当然,有些培训班视频会说静态方法随着类加载而加载,此时并没有对象实例化,所以静态方法无法访问实例相关数据,倒也勉强说得通。看大家自己怎么理解了,能自圆其说即可。

希望上面对static的描述,能从另一个角度帮大家加深对static的理解。

一个神奇的现象

请大家试着运行以下代码:

public class Demo {public static void main(String[] args) {/*** new一个子类对象* 我们知道,子类对象实例化时,会隐式调用父类的无参构造* 所以Father里的System.out.println()会执行* 猜猜打印的内容是什么?*/Son son = new Son();Daughter daughter = new Daughter();}}class Father{/*** 父类构造器*/public Father(){// 打印当前对象所属Class的名字System.out.println(this.getClass().getName());}
}class Son extends Father {
}class Daughter extends Father {
}

不出所料,果然是打印子类Son、Daughter的名字。

这个现象是非常不可思议的!我们编写父类时,子类甚至都还没写呢,而我们却在父类中得到了子类的名字!就好比有个人十年前写了一本书,书中预测了十年后有个英俊非凡的少年将会在温州出生一般!

咳咳...我们来看看这是怎么实现的。

我们都知道,子类实例化时会隐式调用父类的构造器,效果相当于这样:

class Father{/*** 父类构造器*/public Father(){// 打印当前对象所属Class的名字System.out.println(this.getClass().getName());}
}class Son extends Father {public Son() {// 显示调用父类无参构造super();}
}

我把这种现象称为:继承链回溯(我自己生造的一个词)。

调用构造器,其实也是调用方法,只不过构造器比较特殊。但我们可以肯定,这个过程中一定也会传递this。你看,Python的构造器就是传递self:

# 构造函数,变量前面下划线,是访问修饰符,表示私有
def __init__(self, name, age):self.__name = nameself.__age = age

这样一解释,刚才的案例就没什么神秘感了:嗨,不就是方法调用传参嘛!

本质和子类调用方法给父类传参一样一样的!只不过传参的过程很特殊:

  • new的时候自动传参,不是我们主动调用,所以感知不到
  • Java中的this是隐式传递的,所以我们更加注意不到了

我们会在后面用到这个小特性,它对于封装通用工具类非常有用。

思考题

public class ThisDemo {public static void main(String[] args) {// TODO}@Getter@Setter@Accessors(chain = true)static class Father {private String fatherName;}@Getter@Setter@Accessors(chain = true)static class Son extends Father {private String sonName;}
}

问题一:可以new Son().setSonName("").setFatherName(),却不能new Son().setFatherName("").setSonName(),为什么?

问题二:无论怎么调整setter顺序,返回值始终是Father类型,为什么?

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

进群,大家一起学习,一起进步,一起对抗互联网寒冬

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大厂数仓专家实战分享:企业级埋点管理与应用

一.什么是埋点 埋点(Event Tracking),是互联网数据采集工作中的一个俗称,正式应该叫事件跟踪,英文为 Event Tracking,它主要是针对特定用户行为或事件进行捕获、处理和发送的相关技术及其实施过程。 二.埋…

【C++初阶】STL详解(三)vector的介绍与使用

本专栏内容为:C学习专栏,分为初阶和进阶两部分。 通过本专栏的深入学习,你可以了解并掌握C。 💓博主csdn个人主页:小小unicorn ⏩专栏分类:C 🚚代码仓库:小小unicorn的代码仓库&…

PS 颜色取样器标尺工具 基本使用讲解

上文 PS 吸管工具基本使用方法 我们讲完了 吸管工具 那么 我们继续 打开ps先 接着 我们选择这个 颜色取样器工具 选择之后 我们鼠标在图像上随便点一下 就会出现一个标记 然后 我们可以点多几个地方 边上的信息面板就会输出 点1 和 点2 甚至 多个 点3 点4 的 颜色 RGB代码 …

量化交易:建立趋势跟踪策略的五个指标

什么是趋势跟踪策略? 趋势跟踪策略是只需需顺势而为的策略,即在价格上涨时买入,在价格开始下跌时卖出。在趋势跟踪策略中,人们的目标不是预测或预测,而只是关注市场上的任何新兴趋势。 趋势是如何出现的?…

Python学习(一)基础语法

文章目录 1. 入门1.1 解释器的作用1.2 下载1.3 基础语法输入输出语法与引号注释:变量: 数据类型与四则运算数据类型四则运算数据类型的查看type()数据类型的转换int()、int()、float() 流程控制格式化输出循环与遍历逻辑运算符list遍历字典dict遍历 跳出…

Jave 定时任务:使用Timer类执行定时任务为何会发生任务阻塞?如何解决?

IDE:IntelliJ IDEA 2022.2.3 x64 操作系统:win10 x64 位 家庭版 JDK: 1.8 文章目录 一、Timer类是什么?二、Timer类主要由哪些部分组成?1.TaskQueue2. TimerThread 三、示例代码分析四、自定义TimerTask为什么会发生任务相互阻塞的…

RVC从入门到......

RVC变声器官方教程:10分钟克隆你的声音!一键训练,低配显卡用户福音!_哔哩哔哩_bilibili配音:AI逍遥散人(已授权)关注UP主并私信"RVC"(三个字母)自动获取一键训…

飞鼠异地组网工具实战之访问k8s集群内部服务

飞鼠异地组网工具实战之访问k8s集群内部服务 一、飞鼠异地组网工具介绍1.1 飞鼠工具简介1.2 飞鼠工具官网 二、本次实践介绍2.1 本次实践场景描述2.2 本次实践前提2.3 本次实践环境规划 三、检查本地k8s集群环境3.1 检查k8s各节点状态3.2 检查k8s版本3.3 检查k8s系统pod状态 四…

unordered_map,unordered_set模拟实现

目录 一 . 底层结构--哈希 1.直接定址法 2. 除留余数法 哈希桶 3. 一些定义 二 . 模拟实现哈希表 1.哈希表框架 ​编辑 2.插入 3.查找 4 . 删除 5.解决使用问题 6.完整代码 三 .实现unordered_map, unordered_set 1. 初步实现unordered_map, unordered_set 2.…

JavaspringbootMYSQL基于移动端的团购网站26449-计算机毕业设计项目选题推荐(附源码)

目 录 摘要 1 绪论 1.1 选题背景 1.2选题目的及意义 1.3springboot框架介绍 2 基于移动端的团购网站系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1 数据流程 3.3.2 业务流程 2.3 系统功能分析 2.3.1 功能性分析 2.3.2 非功能性分析 2.4 系统用例分析 2.5本章…

WPF 使用.ttf文件中的图标失败

本章讲述问题:WPF 使用.ttf文件中的图标失败,变成白框问题。 在WPF开发过程中,我们需要使用.ttf文件中的图标和文字,但是经常会遇到类似问题:WPF 在XMAL里增加图标字体时没办法实时显示出来只显示一个小方框&#xff0…

【Linux】-进程间通信-匿名管道通信(以及模拟一个进程池)

💖作者:小树苗渴望变成参天大树🎈 🎉作者宣言:认真写好每一篇博客💤 🎊作者gitee:gitee✨ 💞作者专栏:C语言,数据结构初阶,Linux,C 动态规划算法🎄 如 果 你 …