对象及内存管理——实例变量与类变量

本文详细介绍了Java中的实例变量与类变量的特性,包括它们的初始化、内存分配以及在内存管理中的作用。文章通过实例演示了实例变量与类变量的区别,强调了类变量属于类本身,而实例变量属于类的实例。同时,文章讨论了实例变量的三种初始化方式,并解释了类变量的初始化过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引言

        Java内存管理分为:内存分配和内存回收。内存分配指的是创建Java对象时Java虚拟机为该对象在堆内存中所分配的内存空间;内存回收指的是当Java对象失去引用时,Java虚拟机会认为该对象为垃圾并自动清理该对象,最后回收该对象所占用的内存空间。

1、实例变量与类变量

        Java中局部变量可以分为以下三类

1、形参:在方法签名中定义的局部变量,由调用方法的一方负责赋值,当方法结束调用时消亡。

2、方法内的局部变量:方法内定义的局部变量,必须在代码块内对其显式初始化。当初始化完成后生效,方法结束时消亡。

3、代码块内的局部变量:代码块内定义的局部变量,必须在代码块内对其显式初始化。当初始化完成后生效,代码块结束时消亡。

       由于局部变量的生命周期很短暂,所以它们都被存储在栈内存中。
       类中定义的变量称为成员变量。如果定义成员变量时没有使用static进行修饰,该成员变量又被成为非静态变量或实例变量;如果使用static进行修饰,该成员变量又被成为静态变量或类变量。
提示:static只能修饰在类中定义的成员部分,包括成员变量、方法、内部类(枚举类或接口)、初始化块。如果没有使用static修饰类中的这些成员,这些成员属于该类的实例;如果使用static进行修饰,这些成员就属于类本身。总而言之,static只能修饰类中的成员,不能修饰外部类,不能修饰局部变量和局部内部类。
        从表面上看来,Java类中定义成员变量时没有先后顺序,但是实际上Java要求定义成员变量时必须采用合法的前向引用。

public class ErrorDef {
             //下面代码会提示:num2 cannot be resolved to a variable
             int num1 = num2 + 1;  
             int num2 = 10;
 
             //下面代码会提示:num2 cannot be resolved to a variable
             static int num1 = num2 + 1;
             static int num2 = 10;
}    

        上面程序中不管是int型还是使用static修饰过的int型变量num1,在进行num2+1操作时都需要使用变量num2,但是变量num2却在变量num1之后定义,这也就是“非法向前引用”,因此运行上面的代码将会报错。但是如果一个是实例变量,另外一个是类变量,则实例变量总可以引用类变量,并且运行正常,代码如下所示:

public class RightDef {
 
          int num1 = num2 + 1;  
          static int num2 = 10;
 
 
          static int num2 = 10;
          int num1 = num2 + 1;
}

        造成以上状况的原因是:static修饰的成员变量属于类,类变量会随着类的初始化而进行初始化,因为上面代码中num2会随和RightDef类的初始化而初始化;没有使用static修饰的成员变量则属于实例变量,实例变量会随着对象的初始化而初始化。在初始化一个对象之前,一定会先初始化该对象所属的类,所以上面程序运行正常。

1、1 实例变量与类变量的属性

        再次强调,使用static修饰的成员变量是类变量,属于类本身;没有使用static修饰的成员变量是实例变量,属于类的实例。在同一个Java虚拟机中,每一个类只对应一个class对象,但每个类可以创建多个Java对象。正是由于这个原因,所以同一个Java虚拟机中一个类的类变量主需要一块内存空间,但对于实例变量而言,每创建一个类的实例,就需要为该实例变量分配一块内存空间。

class Person {
	public String name;
	public int age;
	public static int eysNum;
	
	public void info() {
		System.out.println("name = " + this.name + ",age = " + this.age);
	}
}


public class FieldTest {
	public static void main(String[] args) {
		//类变量属于该类本身,只要类初始化完成,即可使用类变量
		Person.eysNum = 2;  //①
		System.out.println("Person类的eysNum = " + Person.eysNum);
		
		Person p1 = new Person();
		p1.name = "张三";
		p1.age = 13;
		p1.info();
		System.out.println("p1实例变量的eysNum = " + p1.eysNum);  //②
		
		Person p2 = new Person();
		p2.name = "李四";
		p2.age = 17;
		p2.info();
		System.out.println("p2实例变量的eysNum = " + p2.eysNum);
		
		p1.eysNum = 1;  //③
		System.out.println("Person类的eysNum = " + Person.eysNum);
		System.out.println("p1实例变量的eysNum = " + p1.eysNum);
		System.out.println("p2实例变量的eysNum = " + p2.eysNum);
	}
}

输出结果为:
Person类的eysNum = 2name = 张三,age = 13
p1实例变量的eysNum = 2name = 李四,age = 17 p2实例变量的eysNum = 2 Person类的eysNum = 1 p1实例变量的eysNum = 1 p2实例变量的eysNum = 1
        当执行了①处的代码之后,完成了对Person类中eyeNum的赋值,程序的内存分配示意图如下所示。




          虽然Java允许通过Person对象来访问Person类的eyeNum类变量,但是由于Person对象本身并没有eyeNum类变量,因此程序通过Person对象访问eyeNum类变量时,底层会转换为通过Person类访问eyeNum类变量。换句话来说,不管通过哪个Person对象来访问eyeNum类变量与通过Person类访问eyeNum类变量效果相同。当执行了②处的代码之后,程序的内存分配示意图如下所示。

          当Person类初始化完成之后,类变量也随之初始化完成,以后不管程序创建多少个Person对象,系统都不再为eyeNum类变量分配内存空间;但程序每创建一个Person对象,系统将再次为name,age实例变量分配内存空间并执行初始化过程。       当程序执行到③时,内存中再次增加了一个Person对象。当通过p1对象对eyeNum类变量进行赋值时,其实质是对Person类的eyeNum类变量进行赋值,程序的内存分配示意图如下所示。

1、2 实例变量初始化

         从程序运行的角度来说,每次程序创建Java对象时都需要为实例变量分配内存空间,并对实例变量执行初始化。从语法角度来看,实例变量初始化可在以下三个地方执行:

1、定义实例变量时指定初始值

2、非静态初始化块中对实例变量指定初始值

3、构造器中对实例变量指定初始值

       其中前两种方式的执行顺序与它们在源程序中的排列顺序相同,且比最后一种方式执行的要早。

class Person {
	public String name;  //姓名
	public int age;  //年龄
	
	public Person(String name , int age) {  //构造器
		 this.name = name ;
		 this.age = age;
	}
	
	{  //非静态初始化块
		weight = 2.0;
	}
	
	double weight = 2.3;  //定义实例变量
	
	public String toString() {
		return "Person[name = " + this.name + ",age = " + this.age + ",weight = " + this.weight + "]";
	}
	
}

public class Init {
	public static void main(String[] args) {
		Person p1 = new Person("张三" , 13);
		System.out.println(p1);
		Person p2 = new Person("李四" , 17);
		System.out.println(p2);
	} 
}

输出结果为:
Person[name = 张三,age = 13,weight = 2.3]
Person[name = 李四,age = 17,weight = 2.3]
       上面程序中粗体字代表Java对实例变量的三种初始化方式。每当创建Java对象时,对应的构造器必然会获得执行的机会,除此之外,该类所包含的非静态初始化块将会在构造器之前获得执行的机会。
       从最后输出的结果可以看到,每一个Person类对象的weight实例变量的值都是2.3,而不是非静态初始化块中定义的2.0,造成这种现象的原因是:非静态初始化块在定义实例变量之前,而它们的执行顺序与它们在源代码中的排列顺序相同。执行完所有代码后,程序的内存分配示意图如下所示。


        其实,在定义实例变量时指定初始值和非静态初始化块中指定初始值的地位是平等的,经过编译器处理之后,都将被提取到构造器中执行。例如:double weight = 2.3;
       (1)double weight:创建Java对象时系统为其分配内存。
       (2)weight = 2.3:这条语句将会被提取到Java类的构造器中执行。

1、3 类变量初始化

          类变量属于Java类本身。从程序运行的角度来说,每个Java虚拟机对一个Java类只初始化一次,因此只有每次运行Java程序是,才会初始化该Java类,才会为该类的类变量分配内存空间并初始化。从语法角度来看,类变量初始化可在以下两个地方执行:

1、定义类变量是指定初始值

2、静态初始化块中对类变量指定初始值

       以上两种方式的执行顺序与它们在源程序中的排列顺序相同。

public class StaticInit {
	static int num = 2;  //定义类变量
	
	static {  //静态初始化块
		name="李四";
	}
	
	static String name = "张三";
	
	public static void main(String[] args) {
		System.out.println(StaticInit.name);
		System.out.println(StaticInit.num);
	} 
}

输出结果为:
张三
2
       每次运行该程序时,系统都会对StaticInit类执行初始化:先为所有类变量分配内存空间,再按源代码中的排列顺序执行静态初始化中所指定的初始值和定义类变量时所指定的初始值。

class Price {
	final static Price INSTANCE = new Price(2.8);  //类成员是Price实例
	static double initPrice = 20;
	double currentPrice;
	public Price(double discount) {
		currentPrice = initPrice - discount;
	}
}

public class PriceTest {
	public static void main(String[] args) {
		System.out.println(Price.INSTANCE.currentPrice);  //①
		Price p = new Price(2.8);
		System.out.println(p.currentPrice);  //②
	}
}

输出结果为:
-2.8
17.2
       上面程序中①和②都访问了Price实例对象的currentPrice实力变量,而且都是通过new Price(2.8);来创建的,但是输出的结果却不相同。
       下面内存的角度来分析一下程序。第一次用到Price类时,程序开始对Price类进行初始化,初始化分为以下两个阶段。
       (1)系统为Price的两个类变量分配内存空间。
       (2)按初始化代码的排列顺序对类变量执行初始化。
       初始化第一阶段,系统为INSTANCE、initPrice两个类变量分配内存空间,此时INSTANCE、initPrice的值为默认值null和0.0。进入第二阶段,程序按顺序依次为INSTANCE、initPrice进行赋值,此时INSTANCE需要创建Price实例,此时会立刻执行程序中粗体字部分,但是initPrice此时值还是0.0,所以得出的currentPrice的值就是-2.8,接下来程序为initPrice赋值已经不会对INSTANCE类成员中的currentPrice产生任何变化了。而当Price类初始化完成之后,也就是代码运行到②时,p.currentPrice就已经等于20-discount,即20-2.8=17.2。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值