深入理解Java static关键字

Java中的static关键字主要用于内存管理,static关键字可以修饰变量、方法、代码块和类。被static修饰的内容属于类,独立于类的实例。所有该类的实例共享static修饰的内容。

static变量

1,为什么需要static变量?

 想象这样一个场景:当定义了一个Student类后,想知道创建了多少个Student对象,我们该怎么做呢?因为我们没办法在Student类外部统计到底哪些地方创建了Student对象,所以可以从Student类寻找方法,这个时候我们就可以在Student类中定义个静态变量sCount,用于统计Student对象被创建的个数,Student定义如下:

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
/**
* Created by shymanzhu on 2017/4/23.
*/
public class Student {
private String mName;
private int mId;
private static int sCount = 0;
public Student(String name){
mName = name;
mId = ++sCount;
}
public static int getCount(){
return sCount;
}
public String getName() {
return mName;
}
public int getId() {
return mId;
}
}

 上面是一种场景,我们继续往下想,当创建了上面的Student类实例时,我想每个学生都是学校ID为“806”,该怎么做呢,或许你会想到可以这样改:

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
30
/**
* Created by shymanzhu on 2017/4/23.
*/
public class Student {
private String mName;
private int mId;
private static int sCount = 0;
private int mSchoolId = 806;
public Student(String name){
mName = name;
mId = ++sCount;
}
public static int getCount(){
return sCount;
}
public String getName() {
return mName;
}
public int getSchoolId() {
return mSchoolId;
}
public int getId() {
return mId;
}
}
1
2
3
4
5
6
7
8
9
public class StaticTest {
public static void main(String[] args){
Student shymanzhu = new Student("shymanzhu");
Student fireman = new Student("fireman");
Student feynman = new Student("feynman");
int count = Student.getCount();
System.out.print("Student object count: " + count);
}
}

上面的做法可以达到目的没错,但是会有个问题,每次新建有个Student对象时,都会重复创建同一个mSchoolId,这样就浪费内存空间。我们可以做这样的修改:

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
public class Student {
private String mName;
private int mId;
private static int sCount = 0;
private static int sSchoolId = 806;
public Student(String name){
mName = name;
mId = ++sCount;
}
public static int getCount(){
return sCount;
}
public String getName() {
return mName;
}
public static int getSchoolId() {
return sSchoolId;
}
public int getId() {
return mId;
}
}

 static变量在所有的实例中是共享的,所有对象拥有同一个该变量;也就是说它是属于类的,而不是对象。静态变量存储在方法区内存空间,而实例变量存储在堆内存空间。

iamge

2,static 变量使用场景

 通过上面的例子我们可以总结下在哪些情况下应该使用静态变量:

  • 所有对象共有的属性,比如上面例子中提到的学生的学校名;
  • 对象计数器;
  • 结合final定义静态常量;

static 方法

 在了解完静态变量后,我们来看下静态方法,静态方法就是用static 关键字修饰的方法。那么在哪些情况下需要静态方法呢?

  • 访问私有静态变量时,可以通过定义公有的静态方法来访问,例如上面例子中的Student.getCount()
  • 工具类的方法;当定义个工具类时,对外提供工具方法时,应将该方法定义成静态方法

其实上面的使用场景也解释为什么需要static方法。

static代码块

 static代码块是指static关键字后跟上一段代码,用一对”{}“将代码包括起来,比如在原有的Student类修改成下面的样子:

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
30
31
32
33
public class Student {
private String mName;
private int mId;
private static int sCount = 0;
private static int sSchoolId = 806;
static{
System.out.println("the value of static variable 'sCount' before changed to 2 is " + sCount);
sCount = 2;
System.out.println("static variable 'sCount' changed to value of " + sCount);
}
public Student(String name){
mName = name;
mId = ++sCount;
}
public static int getCount(){
return sCount;
}
public String getName() {
return mName;
}
public static int getSchoolId() {
return sSchoolId;
}
public int getId() {
return mId;
}
}

然后我么将StaticTest类内容修改成如下情况:

1
2
3
4
5
6
7
8
9
10
11
public class StaticTest {
public static void main(String[] args){
System.out.println("Student object count before instantiation " + Student.getCount());
Student shymanzhu = new Student("shymanzhu");
Student fireman = new Student("fireman");
Student feynman = new Student("feynman");
int count = Student.getCount();
System.out.println("Student object count after create three instance " + count);
}
}

我们看下运行的结果:

1
2
3
4
the value of static variable 'sCount' before changed to 2 is 1
static variable 'sCount' changed to value of 2
Student object count before instantiation 2
Student object count after create three instance 5

结合上面的例子,在这总结下static代码块的特点:

  • static代码块的内容只会初始化一次,当首次调用其所在类的静态方法时或者首次生成其所在类的一个对象时(可以注释掉StaticTest.mian()中第一个println验证)
  • static代码块的初始化在静态变量初始化后,在静态方法调用之前。

static 类

 static修饰的类叫做静态类,在了解静态类之前,我们先了解一个概念——嵌套类。嵌套类是指定义在另一个类中的类。在Java中,嵌套类分为两类:静态的和非静态的,静态的我们称为静态嵌套类(也有静态内部类的叫法),static关键字只能修饰嵌套类,不能修饰外部类;非静态的我们称为内部类。

1,为什么需要static类

  同样以上面Student类为例,如果我想用一个类描述学生的私有特征,该怎么做呢,或许很容易想到,在Student所在的package下定义一个Traint类用来描述学生的特征,这样定义可以,但是也会存在问题:后面维护的人并不知道这个Trait类描述的是哪个的特征,或许是老师、学生抑或是学校等等。这个时候就可以用static嵌套类的方式来清楚的表示这个Trait是用来描述学生特征的,而不是其他人的。

 修改后的Student类如下:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class Student {
private String mName;
private int mId;
private static int sCount = 0;
private static int sSchoolId = 806;
private Trait mTrait;
public Student(String name) {
mName = name;
mId = ++sCount;
}
public static int getSchoolId() {
return sSchoolId;
}
public static int getCount() {
return sCount;
}
public String getName() {
return mName;
}
public int getId() {
return mId;
}
public Trait getTrait() {
return mTrait;
}
public void setTrait(Trait trait) {
mTrait = trait;
}
public static class Trait{
private String mHobby;
private boolean mIsFemale;
public Trait(String hobby, boolean isFemale) {
mHobby = hobby;
mIsFemale = isFemale;
}
public String getHobby() {
return mHobby;
}
public boolean isFemale() {
return mIsFemale;
}
}
}

 上面的static嵌套类访问权限是public,当然还可以有其他的权限,比如private 静态嵌套类,当静态嵌套类被定义成private时,就表明该类只能在其外部类中使用,而不能在其他类中被使用,例如在Android中为了避免内存泄漏,在主线程中定义Handler的子类为static

2,static类使用场景

 根据上面的例子,可以总结在以下情况可以考虑使用:

  • 要表示某个类的私有特征时,可以在该类中定义static嵌套类,例如上面例子的Trait类。
  • 当一个类只在某个特定类中使用时,可以将这个类定义成该特定的类的静态嵌套类,并设置为private访问权限。

同样,static嵌套类也是属于类的,独立于外部类的实例而存在。所以在static嵌套类中不能访问外部类的非static方法和非static变量。