对象与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 = 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.mfbz.cn/a/163607.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JVM 调优指南

文章目录 为什么要学 JVM一、JVM 整体布局二、Class 文件规范三、类加载模块四、执行引擎五、GC 垃圾回收1 、JVM内存布局2 、 JVM 有哪些主要的垃圾回收器?3 、分代垃圾回收工作机制 六、对 JVM 进行调优的基础思路七、 GC 情况分析实例 JVM调优指南 -- 楼兰 ​ JV…

系列七、GC垃圾回收【四大垃圾算法-标记压缩算法】

一、原理 在整理压缩阶段,不再对标记的对象回收,而是通过所有存活对象都向一端移动。可以看到,标记的存活对象将会被整理,按照内存地址依次排列。如此一来,当我们需要给新对象分配内存时,JVM只需要持有一个…

永久关机windows系统自动更新

1、打开cmd执行 reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings" /v FlightSettingsMaxPauseDays /t reg_dword /d 3000 /f2、设置,打开windows更新高级选项 修改暂停日期,可长达十年。 3、你小子

解锁数据安全之门:探秘迅软DSE的文件权限控制功能

企业管理者在进行数据安全管控时通常只关注到文件的加密方式,却忽略了以下问题:对于企业内部文档,根据其所承载的涉密程度不同,重要程度也不相同,需要由不同涉密等级的的人员进行处理,这就需要对涉密文档和…

战神传奇【我本沉默精修版】win服务端+双端+充值后台+架设教程

搭建资源下载:战神传奇【我本沉默精修版】win服务端双端充值后台架设教程-海盗空间

详解Java设计模式之职责链模式

原文:详解Java设计模式之职责链模式_java_脚本之家 责任链模式是一种行为设计模式,使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,文中通过代码示例给大家介绍的非常详细,需要的朋友可以参考下 − 目…

TG Pro v2.87(mac温度风扇速度控制工具)

TG Pro 是适用于 macOS 的温度和风扇速度控制工具,可让您监控 Mac 组件(例如 CPU 和 GPU)的温度和风扇速度。如果您担心 Mac 过热或想要手动调整风扇速度以降低噪音水平,这将特别有用。 除了温度和风扇监控,TG Pro 还…

基于截图页面生成前端项目

前两天,在群里看见一个视频,视频中,作者截图twitter首页,然后使用截图直接生成与截图布局非常相近的前端项目,效果还是比较惊艳的。 今天陪老婆回老家,路上clone这个项目的代码到本地,学习了一下…

回归预测 | Matlab实现HPO-ELM猎食者算法优化极限学习机的数据回归预测

回归预测 | Matlab实现HPO-ELM猎食者算法优化极限学习机的数据回归预测 目录 回归预测 | Matlab实现HPO-ELM猎食者算法优化极限学习机的数据回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 Matlab实现HPO-ELM猎食者算法优化极限学习机的数据回归预测(…

Bert学习笔记(简单入门版)

目 录 一、基础架构 二、输入部分 三、预训练:MLMNSP 3.1 MLM:掩码语言模型 3.1.1 mask模型缺点 3.1.2 mask的概率问题 3.1.3 mask代码实践 3.2 NSP 四、如何微调Bert 五、如何提升BERT下游任务表现 5.1 一般做法 5.2 如何在相同领域数据中进…

037、目标检测-算法速览

之——常用算法速览 目录 之——常用算法速览 杂谈 正文 1.区域卷积神经网络 - R-CNN 2.单发多框检测SSD,single shot detection 3.yolo 杂谈 快速过一下目标检测的各类算法。 正文 1.区域卷积神经网络 - R-CNN region_based CNN,奠基性的工作。…

[C++历练之路]vector的介绍以及底层模拟实现

W...Y的主页 😊 代码仓库分享 💕 🍔前言: 我们学习了STL中的string以及其所有重要接口并进行了模拟实现,但是STL中包含的内容不止于此。学习了string之后继续学习STL中的vector,学习成本会大大降低&#…

CentOS7 安装mysql8(离线安装)postgresql14(在线安装)

注:linux系统为vmware虚拟机,和真实工作环境可能有出入,不过正因如此我暴露了NAT转出的IP也没什么大碍 引言 postgresql与mysql目前都是非常受人欢迎的两大数据库,其各有各的优势,初学者先使用简单一张图来说明两者区…

【算法挨揍日记】day31——673. 最长递增子序列的个数、646. 最长数对链

673. 最长递增子序列的个数 673. 最长递增子序列的个数 题目解析: 给定一个未排序的整数数组 nums , 返回最长递增子序列的个数 。 注意 这个数列必须是 严格 递增的。 解题思路: 算法思路: 1. 状态表⽰: 先尝试…

取数游戏2(动态规划java)

取数游戏2 题目描述 给定两个长度为n的整数列A和B,每次你可以从A数列的左端或右端取走一个数。假设第i次取走的数为ax,则第i次取走的数的价值vibi⋅ax,现在希望你求出∑vi的最大值。 输入格式 第一行一个数T ,表示有T 组数据。…

【算法挨揍日记】day27——152. 乘积最大子数组、1567. 乘积为正数的最长子数组长度

152. 乘积最大子数组 152. 乘积最大子数组 题目描述: 给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。 测试用例的答案是一个 32-位 整…

【狂神说Java】Docker概述 | Docker安装 | Docker的常用命令

✅作者简介:CSDN内容合伙人、信息安全专业在校大学生🏆 🔥系列专栏 :【狂神说Java】 📃新人博主 :欢迎点赞收藏关注,会回访! 💬舞台再大,你不上台&#xff0c…

PS学习笔记——视图调整

文章目录 图片拖动图片旋转图片缩放 视图只是我们在对图片进行操作时所看到的图片状态,并不会实际改变图片的属性。目的是方便我们在操作图片时有最舒服的体验 图片拖动 工具栏中有这样一个抓手工具(快捷键H),选择这个抓手工具便可以在图片放大后能用鼠标…

2023年四川省安全员A证证模拟考试题库及四川省安全员A证理论考试试题

题库来源:安全生产模拟考试一点通公众号小程序 2023年四川省安全员A证证模拟考试题库及四川省安全员A证理论考试试题是由安全生产模拟考试一点通提供,四川省安全员A证证模拟考试题库是根据四川省安全员A证最新版教材,四川省安全员A证大纲整理…