当Java程序运行时,JVM需要给数据分配内存空间。内存空间在逻辑上分为栈(stack)与堆(heap)两种结构。理解栈与堆对理解Java程序运行机制很有帮助。当Java程序运行时,被调用方法参数和方法中定义的局部变量都存储在内存栈中,当程序使用new运算符创建对象时,JVM将在堆中分配内存。
当main()方法执行时,JVM首先创建一个活动记录(activation record),它包括方法的参数args、方法中声明的局部变量employee,将其存储在栈中。在main()方法中创建了Employee对象,则该对象在堆中分配内存,上述代码执行后的栈与堆的情况如图4-4所示。如果在main()方法中调用了另一个方法,将创建另一个活动记录,并将其存入栈中。当方法调用结束返回时,活动记录将从栈中弹出,也叫出栈。Java运行时系统将释放为活动记录中变量分配的空间。
2.用UML图表示类:
UML(unified modeling language)称为统一建模语言,是一种面向对象的建模语言,运用统一、标准化的标记和定义实现对软件系统进行面向对象的描述和建模。在UML中可以用类图描述一个类。图4-5所示是Employee类的类图,它用长方形表示,一般包含三个部分:上面是类名;中间是成员变量清单;下面是构造方法和普通方法清单。有时为了简化类的表示,可能省略后两部分,只保留类名部分。在一个UML类图中,可以包含有关成员访问权限的信息。public成员的前面加一个“+”,private成员前加“−”,protected成员前加“#”,不加任何前缀的成员被看作具有默认访问级别。关于类成员的访问权限将在7.2节讨论。从图4-5可以看到,Employee类包含三个私有成员变量、两个构造方法和三个普通方法。在UML图中,成员变量和类型之间用冒号分隔。方法的参数列表放在括号中,参数需指定名称和类型,它的返回值类型写在一个冒号后面。
一般地,把能够返回成员变量值的方法称为访问方法(accessor method),把能够修改成员变量值的方法称为修改方法(mutator method)。访问方法名一般为getXxx(),因此访问方法也称getter方法。修改方法名一般为setXxx(),因此修改方法也称setter方法。访问方法的返回值一般与原来的变量值类型相同,而修改方法的返回值为void。
构造方法主要作用是创建对象并初始化类的成员变量。对类的成员变量,若声明时和在构造方法中都没有初始化,新建对象的成员变量值都被赋予默认值。对于不同类型的成员变量,其默认值不同。整型数据的默认值是0,浮点型数据默认值是0.0,字符型数据默认值是'\u0000',布尔型数据默认值是false,引用类型数据默认值是null
this关键字的另一个用途是在一个构造方法中调用该类的另一个构造方法。例如,假设在Employee类定义了一个构造方法Employee(String name,int age,double salary),现在又要定义一个无参数的构造方法,这时可以在下面的构造方法中调用该构造方法:
如果在构造方法中调用另一个构造方法,则this语句必须是第一条语句。
综上所述,this关键字主要使用在下面三种情况。解决局部变量与成员变量同名的问题;解决方法参数与成员变量同名的问题;用来调用该类的另一个构造方法。Java语言规定,this只能用在非static方法(实例方法和构造方法)中,不能用在static方法中。实际上,在对象调用一个非static方法时,向方法传递了一个引用,这个引用就是对象本身,在方法体中用this表示。
对带参数的方法,调用方法时需要向它传递参数。那么参数是如何传递的呢?在Java语言中,方法的参数传递是按值传递(pass by value),即在调用方法时将实际参数值的一个副本传递给方法中的形式参数,方法调用结束后实际参数的值并不改变。形式参数是局部变量,其作用域只在方法内部,离开方法后自动释放。尽管参数传递是按值传递的,但对于基本数据类型的参数和引用数据类型的参数的传递还是不同的。对于基本数据类型的参数,是将实际参数值的一个副本传递给方法,方法调用结束后,对原来的值没有影响。当参数是引用类型时,实际传递的是引用值,因此在方法的内部有可能改变原来的对象。
从程序运行结果可以看到,当参数为基本数据类型时,若在方法内修改了参数的值,在方法返回时,原来的值不变。当参数为引用类型时,传递的是引用,方法返回时引用没有改变,但对象的状态可能被改变。
注意:如果为方法传递的是不可变的引用类型对象(如String对象),对象在方法内部不可能被改变。
4.静态变量和静态方法:
如果成员变量用static修饰,则该变量称为静态变量或类变量(class variable),否则称为实例变量(instance variable)。如果成员方法用static修饰,则该方法称为静态方法或类方法(class method),否则称为实例方法(instance method)。
实例变量和静态变量的区别是:在创建类的对象时,Java运行时系统为每个对象的实例变量分配一块内存,然后可以通过该对象来访问该实例变量。不同对象的实例变量占用不同的存储空间,因此它们是不同的。而对于静态变量,Java运行且系统在类装载时为这个类的每个静态变量分配一块内存,以后再生成该类的对象时,这些对象将共享同名的静态变量,每个对象对静态变量的改变都会影响到其他对象。
通常,static与final一起使用来定义类常量。例如,Java类库中的Math类中就定义了两个类常量:
静态方法和实例方法的区别是:静态方法属于类,它只能访问静态变量。实例方法可以对当前的实例变量进行操作,也可以对静态变量进行操作。静态方法通常用类名调用,也可以用实例变量调用,实例方法必须由实例来调用。注意,在静态方法中不能使用this和super关键字。
在Java类的设计中,有时希望一个类在任何时候只能有一个实例,这时可以将该类设计为单例模式(singleton)。要将一个类设计为单例模式,类的构造方法的访问修饰符应声明为private,然后在类中定义一个static方法,在该方法中创建类的对象。
注意:对于方法或代码块中声明的变量,编译器不为其赋初始值,使用之前必须为其赋初值。
从上面程序输出结果可以看到,构造方法被最后执行。实际上,程序是按下面顺序为实例变量x初始化的。
(1)首先使用默认值或指定的初值初始化,这里先将x赋值为100。
(2)接下来执行初始化块,重新将x赋值为60。
(3)最后再执行构造方法,再重新将x赋值为58。因此,在创建InitDemo类的对象d后,d的状态是其成员变量值为58。