Java static

本文详细解析了Java中static块的概念、初始化过程、执行顺序及与静态变量的关系,通过实例代码展示了static块在类初始化阶段的作用,并探讨了静态变量的初始化方式及其在编译时的处理方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java中static块                                                                                                                             

Java 程序中时常用些 static {} 这样的结构。 
这样的结构叫 static块 ,一般是一个类初始化时运行的代码。 注意,是类的初始化,不是对象的初始化。 
也就是你在定义对象是,它运行的初始化代码,并且只有第一次定义时才运行。之后就不再运行初始化了。 
一个类中可以可以有很多static块。static块按顺序执行。

看以下代码:

public class TestStatic{
 static{
    System.out.println(1);
 }
 
 static{
    System.out.println(2);
 }
 public static void main(String[] args){
    System.out.println(5);
 }
 static{
    System.out.println(3);
 }
 
 static{
    System.out.println(4);
 }
}
执行的结果是
1
2
3
4
5
一目了然,首先调用的是static静态快的代码,然后调用启动方法.而static块的执行顺序是由代码的编写顺序执行的过程.
static块大家用得比较少,其实就是在对象生成之际产生一系列的static变量。



Java中static块的本质                                         

在网上看到了下面的一段代码:


public class Test {
    static {
        _i = 20;
    }
    public static int _i = 10;
    
    public static void main(String[] args) {
        System.out.println(_i);
    }
}


上述代码会打印出什么结果来呢?10还是20?本文将以此代码为引子,着重讨论一下静态变量的初始化问题。


问题1:静态变量如何初始化


Java类中可以定义一个static块,用于静态变量的初始化。如:


public class Test {
    public static int _i;
    static {
        _i = 10;
    }
}




当然最常用的初始化静态变量的操作是在声明变量时直接进行赋值操作。如:


public class Test {
    public static int _i = 10;
}


那么上述两例在本质上有什么区别吗?回答是没有区别。两例代码编译之后的字节码完全一致,通过 “javap -c”查看到的字节码如下:


public class Test extends java.lang.Object{
public static int _i;


public Test();
  Code:
   0: aload_0
   1: invokespecial #1; //Method java/lang/Object."<init>":()V
   4: return


static {};
  Code:
   0: bipush 10
   2: putstatic #2; //Field _i:I
   5: return


}


通过字节码还可以看出,当类的定义中不含有static块时,编译器会为该类提供一个默认的static块。当然这是在含有静态变量初始化操作的前提下。如果静态变量没有初始化操作,则编译器不会为之提供默认的static块。如:


public class Test {
    public static int _i;
}


其字节码的表现形式为:


public class Test extends java.lang.Object{
public static int _i;


public Test();
  Code:
   0: aload_0
   1: invokespecial #1; //Method java/lang/Object."<init>":()V
   4: return


}


由于静态变量是通过赋值操作进行初始化的,因此可以通过静态函数返回值的方式为其初始化。如:


public class Test {
    public static int _i = init();
    
    private static int init() {
        return 10;
    }
}


其本质与下面的代码相同:


public class Test {
    public static int _i;
    static {
        _i = init();
    }
    
    private static int init() {
        return 10;
    }
}


问题2:JDK如何处理static块


类定义中可以存在多个static块吗?回答是可以。如:


public class Test {
    public static int _i;
    static {
        _i = 10;
    }
    
    public static void main(String[] args) {
    }
    
    static {
        _i = 20;
    }
}


此类编译之后的字节码为:


public class Test extends java.lang.Object{
public static int _i;


public Test();
  Code:
   0: aload_0
   1: invokespecial #1; //Method java/lang/Object."<init>":()V
   4: return


public static void main(java.lang.String[]);
  Code:
   0: return


static {};
  Code:
   0: bipush 10
   2: putstatic #2; //Field _i:I
   5: bipush 20
   7: putstatic #2; //Field _i:I
   10: return


}


观察static{}部分可以看出,上例的代码与下面的代码效果一致:


public class Test {
    public static int _i;
    
    public static void main(String[] args) {
    }
    
    static {
        _i = 10;
        _i = 20;
    }
}


此例可以证明,不仅类定义中可以有多个static块,而且在编译时编译器会将多个static块按照代码的前后位置重新组合成一个static块


问题3:如何看待静态变量的声明


静态变量存放在常量池之中。如何证明呢?如:
public class Test {
    public static int _i = 10;
}




使用“javap -c -verbose”查看其字节码的内容如下:


public class Test extends java.lang.Object
  SourceFile: "Test.java"
  minor version: 0
  major version: 49
  Constant pool:
const #1 = Method #4.#14; //  java/lang/Object."<init>":()V
const #2 = Field #3.#15; //  Test._i:I
const #3 = class #16; //  Test
const #4 = class #17; //  java/lang/Object
const #5 = Asciz _i;
const #6 = Asciz I;
const #7 = Asciz <init>;
const #8 = Asciz ()V;
const #9 = Asciz Code;
const #10 = Asciz LineNumberTable;
const #11 = Asciz <clinit>;
const #12 = Asciz SourceFile;
const #13 = Asciz Test.java;
const #14 = NameAndType #7:#8;//  "<init>":()V
const #15 = NameAndType #5:#6;//  _i:I
const #16 = Asciz Test;
const #17 = Asciz java/lang/Object;


{
public static int _i;




public Test();
  Code:
   Stack=1, Locals=1, Args_size=1
   0: aload_0
   1: invokespecial #1; //Method java/lang/Object."<init>":()V
   4: return
  LineNumberTable: 
   line 2: 0


static {};
  Code:
   Stack=1, Locals=0, Args_size=0
   0: bipush 10
   2: putstatic #2; //Field _i:I
   5: return
  LineNumberTable: 
   line 3: 0


}


我们看到,常量池中const #2指向的就是Test._i,也就是静态变量。静态变量被保存到常量池中的工作原理这里不深入讨论。在此需要注意的是:


静态变量的声明与初始化是两个不同的操作;
静态变量的声明在编译时已经明确了内存的位置。
如:


public class Test {
    public static int _i = 10;
}


上述代码的本质可以视为:


public class Test {
    // 静态变量的声明
    public static int _i;
    // 静态变量的初始化
    static {
        _i = 10;
    }
}


由于静态变量的声明在编译时已经明确,所以静态变量的声明与初始化在编码顺序上可以颠倒。也就是说可以先编写初始化的代码,再编写声明代码。如:


public class Test {
    // 静态变量的初始化
    static {
        _i = 10;
    }
    
    // 静态变量的声明
    public static int _i;
}


对初始问题的解答


解答了上述三个问题,让我们再来看看开篇提到的问题。代码如下:


public class Test {
    static {
        _i = 20;
    }
    public static int _i = 10;
    
    public static void main(String[] args) {
        System.out.println(_i);
    }
}


其本质可以用下面的代码表示:


public class Test {
    static {
        _i = 20;
    }
    public static int _i;
    static {
        _i = 10;
    }
    
    public static void main(String[] args) {
        System.out.println(_i);
    }
}


再简化一下,可以表示为:


public class Test {
    public static int _i;
    
    static {
        _i = 20;
        _i = 10;
    }
    
    public static void main(String[] args) {
        System.out.println(_i);
    }
}


至此,代码已经明确告诉我们打印结果是什么了!



### Java中`static`关键字详解 在Java中,`static`关键字是一个非常重要的概念,它用于修饰类的成员(变量和方法),表示这些成员属于类本身而不是类的实例。这意味着无论创建多少个类的对象,静态成员都只有一份拷贝,并且可以通过类名直接访问,而不需要创建对象。 #### 1. 静态变量(Static Variables) 静态变量也被称为类变量,它们不属于任何一个对象,而是属于整个类。所有该类的对象共享同一个静态变量。静态变量可以在声明时初始化,也可以在静态代码块中初始化。 ```java public class Person { public static int age = 20; } ``` 上述代码中定义了一个名为`age`的静态变量,可以通过类名直接访问: ```java System.out.println(Person.age); // 输出20 ``` 需要注意的是,`static`关键字不会影响变量或方法的作用域。访问权限仍然由`private`、`public`、`protected`以及默认包访问权限决定[^1]。 #### 2. 静态方法(Static Methods) 静态方法可以直接通过类名调用,无需创建类的实例。静态方法只能访问静态成员,不能直接访问非静态成员,因为非静态成员依赖于具体的对象实例。 ```java public class MathUtils { public static int add(int a, int b) { return a + b; } } ``` 调用静态方法的方式如下: ```java int result = MathUtils.add(5, 3); // 结果为8 ``` 此外,静态方法内部无法使用`this`关键字来引用当前对象,因为静态方法并不与任何特定的对象关联[^1]。 #### 3. 静态代码块(Static Code Blocks) 静态代码块是在类加载时执行的一段代码,通常用来初始化静态变量。它可以包含任意多条语句,并且可以多次出现,但它们会在程序运行期间仅被执行一次。 ```java public class StaticBlockExample { static { System.out.println("这是静态代码块"); } } ``` 当首次加载`StaticBlockExample`类时,会打印出“这是静态代码块”。 #### 4. 静态导入(Static Import) 从Java 5开始,支持静态导入功能,允许直接导入某个类的所有静态成员或者指定的静态成员,从而简化了对静态成员的调用。 ```java import static java.lang.Math.*; ``` 之后就可以直接使用数学函数而不必加上`Math.`前缀: ```java double sqrtValue = sqrt(16); // 直接调用sqrt方法 ``` #### 5. 单例模式中的应用 `static`关键字常被用来实现单例设计模式,确保一个类只有一个实例存在,并提供全局访问点。 ```java public class Singleton { private static Singleton instance; private Singleton() {} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } ``` 在这个例子中,`instance`是静态变量,`getInstance()`是静态方法,共同保证了单一实例的存在。 #### 6. JVM与类加载 `static`关键字还涉及到JVM的工作机制,特别是类加载过程。类的加载分为三个阶段:加载、链接和初始化。其中,初始化阶段就是执行类构造器`<clinit>`的过程,这个过程包含了静态变量赋值动作和静态代码块的执行[^3]。 #### 7. 并发问题 由于静态成员在整个应用程序生命周期内都是可访问的,所以在多线程环境下需要特别小心处理同步问题。如果多个线程同时修改同一个静态变量,可能会导致数据不一致的问题,因此可能需要采取适当的锁定策略来保护共享资源。 #### 8. 设计考量 尽管`static`提供了便利性,但也有人认为过度使用`static`可能是反设计模式的做法,因为它可能导致代码难以测试和维护,尤其是对于那些具有副作用的静态方法来说更是如此。此外,静态成员使得单元测试变得更加困难,因为它们不容易被模拟(mock)或替换。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值