Javaでのイニシャライザーとコンストラクターの使用
-
03-07-2019 - |
質問
それで、私は最近Javaスキルを磨いており、以前は知らなかったいくつかの機能を発見しました。静的およびインスタンス初期化子はそのような2つの手法です。
私の質問は、コンストラクターにコードを含める代わりに、いつイニシャライザーを使用するのかということです。いくつかの明らかな可能性を考えました:
-
static / instance initializersを使用して、「final」の値を設定できます。静的/インスタンス変数に対して、コンストラクターはできません
-
静的イニシャライザを使用して、クラス内の静的変数の値を設定できます。これは、「if(someStaticVar == null)// do stuff」」よりも効率的です。各コンストラクターの先頭のコードブロック
これらのケースはどちらも、これらの変数を設定するために必要なコードが単に「var = value」よりも複雑であると想定しています。そうでない場合、単に値を設定する代わりに初期化子を使用する理由はないようです変数の宣言。
ただし、これらは些細な利得(特に最終変数を設定する機能)ではありませんが、初期化子を使用する必要がある状況はかなり限られているようです。
コンストラクタで行われることの多くにイニシャライザを使用することは確かにできますが、そうする理由はわかりません。クラスのすべてのコンストラクターが大量のコードを共有している場合でも、プライベートのinitialize()関数の使用は、新しいコードを記述するときにそのコードを実行するようにロックしないため、イニシャライザーを使用するよりも理にかなっていますコンストラクタ。
何か不足していますか?イニシャライザを使用すべき状況は他にもたくさんありますか?それとも、非常に特定の状況で使用されるのは、実際にはかなり限られたツールですか?
解決
静的イニシャライザは、クレタスが言及したように有用であり、私は同じ方法でそれらを使用します。クラスがロードされたときに初期化される静的変数がある場合、特に複雑な初期化を行いながら静的変数を final のままにすることができるため、静的初期化子を使用する方法がありますコード>。これは大きな勝利です。
" if(someStaticVar == null)// //処理を行う"乱雑でエラーが発生しやすい。静的に初期化され、 final
として宣言されている場合、 null
である可能性を回避できます。
ただし、次のように言うと混乱します:
static / instance initializersを使用して、「final」の値を設定できます。 静的/インスタンス変数。コンストラクターはできません
あなたは両方を言っていると思います:
- 静的初期化子を使用して、「最終」の値を設定できます。コンストラクターではできない静的変数
- インスタンス初期化子を使用して、「最終」の値を設定できます。コンストラクターができないのに対し、インスタンス変数
最初の点は正しいが、2番目の点は間違っている。たとえば、次の操作を実行できます。
class MyClass {
private final int counter;
public MyClass(final int counter) {
this.counter = counter;
}
}
また、多くのコードがコンストラクター間で共有される場合、これを処理する最良の方法の1つは、デフォルト値を提供してコンストラクターをチェーンすることです。これにより、何が行われているのかが明確になります。
class MyClass {
private final int counter;
public MyClass() {
this(0);
}
public MyClass(final int counter) {
this.counter = counter;
}
}
他のヒント
匿名の内部クラスはコンストラクターを持つことができないため(匿名であるため)、インスタンス初期化子に非常に自然に適合します。
私はほとんどの場合、最終的な静的データ、特にコレクションを設定するために静的初期化ブロックを使用します。例:
public class Deck {
private final static List<String> SUITS;
static {
List<String> list = new ArrayList<String>();
list.add("Clubs");
list.add("Spades");
list.add("Hearts");
list.add("Diamonds");
SUITS = Collections.unmodifiableList(list);
}
...
}
この例は、1行のコードで実行できます。
private final static List<String> SUITS =
Collections.unmodifiableList(
Arrays.asList("Clubs", "Spades", "Hearts", "Diamonds")
);
しかし、静的なバージョンは、特に項目を初期化するのが簡単ではない場合、はるかにすっきりさせることができます。
単純な実装でも変更不可能なリストが作成されない場合があり、これは潜在的な間違いです。上記は、パブリックメソッドなどから喜んで返すことができる不変のデータ構造を作成します。
ここで、すでに優れた点をいくつか追加します。静的初期化子はスレッドセーフです。クラスがロードされたときに実行されるため、コンストラクターを使用するよりも単純な静的データの初期化を行います。この場合、静的データが初期化されているかどうかを確認し、実際に初期化するために同期ブロックが必要になります。
public class MyClass {
static private Properties propTable;
static
{
try
{
propTable.load(new FileInputStream("/data/user.prop"));
}
catch (Exception e)
{
propTable.put("user", System.getProperty("user"));
propTable.put("password", System.getProperty("password"));
}
}
対
public class MyClass
{
public MyClass()
{
synchronized (MyClass.class)
{
if (propTable == null)
{
try
{
propTable.load(new FileInputStream("/data/user.prop"));
}
catch (Exception e)
{
propTable.put("user", System.getProperty("user"));
propTable.put("password", System.getProperty("password"));
}
}
}
}
忘れないでください。インスタンスレベルではなく、クラスで同期する必要があります。これにより、クラスがロードされるときに1回限りのコストではなく、構築されるすべてのインスタンスにコストがかかります。さらに、見苦しい;-)
イニシャライザとそのコンストラクタの初期化順序に対する答えを探して記事全体を読みました。私はそれを見つけられなかったので、私の理解を確認するためにいくつかのコードを書きました。この小さなデモンストレーションをコメントとして追加すると思いました。理解度をテストするには、一番下で読む前に答えを予測できるかどうかを確認してください。
/**
* Demonstrate order of initialization in Java.
* @author Daniel S. Wilkerson
*/
public class CtorOrder {
public static void main(String[] args) {
B a = new B();
}
}
class A {
A() {
System.out.println("A ctor");
}
}
class B extends A {
int x = initX();
int initX() {
System.out.println("B initX");
return 1;
}
B() {
super();
System.out.println("B ctor");
}
}
出力:
java CtorOrder
A ctor
B initX
B ctor
静的初期化子は、静的コンテキストのコンストラクタに相当します。インスタンス初期化子よりも頻繁にそれを見るでしょう。静的環境をセットアップするためにコードを実行する必要がある場合があります。
一般に、インスタンス初期化子は匿名の内部クラスに最適です。 JMockのクックブックを見て、コードを読みやすくするための革新的な使用方法を確認してください。
場合によっては、コンストラクター間でチェーンするのが複雑なロジックがある場合(たとえば、サブクラス化していて、super()を呼び出す必要があるためthis()を呼び出せない場合)、一般的なことを行うことで重複を避けることができますインスタンスinitalizerで。ただし、インスタンス初期化子は非常にまれなので、多くの人にとって驚くべき構文なので、コンストラクターの動作が必要な場合はクラスを具体的にし、匿名ではないようにします。
JMockは例外です。これは、フレームワークの使用方法が意図されているためです。
選択する際に考慮する必要がある重要な側面が1つあります:
イニシャライザーブロックはクラス/オブジェクトのメンバーですが、コンストラクターはそうではありません。 これは、拡張/サブクラス化を検討する際に重要です:
- イニシャライザーはサブクラスに継承されます。 (ただし、シャドウイング可能)
これは、サブクラスが親クラスの意図どおりに初期化されることが基本的に保証されることを意味します。
ただし、 - コンストラクタは継承されません 。 (暗黙的に
super()
[つまり、パラメータなし]を呼び出すか、特定のsuper(...)
を手動で呼び出す必要があります。)
これは、暗黙的または明示的なsuper(...)
呼び出しが、親クラスの意図どおりにサブクラスを初期化しない可能性があることを意味します。
初期化子ブロックのこの例を検討してください:
class ParentWithInitializer {
protected final String aFieldToInitialize;
{
aFieldToInitialize = "init";
System.out.println("initializing in initializer block of: "
+ this.getClass().getSimpleName());
}
}
class ChildOfParentWithInitializer extends ParentWithInitializer{
public static void main(String... args){
System.out.println(new ChildOfParentWithInitializer().aFieldToInitialize);
}
}
出力:
初期化ブロックの初期化:ChildOfParentWithInitializer
init
-&gt;サブクラスが実装するコンストラクターに関係なく、フィールドは初期化されます。
次に、コンストラクターを使用してこの例を検討します。
class ParentWithConstructor {
protected final String aFieldToInitialize;
// different constructors initialize the value differently:
ParentWithConstructor(){
//init a null object
aFieldToInitialize = null;
System.out.println("Constructor of "
+ this.getClass().getSimpleName() + " inits to null");
}
ParentWithConstructor(String... params) {
//init all fields to intended values
aFieldToInitialize = "intended init Value";
System.out.println("initializing in parameterized constructor of:"
+ this.getClass().getSimpleName());
}
}
class ChildOfParentWithConstructor extends ParentWithConstructor{
public static void main (String... args){
System.out.println(new ChildOfParentWithConstructor().aFieldToInitialize);
}
}
出力:
ChildOfParentWithConstructorのコンストラクターをnullに初期化する
null
-&gt;これにより、デフォルトではフィールドが null
に初期化されますが、必要な結果ではない場合があります。
また、上記のすばらしい回答すべてに加えて、1つのポイントを追加したいと思います。 Class.forName(&quot;&quot;)を使用してJDBCにドライバーをロードすると、クラスのロードが発生し、Driverクラスの静的初期化子が起動され、その中のコードがDriverをDriver Managerに登録します。これは、静的コードブロックの重要な使用法の1つです。
あなたが述べたように、それは多くの場合役に立たず、あまり使われていない構文と同様に、あなたのコードを見ている次の人がそれを引き出すために30秒を費やすのを止めるためにそれを避けたいと思うでしょう金庫。
一方、それはいくつかのことを行う唯一の方法です(あなたはそれらをほとんどカバーしていると思います)。
静的変数自体はとにかくある程度回避する必要があります-常にではありませんが、多くの変数を使用する場合、または1つのクラスで多く使用する場合は、異なるアプローチを見つけるかもしれません。あなたの将来はあなたに感謝します。