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

简介: 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、局部变量的初始化和内中的运行机制


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


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


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


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


目录
相关文章
|
3天前
|
编译器 C++
【C++基础(八)】类和对象(下)--初始化列表,友元,匿名对象
【C++基础(八)】类和对象(下)--初始化列表,友元,匿名对象
|
6月前
|
搜索推荐 编译器 C++
【C++从0到王者】第三站:类和对象(中)构造函数与析构函数
【C++从0到王者】第三站:类和对象(中)构造函数与析构函数
29 0
|
3天前
|
编译器 C语言 C++
C++类和对象的细节原理:this指针、构造函数和析构函数、深浅拷贝、运算符重载、初始化列表、类的各种成员和方法
C++类和对象的细节原理:this指针、构造函数和析构函数、深浅拷贝、运算符重载、初始化列表、类的各种成员和方法
40 0
|
3天前
|
存储 C++
第十一章 C++成员函数与静态成员详解
第十一章 C++成员函数与静态成员详解
26 0
|
8月前
|
Java 编译器 C++
【C++基础】类与对象(下) 初始化列表、友元、内部类、匿名对象
【C++基础】类与对象(下) 初始化列表、友元、内部类、匿名对象
33 0
【C++基础】类与对象(下) 初始化列表、友元、内部类、匿名对象
|
9月前
|
Java
Java面向对象中 static关键字的详解
Java面向对象中 static关键字的详解
36 0
|
9月前
|
Java
Java面向对象中 this关键字的详解
Java面向对象中 this关键字的详解
45 0
|
9月前
|
Java
Java面向对象中 final关键字的详解
Java面向对象中 final关键字的详解
33 0
|
10月前
|
存储 C语言 C++
【C++学习】类和对象 | 友元 | 内部类 | 匿名对象 | 内存管理 | new
【C++学习】类和对象 | 友元 | 内部类 | 匿名对象 | 内存管理 | new
54 0
|
11月前
类与对象中this指针的使用
类与对象中this指针的使用
44 0