Java程序的变量大体可分为成员变量和局部变量。其中局部变量可分为如下3类。

  • 形参:在方法签名中定义的局部变量,由方法调用者负责为其赋值,随方法的结束而消亡。
  • 方法内的局部变量:在方法内定义的局部变量,必须在方法内对其进行显示初始化。这种类型的局部变量从初始化完成后开始生效,随方法的结束而消亡。
  • 代码块的局部变量:在代码块内定义的局部变量,必须在代码块内对其进行显式初始化,这种类型的局部变量从初始化完成后开始生效,随代码的结束而消亡。

局部变量的作用时间很短暂,它们都被存储在方法的栈内存中。类体内定义的变量被称为成员变量(英文是Field)。如果定义该成员变量时没有使用static修饰,该成员变量又被称为非静态变量或实例变量;如果使用了static修饰,则该成员变量又可被称为静态变量或类变量

对于static关键字而言,从词义上来看,它是“静态”的意思。但从Java程序的角度来看,static的作用就是将实例成员变为类成员。static只能修饰在类里定义的成员部分,包括成员变量、方法、内部类、初始化块、内部枚举类。如果没有使用static修饰这里类里的成员,这里成员属于该类的实例;如果使用了static修饰,这些成员就属于类本身。从这个意义上看,static只能修饰类里的成员,不能修饰外部类,不能修改局部变量、局部内部类。

表明上看,Java类里定义成员变量时没有先后顺序,但实际上Java要求定义成员变量时必须采用合法的前后引用。示例如下

public class ErrorDef {int num1 = num2 + 5; //Cannot reference a field before it is definedint num2 = 20;
}

上面程序中定义num1成员变量的初始值时,需要根据num2变量的值进行计数,这就是“非法前后引用”。
类似地,两个类变量也不允许采用这样“非法前后引用”,示例如下。

public class ErrorDef {static int num1 = num2 + 5; //Cannot reference a field before it is definedstatic int num2 = 20;
}

但如果一个是实例变量,一个是类变量,则实例变量总是可以引用类变量,示例如下

public class RightDef {int num1 = num2 + 5;static int num2 = 20;
}

上面程序中num1是一个实例变量,而num2是一个类变量。虽然num2位于num1之后被定义,但nun1的初始值却可根据num2计算得到。这是因为,num2变量是一个类变量,num1是实例变量,而类变量的初始化时机总是处于实例变量的初始化时机之前。所以,虽然源代码中先定义了num1,再定义了num2,但num2的初始化时机总是位于num1之前,因此num1变量的初始化可根据num2的值计算得到

实例变量和类变量的属性

使用static修饰的成员变量是类变量,属于该类本身;没有使用static修饰的成员变量是实例变量,属于该类的实例。在同一个JVM内,每个类只对应一个Class对象,但每个类可以创建多个Java对象。

由于同一个JVM内每个类只对应一个Class对象,因此同一个JVM内的一个类的类变量只需一块内存空间;但对于实例变量而言,该类每创建一次实例,就需要为实例变量分配一块内存空间。也就是说,程序中有几个实例,实例变量就需要几块内存空间。

下面程序可以很好地表现出来实例变量属于对象,而类变量属于类的特性。


class Person {static int eyeNum;String name;int age;public void info() {System.out.println("Name: " + name + ", Age: " + age);}
}public class FieldTest {public static void main(String[] args) {// 类变量属于该类本身,只要改类初始化完成,程序即可使用类变量Person.eyeNum = 2; // ①// 通过Person类访问 eyeNum类变量System.out.println("Person的eyeNum属性:" + Person.eyeNum);// 创建第一个Person对象Person p1 = new Person();p1.name = "zhangsan";p1.age = 22;System.out.println("通过p1变量访问eyeNum类变量:" + p1.eyeNum); // ②p1.info();// 创建第二个Person对象Person p2 = new Person();p2.name = "lisi";p2.age = 30;p2.info();// 通过p2修改Person类的eyeNum类变量p2.eyeNum = 4; // ③System.out.println("通过p1变量访问eyeNum类变量:" + p1.eyeNum);System.out.println("通过p2变量访问eyeNum类变量:" + p2.eyeNum);System.out.println("通过Person类访问eyeNum类变量:" + Person.eyeNum);}
}

上面程序中①行代码直接对Person类的eyeNum类变量赋值。这没任何问题,因为eyeNum类变量是属于Person类的,当Person类初始化完成后,eyeNum类变量也随之初始化完成。因此,程序即可对该类变量赋值,也可访问该类变量的值。

执行①行代码之后,程序的内存分配如图所示。

一旦Person类初始化完成,程序即可通过Person类访问eyeNum类变量。除此之外,Java还允许通过Person类的任意实例来访问eyeNum类变量

虽然Java允许通过Person对象来访问Person类的eyeNum类变量,但由于Person对象本身并没有eyeNum类变量(只有实例变量才属于Person实例),因此程序通过Person对象来访问eyeNum类变量时,底层依然会转换为通过Person访问eyeNum类变量。也就是说,不管通过哪个Person对象来访问eyeNum类变量,都与通过Person类访问eyeNum类变量的效果完全相同。因此,在②行代码处通过p1来访问eyeNum变量将再次输出2.

执行完②行代码后,程序的内存分配如图

从图中可以看出,当程序创建Person对象时,系统不再为eyeNum类变量分配内存空间,执行初始化,而只为Person对象
的实例变量执行初始化 – 因为实例变量才属于Person实例,而类变量属于Person类本身。

当Person类初始化完成之后,类变量也随之初始化完成,以后不管程序创建多少个Person对象,系统不再为eyeNum类变量分配内存,但程序每创建一个Person对象,系统将再次为name、age实例变量分配内存,并执行初始化。

当程序执行完③行代码之后,内存中再次增加了一个Person对象。当程序通过p2对eyeNum类变量进行赋值时,实际上依然是对Person类的eyeNum类变量进行赋值。此时程序的内存分配如图所示。

当Person类的eyeNum类变量被改变之后,程序通过p1、p2、Person类访问eyeNum类变量都将输出4。这是由于,不管通过哪个Person对象来访问eyeNum类变量,底层都将转换为通过Person来访问eyeNum类变量。由于p1和p2两个变量指向不同的Java对象,当通过它们访问实例变量时,程序将输出不同的结果。

实例变量的初始化时机

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

  • 定义实例变量时指定初始值
  • 非静态初始化块中对实例变量指定初始值
  • 构造器中对实例变量指定初始值

其中第1、2种方式(定义时指定的初始值和非静态初始化块中指定的初始值)比第3种方式(构造器中指定初始值)更早执行,但第1、2中方式的执行顺序与他们在源程序中的排列顺序相同。


class Cat {String name;int age;public Cat(String name, int age) {System.out.println("执行非静态初始化块");this.name = name;this.age = age;}{System.out.println("执行构造器");weight = 3.0;}double weight = 2.5;public String toString() {return "Name: " + name + ", Age: " + age + "Weight: " + weight;}
}public class InitTst {public static void main(String[] args) {Cat cat1 = new Cat("tom", 3); // ①System.out.println(cat1);Cat cat2 = new Cat("jiafei", 2); // ②System.out.println(cat2);}
}

每当程序调用指定构造器来创建Java对象时,该构造器必然会获得执行的机会。除此之外,该类所包含的非静态初始化块将会获得执行的机会,而且总是在构造器执行之前获得执行。

当程序执行①行代码创建第一个Cat对象的时候,程序将会先执行Cat类的非静态初始化块,再调用该Cat类的构造器来初始化该Cat实例。执行完①行代码后的内存分配如图所示。

从图中可以看出,该Cat对象的weight实例变量的值为2.5,二不是初始化块中指定的。这是因为,初始化块中指定初始值,定义weight时指定初始值,都属于对该实例变量执行的初始化操作,他们的执行顺序与它们的顺序相同。在本程序中,初始化块中对weight的赋值位于定义weight语句之前,因此程序将先执行初始化块中的初始化操作,执行完成后weight实例变量的值为3.0,然后再执行定义weight时指定的初始值,执行完成后weight实例变量的值为2.5。 从这个意义上来看,初始化块中对weight所指定的初始化值每次都将被2.5所覆盖。

当执行②行代码再次创建一个Cat对象时,程序将再一次调用非静态初始化块、相应的构造器来初始化Cat对象。

执行完②行代码后,程序的内存分配如图所示。

类变量的初始化时机

实例变量属于Java类本身,只有当程序初始化该Java类时才会为该类的类变量分配内存空间,并执行初始化。
从程序运行的角度来看,每JVM对一个Java类只初始化一次,因此Java程序每运行一次,系统只为类变量分配一次内存空间,执行一次初始化。
从语法角度来看,程序可以在2个地方对类变量执行初始化:

  • 定义类变量时指定初始值
  • 静态初始化块中对类变量指定初始值。

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

public class StaticInitTest {// 定义count类变量,定义时指定初始值static int count = 2;// 通过静态初始化块为name类变量指定初始值static {System.out.println("StaticInitTest的静态初始化块");name = "hello";}// 定义name类变量时指定初始值static String name = "itmyhome";public static void main(String[] args) {System.out.println("count类变量的值" + StaticInitTest.count);System.out.println("name类变量的值" + StaticInitTest.name);}
}

静态初始化块中为类变量指定初始值,每次运行该程序,系统将会对StaticInitTest类执行初始化:先为所有类变量分配内存空间,再按源代码中的排序执行静态初始化块中所指定的初始值和定义类变量时所指定的初始值.

对于本例程序而已,静态初始化夸中对name变量的指定初始值位于定义name变量时指定初始值之前,因此系统先将name类变量赋值为“hello”,然后再将该name类变量赋值为“itmyhome”。每运行该程序一次,这个初始化过程只执行一次,因此运行上面程序将看到输出name类变量的值为“itmyhome”.

下面程序更清楚地表现了类变量的初始化过程。首先定义了Price类,该Price类里有一个静态的initPrice变量,用于代表初始价格。每次创建Price实例时,系统会以initPrice为基础,减去当前打折价格(由discount参数代表)即得到该Price的currentPrice变量值


class Price {// 类成员是Price实例final static Price INSTANCE = new Price(2.8);// 再定义一个类变量static double initPrice = 20;// 定义该Price的currentPrice实例变量double currentPrice;public Price(double distinct) {// 根据静态变量计算实例变量currentPrice = initPrice - distinct;}
}public class PriceTest {public static void main(String[] args) {// 通过Price的INSTANCE访问currentPrice实例变量System.out.println(Price.INSTANCE.currentPrice); // ①// 创建Price实例Price p = new Price(2.8);// 通过创建的Price实例访问currentPrice实例变量System.out.println(p.currentPrice); // ②}
}

上面程序中①、②行代码都访问Price实例的currentPrice实例变量,而且程序都是通过new Price(2.8)来创建Price实例的。表面上看,程序输出两个Price的currentPrice都应该返回17.2(由20减去2.8得到),但实际上运行程序并没有输出两个17.2,而是输出-2.8和17.2

如果仅仅停留在代码表面来看这个问题,往往很难得到正确的结果,下面从内存角度来分析这个程序。第一次用到Price类时,程序开始对Price类进行初始化,初始化分成以下2个阶段。

  • (1)系统为Price的两个类变量分配内存空间。
  • (2)按初始化代码(定义时指定初始化值和初始化块中执行初始值)的排列顺序对类变量执行初始化。

初始化第一阶段,系统先为INSTANCE、initPrice两个类变量分配内存空间,此时INSTANCE、initPrice的默认值null和0.0。截止初始化进入第二个阶段,程序按顺序依次为INSTANCE、initPrice进行复制。对INSTANCE赋值时要调用Price(2.8),创建Price实例,此时立即执行程序中③代码为currentPrice进行赋值,此时initPrice类变量的值为0,因此赋值的结果是currentPrice等于-2.8。接着,程序再次将initPrice赋为20,但此时对INSTANCE的currentPrice实例变量以及不起作用了。

当Price类初始化完成后,INSTANCE类变量引用到一个currentPrice为-2.8的Price实例,而initPrice类变量的值为20.0。当再次创建Price实例时,该Price实例的currentPrice实例变量的值才等于20.0

Java实例变量和类变量相关推荐

  1. java 实例变量和类变量

    当JAVA程序执行时,类的字节码文件就被加载到内存,如果该类没有创建对象,类的实例成员变量不会被分配内存,但是类中的类变量在类被加载到内存的时候,就分配了相应的内存空间.如果该类创建对象,那么不同的实 ...

  2. java实例变量,局部变量,类变量和final变量

    题目 答案:B 解析 实例变量: 定义在类中的变量是类的成员变量,可以不进行初始化, java 会自动进行初始化.(如果是引用类默认初始化为 null, 如果是基本类型,默认初始化为 0 ) 局部变量 ...

  3. 阿里P8亲自讲解!java实例变量和类变量

    前言 Spring如何解决的循环依赖,是近两年流行起来的一道Java面试题.其实笔者本人对这类框架源码题还是持一定的怀疑态度的.如果笔者作为面试官,可能会问一些诸如"如果注入的属性为null ...

  4. java实例变量成员变量_Java的类成员变量、实例变量、类变量,成员方法、实例方法、类方法...

    总是被这些相似的概念搞晕,查阅了资料后做个小总结,以变量为例,方法辨析类似. 1.多胞胎名字汇总辨析 成员变量和成员方法是范围最大的定义,提到成员变量就可以理解成你所定义在一个类体中的各类变量的统称, ...

  5. jni java共享变量_Android JNI开发系列(十)JNI访问 Java 实例变量和静态变量

    JNI访问 Java 实例变量和静态变量 Java 中的实例变量和静态变量,在本地代码中如何来访问和修改.静态变量也称为类变量(属性),在所有实例对象中共享同一份数据,可以直接通过类名.变量名来访问. ...

  6. Java实例变量初始化

    原文地址:https://blog.csdn.net/cauchyweierstrass/article/details/48943077 题目是这样的:求下面程序的输出: public class ...

  7. java实例变量可以被覆盖吗_Java继承覆盖实例变量

    参见英文答案 > Java Inheritance – instance variables overriding                                    3个 我 ...

  8. 实例变量和类变量、实例方法类方法区别

    实例变量 实例变量声明在一个类中,但在方法.构造方法和语句块之外: 当一个对象被实例化之后,每个实例变量的值就跟着确定: 实例变量在对象创建的时候创建,在对象被销毁的时候销毁: 实例变量的值应该至少被 ...

  9. Python基础学习——面向对象编程(第一讲:面向对象概述、面向对象三个基本特征(封装性、继承性、多态性)、类和对象(定义类、创建和使用对象、实例变量、类变量、构造方法、实例方法、类方法、静态方法))

    面向对象是Python最重要的特性,在Python中一切数据类型都是面向对象的. 1.面向对象概述 面向对象的编程思想是,按照真实世界客观事物的自然规律进行分析,客观世界中存在什么样的实体,构建软件系 ...

  10. 实例变量和类变量、类方法和实例方法

    类体中包括成员变量和局部变量,而成员变量又可以细分为实例变量和类变量,在声明成员变量的时候,用static给予修饰的称作类变量,否则称作实例变量.(类变量也称为static变量,静态变量) 那么,类变 ...

最新文章

  1. 《剑指offer》第1~11题:刷题week1[C++题解]
  2. malloc()背后的实现原理——内存池
  3. redis设置密码和启动 redis数据类型
  4. c++如何输入数组_从一个数组中找出 N 个数,其和为 M 的所有可能最 nice 的解法...
  5. python convert函数_Python内置函数
  6. 论程序员如何规划职业路线?网友:从码农到工程师?
  7. golang 排序_常用排序算法之冒泡排序
  8. Purism释出Librem 5智能型手机新进展
  9. Java web 实战项目案例
  10. 利用Excel的LINEST计算线性拟合的斜率和截距的不确定性
  11. Android Studio 4.1中的模板插件
  12. typecho图片插件_Ideal ——接近Typecho完美的插件
  13. 如何使用计算机对文件修改密码,win7系统给共享文件夹设置密码的操作办法
  14. 武大地理信息科学本科生的专业相关网站总结分享(包括制图、专业课、自学复习等)
  15. oracle分区表的作用
  16. 欧拉角细节/旋转顺序/内旋外旋
  17. 【C进阶】第十篇——数据在内存中的存储
  18. 邮政社招笔试题库_中国邮政 招聘考试试题及答案 总括版
  19. Flowable-6.6.0 工作流引擎(windows平台zip包)下载
  20. win与linux谁运行快,Linux、Windows,谁速度更快

热门文章

  1. 菜鸟教程java在线编辑器_HTML 编辑器
  2. 2.页面截长图工具-FireShot
  3. 网络系统建模与仿真技术,信息网络建模与仿真
  4. 计算机组成原理课程要求及目的,《计算机组成原理》课程教学大纲
  5. 移远EC20 R2.0 AT指令拨号流程
  6. python+opencv简单人脸识别(源码)(有手就行)
  7. 2013年9月份第2周51Aspx源码发布详情
  8. java file 权限_Java文件权限(设置)
  9. Kepserver如何连接InTouch
  10. Java硬核福利,实战虚拟机+Springboot+缓存,java电子书教材下载