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<T> {
Iterator<T> iterator();
default void forEach(Consumer<? super T> action) { /* */ }
default Spliterator<T> spliterator() { /* */ }
}
Q3。 静的クラスメンバーとは何ですか?
クラスのStaticフィールドとメソッドは、クラスの特定のインスタンスにバインドされていません。 代わりに、それらはクラスオブジェクト自体にバインドされます。 staticメソッドの呼び出しまたはstaticフィールドのアドレス指定は、インスタンスメソッドおよびフィールドとは異なり、参照をウォークして実際のフィールドを判別する必要がないため、コンパイル時に解決されます。参照しているオブジェクト。
Q4。 クラスに抽象メンバーがない場合、クラスを抽象と宣言できますか? そのようなクラスの目的は何でしょうか?
はい、クラスに abstract メンバーが含まれていない場合でも、クラスをabstractとして宣言できます。 抽象クラスとしてインスタンス化することはできませんが、一部の階層のルートオブジェクトとして機能し、実装に役立つメソッドを提供できます。
Q5。 コンストラクターチェーンとは何ですか?
コンストラクターチェーンは、相互に順番に呼び出す複数のコンストラクターを提供することにより、オブジェクトの構築を簡素化する方法です。
最も具体的なコンストラクターは、考えられるすべての引数を取ることができ、最も詳細なオブジェクト構成に使用できます。 あまり具体的でないコンストラクターは、引数の一部にデフォルト値を指定することにより、より具体的なコンストラクターを呼び出すことができます。 チェーンの最上位では、引数のないコンストラクターがデフォルト値でオブジェクトをインスタンス化できます。
これは、特定の日数以内に利用可能な割引をパーセントでモデル化するクラスの例です。 no-argコンストラクターを使用するときに指定しない場合、デフォルト値の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つの列挙値をどのように比較しますか:With equals()またはWith ==?
実際には、両方を使用できます。 enum 値はオブジェクトであるため、 equals()と比較できますが、内部では static 定数として実装されているため、次のようになります。それらを==とよく比較してください。 これは主にコードスタイルの問題ですが、文字スペースを節約したい場合(そして、不要なメソッド呼び出しをスキップしたい場合)、enumを==と比較する必要があります。
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;
}
}
ただし、このアプローチには重大な欠点がある可能性があります。このクラスに最初にアクセスしたときにインスタンスがインスタンス化されます。 このクラスの初期化が重い操作であり、インスタンスが実際に必要になるまで(おそらく決して)延期したい場合は、同時にスレッドセーフを維持します。 この場合、ダブルチェックロックと呼ばれる手法を使用する必要があります。
Q13。 Var-Argとは何ですか? Var-Argの制限は何ですか? メソッド本体内でどのように使用できますか?
Var-argは、メソッドの可変長引数です。 メソッドにはvar-argが1つしかない場合があり、引数のリストの最後に来る必要があります。 これは、タイプ名の後に省略記号と引数名が続くものとして指定されます。 メソッド本体内では、var-argが指定されたタイプの配列として使用されます。
標準ライブラリの例を次に示します— Collections.addAll メソッドは、コレクション、可変数の要素を受け取り、すべての要素をコレクションに追加します。
public static <T> 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;
}