static关键字浅析
在介绍static
关键字之前,我们先考虑两个问题:
1. 如果只为特定域分配存储空间,而不创建对象,应该怎么解决。
2. 如果不创建对象,也能调用对象内的方法,应该怎么解决。
我们在使用Java
进行编码时,如果要获得一个对象,那么就要通过new
关键字来创建对象,这时数据存储空间才被分配,对象内的方法才可以被外界调用。
如果希望解决文章开头提出的两个问题,应该怎么办呢?
答:通过static
关键字就可以解决以上两个问题。
那么为什么通过static
关键字就可以解决以上两个问题呢,我们慢慢来分析。
一、static初体验
在解释static
为什么会解决以上两个问题之前,我们先来体验下static
。
- 假如有一个类
TestStatic
,其中有个用static
关键字修饰的字段numStatic
,给其赋一个初始值为520
,还有一个没有用static
关键字修饰的字段numNoStatic
,给它赋一个初始值为1024
。
public class TestStatic {
static int numStatic = 520;
int numNoStatic = 1024;
}
- 现在我们实例化两个
TestStatic
对象,分别为testStatic1
和testStatic1
。
如果对testStatic2.numStatic
执行++
操作,即testStatic2.numStatic++;
,众所周知,testStatic2.numStatic
的值会变成521
,那么问题来了,testStatic1.numStatic
的值是多少,保持520
不变,还是变成521
?
废话少说,上代码:
TestStatic testStatic1=new TestStatic();
TestStatic testStatic2=new TestStatic();
System.out.println(testStatic1.numStatic);
System.out.println(testStatic2.numStatic);
testStatic2.numStatic++;
System.out.println(testStatic1.numStatic);
System.out.println(testStatic2.numStatic);
结果为:
520
520
521
521
从打印的结果来看,被static
修饰的变量和对象无关。
- 如果对没有被
static
修饰的变量进行操作,会对其值产生什么影响呢?
我们对testStatic2.numNoStatic
执行++
操作,即testStatic2.numNoStatic++;
,然后打印testStatic1.numNoStatic
和testStatic2.numNoStatic
的值。
System.out.println(testStatic1.numNoStatic);
System.out.println(testStatic2.numNoStatic);
testStatic2.numNoStatic++;
System.out.println(testStatic1.numNoStatic);
System.out.println(testStatic2.numNoStatic);
结果为:
1024
1024
1024
1025
可以看到,对testStatic2
对象中numNoStatic
的值进行修改,不会影响到testStatic1
对象中numNoStatic
的值。
那么也就是说:未被static
修饰的变量和对象有关。
二、static再体验
- 在类
TestStatic
中,添加两个方法,一个是用static
关键字修饰的静态方法add()
,另一个是没有用static
关键字修饰的非静态方法sub()
。
public static void add(){
System.out.println(numStatic++);
System.out.println(numNoStatic++);//会报错,提示“非静态字段不能被静态上下文引用”
}
public void sub(){
System.out.println(numStatic--);
System.out.println(numNoStatic--);
}
可以看到:静态方法中可以使用静态字段,但是不可以使用非静态字段;非静态方法中可以使用静态字段,也可以使用非静态字段。
- 那么静态方法可以调用静态方法或非静态方法吗,还有,非静态方法可以调用静态方法或非静态方法吗?继续上代码。其中
useAddOrSubByStatic()
方法是静态方法,useAddOrSub()
方法是非静态方法。
public static void useAddOrSubByStatic(){
add();
sub();//会报错,提示“非静态方法不能被静态方法引用”
}
public void useAddOrSub(){
add();
sub();
}
可以看到:静态方法可以调用静态方法,但是不可以调用非静态方法;非静态方法可以调用静态方法和非静态方法。
三、为什么会这样
通过static初体验和static再体验后,我们发现了一些奇怪的现象:
- 被
static
关键字修饰过的字段(静态字段),不管所在类被实例成几个对象,该字段的值只有一个,或者说这些对象的静态字段都指向了同一个存储空间;而那些未被static
关键字修饰过的字段(非静态字段),每个实例化的对象都有唯一的字段,或者说每个对象的非静态字段都有自己的存储空间。 - 被
static
关键字修饰过的方法(静态方法),只可以使用被static
关键字修饰过的字段(静态字段),而不能使用未被static
关键字修饰过的字段(非静态字段),只可以调用被static
关键字修饰过的方法(静态方法),而不能使用未被static
关键字修饰过的方法(非静态方法);普通的方法则没有这些问题。
为什么加了static
关键字后,不管是字段还是方法都变得“另类”了呢?
在解释staitc
为什么会有这种功效之前,我们需要先明确一个概念,那就是:static对每个类来说只有一份存储空间,而非static对每个对象都有一个存储空间。这句话听起来挺拗口的,不过不要紧,让我们来解释下。
Java
虚拟机在执行Java
程序时,会把它管理的内存划分为若干不同的数据区,包括:方法区、栈、堆、程序计数器等数据区,其中我们只要关注方法区、栈、堆这三个主要的数据区即可。
tips:栈(存放方法执行的参数,如局部变量、方法出口等),堆(存放创建好的对象和数组),方法区(存放被加载的类信息、常量、静态变量、编译后的代码等)
-
当虚拟机编译代码完成后,会将编译后的代码、静态变量、静态方法、常量等加载到方法区中;
加载到方法区
-
当通过
new
创建一个对象时,会在栈中创建一个当前对象的栈帧,并且在堆中会开辟一块内存,加载当前对象的信息;
创建对象
-
当调用静态方法
add()
时,方法内会用到静态变量numStatic
和非静态变量numNoStatic
,找静态变量numStatic
非常容易,因为add()
方法和numStatic
变量都在方法区中,但是找numNoStatic
变量却找不到了,因为numNoStatic
变量在堆的一个对象块中;
静态方法使用静态及非静态变量
-
当调用非静态方法
sub()
时,方法同样会用到静态变量numStatic
和非静态变量numNoStatic
,sub()
方法可以找到非静态变量numNoStatic
非常容易,因为它们都在堆的同一个对象块中,sub()
方法去找方法区中的静态变量numStatic
也同样可以找到(因为方法区存储的就是类的信息);
非静态方法使用静态及非静态变量
-
静态方法调用非静态方法时和步骤3类似,也是因为在方法区中的静态方法
useAddOrSubByStatic()
找不到在堆中对象块中的非静态方法sub()
;
非静态方法useAddOrsub()
则可以调用同属于堆中对象块中的非静态方法sub()
,也可以调用方法区中的静态方法add()
。
静态方法及非静态方法的互调
四、结尾
通过以上的分析,我们可以总结出以下内容:
当声明一个变量、方法、代码块等事物为static
时,就意味着这个事物不会与所在类的实例对象有关联。也就是说,即使从未创建某个类的对象,也可以调用其中的static
关键字修饰的事物。同样的,不管对同一个类创建了多少个不同的实例对象,其中用static
关键字修饰的事物也只有一个(一个存储空间)。