私が理解した限りでは、「静的初期化ブロック」は、静的フィールドの値を1行ではできない場合に設定するために使用されます。
しかし、そのために特別なブロックが必要な理由はわかりません。たとえば、フィールドを静的(値の割り当てなし)として宣言します。そして、上記の宣言された静的フィールドに値を生成して割り当てるコードを数行書いてください。
なぜstatic {...}
のような特別なブロックにこの行が必要なのですか?
非静的ブロック:
{
// Do Something...
}
呼び出される毎回クラスのインスタンスが構築されます。 静的ブロックは、クラス自体が初期化されたときに、onceと呼ばれる場合にのみ呼び出されます。 。
例:
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
を持たないブロック内にあった場合、それは静的初期化ブロックの代わりにインスタンス初期化ブロックとしてカウントされるので、合計で1回ではなくper構築されたインスタンスとして実行されます。
今回のケースでは、代わりに静的メソッドを使用できます。
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"が省略されている点を除けば、静的初期化子ブロックのように見えます。
実行時にクラスを一度だけロードするなど、実際には値を割り当てたくない場合にも便利です。
例えば。
static {
try {
Class.forName("com.example.jdbc.Driver");
} catch (ClassNotFoundException e) {
throw new ExceptionInInitializerError("Cannot load JDBC driver.", e);
}
}
ねえ、別の利点があります、あなたは例外を処理するためにそれを使用することができます。 getStuff()
がここで本当にがcatchブロックに属しているException
をスローすると想像してみてください。
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ドライバ)。それは他の方法でも可能です。ただし、上記の2つのことは、static {}
ブロックのような構成要素でしか実行できません。
オブジェクトが静的ブロック内に構築される前に、クラスに対して1回だけコードを実行できます。
例えば。
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
フィールドaliases
にもアクセスします。 static
ブロックはすでにEnum
の値インスタンス(例えばENGLISH
)にアクセスできることを理解することが重要です。これは、Enum
ブロックが呼び出される前にstatic private
フィールドがインスタンスで初期化されているのと同様に、 static
型の場合の初期化と実行の順序 が原因です。
Enum
定数。これには、Enumコンストラクターとインスタンスブロック、およびインスタンスの初期化も最初に行われる必要があります。static
ブロックと静的フィールドの初期化。このアウトオブオーダ初期化(static
ブロックの前のコンストラクタ)は注意することが重要です。これは、シングルトンと同様にインスタンスを使用して静的フィールドを初期化したときにも発生します(単純化したもの)。
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
明らかに、静的な初期化は実際に起こる可能性がありますbeforeコンストラクタ、そしてその後でさえも:
MainメソッドでFooにアクセスするだけで、クラスがロードされ、静的初期化が開始されます。しかし、静的初期化の一部として、静的フィールドのコンストラクタを再度呼び出します。その後、静的初期化を再開し、mainメソッド内から呼び出されたコンストラクタを完成させます。やや複雑な状況で、私は通常のコーディングでは対処する必要がないことを願っています。
これに関するより詳しい情報は、 " Effective Java "という本を見てください。
静的変数を実行時に設定する必要がある場合は、static {...}
ブロックが非常に役立ちます。
例えば、静的メンバーを構成ファイルまたはデータベースに保管されている値に設定する必要がある場合です。
初期のメンバー宣言では値を追加できないため、静的なMap
メンバーに値を追加する場合にも便利です。
静的フィールド(クラスのインスタンスではなくクラスに属しているため、つまり、オブジェクトではなくクラスに関連付けられているため、 "クラス変数"とも呼ばれます)を使用して初期化します。したがって、このクラスのインスタンスを作成したくない場合、この静的フィールドを操作したい場合は、次の3つの方法で行うことができます。
1 - 変数を宣言したときに初期化します。
static int x = 3;
2 - 静的初期化ブロックを持っている:
static int x;
static {
x=3;
}
3 - クラス変数にアクセスして初期化するクラスメソッド(静的メソッド)を用意します。これは上記の静的ブロックの代わりになります。あなたはプライベートな静的メソッドを書くことができます:
public static int x=initializeX();
private static int initializeX(){
return 3;
}
では、なぜ静的メソッドの代わりに静的初期化ブロックを使用するのでしょうか。
それは本当にあなたのプログラムに必要なもの次第です。しかし、静的初期化ブロックが1回呼び出されることを知っておく必要があります。クラスメソッドの唯一の利点は、クラス変数を再初期化する必要がある場合に後で再利用できることです。
プログラムに複雑な配列があるとしましょう。これを初期化して(たとえばfor loopを使用して)、その後この配列の値はプログラム全体で変化しますが、ある時点で再初期化したい(初期値に戻ります)。この場合、private 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
}
}
最初に、アプリケーションクラス自体が実行時に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"と表示されます。私はクラスをインスタンス化していないことに注意してください。
補足として、@Pointyが言ったように
"static"セクションのコードは、クラスのインスタンスが構築される前(および他の場所から静的メソッドが呼び出される前)のクラスロード時に実行されます。
静的ブロックにSystem.loadLibrary("I_am_native_library")
を追加することになっています。
static{
System.loadLibrary("I_am_a_library");
}
関連ライブラリがメモリにロードされる前にネイティブメソッドが呼び出されないことが保証されます。
Oracleからの loadLibraryによると :
このメソッドが同じライブラリ名で複数回呼び出された場合、2回目以降の呼び出しは無視されます。
非常に意外なことに、System.loadLibraryを置くことはライブラリが複数回ロードされるのを避けるためには使われません。
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");
}
}