变量概要
在Java程序中,变量可分为成员变量和局部变量。成员变量是指定义在类体内的变量,它们存储在类的栈中。如果定义该成员变量时没有使用static修饰,该成员变量又被称为非静态变量或实例变量,否则就被称为静态变量或类变量。局部变量可分为3类:- 形参:在方法签名中定义的局部变量,由方法调用者为其赋值,随方法的结束而消亡;
- 方法内的局部变量:必须在方法内对其进行显示初始化,从初始化完成后开始生效,随方法的结束而消亡;
- 代码块内的局部变量:必须在代码块内对其进行显示初始化,从初始化完成后开始生效,随代码块的结束而消亡。
类变量属于该类本身,而实例变量属于该类的实例。在同一个JVM内,每个类只对应一个Class对象,但每个类可以创建多个Java对象。
由于同一个JVM内每个类只对应一个Class对象,因此用一个JVM内的一个类的类变量只需一块内存空间;但对于实例变量而言,该类每创建一次实例,就需要为实例变量分配一块内存空间。也就是说程序中有几个实例,实例变量就需要几块内存空间。
成员变量初始化顺序
Java虚拟机每次创建对象都会为实例变量分配内存空间,并对实例变量执行初始化。在代码中,程序可以在3个地方对实例变量执行初始化:1、定义实例变量时指定初始值;
2、非静态初始化块中对实例变量指定初始值;
3、构造器中对实例变量指定初始值。
其中前两种方式比最后一种方式更早执行,但前两种方式的执行顺序与它们在源程序中的排列顺序相同。
示例一:
package test;
/**
* 输出结果:
block
constructor
name=Jimy
*
* @author hbin(cn.binbin@qq.com)
* @site http://blog.csdn.net/binbinxyz
* @date 2014-5-29
*/
public class Person
{
private String name = "Tom";
{
System.out.println("block");
name = "Jack";
}
public Person()
{
System.out.println("constructor");
name = "Jimy";
}
public static void main(String[] args)
{
System.out.println("name=" + new Person().name);
}
}
示例二:
package test;
/**
* 输出结果:
block
name=Jack
*
* @author hbin(cn.binbin@qq.com)
* @site http://blog.csdn.net/binbinxyz
* @date 2014-5-29
*/
public class Person_1
{
private String name = "Tom";
{
System.out.println("block");
name = "Jack";
}
public static void main(String[] args)
{
System.out.println("name=" + new Person_1().name);
}
}
示例三:
package test;
/**
* 输出结果:
block
name=Tom
*
* @author hbin(cn.binbin@qq.com)
* @site http://blog.csdn.net/binbinxyz
* @date 2014-5-29
*/
public class Person_2
{
{
System.out.println("block");
name = "Jack";
}
private String name = "Tom";
public static void main(String[] args)
{
System.out.println("name=" + new Person_2().name);
}
}
示例一说明:实例变量的初始化方式1和2比方式3更早执行。
示例二和三对比说明:实例变量的初始化方式1和2的执行顺序与它们在源程序中的排列顺序相同。
静态变量初始化顺序
Java虚拟机对一个Java类只初始化一次,因此Java程序每运行一次,系统只为类变量分配一次内存空间,执行一次初始化。在代码中,程序可以在2个地方对类变量执行初始化:1、定义类变量时指定初始值;
2、静态初始化块中对类变量指定初始值。
这两种方式的执行顺序与它们在源程序中的排列顺序相同。
示例四:
package test;
/**
* 输出结果:
static block
id=2
*
* @author hbin(cn.binbin@qq.com)
* @site http://blog.csdn.net/binbinxyz
* @date 2014-5-29
*/
public class Person_3
{
private static int id = 1;
static
{
System.out.println("static block");
id = 2;
}
public static void main(String[] args)
{
System.out.println("id=" + Person_3.id);
}
}
示例五:
package test;
/**
* 输出结果:
static block
id=1
*
* @author hbin(cn.binbin@qq.com)
* @site http://blog.csdn.net/binbinxyz
* @date 2014-5-29
*/
public class Person_4
{
static
{
System.out.println("static block");
id = 2;
}
private static int id = 1;
public static void main(String[] args)
{
System.out.println("id=" + Person_4.id);
}
}
示例四和五对比说明:静态变量的初始化方式1和2的执行顺序与它们在源程序中的排列顺序相同。
经典示例
package test;
public class Price
{
private static Price INSTANCE = new Price(2);
private static int INIT_PRICE = 20;
private int currentPrice;
public Price(int discount)
{
currentPrice = INIT_PRICE - discount;
}
public static void main(String[] args)
{
System.out.println(Price.INSTANCE.currentPrice);
System.out.println(new Price(2).currentPrice);
}
}
表面上看,程序输出两个Price的currentPrice都应该返回18(20-2),但实际上运行后输出结果为-2和18。如果仅仅停留在代码表面看,很难得到正确结果,下面从内存角度来分析这个程序。首先看类中的主函数main,执行System.out.println(Price.INSTANCE.currentPrice);时程序第一次用到Price类,这个时候会对Price类进行初始化,初始化过程为:
1、为Price的两个类变量(INSTANCE和INIT_PRICE)分配内存空间,此时INSTANCE、INIT_PRICE的值为默认值null和0;
2、按照初始化代码(定义时指定初始值和初始化块中执行初始值)的排列顺序对类变量执行初始化;
a)对INSTANCE执行初始化:创建Price实例用到Price类的带参数构造器,执行其中的currentPrice=INIT_PRICE-discount。因为此时的INIT_PRICE=0,所以currentPrice=-2;
b)对INIT_PRICE执行初始化:INIT_PRICE=20;
3、这个时候主函数main()中System.out.println(Price.INSTANCE.currentPrice);执行完毕,出书结果为-2;
4、之后执行new Price(2);这行代码,会先执行1、2、3过程,此时currentPrice=-2,INIT_PRICE=20。然后调用Price类的带参构造器执行currentPrice=INIT_PRICE– discount,得到 currentPrice=18。