Javaクラス構造と初期化インタビューの質問
1. 前書き
クラス構造と初期化は、すべてのJavaプログラマーが熟知しているべき基本です。 この記事では、発生する可能性のあるトピックに関するいくつかのインタビューの質問に対する回答を提供します。
Q1. クラス、メソッド、フィールド、またはローカル変数に適用する場合の最終キーワードの意味を説明します。
finalキーワードは、異なる言語構造に適用されると、複数の異なる意味を持ちます。
-
finalクラスは、サブクラス化できないクラスです
-
finalメソッドは、サブクラスでオーバーライドできないメソッドです。
-
finalフィールドは、コンストラクターまたは初期化子ブロックで初期化する必要があり、その後は変更できないフィールドです。
-
final変数は、一度だけ割り当てることができる(そして割り当てる必要がある)変数であり、その後は変更されません。
Q2. デフォルトのメソッドとは何ですか?
Java 8より前は、インターフェースには抽象メソッドしかありませんでした。 ボディのないメソッド。 Java 8以降では、インターフェイスメソッドにデフォルトの実装を含めることができます。 実装クラスがこのメソッドをオーバーライドしない場合、デフォルトの実装が使用されます。 このようなメソッドは、defaultキーワードで適切にマークされます。
defaultメソッドの主な使用例の1つは、既存のインターフェイスにメソッドを追加することです。 このようなインターフェースメソッドをdefaultとしてマークしないと、このインターフェースの既存の実装はすべて機能しなくなります。 default実装のメソッドを追加すると、レガシーコードとこのインターフェイスの新しいバージョンとのバイナリ互換性が保証されます。
この良い例は、クラスをfor-eachループのターゲットにすることができるIteratorインターフェースです。 このインターフェースはJava5で最初に登場しましたが、Java 8では、forEachとspliteratorの2つの追加メソッドを受け取りました。 これらは実装でデフォルトのメソッドとして定義されているため、後方互換性を壊しません:
public interface Iterable {
Iterator iterator();
default void forEach(Consumer super T> action) { /* */ }
default Spliterator spliterator() { /* */ }
}
Q3. 静的クラスメンバーとは何ですか?
クラスのStaticフィールドとメソッドは、クラスの特定のインスタンスにバインドされていません。 代わりに、クラスオブジェクト自体にバインドされます。 staticメソッドの呼び出しまたはstaticフィールドのアドレス指定は、インスタンスメソッドおよびフィールドとは異なり、参照をウォークして実際のオブジェクトを判別する必要がないため、コンパイル時に解決されます。を参照してください。
Q4. クラスに抽象メンバーが含まれていない場合、クラスを抽象宣言できますか? そのようなクラスの目的は何でしょうか?
はい、クラスにabstractメンバーが含まれていない場合でも、クラスをabstractとして宣言できます。 抽象クラスとしてインスタンス化することはできませんが、何らかの階層のルートオブジェクトとして機能し、その実装に役立つメソッドを提供できます。
Q5. コンストラクターチェーンとは何ですか?
コンストラクターチェーンは、互いに順番に呼び出す複数のコンストラクターを提供することにより、オブジェクトの構築を簡素化する方法です。
最も具体的なコンストラクターは、可能なすべての引数を取り、最も詳細なオブジェクト構成に使用できます。 特定性の低いコンストラクターは、引数の一部にデフォルト値を指定することにより、特定性の高いコンストラクターを呼び出すことができます。 チェーンの先頭で、引数なしのコンストラクターは、デフォルト値でオブジェクトをインスタンス化できます。
これは、特定の日数以内に利用可能な割引をパーセントでモデル化するクラスの例です。 引数なしのコンストラクターを使用するときに指定しない場合は、デフォルト値の10%と2日が使用されます。
public class Discount {
private int percent;
private int days;
public Discount() {
this(10);
}
public Discount(int percent) {
this(percent, 2);
}
public Discount(int percent, int days) {
this.percent = percent;
this.days = days;
}
}
Q6. メソッドのオーバーライドとオーバーロードとは何ですか? それらはどう違いますか?
メソッドのオーバーライドは、スーパークラスと同じシグネチャでメソッドを定義すると、サブクラスで実行されます。 これにより、ランタイムは、メソッドを呼び出す実際のオブジェクトタイプに応じてメソッドを選択できます。 メソッドtoString、equals、およびhashCodeは、サブクラスで非常に頻繁にオーバーライドされます。
メソッドのオーバーロードは同じクラスで発生します。 オーバーロードは、同じ名前で異なるタイプまたは引数の数を持つメソッドを作成するときに発生します。 これにより、提供する引数のタイプに応じて特定のコードを実行できますが、メソッドの名前は変わりません。
java.io.Writer抽象クラスでのオーバーロードの例を次に示します。 次のメソッドはどちらもwriteという名前ですが、一方はintを受け取り、もう一方はchar配列を受け取ります。
public abstract class Writer {
public void write(int c) throws IOException {
// ...
}
public void write(char cbuf[]) throws IOException {
// ...
}
}
Q7. 静的メソッドをオーバーライドできますか?
いいえ、できません。 定義により、メソッドの実装が実行時に実際のインスタンスのタイプ(動的メソッド検索と呼ばれるプロセス)によって決定される場合にのみ、メソッドをオーバーライドできます。 staticメソッドの実装は、コンパイル時に参照の型を使用して決定されるため、オーバーライドしてもあまり意味がありません。 スーパークラスとまったく同じシグニチャを持つstaticメソッドをサブクラスに追加できますが、これは技術的にオーバーライドされません。
Q8. 不変クラスとは何ですか?どのように作成できますか?
不変クラスのインスタンスは、作成後に変更することはできません。 変更するとは、インスタンスのフィールドの値を変更して状態を変更することを意味します。 不変クラスには多くの利点があります。それらはスレッドセーフであり、考慮すべき可変状態がない場合にそれらについて推論するのがはるかに簡単です。
クラスを不変にするには、次のことを確認する必要があります。
-
すべてのフィールドはprivateおよびfinalとして宣言する必要があります。これは、コンストラクターで初期化する必要があり、それ以降は変更しないことを意味します。
-
クラスには、フィールドの値を変更するセッターまたはその他のメソッドを含めることはできません。
-
コンストラクタを介して渡されたクラスのすべてのフィールドも不変であるか、フィールドの初期化前に値をコピーする必要があります(または、これらの値を保持して変更することでこのクラスの状態を変更できます)。
-
クラスのメソッドはオーバーライド可能であってはなりません。すべてのメソッドをfinalにするか、コンストラクターをprivateにして、staticファクトリメソッドを介してのみ呼び出す必要があります。
Q9. 2つの列挙値をどのように比較しますか:Equals()または==を使用しますか?
実際には、両方を使用できます。 enumの値はオブジェクトであるため、equals()と比較できますが、内部ではstatic定数として実装されているため、==と比較することもできます。 s。 これは主にコードスタイルの問題ですが、文字スペースを節約したい場合(そして、不要なメソッド呼び出しをスキップしたい場合)、列挙型を==と比較する必要があります。
Q10. 初期化ブロックとは何ですか? 静的初期化ブロックとは何ですか?
イニシャライザブロックは、インスタンスの作成中に実行されるクラススコープ内の波括弧で囲まれたコードブロックです。 これを使用して、インプレース初期化ワンライナーよりも複雑なものでフィールドを初期化できます。
実際、コンパイラーはこのブロックをすべてのコンストラクター内にコピーするだけなので、すべてのコンストラクターから共通のコードを抽出するのに便利な方法です。
静的初期化ブロックは、前にstatic修飾子が付いた中括弧付きのコードブロックです。 クラスのロード中に1回実行され、静的フィールドの初期化またはいくつかの副作用に使用できます。
Q11. マーカーインターフェイスとは Javaのマーカーインターフェイスの注目すべき例は何ですか?
マーカーインターフェイスは、メソッドのないインターフェイスです。 通常、クラスによって実装されるか、特定のプロパティを示すために別のインターフェイスによって拡張されます。 標準Javaライブラリで最も広く知られているマーカーインターフェイスは次のとおりです。
-
Serializableは、このクラスをシリアル化できることを明示的に表すために使用されます。
-
Cloneableを使用すると、cloneメソッドを使用してオブジェクトのクローンを作成できます(Cloneableインターフェイスが配置されていない場合、このメソッドはCloneNotSupportedExceptionをスローします)。
-
Remoteは、RMIで使用され、リモートで呼び出すことができるメソッドを指定します。
Q12. シングルトンとは何ですか?Javaでどのように実装できますか?
シングルトンは、オブジェクト指向プログラミングのパターンです。 シングルトンクラスには、通常はグローバルに表示およびアクセス可能なインスタンスが1つしかありません。
Javaでシングルトンを作成する方法は複数あります。 以下は、インプレースで初期化されるstaticフィールドを使用した最も単純な例です。 staticフィールドはスレッドセーフな方法で初期化されることが保証されているため、初期化はスレッドセーフです。 コンストラクターはprivateであるため、外部コードがクラスの複数のインスタンスを作成する方法はありません。
public class SingletonExample {
private static SingletonExample instance = new SingletonExample();
private SingletonExample() {}
public static SingletonExample getInstance() {
return instance;
}
}
ただし、このアプローチには重大な欠点があります。このクラスが最初にアクセスされると、インスタンスがインスタンス化されます。 このクラスの初期化が重い操作であり、インスタンスが実際に必要になるまで(おそらくまったくしないまで)延期したいが、同時にスレッドセーフに保ちたい場合。 この場合、double-checked lockingと呼ばれる手法を使用する必要があります。
Q13. Var-Argとは何ですか? Var-Argの制限は何ですか? メソッド本体内でどのように使用できますか?
Var-argは、メソッドの可変長引数です。 メソッドはvar-argを1つだけ持つことができ、引数のリストの最後に来る必要があります。 タイプ名の後に省略記号と引数名が続くものとして指定されます。 メソッド本体内では、var-argが指定された型の配列として使用されます。
これは、標準ライブラリの例です。コレクションと可変数の要素を受け取り、すべての要素をコレクションに追加するCollections.addAllメソッドです。
public static boolean addAll(
Collection super T> c, T... elements) {
boolean result = false;
for (T element : elements)
result |= c.add(element);
return result;
}
Q14. スーパークラスのオーバーライドされたメソッドにアクセスできますか? 同様の方法でスーパースーパークラスのオーバーライドされたメソッドにアクセスできますか?
スーパークラスのオーバーライドされたメソッドにアクセスするには、superキーワードを使用できます。 ただし、スーパースーパークラスのオーバーライドされたメソッドにアクセスする同様の方法はありません。
標準ライブラリの例として、LinkedHashMapクラスはHashMapを拡張し、ほとんどの場合その機能を再利用して、反復順序を維持するために値の上にリンクリストを追加します。 LinkedHashMapは、スーパークラスのclearメソッドを再利用してから、リンクリストの先頭と末尾の参照をクリアします。
public void clear() {
super.clear();
head = tail = null;
}