Answers:
在非静态块:
{
// 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 int widgets = 0; static{widgets = 2;}
private static int widgets = 0; static{widgets = 2;}
发现“ =”分配是按顺序发生的,这意味着放在首位的“ =”将首先分配。上面的示例将“小部件”的值设置为2。(PS不知道注释只能在5分钟内进行编辑...)
这是一个例子:
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
。也看到这个和这个答案。
我想说static block
的只是语法糖。您无法使用static
block进行任何操作,而不能使用其他任何操作。
重用此处发布的一些示例。
无需使用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;
}
在静态块中构造对象之前,可以为一个类执行一次代码位。
例如
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
),以将一组别名映射回原始的枚举类型。它旨在作为Enum
自身提供的内置valueOf方法的扩展。
如您所见,静态初始化程序甚至访问private
field aliases
。重要的是要了解该static
块已经可以访问Enum
值实例(例如ENGLISH
)。这是因为对于type而言,初始化和执行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方法内部调用的构造函数。我希望在正常的编码中不必处理这种复杂的情况。
有关更多信息,请参见《有效的Java》一书。
aliases
并不意味着静态块可以访问非静态成员。aliases
通过Language
/ static / values()
方法返回的值进行访问。正如您提到的那样,此时枚举变量已经可用这一事实是不寻常的一点-在这种情况下,常规类的非静态成员将不可访问。
class Foo { static final Foo Inst1; static final Foo Inst2; static{ Inst1 = new Foo("Inst1"); Inst2 = new Foo("Inst2"); } static { System.out.println("Inst1: " + Inst1.member); System.out.println("Inst2: " + Inst2.member); } private final String member; private Foo(String member){ this.member = member; } }
上面的代码与枚举示例没有什么不同,并且仍然允许访问静态块内的实例变量
因此,您有一个静态字段(也称为“类变量”,因为它属于类而不是类的实例;换句话说,它与类而不是与任何对象关联),并且您想对其进行初始化。因此,如果您不想创建此类的实例,并且想要操纵此静态字段,则可以通过以下三种方式来实现:
1-声明变量时只需对其进行初始化:
static int x = 3;
2-有一个静态初始化块:
static int x;
static {
x=3;
}
3-有一个访问类变量并对其进行初始化的类方法(静态方法):这是上述静态块的替代方法;您可以编写一个私有静态方法:
public static int x=initializeX();
private static int initializeX(){
return 3;
}
现在为什么要使用静态初始化块而不是静态方法?
这实际上取决于您程序中的需求。但是您必须知道静态初始化块被调用一次,并且class方法的唯一优点是,如果您需要重新初始化class变量,则可以在以后重用它们。
假设您的程序中有一个复杂的数组。您对其进行初始化(例如,使用for循环),然后此数组中的值将在整个程序中更改,但是在某些时候您想对其进行初始化(返回初始值)。在这种情况下,您可以调用私有静态方法。如果您不需要在程序中重新初始化值,则可以只使用static块,而无需使用静态方法,因为您以后不会在程序中使用它。
注意:静态块按它们在代码中出现的顺序进行调用。
范例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
}
}
作为补充,就像@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”进行控制台。请注意,我还没有实例化任何类。
静态块可用于任何以动态方式初始化静态数据成员的技术,或者可以说是针对静态数据成员的动态初始化使用静态块。由于非静态数据成员的初始化,我们具有构造函数,但没有我们可以动态初始化静态数据成员的任何地方
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);
}
}
现在,当编译器转到Solution.x时,我的静态int x将动态初始化..Bcoz,它将在类加载时加载解决方案类和静态块加载。.因此,我们可以动态初始化该静态数据成员。
}
{...}
vs 的区别static {...}
。(在这种情况下,乔恩·斯基特(Jon Skeet)肯定会更好地回答您的问题)