Java コアという言葉を初めて聞く人にとって、これらは言語の基本的な基礎です。この知識があれば、安心してインターンシップ・インターンシップに参加することができます。
これらの質問は、面接前に知識をリフレッシュしたり、自分自身のために何か新しいことを学ぶのに役立ちます。実践的なスキルを取得するには、JavaRushで学習してください。 元の記事 他の部分へのリンク: Java Core。インタビューの質問、パート 1 Java コア。面接の質問、パート 3
なぜ Finalize() メソッドを避けるべきなのでしょうか?
finalize()
オブジェクトが占有しているメモリを解放する前に、ガベージ コレクターによってメソッドが呼び出されるということは誰もが知っています。以下は、メソッド呼び出しが保証されていないことを証明するプログラム例ですfinalize()
。
public class TryCatchFinallyTest implements Runnable {
private void testMethod() throws InterruptedException
{
try
{
System.out.println("In try block");
throw new NullPointerException();
}
catch(NullPointerException npe)
{
System.out.println("In catch block");
}
finally
{
System.out.println("In finally block");
}
}
@Override
protected void finalize() throws Throwable {
System.out.println("In finalize block");
super.finalize();
}
@Override
public void run() {
try {
testMethod();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class TestMain
{
@SuppressWarnings("deprecation")
public static void main(String[] args) {
for(int i=1;i< =3;i++)
{
new Thread(new TryCatchFinallyTest()).start();
}
}
}
出力: try ブロック内 catch ブロック内finally ブロック内 try ブロック内 catch ブロック内finally ブロック内 try ブロック内 catch ブロック内finally ブロック内 驚くべきことに、メソッドはfinalize
どのスレッドに対しても実行されませんでした。これは私の言葉を証明します。その理由は、ファイナライザーが別のガベージ コレクター スレッドによって実行されるためだと思います。Java 仮想マシンが終了するのが早すぎると、ガベージ コレクターにはファイナライザーを作成して実行するための十分な時間がありません。このメソッドを使用しないその他の理由としては、次のことfinalize()
が考えられます。
- このメソッドは
finalize()
コンストラクターのようなチェーンでは機能しません。これは、クラス コンストラクターを呼び出すと、スーパークラス コンストラクターが無条件に呼び出されることを意味します。しかし、 メソッドの場合finalize()
、これは起こりません。スーパークラスメソッドはfinalize()
明示的に呼び出す必要があります。 - メソッドによってスローされた例外は
finalize
ガベージ コレクター スレッドによって無視され、それ以上伝播されません。つまり、イベントはログに記録されません。これは非常にまずいことですよね。 -
また、メソッドが
finalize()
クラスに存在する場合、パフォーマンスが大幅に低下します。『Effective Programming (第 2 版)』の中で、Joshua Bloch 氏は
次のように述べています。私のマシンでは、単純なオブジェクトの作成と破棄にかかる時間は約 5.6 ナノ秒です。
ファイナライザーを追加すると、時間が 2400 ナノ秒に増加します。言い換えれば、ファイナライザーを使用してオブジェクトを作成および削除すると、約 430 倍遅くなります。」
HashMap をマルチスレッド環境で使用すべきではないのはなぜですか? これにより無限ループが発生する可能性がありますか?
HashMap
これは非同期コレクションであり、これに対応する同期コレクションは であることがわかっていますHashTable
。したがって、すべてのスレッドがコレクションの 1 つのインスタンスにアクセスできるマルチスレッド環境でコレクションにアクセスする場合は、HashTable
ダーティ リードの回避やデータの一貫性の確保などの明白な理由から、この方法を使用する方が安全です。このマルチスレッド環境では、最悪の場合、無限ループが発生します。はい、それは本当だ。HashMap.get()
無限ループを引き起こす可能性があります。どうやって見てみましょうか?メソッドのソース コードを見るとHashMap.get(Object key)
、次のようになります。
public Object get(Object key) {
Object k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length);
Entry e = table[i];
while (true) {
if (e == null)
return e;
if (e.hash == hash && eq(k, e.key))
return e.value;
e = e.next;
}
}
while(true)
e.next
何らかの理由でそれ自体を指すことができる場合、マルチスレッド ランタイム環境では常に無限ループの犠牲になる可能性があります。これにより無限ループが発生しますが、どのようにしてe.next
自分自身 (つまり、e
) を指すのでしょうか? これは、サイズ変更 void transfer(Entry[] newTable)
中に呼び出されるメソッドで発生する可能性があります。HashMap
do {
Entry next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
このコード部分は、別のスレッドがマップ インスタンス ( ) を変更しようとしているのと同時にサイズ変更が発生した場合、無限ループを作成する傾向がありますHashMap
。このシナリオを回避する唯一の方法は、コード内で同期を使用するか、さらに良いのは、同期されたコレクションを使用することです。
抽象化とカプセル化について説明します。それらはどのように接続されているのでしょうか?
簡単に言うと、「抽象化では、現在のビューにとって重要なオブジェクトのプロパティのみが表示されます。 」オブジェクト指向プログラミング理論では、抽象化には、作業を実行し、状態を変更および報告し、システム内の他のオブジェクトと「対話」できる抽象的な「アクター」を表すオブジェクトを定義する機能が含まれます。どのプログラミング言語でも抽象化はさまざまな方法で機能します。これは、低レベル言語コマンドのインターフェイスを定義するルーチンの作成からわかります。一部の抽象化は、デザイン パターンなど、その構築の基礎となる抽象化を完全に隠すことによって、プログラマのニーズの全体的な表現の幅を制限しようとします。一般に、抽象化は 2 つの方法で見ることができます。 データ抽象化は、複雑なデータ型を作成し、データ モデルと対話するための意味のある操作のみを公開すると同時に、すべての実装の詳細を外界から隠す方法です。 実行抽象化は、すべての重要なステートメントを識別し、それらを作業単位として公開するプロセスです。通常、何らかの作業を行うメソッドを作成するときにこの機能を使用します。 クラス内にデータとメソッドを閉じ込めることと、(アクセス制御を使用した) 隠蔽の実行を組み合わせることは、多くの場合、カプセル化と呼ばれます。結果として、特性と動作を備えたデータ型が得られます。カプセル化には基本的に、データの隠蔽と実装の隠蔽も含まれます。 「変化する可能性のあるすべてをカプセル化する」。この引用はよく知られた設計原則です。さらに言えば、どのクラスでも、実行時にデータ変更が発生する可能性があり、将来のバージョンで実装変更が発生する可能性があります。したがって、カプセル化はデータと実装の両方に適用されます。したがって、次のように接続できます。- 抽象化とは主にクラスでできること [アイデア]です
- カプセル化は詳細ですこの機能を実現する方法[実装]
インターフェースと抽象クラスの違いは?
主な違いは次のとおりです。- インターフェイスはメソッドを実装できませんが、抽象クラスは実装できます。
- クラスは多くのインターフェイスを実装できますが、スーパークラス (抽象または非抽象) を 1 つだけ持つことができます。
- インターフェイスはクラス階層の一部ではありません。関連のないクラスでも同じインターフェイスを実装できます。
Cat
と はDog
抽象クラス から継承できAnimal
、この抽象基本クラスはメソッドvoid Breathe()
- Breath を実装します。これにより、すべての動物が同じ方法で実行されます。私のクラスに適用でき、他のクラスにも適用できる動詞は何ですか? これらの動詞ごとにインターフェイスを作成します。たとえば、すべての動物は食べることができるので、インターフェイスを作成しIFeedable
、Animal
そのインターフェイスを実装させます。インターフェースを実装するのに十分なだけ(Dog
私を好きになることができる) が、すべてではありません。誰かが言いました、「主な違いは、どこに実装するかです。」インターフェイスを作成するときは、インターフェイスを実装する任意のクラスに実装を移動できます。抽象クラスを作成すると、すべての派生クラスの実装を 1 か所で共有し、コードの重複などの多くの悪いことを回避できます。 Horse
ILikeable
StringBuffer はどのようにメモリを節約しますか?
このクラスはString
不変オブジェクトとして実装されます。つまり、最初にオブジェクトに何かを入れることを決定するとString
、仮想マシンは元の値とまったく同じサイズの固定長配列を割り当てます。これは仮想マシン内で定数として扱われるため、文字列の値が変わらなければパフォーマンスが大幅に向上します。ただし、何らかの方法で文字列の内容を変更することにした場合、仮想マシンが実際に行うことは、元の文字列の内容を一時領域にコピーし、変更を加えて、それらの変更を新しいメモリ配列に保存することです。したがって、初期化後に文字列の値を変更するのはコストのかかる操作です。 StringBuffer
一方、 は仮想マシン内で動的に拡張する配列として実装されます。つまり、既存のメモリ セルに対して変更操作を実行でき、必要に応じて新しいメモリが割り当てられます。ただし、仮想マシンの内容は各インスタンス間で一貫性がないとみなされるため、仮想マシンが最適化を行う方法はありませんStringBuffer
。
wait メソッドと Notice メソッドが Thread ではなく Object クラスで宣言されているのはなぜですか?
wait
、notify
、メソッドは、notifyAll
スレッドが共有リソースにアクセスできるようにする場合にのみ必要であり、共有リソースはヒープ内の任意の Java オブジェクトである可能性があります。したがって、これらのメソッドは基本クラスで定義されObject
、各オブジェクトがスレッドをモニター上で待機できるようにするコントロールを持つようになります。Java には、共有リソースを共有するために使用される特別なオブジェクトはありません。そのようなデータ構造は定義されていません。Object
したがって、クラスが共有リソースになり、wait()
、notify()
、などのヘルパー メソッドを提供できるようにするのはクラスの責任ですnotifyAll()
。Java は、Charles Hoare のモニターのアイデアに基づいています。Java では、すべてのオブジェクトにモニターがあります。スレッドはモニター上で待機するため、待機を実行するには 2 つのパラメーターが必要です。
- スレッド
- モニター(任意のオブジェクト)。
wait
) を定義することはできます。これは良い設計です。他のスレッドを特定のモニターで強制的に待機させると、「侵入」が発生し、並列プログラムの設計/プログラミングが困難になるためです。Java では、他のスレッドに干渉するすべての操作 (たとえば、stop()
) が非推奨であることに注意してください。
Java でデッドロックを作成し、それを修正するプログラムを作成する
Java ではdeadlock
、これは、少なくとも 2 つのスレッドが異なるリソース上のブロックを保持し、両方のスレッドがタスクを完了するために他のリソースが使用可能になるのを待っている状況を指します。そして、それらのどれも、保持されているリソースのロックを残すことはできません。 プログラム例:
package thread;
public class ResolveDeadLockTest {
public static void main(String[] args) {
ResolveDeadLockTest test = new ResolveDeadLockTest();
final A a = test.new A();
final B b = test.new B();
// Thread-1
Runnable block1 = new Runnable() {
public void run() {
synchronized (a) {
try {
// Добавляем задержку, чтобы обе нити могли начать попытки
// блокирования ресурсов
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Thread-1 заняла A но также нуждается в B
synchronized (b) {
System.out.println("In block 1");
}
}
}
};
// Thread-2
Runnable block2 = new Runnable() {
public void run() {
synchronized (b) {
// Thread-2 заняла B но также нуждается в A
synchronized (a) {
System.out.println("In block 2");
}
}
}
};
new Thread(block1).start();
new Thread(block2).start();
}
// Resource A
private class A {
private int i = 10;
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
}
// Resource B
private class B {
private int i = 20;
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
}
}
上記のコードを実行すると、非常に明白な理由 (上で説明) によりデッドロックが発生します。次に、この問題を解決する必要があります。私は、あらゆる問題の解決策は、問題自体の根本にあると信じています。私たちの場合、A と B へのアクセス モデルが主な問題です。したがって、これを解決するには、共有リソースへのアクセス演算子の順序を変更するだけです。変更後は次のようになります。
// Thread-1
Runnable block1 = new Runnable() {
public void run() {
synchronized (b) {
try {
// Добавляем задержку, чтобы обе нити могли начать попытки
// блокирования ресурсов
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Thread-1 заняла B но также нуждается в А
synchronized (a) {
System.out.println("In block 1");
}
}
}
};
// Thread-2
Runnable block2 = new Runnable() {
public void run() {
synchronized (b) {
// Thread-2 заняла B но также нуждается в А
synchronized (a) {
System.out.println("In block 2");
}
}
}
};
このクラスを再度実行すると、デッドロックは発生しなくなります。これがデッドロックを回避し、デッドロックが発生した場合に解決するのに役立つことを願っています。
Serializable インターフェイスを実装するクラスにシリアル化不可能なコンポーネントが含まれている場合はどうなりますか? これを修正するにはどうすればよいでしょうか?
この場合、NotSerializableException
実行中にスローされます。この問題を解決するには、非常に簡単な解決策があります。これらのボックスをチェックしますtransient
。これは、チェックされたフィールドがシリアル化されないことを意味します。これらのフィールドの状態も保存したい場合は、すでに を実装している参照変数を考慮する必要がありますSerializable
。readResolve()
およびメソッドも使用する必要がある場合がありますwriteResolve()
。要約しましょう:
- まず、フィールドを非シリアル化可能にします
transient
。 - まず
writeObject
、defaultWriteObject
スレッドを呼び出してすべての非transient
フィールドを保存し、次に残りのメソッドを呼び出して、シリアル化不可能なオブジェクトの個々のプロパティをシリアル化します。 - では
readObject
、最初にdefaultReadObject
ストリームを呼び出して非transient
フィールドをすべて読み取り、次に他のメソッド ( で追加したメソッドに対応するwriteObject
) を呼び出して非transient
オブジェクトを逆シリアル化します。
Java の一時的キーワードと揮発性キーワードについて説明する
「このキーワードは、transient
シリアル化されないフィールドを示すために使用されます。」Java 言語仕様によると、変数はオブジェクトの永続状態の一部ではないことを示すために、一時インジケーターでマークできます。たとえば、他のフィールドから派生したフィールドを含めることができ、それらのフィールドの状態をシリアル化して復元するよりも、プログラムで取得することをお勧めします。たとえば、クラスでは、(director) や(rate)BankPayment.java
などのフィールドをシリアル化でき、(未払い利息) は、シリアル化解除後であってもいつでも計算できます。思い出してください。Java の各スレッドは独自のローカル メモリを持ち、このローカル メモリ上で読み取り/書き込み操作を実行します。すべての操作が完了すると、変数の変更された状態が共有メモリに書き込まれ、そこからすべてのスレッドが変数にアクセスします。通常、これは仮想マシン内の通常のスレッドです。ただし、volatile 修飾子は、スレッドによるその変数へのアクセスが、その変数の自身のコピーとメモリ内の変数のマスター コピーと常に一致する必要があることを仮想マシンに指示します。これは、スレッドが変数の状態を読み取ろうとするたびに、内部メモリの状態をクリアし、メイン メモリから変数を更新する必要があることを意味します。 ロックフリーのアルゴリズムで最も役立ちます。共有データを格納する変数を揮発性としてマークすると、その変数にアクセスするためにロックを使用しなくても、あるスレッドによって行われたすべての変更が他のスレッドにも表示されるようになります。または、計算が繰り返されないように「発生後」関係を作成し、変更がリアルタイムで表示されるようにする場合も同様です。マルチスレッド環境で不変オブジェクトを安全に公開するには、Volatile を使用する必要があります。フィールド宣言により、すべてのスレッドが現在使用可能なインスタンスへの参照を常に参照できるようになります。 principal
rate
interest
Volatile
public volatile ImmutableObject
イテレータとリストイテレータの違いは?
、 、を使用してIterator
要素を反復処理できます。ただし、要素を反復処理する場合にのみ使用できます。その他の違いについては以下で説明します。あなたはできる: Set
List
Map
ListIterator
List
- 逆の順序で繰り返します。
- どこでもインデックスを取得します。
- 任意の値をどこにでも追加します。
- 現在の位置に任意の値を設定します。
GO TO FULL VERSION