Java学习笔记之深入理解关键字static

基本概念

static表示“全局”或者“静态”的意思,用来修饰成员变量和成员方法,也可以形成静态static代码块,但是Java语言中没有全局变量这个概念。
在Java中,用static修饰的方法称为静态方法(类方法),用static修饰的变量称为静态变量(类变量)。
为什么需要static关键字?可以通过以下两种情形进行思考

  • 情形一:只想为某特定域分配单一存储空间,而不去考虑究竟要创建多少对象,甚至根本就不创建任何对象。
  • 情形二:希望某个方法不与包含它的类的任何对象关联在一起。也就是说,即使没有创建对象,也可以调用这个方法。

一般情况下,执行new来创建对象时,数据存储空间才被分配,其方法才供外界调用。要想解决以上两种情形,通过new肯定是不行的,而通过static关键字就可以满足这两方面的需要。
static有以下几个特点:

  • 被static修饰的成员变量和成员方法独立于该类的任何对象。即不依赖于类特定的实例,被类的所有实例共享
  • static变量和方法可以直接通过类来访问,而无需创建任何对象
  • static方法是没有this的方法,即它不属于对象,而是属于类
  • 在static方法内部不能访问非静态成员变量和非静态成员方法,因为非静态成员变量/方法是依赖于具体的对象,必须创建对象后才能使用(非绝对)
  • 类的构造器实际上也是静态方法,只不过没有显示地声明

static变量

类成员变量可分为两类:静态变量和实例变量。静态变量不属于某个实例对象,而是属于类;而实例变量属于某个对象的属性。可通过以下代码进行理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test {
static int a;
int b;
public static void main(String[] args) {
Test test1 = new Test();
System.out.println(a);
//System.out.println(b); //It's not OK!
System.out.println(test1.b); //It's OK!

Test test2 = new Test();
a = 1;
test2.b = 1;
System.out.println(a);
System.out.println(test1.b);
System.out.println(test2.b);
}
}

输出结果:

1
2
3
4
5
0
0
1
0
1

具体区别可参考Java学习笔记之变量类型

static方法

静态方法可以直接通过类名调用,任何的实例也都可以调用。但在static方法内部不能访问非静态成员变量和非静态成员方法,因为非静态成员变量/方法是依赖于具体的对象,必须创建对象后才能使用。这种说法不是完全绝对的,因为:如果传递一个对象的引用到静态方法里,然后通过这个引用就可以调用非静态方法和访问非静态数据成员了。但通常要达到这样的效果,我们只需写一个非静态方法即可,而无需传递引用。可通过以下代码进行理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class staticDemo {
public void print() {
System.out.println("staticDemo!");
}
// 静态方法
public static void printStatic() {
//print(); // It's not OK!
staticDemo demo = new staticDemo();
demo.print(); // 在静态方法中调用非静态方法必须先创建对象
}
// 非静态方法
public void printNoStatic() {
print();
}
}

注意:static方法不能被重写(Overriding)。因为重写是基于运行时动态绑定的,而static方法是基于编译期静态绑定的。虽然不能被重写,但可以在子类中再次被声明为static,且属于该子类。

static代码块

static代码块也叫静态代码块,是在类中独立于类成员的static语句块,可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果static代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。
静态代码块可以用来优化程序性能,可通过以下程序进行理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person{
private Date birthDate;

public Person(Date birthDate) {
this.birthDate = birthDate;
}

boolean isBornBoomer() {
Date startDate = Date.valueOf("1946");
Date endDate = Date.valueOf("1964");
return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
}
}

isBornBoomer是用来这个人是否是1946-1964年出生的,而每次isBornBoomer被调用的时候,都会生成startDate和birthDate两个对象,造成了空间浪费,如果改成这样效率会更好:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person{
private Date birthDate;
private static Date startDate,endDate;
static{
startDate = Date.valueOf("1946");
endDate = Date.valueOf("1964");
}

public Person(Date birthDate) {
this.birthDate = birthDate;
}

boolean isBornBoomer() {
return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
}
}

静态代码块的执行顺序可根据以下代码进行理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class StaticCode  
{

static
{
System.out.println("A");
}
public void show()
{

System.out.println("Hello World !");
}
}
class StaticCodeDemo {
static
{
System.out.println("B");
}
public static void main(String[] args)
{

new StaticCode().show();
}
static
{
System.out.println("C");
}
}

输出结果:

1
2
3
4
B
C
A
Hello World !

static和final一起使用

  • 对于static final修饰的变量,表示一旦赋值就不可修改,并且可通过类名直接访问
  • 对于static final修饰的方法,表示该方法不可覆盖,并且可通过类名直接调用

对于static final修饰的引用类型对象,特别是容器类型(如ArrayList、HashMap),不可以改变容器对象本身(即不能改变将其引用指向其他对象),但可以修改容器中存放的对象。

可通过以下程序进行理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.util.ArrayList;

public class StaticFinalDemo {
private static final String STR_STATIC_FINAL = "a";
private static String strStatic = "b";
private final String strFinal = "c";
private static final ArrayList < String > ARRAY_LIST = new ArrayList < String > ();

private void print() {
System.out.println("-------------值处理前----------");
System.out.println("STR_STATIC_FINAL = " + STR_STATIC_FINAL);
System.out.println("strStatic = " + strStatic);
System.out.println("strFinal = " + strFinal);
System.out.println("ARRAY_LIST = " + ARRAY_LIST);
// STR_STATIC_FINAL = "aa"; // It's not OK! final表示终态,不可以改变变量本身
strStatic = "bb"; // 可进行更改
// strFinal = "cc"; // It's not OK! final表示终态,不可以改变变量本身
// ARRAY_LIST = new ArrayList<String>(); // It's not OK! final表示终态,不可以将ARRAY_LIST引用指向其他对象
ARRAY_LIST.add("d"); // 可以修改容器中存放的对象
System.out.println("-------------值处理后----------");
System.out.println("STR_STATIC_FINAL = " + STR_STATIC_FINAL);
System.out.println("strStatic = " + strStatic);
System.out.println("strFinal = " + strFinal);
System.out.println("ARRAY_LIST = " + ARRAY_LIST);
}
public static void main(String args[]) {
new StaticFinalDemo().print();
}
}

输出结果:

1
2
3
4
5
6
7
8
9
10
-------------值处理前----------
STR_STATIC_FINAL = a
strStatic = b
strFinal = c
ARRAY_LIST = []
-------------值处理后----------
STR_STATIC_FINAL = a
strStatic = bb
strFinal = c
ARRAY_LIST = [d]

参考资料

  1. Java编程思想
  2. Java中的static关键字解析