Java的变量存储

注:本文的描述针对beginner做了简化。

1. 非static的成员变量

我们通过下面的例子来简要说明Java是如何维护变量的。我们需要借助类与对象一节中使用的Cat类,

package animal;

public class Cat {

	// Cat的属性(成员变量)
	public int age = 0;
	public String name = "";
	public String owner = "";
	
}

创建一个包含main方法的类来使用Cat类,并定义其他类型的变量。(为了简洁起见,我们将变量定义在main函数里,这样并不影响我们理解Java维护变量的机制) 这里我们创建两个基本数据类型变量ab,一个String类型的变量str,和三个Cat类的对象cat1, cat2, cat4; 对应的内存图见Fig.1.

import animal.Cat;

public class JavaMemoryManagement {

	public static void main(String[] args) {
		int a = 3;
		double b = 2.1;

		String str = "Hello world";  

		Cat cat1 = new Cat();
		Cat cat2 = new Cat();
		Cat cat4;
		
		// 给cat1的属性赋值
		cat1.age = 3;
		cat1.name = "Ame";
		cat1.owner = "XU, Xiaoyan";
		
	}

}
  • 堆stack栈heap可简单理解为内存中两种不同的存储空间;
  • Java运行时,会在上创建一个变量表,用来记录所有运行过程中需要用到的变量;
  • 对于 基本数据类型 int,double等,可以简单理解为Java把数据直接写在变量对应的栈中,如;(实际是分开存储,但是基本类型的数值还是存储在栈上,对应过程比较复杂,beginner没必要深究)
  • String类型变量:
    • Java会在变量表中记录str变量;
    • 为其赋值时,Java会在堆上开辟一个存储空间,写入值"Hello world",并将str指向这个存储空间(str记录该存储空间的内存地址);
    • 使用str变量时,Java会按照其记录的内存地址(沿着箭头)找到堆上的数据,并使用;
  • Cat类生成的对象:
    • Cat cat2 = new Cat();
      • 等号左边Cat cat2声明了一个Cat类型的变量,叫cat2
      • 等号右边new Cat()在堆中开辟一个存储空间来存储这个新对象,用来存储cat2对象的实体;
      • 等号将变量表中的cat2指向上述开辟出来的内存空间;
      • cat2的所有属性均为初始值。
    • cat1 的创建和使用过程:
      • Cat cat1 = new Cat()会在变量表中记录一个Cat类型的变量cat1,同时在堆上开辟一块内存空间来存储这个新对象,并将cat1指向这个内存空间;
      • 通过后续代码cat1.age = 3等给cat1的属性赋值,会修改cat1对应的对象的值。
    • Cat cat4;
      • 这条语句只声明一个Cat类的变量,叫cat4(即在变量表中录入cat4);
      • 因为没有new Cat()Java不会cat4开辟内存空间!因此cat4只有其名,未有其实,我们通常说cat4 is null;
      • 这种情况下,如果尝试使用cat4的属性或方法,例如cat4.age,系统会出错!

Fig. 1. Java内存管理的大致示意图

2. static成员变量和方法

如下代码中,我们给Cat类添加两个static成员变量lifespanspecies,和一个static的成员方法staticPrint().

public class Cat {

	// 属于Cat类的静态变量
	public static String species = "Animal";
	public static int lifespan = 20;

	// Cat类型的<对象>拥有的变量
	public int age = 0;
	public String name = "";
	public String owner = "";

	// 属于Cat类的静态方法
	public static void staticPrint(String something){
		System.out.println("staticPrint method of Cat: " + something);
	}

	// Cat类型的<对象>拥有的方法
	public void print(String something){
		System.out.println("Non-static print method of Cat: " + something);
	}

}

Static成员变量lifespanspecies会被记录到一个特殊的metaspace类型的存储空间,他们有如下特性:

  • (1) static成员变量不属于任何对象!它只属于类
  • (2) static成员变量在其作用范围内有全局变量的意味;
  • (3) static成员变量在程序运行期间会一直存在
  • (4) 直接用类名.变量名使用static成员变量。

static的成员方法同样只属于类,不属于任何对象;可以直接用**类名.方法名(参数)**直接调用。

例. 在JavaMemoryManagement.java类里,使用Catlifespanspecies变量,并调用CatstaticPrint方法(传入参数"Hello Cat")。

public class JavaMemoryManagement {
	public static void main(String[] args) {

		//用Cat.lifespan调用 Cat的lifespan静态属性
		System.out.println("Cat's lifespan = " + Cat.lifespan);
		System.out.println("Cat belongs to " + Cat.species);

		// 用Cat.staticPrint()调用Cat的staticPrint静态方法,并传入参数
		Cat.staticPrint("Hello Cat");
	}
}

注:

  • 静态方法只能调用静态变量和静态方法!
  • 静态方法不能使用非静态的变量和方法!

例. 在下面的例子中,a为非static的类成员变量,print()是非静态的成员方法,尝试在staticmain方法里引用a将会导致错误。

public class TestStatic {
	
	int a = 1;
	
	public static void main(String[] args) {
		int b = a + 1;  // 报错:静态的main无法引用非静态的成员变量a

		print(); // 报错:静态的main无法调用非静态的print()方法
	}

	public void print(){
		System.out.println("This is a non-static method.");
	}

}

提问:那么上述例子中,如果想要引用a变量和print()方法,该怎么办呢?