对象与this

news2024/11/19 8:34:22
作者简介:大家好,我是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 = name
        self.__age = age

    # get方法,可不写。你会发现Python里方法形参都有self,其实就相当于Java里的this,只不过Java通常是隐式的
    def get_name(self):
        return self.__name

    def get_age(self):
        return self.__age

    def 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 = name
    self.__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.coloradmin.cn/o/1225998.html

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

相关文章

vscode pandas无法使用

一、代码内容 import csv csv_reader csv.reader(open("data.csv")) for row in csv_reader:print(row) print(row[2]) 二、错误提示 ModuleNotFoundError: No module named pandas 三、安装pandas 然后我安装pandas,因为我的python的版本是python …

C++类与对象(1)—初步认识

目录 一、面向过程和面向对象 二、类 1、定义 2、类的两种定义方式 3、访问限定符 4、命名规范化 5、类的实例化 6、计算类对象的大小 7、存储方式 三、this指针 1、定义 2、存储位置 3、辨析 四、封装好处 一、面向过程和面向对象 C语言是面向过程的&#xf…

新版mmdetection3d将3D bbox绘制到图像

环境信息 使用 python mmdet3d/utils/collect_env.py收集环境信息 sys.platform: linux Python: 3.7.12 | packaged by conda-forge | (default, Oct 26 2021, 06:08:21) [GCC 9.4.0] CUDA available: True numpy_random_seed: 2147483648 GPU 0,1: NVIDIA GeForce RTX 3090 …

11月19日

一、选择题1.一般认为,世界上第1台电子数字计算机诞生于()年。 A. 1946 B. 1952 C. 1959 D. 1962 1946 记:4 5 6 7 8 平均十年一代 一、计算机发展五代1946年, 世界上第一台数字电子计算机ENIAC 1.1946年开始,第一代电子管计算机…

<Linux>权限管理|权限分类|权限设置|权限掩码|粘滞位

文章目录 Linux权限的概念Linux权限管理a. 文件访问者的分类b. 文件类型和访问权限c. 文件权限表示方法d. 文件权限的设置权限掩码file指令粘滞位 权限总结权限作业 Linux权限的概念 Linux下有两种用户:超级用户(root)和普通用户。 超级用户:可以在Lin…

Python操作Excel常用方法汇总

目录 引言 一、使用pandas库操作Excel 1、读取Excel文件 2、写入Excel文件 3、处理Excel数据 二、使用openpyxl库操作Excel 1、读取Excel文件 2、写入Excel文件 3、处理Excel数据 三、高级功能 总结 引言 Python是一种功能强大的编程语言,它可以用来处理…

MongoDB相关基础操作(库、集合、文档)

文章目录 一、库的相关操作1、查看数据库2、查看当前库3、创建数据库4、删除数据库 二、集合的相关操作1、查看库中所有集合2、创建集合2.1、显示创建2.2、隐式创建 3、删除集合 三、文档的相关操作1、插入文档1.1、插入单条文档1.2、插入多条文档1.3、脚本方式 2、查询文档3、…

电商平台革新:食派士小程序的无代码开发与广告推广集成

食派士小程序:无代码开发的连接神器 食派士小程序,作为上海食派士商贸发展有限公司的专利产品,是一种凭借无代码开发,就能实现与各种系统的连接和集成的电商解决方案。它采用无代码开发的方式,避免了API开发的复杂过程…

原型网络Prototypical Network的python代码逐行解释,新手小白也可学会!!-----系列8

文章目录 前言一、原始代码二、对每一行代码的解释:总结 前言 这是该系列原型网络的最后一段代码及其详细解释,感谢各位的阅读! 一、原始代码 if __name__ __main__:##载入数据labels_trainData, labels_testData load_data() # labels_…

常见树种(贵州省):001松类

摘要:本专栏树种介绍图片来源于PPBC中国植物图像库(下附网址),本文整理仅做交流学习使用,同时便于查找,如有侵权请联系删除。 图片网址:PPBC中国植物图像库——最大的植物分类图片库 一、华山松…

麻将馆电脑计费系统,棋牌室怎么用电脑控制灯计时,佳易王计时计费系统软件下载

麻将馆电脑计费系统,棋牌室怎么用电脑控制灯计时,佳易王计时计费系统软件下 棋牌室电脑灯控系统,需要安装一个灯控器,软件发出开灯和关灯的指令,相应的灯就打开或关闭。在点击开始计时的时候,开灯&#xff…

黔院长 | 为什么要调经络?原来通经络对人体健康如此重要!

人体的组成较为复杂,在外有皮肤、毛发;在内有经络、五脏;其他还有我们看不到的精气、津液等等,也因此人体会生各种各样的疾病。 为什么说经络畅通对人体健康如此重要?身体内外始终是一个统一的整体,内外之间…

vue3基础学习

##以前怎么玩的? ###MVC Model:Bean View:视图 Controller ##vue的ref reactive ref:必须是简单类型 reactive:必须不能是简单类型 ###创建一个Vue项目 npm init vuelatest ###生命周期 ###setup相关 ####Vue2的一些写法 -- options API ####Vue3的写法 组合式API Vu…

Python —— Mock接口测试

前言 今天跟小伙伴们一起来学习一下如何编写Python脚本进行mock测试。 什么是mock? 测试桩,模拟被测对象的返回,用于测试 通常意义的mock指的就是mock server, 模拟服务端返回的接口数据,用于前端开发,第三方接口联调 为什么…

数据结构与算法-哈夫曼树与图

🌞 “永远积极向上,永远豪情满怀,永远热泪盈眶!” 哈夫曼树与图 🎈1.哈夫曼树🔭1.1树与二叉树的转换🔭1.2森林与二叉树的转换🔭1.3哈夫曼树🔎1.3.1哈夫曼树的概念&#x…

大数据HCIE成神之路之数学(2)——线性代数

线性代数 1.1 线性代数内容介绍1.1.1 线性代数介绍1.1.2 代码实现介绍 1.2 线性代数实现1.2.1 reshape运算1.2.2 转置实现1.2.3 矩阵乘法实现1.2.4 矩阵对应运算1.2.5 逆矩阵实现1.2.6 特征值与特征向量1.2.7 求行列式1.2.8 奇异值分解实现1.2.9 线性方程组求解 1.1 线性代数内…

基于STM32单片机数字电压表自动切换量程及源程序

一、系统方案 1、本设计采用这STM32单片机作为主控器。 2、液晶1602显示。 3、内部ADC采集电压0-12V,自动切换档位。 二、硬件设计 原理图如下: 三、单片机软件设计 1、首先是系统初始化 u8 i; u16 a,b,c,d; u16 adcx; float adc; unsigned char datas…

AIGC实战 - 使用变分自编码器生成面部图像

AIGC实战 - 使用变分自编码器生成面部图像 0. 前言1. 数据集分析2. 训练变分自编码器2.1 变分自编码器架构2.2 变分自编码器分析 3. 生成新的面部图像4. 潜空间算术5. 人脸变换小结系列链接 0. 前言 在自编码器和变分自编码器上,我们都仅使用具有两个维度的潜空间。…

Alien Skin Exposure2024免费版图片颜色滤镜插件

Alien Skin Exposure一款非常专业的图片后期处理软件,内含500多种照片滤镜。是一款图片后期处理功能非常强大的软件。这款软件可以对图片的后期效果做很好的处理。 打开Alien Skin Exposure软件,会显示下面这个界面,如图1. ExposureX8win-安…

爱奇艺大数据离在线混部

混部作为一种提高资源利用率、降低成本的的方案,被业界普遍认可。爱奇艺在云原生化与降本增效的过程中,成功将大数据离线计算、音视频内容处理等工作负载与在线业务进行了混部,并且取得了阶段性收益。本文重点以大数据为例,介绍从…