昨天太困了,就没有写东西,因此今天会补两篇。
今天的内容将围绕Java的变量,目的是对变量有更深刻的认识。
Java实例变量、类变量与局部变量的概念
Java程序的变量大体可以分为成员变量和局部变量。其中局部变量可以分为如下3类:
- 形参,在方法签名中定义的局部变量,由方法调用者负责为其进行赋值,随方法的结束而消亡。
- 方法内的局部变量,在方法内定义的局部变量,必须在方法内对其进行显式初始化。这种类型的局部变量从初始化完成后开始生效,随方法的结束而消亡。
- 代码块的局部变量,在代码块内定义的局部变量,必须在代码块内对其进行显式初始化,这种类型的局部变量从初始化完成后开始生效,随代码的结束而消亡。
局部变量的作用时间很短暂,它们被存储在方法的栈内存中,类体定义的变量被称为成员变量(Field)。如果定义该成员变量时没有使用static修饰,该成员变量被称为非静态变量或实例变量,如果使用了static修饰,则该成员变量又可被称为静态变量或类变量。
- 实例变量,也叫对象变量,类成员变量,从属于类,由类生成对象时,才分配存储空间,各个对象的实例变量互不干扰,能通过对象的引用来访问实例变量。但是在Java多线程中,实例变量是多个线程共享的资源,需要注意同步访问时可能出现的问题。
public class Test1 {
// 实例变量
private String name;
private int age;
// 实例方法
public void sleep() {
System.out.println("人会睡觉");
}
}
- 类变量,也叫静态变量,是一种特殊的实例变量,用static关键字修饰;一个类的静态变量,所有由这个类生成的对象都共用这个类变量,类在装载时就分配存储空间。一个对象修改了变量,则所有对象中的这个变量的值都会发生改变。
对于static关键字而言,从词义上来看,是“静态”的意思,但是从Java程序的角度来看,static的作用就是将实例成员变成类成员。static只能修饰在类里定义的成员部分,包括成员变量,方法,内部类,初始化块,内部枚举类。如果没有使用static修饰这里类的成员,这里成员属于该类的实例;如果使用了static修饰,这里的成员就属于类本身。从这个意义上看,static只能修饰类里的成员,不能修饰外部类,不能修改局部变量,局部内部类。
public class Test1 {
public static int eyeNum;
public String name;
public int age;
public void info() {
System.out.println("Name:" + name + ",Age:" + age);
}
}
public class Test1Demo {
public static void main(String[] args) {
System.out.println(Test1.eyeNum);
System.out.println("--------------");
Test1.eyeNum = 2;
System.out.println("Test1的eyeNum属性: " + Test1.eyeNum);
System.out.println("--------------");
Test1 t1 = new Test1();
t1.name = "t1";
t1.age = 18;
t1.info();
System.out.println("通过t1变量访问eyeNum类属性: " + t1.eyeNum);
System.out.println("--------------");
Test1 t2 = new Test1();
t2.name = "t2";
t2.age = 19;
t2.info();
t2.eyeNum = 4;
System.out.println("--------------");
System.out.println("通过t1变量访问eyeNum类属性: " + t1.eyeNum);
System.out.println("通过t2变量访问eyeNum类属性: " + t2.eyeNum);
System.out.println("通过Test1类访问eyeNum类属性: " + Test1.eyeNum);
}
}
===============================
结果
===============================
0
--------------
Test1的eyeNum属性: 2
--------------
Name:t1,Age:18
通过t1变量访问eyeNum类属性: 2
--------------
Name:t2,Age:19
--------------
通过t1变量访问eyeNum类属性: 4
通过t2变量访问eyeNum类属性: 4
通过Test1类访问eyeNum类属性: 4
这里,虽然Java允许通过Test1对象来访问Test1类的eyeNum类变量,但由于Test1对象本身并没有eyeNum类变量,因此程序通过Test1对象访问eyeNum类变量时,底层依然会转换为通过Test1访问eyeNum类变量。也就是说不管通过哪个Test1对象访问eyeNum类变量,都与通过Test1类访问eyeNum类变量的效果完全相同。
- 局部变量,方法中或者局部块中声明定义的变量或方法的参数被称为局部变量,它们只存在于创建它们大的block里({}之间)无法在block外进行任何操作,如读取。赋值。在Java多线程中,每一个线程都复制一份局部变量,可以防止同步问题发生。
public class Test1 {
// 实例变量
private String name;
private int age;
// 实例方法
public void test() {
// 局部变量
int tmp = 1;
System.out.println(tmp);
}
- 成员变量和局部变量的区别
成员变量:
- 成员变量定义在类中,在整个类中都可以被访问。
- 成员变量随着对象的建立而建立,随着对象的消失而消失,存在于对象所在的堆内存中。
- 成员变量有默认初始化值。
局部变量:
- 局部变量只定义在局部范围内,如:函数内,语句内等,只在所属的区域有效。
- 局部变量存在于栈内存中,作用的范围结束,变量空间会自动释放。
- 局部变量没有默认初始化值。
- 成员变量和静态变量的区别
两个变量额声明周期不同:
- 成员变量随着对象的创建而存在,随着对象被回收而释放。
- 静态变量随着类的加载而存在,随着类的消失而消失。
调用方式不同:
- 成员变量只能被对象调用。
- 静态变量可以被对象调用,还可以被类名调用。
别名不同
- 成员变量也称为实例变量。
- 静态变量也称为类变量。
数据存储位置不同
- 成员变量存储在堆内存的对象中,所以也叫对象的特有数据。
- 静态变量数据存储在方法区(共享数据区)的静态区,所以也叫对象的共享数据。
变量的声明和变量的初始化
- 定义基本类型变量时
- 定义局部基本类型变量,定义局部变量时,在使用该变量之前必须对其进行赋值初始化,否则编译器会报错,建议声明变量和赋值同时进行。
- 定义类基本类型变量,无论是静态变量还是动态变量,在创建对象时,系统会自动对基本类型变量进行初始化。
- 定义非基本类型变量时
- 局部变量,同基本类型变量一样,使用前必须对其进行初始化,否则编译器会报错。
- 全局变量,若只声明变量,创建对象时,会将其赋值为null,若声明变量的同时进行初始化,此刻会指向一片对象内存区域,不再为null,而是系统初始化内容。
总结,局部变量使用时,必须进行初始化,否则编译器报错,全局变量使用前没有手动初始化,系统自动对其进行初始化。基本类型,系统会为其赋值(int=0, short=(short)0, byte=(byte)0, boolean=false, long=0L, char='\u0000'或null, float=0.0f, double=0.0), 对于非基本类型会赋值为null(初始化除外)。
public class MyTest1 {
int e;
@Override
public String toString() {
return "MyTest1{" +
"e=" + e +
'}';
}
}}
public class MyTest2 {
public static int f;
}
public class MyTest {
int a;
long b;
String c = new String();
char d;
List lsit = new ArrayList();
MyTest1 mt1 = new MyTest1();
@Override
public String toString() {
return "MyTest{" +
"a=" + a +
", b=" + b +
", c='" + c + '\'' +
", d=" + d +
", lsit=" + lsit +
", mt1=" + mt1 +
", MyTest2.f=" + MyTest2.f +
'}';
}
}
public class Test {
public static void main(String[] args) {
MyTest mt = new MyTest();
System.out.println(mt);
}
}
=========================
结果
=========================
MyTest{a=0, b=0, c='', d=, lsit=[], mt1=MyTest1{e=0}, MyTest2.f=0}
- 实例变量的初始化时机
对于实例变量而言,它属于Java对象本身,每次程序创建Java对象时都需要为实例变量分配内存空间,并执行初始化。从程序运行的角度来看,每次创建Java对象都会为实例变量分配内存空间,并对实例变量执行初始化。从语法角度来看,程序可以在3个地方对实例变量执行初始化。
定义实例变量时指定初始化。
非静态初始化中对实例变量指定初始化。
构造器中对实例变量指定初始化。
其中前两种方式比最后一种更早执行,但第1、2种方式执行顺序与它们在程序中的顺序相同。
public class Cat {
private String name;
private 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;
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
", weight=" + weight +
'}';
}
}
public class CatTest {
public static void main(String[] args) {
Cat c1 = new Cat("tom", 3);
System.out.println(c1);
System.out.println("--------------------");
Cat c2 = new Cat("蓝猫", 6);
System.out.println(c2);
}
}
======================
结果
======================
执行非静态初始化
执行构造器
Cat{name='tom', age=3, weight=2.5}
--------------------
执行非静态初始化
执行构造器
Cat{name='蓝猫', age=6, weight=2.5}
注意,Cat对象的weight实例变量的值为2.5,不是初始化块中指定的。因为,初始化块中指定初始值,定义weight时指定初始值,都属于对该实例变量执行的初始化操作,它们的执行顺序与它们的顺序相同。在上述程序中,初始化块中对weight的赋值位于定义weight语句之前,因此程序将先执行初始化块中的初始化操作,执行完后weight实例化变量的值为3.0,然后在执行weight时指定的初始值,执行完后weight实例变量的值为2.5.从这个意义上来看。初始化块中对weight所指定的初始化值每次都将被2.5所覆盖。
- 类变量的初始化时机
类变量属于Java类本身,只有当程序初始化该Java类时才会为该类的类变量分配内存空间,并执行初始化。从程序运行角度来看,JVM对一个Java类只初始化一次,因此Java程序每运行一次,系统只为类变量分配一次内存空间,执行一次初始化。从语法角度看,程序可以在2个地方对类变量执行初始化。
定义类变量时指定初始值。
静态初始化块中对类变量指定初始值。
这两种方式的执行顺序与它们在程序中的排列顺序相同。
public class StaticInitTest {
static int count = 3;
static {
System.out.println("StaticInitTest的静态初始化块");
name = "hello";
}
// String name = "haha"; 注意,编译器报错
static String name = "haha";
public static void main(String[] args) {
System.out.println("count类变量的值" + StaticInitTest.count);
System.out.println("name类变量的值" + StaticInitTest.name);
}
}
======================
结果
======================
StaticInitTest的静态初始化块
count类变量的值3
name类变量的值haha
静态初始化块中为类变量指定初始值,每次运行该程序,系统会将StaticInitTest类执行初始化操作,先为所有类变量分配内存空间,再按源代码中的排序执行静态初始化块中所指定的初始化值和定义类变量时所指定的初始值。
在上述程序中,静态初始化块中name变量的指定初始值位于定义name变量时指定初始值之前,因此系统先将name类变量赋值为“hello”,然后再将该name类变量赋值为“haha”。每运行该程序一次,这个初始化过程只执行一次,因此运行上述程序将看到输出name类变量的值为“haha”。
public class Price {
final static Price INSTANCE = new Price(2.8);
static double initPrice = 20;
double currentPrice;
public Price(double distinct) {
currentPrice = initPrice - distinct;
}
}
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类时,程序开始对Price类进行初始化,初始化操作分为2个阶段。
- 系统为Price的两个类变量分配内存空间。
- 按初始化代码(定义时指定初始化值和初始化块中执行初始值)的排列顺序对类变量执行初始化。
初始化第一阶段,系统先为INSTANCE、initPrice两个类变量分配内存空间,此时INSTANCE、initPrice的默认值为null和0.0。截止初始化进入第二个阶段,程序按顺序依次为INSTANCE、initPrice进行赋值。对INSTANCE赋值时要调用Price(2.8),创建Price实例,此时立刻执行程序中的 double currentPrice; 代码为currentPrice进行赋值,此时initPrice类变量的值为0,因此赋值的结果是double currentPrice等于-2.8。接着,程序再次将initPrice赋值为20,但此时对INSTANCE的currentPrice实例变量已经不起任何作用了。
参考链接
Java实例变量、类变量与局部变量
java中声明变量和初始化变量的相关感想
Java实例变量和类变量