据我了解,如果不能在一行中完成,则“静态初始化块”用于设置静态字段的值。
但我不明白为什么我们需要一个特殊的块。例如,我们将一个字段声明为静态的(没有赋值)。然后编写几行代码,为上面声明的静态字段生成并赋值。
为什么我们需要将这些行放在一个特殊的块中,例如:static {...}
?
据我了解,如果不能在一行中完成,则“静态初始化块”用于设置静态字段的值。
但我不明白为什么我们需要一个特殊的块。例如,我们将一个字段声明为静态的(没有赋值)。然后编写几行代码,为上面声明的静态字段生成并赋值。
为什么我们需要将这些行放在一个特殊的块中,例如:static {...}
?
非静态块:
{
// Do Something...
}
每次构造类的实例时调用。静态块只被调用一次,当类本身被初始化时,不管你创建了多少该类型的对象。
例子:
public class Test {
static{
System.out.println("Static");
}
{
System.out.println("Non-static block");
}
public static void main(String[] args) {
Test t = new Test();
Test t2 = new Test();
}
}
这打印:
Static
Non-static block
Non-static block
如果它们不在静态初始化块中,它们会在哪里?您将如何声明一个仅用于初始化目的的本地变量,并将其与字段区分开来?例如,你想怎么写:
public class Foo {
private static final int widgets;
static {
int first = Widgets.getFirstCount();
int second = Widgets.getSecondCount();
// Imagine more complex logic here which really used first/second
widgets = first + second;
}
}
如果first
并且second
不在一个块中,它们看起来像字段。如果它们在一个没有static
前面的块中,那将被视为实例初始化块而不是静态初始化块,因此它将在每个构造实例中执行一次,而不是总共执行一次。
现在在这种特殊情况下,您可以改用静态方法:
public class Foo {
private static final int widgets = getWidgets();
static int getWidgets() {
int first = Widgets.getFirstCount();
int second = Widgets.getSecondCount();
// Imagine more complex logic here which really used first/second
return first + second;
}
}
...但是当您希望在同一个块中分配多个变量或没有变量时,这不起作用(例如,如果您只想记录某些内容 - 或者可能初始化本机库)。
这是一个例子:
private static final HashMap<String, String> MAP = new HashMap<String, String>();
static {
MAP.put("banana", "honey");
MAP.put("peanut butter", "jelly");
MAP.put("rice", "beans");
}
“静态”部分中的代码将在类加载时执行,在构造类的任何实例之前(以及在从其他地方调用任何静态方法之前)。这样你就可以确保类资源都可以使用了。
也可以有非静态初始化块。这些行为类似于为类定义的构造方法集的扩展。它们看起来就像静态初始化块,除了关键字“static”被省略了。
当您实际上不想将值分配给任何东西时,它也很有用,例如在运行时仅加载某个类一次。
例如
static {
try {
Class.forName("com.example.jdbc.Driver");
} catch (ClassNotFoundException e) {
throw new ExceptionInInitializerError("Cannot load JDBC driver.", e);
}
}
嘿,还有一个好处,你可以用它来处理异常。想象一下,getStuff()
这里抛出一个Exception
真正属于catch 块的:
private static Object stuff = getStuff(); // Won't compile: unhandled exception.
那么static
初始化器在这里很有用。您可以在那里处理异常。
另一个例子是之后做一些在分配期间不能做的事情:
private static Properties config = new Properties();
static {
try {
config.load(Thread.currentThread().getClassLoader().getResourceAsStream("config.properties");
} catch (IOException e) {
throw new ExceptionInInitializerError("Cannot load properties file.", e);
}
}
回到 JDBC 驱动程序示例,任何体面的 JDBC 驱动程序本身也使用static
初始化程序在DriverManager
. 另请参阅this和this answer。
我会说static block
只是语法糖。你不能用static
块做任何事情,而不能用其他任何东西。
重复使用此处发布的一些示例。
static
这段代码可以在不使用初始化程序的情况下重写。
方法#1:使用static
private static final HashMap<String, String> MAP;
static {
MAP.put("banana", "honey");
MAP.put("peanut butter", "jelly");
MAP.put("rice", "beans");
}
方法#2:没有static
private static final HashMap<String, String> MAP = getMap();
private static HashMap<String, String> getMap()
{
HashMap<String, String> ret = new HashMap<>();
ret.put("banana", "honey");
ret.put("peanut butter", "jelly");
ret.put("rice", "beans");
return ret;
}
有几个实际原因需要它存在:
static final
可能引发异常的成员static final
使用计算值初始化成员人们倾向于使用static {}
块作为一种方便的方式来初始化类在运行时中所依赖的事物——例如确保加载特定的类(例如,JDBC 驱动程序)。这可以通过其他方式完成;但是,我上面提到的两件事只能通过像static {}
块这样的构造来完成。
在静态块中构造对象之前,您可以为类执行一次代码。
例如
class A {
static int var1 = 6;
static int var2 = 9;
static int var3;
static long var4;
static Date date1;
static Date date2;
static {
date1 = new Date();
for(int cnt = 0; cnt < var2; cnt++){
var3 += var1;
}
System.out.println("End first static init: " + new Date());
}
}
认为静态块只能访问静态字段是一种常见的误解。为此,我想在下面展示我在现实项目中经常使用的一段代码(部分复制自另一个略有不同的上下文中的答案):
public enum Language {
ENGLISH("eng", "en", "en_GB", "en_US"),
GERMAN("de", "ge"),
CROATIAN("hr", "cro"),
RUSSIAN("ru"),
BELGIAN("be",";-)");
static final private Map<String,Language> ALIAS_MAP = new HashMap<String,Language>();
static {
for (Language l:Language.values()) {
// ignoring the case by normalizing to uppercase
ALIAS_MAP.put(l.name().toUpperCase(),l);
for (String alias:l.aliases) ALIAS_MAP.put(alias.toUpperCase(),l);
}
}
static public boolean has(String value) {
// ignoring the case by normalizing to uppercase
return ALIAS_MAP.containsKey(value.toUpper());
}
static public Language fromString(String value) {
if (value == null) throw new NullPointerException("alias null");
Language l = ALIAS_MAP.get(value);
if (l == null) throw new IllegalArgumentException("Not an alias: "+value);
return l;
}
private List<String> aliases;
private Language(String... aliases) {
this.aliases = Arrays.asList(aliases);
}
}
这里初始化器用于维护索引 ( ALIAS_MAP
),将一组别名映射回原始枚举类型。它旨在作为其自身提供的内置 valueOf 方法的扩展Enum
。
如您所见,静态初始化程序甚至访问private
field aliases
。重要的是要理解static
块已经可以访问Enum
值实例(例如ENGLISH
)。这是因为在types的情况下初始化和执行的顺序Enum
,就像在调用块static private
之前已经使用实例初始化字段一样:static
Enum
作为隐式静态字段的常量。这需要 Enum 构造函数和实例块,以及实例初始化首先发生。static
按发生顺序阻塞和初始化静态字段。这种乱序初始化(static
块之前的构造函数)很重要。当我们使用类似于 Singleton 的实例初始化静态字段时也会发生这种情况(进行了简化):
public class Foo {
static { System.out.println("Static Block 1"); }
public static final Foo FOO = new Foo();
static { System.out.println("Static Block 2"); }
public Foo() { System.out.println("Constructor"); }
static public void main(String p[]) {
System.out.println("In Main");
new Foo();
}
}
我们看到的是以下输出:
Static Block 1
Constructor
Static Block 2
In Main
Constructor
很明显,静态初始化实际上可以发生在构造函数之前,甚至之后:
只需在 main 方法中访问 Foo ,就会加载类并启动静态初始化。但是作为静态初始化的一部分,我们再次调用静态字段的构造函数,之后它恢复静态初始化,并完成从 main 方法中调用的构造函数。我希望在正常编码中我们不必处理的相当复杂的情况。
有关这方面的更多信息,请参阅“ Effective Java ”一书。
所以你有一个静态字段(它也被称为“类变量”,因为它属于类而不是类的实例;换句话说,它与类相关联而不是与任何对象相关联)并且你想要初始化它。因此,如果您不想创建此类的实例并且想要操作此静态字段,则可以通过三种方式进行:
1-在声明变量时初始化它:
static int x = 3;
2-有一个静态初始化块:
static int x;
static {
x=3;
}
3-有一个访问类变量并初始化它的类方法(静态方法):这是上述静态块的替代方案;您可以编写一个私有静态方法:
public static int x=initializeX();
private static int initializeX(){
return 3;
}
现在为什么要使用静态初始化块而不是静态方法?
这完全取决于您在程序中需要什么。但是您必须知道静态初始化块被调用一次,并且类方法的唯一优点是如果您需要重新初始化类变量,它们可以在以后重用。
假设您的程序中有一个复杂的数组。您初始化它(例如使用for循环),然后这个数组中的值将在整个程序中发生变化,但在某些时候您想要重新初始化它(回到初始值)。在这种情况下,您可以调用私有静态方法。如果您不需要在程序中重新初始化值,您可以只使用静态块并且不需要静态方法,因为您以后不会在程序中使用它。
注意:静态块按照它们在代码中出现的顺序被调用。
示例 1:
class A{
public static int a =f();
// this is a static method
private static int f(){
return 3;
}
// this is a static block
static {
a=5;
}
public static void main(String args[]) {
// As I mentioned, you do not need to create an instance of the class to use the class variable
System.out.print(A.a); // this will print 5
}
}
示例 2:
class A{
static {
a=5;
}
public static int a =f();
private static int f(){
return 3;
}
public static void main(String args[]) {
System.out.print(A.a); // this will print 3
}
}
如果您的静态变量需要在运行时设置,那么static {...}
块非常有用。
例如,如果您需要将静态成员设置为存储在配置文件或数据库中的值。
当您想向静态Map
成员添加值时也很有用,因为您无法在初始成员声明中添加这些值。
作为补充,就像@Pointy 说的
“静态”部分中的代码将在类加载时执行,在构造类的任何实例之前(以及在从其他地方调用任何静态方法之前)。
它应该添加System.loadLibrary("I_am_native_library")
到静态块中。
static{
System.loadLibrary("I_am_a_library");
}
它将保证在相关库加载到内存之前不会调用本机方法。
如果使用相同的库名称多次调用此方法,则忽略第二次和后续调用。
所以出乎意料的是,不使用 System.loadLibrary 来避免库被多次加载。
您首先需要了解您的应用程序类本身java.class.Class
在运行时被实例化为对象。这是运行静态块的时间。所以你实际上可以这样做:
public class Main {
private static int myInt;
static {
myInt = 1;
System.out.println("myInt is 1");
}
// needed only to run this class
public static void main(String[] args) {
}
}
它会打印“myInt is 1”到控制台。请注意,我没有实例化任何类。
static int B,H;
static boolean flag = true;
static{
Scanner scan = new Scanner(System.in);
B = scan.nextInt();
scan.nextLine();
H = scan.nextInt();
if(B < 0 || H < 0){
flag = false;
System.out.println("java.lang.Exception: Breadth and height must be positive");
}
}
静态块用于任何技术以动态方式初始化静态数据成员,或者我们可以说静态数据成员的动态初始化正在使用静态块。因为对于非静态数据成员初始化,我们有构造函数但我们没有我们可以动态初始化静态数据成员的任何地方
Eg:-class Solution{
// static int x=10;
static int x;
static{
try{
x=System.out.println();
}
catch(Exception e){}
}
}
class Solution1{
public static void main(String a[]){
System.out.println(Solution.x);
}
}
现在我的静态 int x 将动态初始化 ..Bcoz 当编译器转到 Solution.x 时,它将在类加载时加载解决方案类和静态块加载..所以我们可以动态初始化该静态数据成员..
}