【西行 - 计收猪八戒】 面向对象之成员变量和局部变量

简介: Java 语言中,根据定义变量位置不同,可以将变量分为两大类:成员变量和局部变量。成员变量和局部变量的运行机制存在较大差异,本篇会详细介绍这两种变量,废话不多说,开始了。


1、成员变量和局部变量


定义:


成员变量:在类范围里定义的变量,也就是前面几篇介绍的 Field。


局部变量:在方法里定义的变量。


注意:在变量命名规范上,我们应该做到知名识意,且变量名要首字母小写,后面每个单词首字母大写。


成员变量又可根据是否有 static 关键字修饰分为:类 Field 和 实例 Field 两种。


类 Field 被 static 关键字修饰,它从这个类准备阶段就开始存在,直到系统完全销毁这个类时消亡。作用域上,类 Field 和这个类的生存范围相同。


实例 Field 未被 static 关键字修饰,它从这个类的实例被创建起开始存在,直到系统完全销毁这个实例。作用域上,实例 Field 和这个实例的生存范围相同。


类 Field 访问形式:

类名.类Field
实例.类Field


只要类存在,我们就可以通过类名访问其类成员变量。虽说也可以通过实例访问类 Field ,但由于这个实例并不拥有 类 Field ,所以本质上还是操作的类 field(修改值的时候,改的是类值而不是实例值)。


实例 Field 访问形式:

实例.实例Field


下面看一下具体代码了解其基本使用:

public class VariableTest {
    // 定义类 field
    public String name;
    // 定义实例 field
    public static int age;
    public static void main(String[] args) {
        // VariableTest 类已经初始化了,则 age 变量起作用了,输出 0
        System.out.println("VariableTest 的 age 类 Field 值:" + VariableTest.age);
        // 创建 VariableTest 对象
        VariableTest v = new VariableTest();
        // 通过实例访问类field 和实例 field
        System.out.println("v 对象的 name field 值是:" + v.name + "v 对象的 age field 值是:" + v.age);
        // 为 v 实例变量赋值
        v.name = "J3";
        // 通过实例访问类 field,依然是访问 VariableTest 的 age 类 field
        v.age = 18;
        System.out.println("v 对象的 name field 值是:" + v.name + "v 对象的 age field 值是:" + v.age);
        System.out.println("VariableTest 的 age 的类 field 值:" + VariableTest.age);
        VariableTest v2 = new VariableTest();
        System.out.println("v2 对象的 age 类 field 值:" + v2.age);
    }
}


由程序可知,成员变量无须显示初始化,系统会自动为成员变量进行默认初始化,初始化规则与数组动态初始化元素赋值规则完全相同。


从运行结果可以看出,类 Field 的作用于比实例 Field 的作用于更大:实例 Field 随实例的存在而存在,而类 Field 则随类的存在而存在。


虽然实例可以访问类 Field ,但其本质还是访问的类 Field 指向的都是同一片内存空间(不提倡实例调用类 Field,可读性不好)。


局部变量根据定义形式不同,可以分为如下三种:


形参:在定义方法签名时定义的变量,形参的作用域在整个方法内有效。


方法局部变量:在方法体内定义的局部变量,它的作用域是从定义该变量的地方生效,到该方法结束时失效。


代码块局部变量:在代码块中定义的局部变量,这个局部变量的作用域从定义该变量的地方生效,到改代码块结束时失效。


局部变量与成员变量不同,必须显示初始化。也就是说,局部变量定义的时候必须制定默认值,否则不可访问它们。


下面演示局部变量基本使用:

public class VariableTest {
    public static void main(String[] args) {
        // ===================代码块======================
        {
            // 定义一个代码块局部变量 a
            int a;
            // 下面代码将出现错误,因为 a 变量还未初始化
            // System.out.println("代码库局部变量 a 的值:" + a);
            a = 5;
            System.out.println("代码块局部变量 a 的值:" + a);
        }
        // 下面试图访问的 a 变量并不存在
        // System.out.println(a);
        // ===================方法变量======================
        // 定义一个方法局部变量 b
        int b;
        // 下面代码将出现错误,因为 b 变量还未初始化
        // System.out.println("代码库局部变量 b 的值:" + b);
        b = 5;
        System.out.println("方法局部变量 b 的值:" + b);
        // ===================形参变量======================
        VariableTest variableTest = new VariableTest();
        variableTest.sum(2, 3);
    }
    public void sum(int c, int d) {
        System.out.println("形参 c 和 d 相加的值:" + (c + d));
    }
}


对于上面程序中的形参是真个方法体内有效,初始化是在调用方法时由系统完成,形参的值由方法的调用者负责指定。


在同一个类里,成员变量的作用范围是整个类内有效,一个类不能定义两个同名成员变量,即使一个是类 Field ,一个是实例 Field 也不行;一个方法不能定义两个同名的局部变量,即使一个是方法局部变量,一个是代码块局部变量或者形参也不行。


Java 运行局部变量和成员变量同名,如果方法里的局部变量和成员变量同名,局部变量会覆盖成员变量,如果需要在这个方法里引用被覆盖的成员变量,则可使用 this 或类名作为调用者来限定访问成员变量。


同名变量案例:

public class VariableTest {
    // 定义 name 实例 field
    private String name = "J3";
    // 定义 age 类 field
    private static int age = 18;
    public static void main(String[] args) {
        // 方法里的局部变量,局部变量覆盖成员变量
        int age = 28;
        // 直接访问 age ,将输出局部变量的值
        System.out.println(age);
        // 使用类名调用
        System.out.println(VariableTest.age);
    }
    public void info() {
        // 方法里的局部变量,局部变量覆盖成员变量
        String name = "西行";
        // 直接访问 name 输出局部变量的值
        System.out.println(name);
        // 使用 this ,限定调用成员变量
        System.out.println(this.name);
    }
}


2、成员变量的初始化和内存中的运行机制


当系统加载类或创建该类的实例时,系统自动为成员变量分配内存空间,并在分配内存空间后,自动为成员变量指定初始值。


看下面代码,根据 VariableTest 类创建了两个实例,配合示意图来说明 Java 成员变量的初始化和内存中的运行机制。

public class VariableTest {
    // 定义 name 实例 field
    private String name;
    // 定义 age 类 field
    private static int age;
    public static void main(String[] args) {
        // 创建两个对象
        VariableTest v1 = new VariableTest();
        VariableTest v2 = new VariableTest();
        // 给实例 field 赋值
        v1.name = "J3";
        v2.name = "西行";
        // 给类 field 赋值
        v1.age = 28;
        v2.age = 38;
        System.out.println("v1 中实例变量值:" + v1.name + ",v1 中类变量值:" + v1.age);
        System.out.println("v2 中实例变量值:" + v2.name + ",v2 中类变量值:" + v2.age);
    }
}


当程序执行到第 9 行代码时,如果这行代码是第一次使用 VariableTest 类,则系统会在第一次使用 VariableTest 类时加载这个类,并初始化这个类。再类的准备阶段,系统将会为该类的类 Field 分配内存空间,并指定默认初始值。当 VariableTest 类初始化完成后,系统内存中的存储示意图如下:


image.png


当 VariableTest 类初始化完成后,系统将在堆内存中为 VariableTest 类分配一块内存区,在这块内存区里包含了保存 age 类 Field 的内存,并设置 age 的默认初始值:0。


系统接着创建了一个 VariableTest 对象,并把这个 VariableTest 对象赋给 v1 变量,VariableTest 对象里包含了名为 name 的实例 Field ,实例 Field 是在创建实例时分配内存空间并指定初始值的。当创建了第一个 VariableTest 对象后,系统内存的存储的示意图如下:


image.png


有图可知,age 类 Field 并不属于 VariableTest 对象,它时属于 VariableTest 类的,所以创建第一个对象时并不需要为 age 类 Field 分配内存,系统只是为 name 实例 Field 分配了内存空间,并指定默认初始值:null。


接着执行 10 行代码创建第二个 VariableTest 对象,此时因为 VariableTest 类已经存在于堆内存中了,所以不需要对 VariableTest 类进行初始化。并且创建第二个 VariableTest 对象与创建第一个 VariableTest 对象并没有什么不同。


当程序执行 12、13 代码时,将为 v1 的 name 实例 Field 赋值,也就是下图中 name 值向 “J3” 字符串。指向完后,两个 VariableTest 对象在内存中的存储示意图如下:


image.png


上图可以看出,name 实例 Field 是属于单个 VariableTest 实例的,因此修改第一个 VariableTest 对象的 name 实例 Field 时仅仅与该对象有关,同样修改第二个对象的 name 实例 Field 时,也与 VariableTest 类和其他 VariableTest 对象无关。


最后当程序执行到 15、16 行代码时,此时通过 VariableTest 对象来修改 VariableTest 的类 Field,结合图和运行结果不难看出,v1、v2 对象修改的都是统一内存中的值。修改成功后,内存中的存储示意图如下:


image.png


从上图可以看出,通过 v1 和 v2 来访问类 Field 时,实际上访问的时 VariableTest 类的 age 。


实际上,不管通过那个 VariableTest 实例来访问 age Field ,本质上还是通过 VariableTest 类来访问 age FIeld,他们所访问的时同一块内存。所以以后建议,当程序需要访问类 Field 时,尽量使用类作为主调,而不是使用对象作为主调,避免歧义,题号程序可读性。


3、局部变量的初始化和内中的运行机制


局部变量定义后,必须经过显示初始化后才能使用,系统不会为局部变量执行初始化。因为定义局部变量后,系统并未为这个变量分配内存空间,知道等到程序为这个变量赋初始值时,系统才会为局部变量分配内存,并将初始值保存到这块内存中。


与成员变量不同,局部变量不属于任何类或实例,因此它总是保存在其所在的方法的栈内存中。如果局部变量是基本类型的变量,则直接把这个变量的值保存在改变量对应的内存中;如果局部变量是一个引用类型的变量,则这个变量里存放的是地址,通过该地址引用到该变量实际引用的对象或数组。


栈内存中的变量无需系统垃圾回收,往往随方法或代码块的运行结束而结束。因此,局部变量的作用域是从初始化该变量开始,直到该方法或改代码快运行完成而结束。因为局部变量只保存基本类型的值或对象的引用,因此局部变量所占的内存通常比较小。


好了,今天的内容到这里就结束了


目录
相关文章
|
消息中间件 关系型数据库 Kafka
Flink CDC产品常见问题之 Oraclecdc JdbcIncrementalSource 捕获不到数据如何解决
Flink CDC(Change Data Capture)是一个基于Apache Flink的实时数据变更捕获库,用于实现数据库的实时同步和变更流的处理;在本汇总中,我们组织了关于Flink CDC产品在实践中用户经常提出的问题及其解答,目的是辅助用户更好地理解和应用这一技术,优化实时数据处理流程。
|
关系型数据库 MySQL Java
MySQL支持哪些编程语言?
MySQL支持哪些编程语言?
515 1
|
存储 人工智能 缓存
AI 提示词模板相关的架构设计
现在很多企业纷纷研发大语言模型以解决业务问题。提示词在与模型交互中起到关键作用。为优化提示词模板的修改、提高渲染效率及确保安全性,架构设计注重可修改性、安全性、可靠性和性能。设计包括:将提示词存储在OSS以方便修改和版本控制;使用本地缓存提升读取性能;模板引擎增强灵活性;秘钥安全存储在加密系统中;并通过配置中心动态调整。此设计旨在提供高效、安全且可靠的AI交互体验等。
1172 78
AI 提示词模板相关的架构设计
|
机器学习/深度学习 搜索推荐 数据可视化
机器学习中7种常用的线性降维技术总结
上篇文章中我们主要总结了非线性的降维技术,本文我们来总结一下常见的线性降维技术。
915 6
|
SQL Java 数据库连接
1天搞定SpringBoot+Vue全栈开发 (3)MybatisPlus(数据库操作)
1天搞定SpringBoot+Vue全栈开发 (3)MybatisPlus(数据库操作)
绘梦相似,AIGC图生图:相似图像生成模型魔搭社区开源体验
日常我们在艺术创作和产品设计中,需要多张风格相似的图片
|
开发框架 负载均衡 Dubbo
带你读《Apache Dubbo微服务开发从入门到精通》—— 一、 Dubbo简介
带你读《Apache Dubbo微服务开发从入门到精通》—— 一、 Dubbo简介
1176 96
带你读《Apache Dubbo微服务开发从入门到精通》—— 一、 Dubbo简介
|
数据可视化 定位技术
ArcGIS应用基础2 制作数据统计图
😃在本文中,你将学会ArcGIS要素可视化,按空间位置选择要素,属性表汇总统计及统计图表绘制的基本流程
594 0
|
监控 网络架构 Windows
键盘没有pause break,该怎么办?
键盘没有pause break,该怎么办?
|
运维 负载均衡 算法
Nacos必知必会:这些知识点你一定要掌握!
Nacos必知必会:这些知识点你一定要掌握!
1818 0