どちらのアプローチにも利点はありますか?
例 1:
class A {
B b = new B();
}
例 2:
class A {
B b;
A() {
b = new B();
}
}
どちらのアプローチにも利点はありますか?
例 1:
class A {
B b = new B();
}
例 2:
class A {
B b;
A() {
b = new B();
}
}
さらに、初期化ブロックがあり、これもコンパイラーによってコンストラクターに入れられます。
{
a = new A();
}
Sunの説明とアドバイスをチェック
このチュートリアルから:
ただし、フィールド宣言はメソッドの一部ではないため、ステートメントとして実行することはできません。代わりに、Java コンパイラはインスタンス フィールドの初期化コードを自動的に生成し、それをクラスのコンストラクタまたは複数のコンストラクタに配置します。初期化コードは、ソース コードに現れる順序でコンストラクターに挿入されます。つまり、フィールド初期化子は、その前に宣言されたフィールドの初期値を使用できます。
さらに、フィールドを遅延初期化することもできます。フィールドの初期化が高価な操作である場合は、必要に応じてすぐに初期化できます。
ExpensiveObject o;
public ExpensiveObject getExpensiveObject() {
if (o == null) {
o = new ExpensiveObject();
}
return o;
}
最終的に (Bill が指摘したように)、依存関係の管理のために、クラス内のどこでも演算子を使用しない方がよいでしょう。new
代わりに、依存性注入を使用することをお勧めします。つまり、他の誰か (別のクラス/フレームワーク) がインスタンス化して、クラスに依存性を注入できるようにします。
別のオプションは、Dependency Injectionを使用することです。
class A{
B b;
A(B b) {
this.b = b;
}
}
B
これにより、のコンストラクターからオブジェクトを作成する責任が取り除かれますA
。これにより、コードがよりテストしやすくなり、長期的には保守が容易になります。A
アイデアは、2 つのクラスとの間の結合を減らすことB
です。これがもたらす利点は、拡張する (またはインターフェースの場合はB
実装する) 任意のオブジェクトをのコンストラクターに渡すことができるようになり、それが機能することです。欠点の 1 つは、オブジェクトのカプセル化をあきらめて、コンストラクターの呼び出し元に公開されることです。利点がこのトレードオフに値するかどうかを検討する必要がありますが、多くの場合はそうです。B
A
B
A
私の個人的な「ルール」(破られることはほとんどありません)は次のとおりです。
したがって、次のようなコードがあります。
public class X
{
public static final int USED_AS_A_CASE_LABEL = 1; // only exception - the compiler makes me
private static final int A;
private final int b;
private int c;
static
{
A = 42;
}
{
b = 7;
}
public X(final int val)
{
c = val;
}
public void foo(final boolean f)
{
final int d;
final int e;
d = 7;
// I will eat my own eyes before using ?: - personal taste.
if(f)
{
e = 1;
}
else
{
e = 2;
}
}
}
このようにして、変数宣言 (ブロックの開始時) とその代入 (宣言後すぐに) を探す場所が常に 100% 確実になります。これは、使用されていない値で変数を初期化することがないため、より効率的になる可能性があります (たとえば、変数を宣言して初期化し、変数の半分が値を持つ必要がある前に例外をスローします)。また、無意味な初期化を行うこともありません (int i = 0; のように、後で "i" が使用される前に do i = 5;.
私は一貫性を非常に重視しているので、この「ルール」に従うことは常に行っていることであり、何かを探すために探し回る必要がないため、コードの操作がはるかに簡単になります。
あなたのマイレージは異なる場合があります。
例2は柔軟性が低くなります。別のコンストラクターを追加する場合は、そのコンストラクターでもフィールドをインスタンス化することを忘れないでください。フィールドを直接インスタンス化するか、ゲッターのどこかに遅延読み込みを導入します。
インスタンス化に単なる単純なもの以上のものが必要なnew
場合は、初期化ブロックを使用します。これは、使用されるコンストラクターに関係なく実行されます。例えば
public class A {
private Properties properties;
{
try {
properties = new Properties();
properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("file.properties"));
} catch (IOException e) {
throw new ConfigurationException("Failed to load properties file.", e); // It's a subclass of RuntimeException.
}
}
// ...
}
初期化が単純でロジックを必要としない限り、ほとんど好みの問題だと思います。
コンストラクターのアプローチは、初期化ブロックを使用しない場合、もう少し脆弱です。後で 2 番目のコンストラクターを追加して、そこで b を初期化するのを忘れた場合、その最後のコンストラクターを使用した場合にのみ null b を取得するためです。
Java での初期化の詳細については、http://java.sun.com/docs/books/tutorial/java/javaOO/initial.htmlを参照してください。
どちらの方法も受け入れられます。後者の場合、b=new B()
別のコンストラクターが存在する場合、初期化されない可能性があることに注意してください。コンストラクターの外側のイニシャライザー コードを共通のコンストラクターと考えて、コードを実行します。
例2の方がいいと思います。ベスト プラクティスは、コンストラクターの外側で宣言し、コンストラクターで初期化することだと思います。
2 つ目は、遅延初期化の例です。最初の 1 つはより単純な初期化で、本質的に同じです。
class MyClass extends FooClass {
String a = null;
public MyClass() {
super(); // Superclass calls init();
}
@Override
protected void init() {
super.init();
if (something)
a = getStringYadaYada();
}
}
上記につきましては、
String a = null;
とにかくそれがデフォルトであるため、null initは回避できます。ただし、別のデフォルト値が必要な場合は、初期化順序が制御されていないため、次のように修正します。
class MyClass extends FooClass
{
String a;
{
if( a==null ) a="my custom default value";
}
...