关于static关键字及其初始化

什么是static

通常来说,创建类之后,只有执行new来创建对象时,数据存储空间才被分配,这个类的方法才能被外界调用。但是有两种特殊情况用这个方法不能解决:

  1. 只想为某特定域分配单一存储空间,而不去考虑是否创建对象或创建多少对象。
  2. 希望方法不于包含它的类的任何对象关联。即:即使没有创建对象,也可以调用这个方法

static关键字可以满足这两个需要。即使从未创建某个类的任何对象,也可以调用其static方法或访问static域。

static作用于字段

static作用于某个字段时,会改变数据创建的方式,其本质是:一个static字段对每个类来说都只有一份存储空间,而非static字段则是对每个对象都有一个存储空间。

class StaticTest {
  static int i = 47;
}

class Static {
  public static void main(String[] args) {
    StaticTest st1 = new StaticTest();
    StaticTest st2 = new StaticTest();
    System.out.println("st1:" + st1.i + " st2:" + st2.i);
    StaticTest.i++;
    System.out.println("st1Increment:" + st1.i + " st2Increment:" + st2.i);
  }
} /* output:
st1:47 st2:47
st1Increment:48 st2Increment:48
*///:~

在示例中,即使创建了两个StaticTest对象,StaticTest.i也只有一份存储空间(即:st1.i 和st2.i 指向同一个存储空间)。
这也是为什么我们可以通过类名去直接引用静态成员:

StaticTest.i++;

如上使用类名是引用static变量的首选方式(另一种方式是通过对象定位,如st1.i),因为它强调了变量的static结构,某些情况下为编译器进行优化提供了更好的机会。

static作用于方法

static作用于方法时,不像其作用于字段时有那么大的差别。static方法的重要用法是 在不创建任何对象的前提下就可以调用static方法。这一点对定义main()方法十分重要,因为它是运行一个应用时的切入点。

静态数据的初始化

在类的内部,即使变量定义散布于方法定义之间,它们仍旧会在任何方法(包括构造器)被调用之前得到初始化。

public class ExplicitStatic {
  public static void main(String[] args) {
    System.out.println("Inside main");
    Cups.cup1.f(99); //(1)
  }
  // static Cups cups1 = new Cups(); //(2)
  // static Cups cups2 = new Cups(); //(2)
}

class Cups {
  static Cup cup1;
  static Cup cup2;
  static {
    cup1 = new Cup(1);
    cup2 = new Cup(2);
  }
  Cups() {
    System.out.println("Cups()");
  }
}

class Cup {
  Cup(int marker) {
    System.out.println("Cup(" + marker + ")");
  }
  void f(int marker) {
    System.out.println("f(" + marker + ")");
  }
}

此时(执行(1)时)的输出为:

Inside main
Cup(1)
Cup(2)
f(99)

当注释掉(1),执行(2)时,输出为:

Cup(1)
Cup(2)
Cups()
Cups()
Inside main

可见,初始化的顺序是:先静态对象,后非静态对象,且静态初始化动作只进行一次

假设有名为Dog的类,总结一下对象的创建过程:

  1. 构造器(constructor)实际上是static静态方法。首次创建类型为Dog的对象时,或者Dog类的静态方法/静态域被首次访问时,Java解释器必须查找类路径,定位Dog.class文件。
  2. 载入Dog.class并创建Class对象,此时有关静态初始化的所有动作都会执行。静态初始化只在Class对象首次加载时进行一次
  3. 用new Dog()创建对象时,首先在堆上为Dog对象分配足够的存储空间。
  4. 这块存储空间被清零,自动将Dog对象所有基本数据类型设置成默认值,引用设置成null.
  5. 执行出现于字段定义处的初始化动作。
  6. 执行构造器。
class SelfCounter { 
  private static int count;
  private int id = count++; 
  public String toString() { return "SelfCounter " + id; }
}

class WithFinalFields { 
  final SelfCounter scf = new SelfCounter(); 
  static final SelfCounter scsf = new SelfCounter(); 
  public String toString() { 
    return "scf = " + scf + "\nscsf = " + scsf; 
  } 
}

public class E18_FinalFields { 
  public static void main(String args[]) { 
    System.out.println("First object:"); 
    System.out.println(new WithFinalFields()); 
    System.out.println("Second object:"); 
    System.out.println(new WithFinalFields()); 
  } 
} /* Output:
First object:
scf = SelfCounter 1 
scsf = SelfCounter 0 
Second object:
scf = SelfCounter 2 
scsf = SelfCounter 0 
*///:~

non-static field 的初始化(第 3 行),目的是初始化对象的 non-static field。所以它们在对象“被创建”的时候,才会执行。总共只有一个第 9 行的 self_counter_static_final 对象会被创建。

整个程序总共创建了 3 个 SelfCounter 对象,顺序分别是:

  1. WithFinalFields 的 static field
    - SelfCounter 对象被创建,执行第 3 行 (id=0)
  2. 第 1 个 WithFinalFields 对象的 non-static field
    - SelfCounter 对象被创建,执行第 3 行 (id=1)
  3. 第 2 个 WithFinalFields 对象的 non-static field
    - SelfCounter 对象被创建,执行第 3 行 (id=2)

继承中的初始化

基类在导出类构造器可以访问它之前,就已经完成了初始化。

参考:《Thinking in Java 4th Edition》第二、五、七章

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值