以下のコンテンツは、元のJavaインタビューの質問セットと回答が完全に修正された後に与えられた責任ある質問と回答です。元の質問には多くの重複した質問と貴重な質問があり、多くの参照の回答も間違っていました。変更されたJavaインタビューの質問セットは、JDKの最新バージョンを参照し、EJB 2.xなどの役に立たないコンテンツを削除し、データ構造とアルゴリズム関連の質問、古典的なインタビュープログラミングの質問、大規模なWebサイトの技術アーキテクチャ、オペレーティングシステム、データベース、ソフトウェアテスト、設計パターン、UML、その他のコンテンツをサプリメントします。同時に、ハッシュコード法の設計、ゴミ収集のヒープと生成、Java、Nio.2などの新しい同時プログラミングなど、多くの知識ポイントも深く分析されました。
Javaプログラマーインタビューの質問セット(1-50)
1。Javaの基本
1.オブジェクト指向の特性の側面は何ですか?
回答:オブジェクト指向の機能の主な側面は次のとおりです。
1)要約:抽象化とは、データの抽象化や動作の抽象化など、オブジェクトのタイプの共通の特性をオブジェクトのクラスに要約するプロセスです。抽象化は、オブジェクトがどの属性と動作に持っているかにのみ焦点を当てており、これらの動作の詳細が何であるかに注意を払っていません。
2)継承:継承とは、既存のクラスから継承情報を取得し、新しいクラスを作成するプロセスです。継承された情報を提供するクラスは、親クラス(スーパークラス、ベースクラス)と呼ばれます。継承された情報を取得するクラスは、サブクラス(派生クラス)と呼ばれます。継承は、変化するソフトウェアシステムにある程度の連続性を与え、継承はプログラムのさまざまな要因をカプセル化する重要な手段でもあります(理解できない場合は、ヤンホン博士の「ジャワとパターン」または「デザインパターンの例外」のブリッジモードに関する部分を読んでください)。
3)カプセル化:カプセル化はデータを操作方法に結合することであり、データへのアクセスは定義されたインターフェイスを介してのみ達成できると一般に考えられています。オブジェクト指向の本質は、現実の世界を完全に自律的で閉じたオブジェクトのシリーズとして描写することです。クラスで書く方法は、実装の詳細をカプセル化することです。クラスは、データとデータ操作をカプセル化することです。パッケージングは、隠されているすべてのものを隠すことであり、外の世界に最もシンプルなプログラミングインターフェイスを提供することであると言えます(通常の洗濯機と完全に自動洗濯機の違いについて考えることができます。完全に自動洗濯機のパッケージ化が良く、したがって、現在使用しているスマートフォンが十分にパッケージ化されていることは明らかです。
4)多型:多型とは、異なるサブタイプのオブジェクトが同じメッセージに対して異なる反応を可能にすることを指します。簡単に言えば、同じ方法を同じオブジェクト参照で呼び出すことですが、異なることを行います。多型は、コンパイル時間の多型とランタイム多型に分けられます。オブジェクトの方法がオブジェクトから外の世界に提供されるサービスと見なされている場合、ランタイムの多型は次のように説明できます。システムAがシステムBが提供するサービスにアクセスする場合、システムBにはサービスを提供する複数の方法がありますが、すべてが電気シェーバーがシステムAであるように、電源システムはシステムBです。オブジェクトですが、電源システムの根底にある実装が何であるか、どのように電力を獲得するかはわかりません。メソッドオーバーロードは、コンパイル時間の多型(予備結合とも呼ばれます)を実装し、メソッドのオーバーライドはランタイム多型(ポストバインディングとも呼ばれる)を実装します。ランタイムの多型は、オブジェクト指向の最も重要なことです。多型を実装するには、2つのことを行う必要があります。1。メソッドの書き換え(サブクラスは親クラスを継承し、親クラスの既存または抽象的なメソッドを書き直します)。 2。オブジェクトモデリング(同じ参照を呼び出す親タイプの参照を使用した子タイプオブジェクトを参照すると、異なるサブクラスオブジェクトに従って異なる動作が表示されます)。
2。パブリック、プライベート、保護された、および書き込み(デフォルト)にアクセスすることの違いは何ですか?
回答:違いは次のとおりです。
スコープはBUNサブクラスと同じです。
public√√√√√√
保護された√√√×
デフォルト√√××
プライベート√×××
デフォルトは、クラスメンバーがアクセスの変更を書き込まない場合のデフォルトです。デフォルトでは、同じパッケージの他のクラスの公開に相当し、同じパッケージにない他のクラスのプライベートです。保護されていることは、公共からサブクラスに相当し、親子関係のない同じパッケージにないクラスにプライベートです。
3。文字列は最も基本的なデータ型ですか?
回答:いいえ。Javaには8つの基本的なデータ型のみがあります。バイト、ショート、int、long、float、double、char、およびboolean。基本タイプ(プリミティブタイプ)と列挙タイプ(列挙タイプ)を除き、残りは参照タイプ(参照タイプ)です。
4。フロートF = 3.4;それは正しいですか?
回答:間違っています。 3.4は二重精度数です。フローティングポイントタイプ(FLOAT)にダブルを割り当てると、精度損失(ダウンキャスティング、狭窄とも呼ばれます)を引き起こすため、float f =(float)3.4をキャストする必要があります。またはフロートf = 3.4f;を書き込みます。
5。短いS1 = 1; S1 = S1 + 1;何か問題がありますか?短いS1 = 1; S1 += 1;何か問題がありますか?
回答:短いS1 = 1の場合; S1 = S1 + 1; 1はINTタイプであるため、S1+1操作の結果もINTタイプであり、短いタイプに値を割り当てるにはキャストタイプが必要です。および短いS1 = 1; S1 += 1; S1+= 1; S1 =(Short)(S1 + 1)に相当します。暗黙のキャストがあります。
6。ジャワにgotoはありますか?
回答: GOTOはJavaの予約済みの単語であり、Javaの現在のバージョンでは使用されていません。 (Javaキーワードのリストは、GoToとConstを含むJames Gosling(Javaの父)によって書かれた本「The Javaプログラミング言語」の付録に記載されていますが、これら2つは現在使用できないキーワードです。言葉))
7. INTと整数の違いは何ですか?
回答: Javaはほぼ純粋なオブジェクト指向のプログラミング言語ですが、プログラミングの利便性のために、オブジェクトではない基本的なデータ型を導入しています。ただし、これらの基本データ型をオブジェクトとして操作するために、Javaは各基本データ型に対応するラッパークラスを導入しました。 intのパッケージクラスは整数です。 JDK 1.5以降、自動梱包/除外メカニズムが導入されているため、2つを互いに変換できます。
Javaは、プリミティブタイプごとにラッパータイプを提供します。
プリミティブタイプ: Boolean、Char、Byte、Short、int、long、float、double
パッケージングタイプ:ブール、キャラクター、バイト、ショート、整数、ロング、フロート、ダブル
パッケージcom.lovo; //なぜhovertree.compublic class autounboxingtestについて尋ねるのか{public static void main(string [] args){integer a = new Integer(3);整数B = 3; //自動ボックス3への整数タイプint c = 3; System.out.println(a == b); // false 2つの参照は、同じオブジェクトSystem.out.println(a == c)を参照しません。 // true aはintタイプに自動的にボックスを解除してからc}と比較します追加:私は最近、自動パッキングとアンボクシングにも関連するインタビューの質問に遭遇しました。コードは次のとおりです。
public class test03 {public static void main(string [] args){integer f1 = 100、f2 = 100、f3 = 150、f4 = 150; System.out.println(f1 == f2); System.out.println(f3 == f4); }} // hovertree.comあなたがそれを理解していないなら、両方の出力が真または偽であると考えるのは簡単です。まず第一に、4つの変数F1、F2、F3、およびF4はすべて整数オブジェクトであるため、次の==操作は値ではなく参照を比較することに注意することが重要です。梱包の本質は何ですか? INT値を整数オブジェクトに割り当てると、整数クラスの静的メソッド値を呼び出します。 Valueofのソースコードを見ると、何が起こっているのかがわかります。
public static integer valueof(int i){if(i> = integercache.low && i <= integercache.high)return integercache.cache [i +(-integercache.low)];新しい整数(i)を返します。 } // hovertree.comintegercacheは整数の内部クラスであり、そのコードは次のようになります。
/** * JLSが要求する * -128と127(包括的)の間の値のオブジェクトIDセマンティクスをサポートするキャッシュ。 * *キャッシュは、最初の使用法で初期化されます。キャッシュ *のサイズは、{@code -xx:autoboxcachemax = <size>}オプションによって制御できます。 * VMの初期化中、java.lang.integer.integercache.highプロパティ *は、 * sun.misc.vmクラスのプライベートシステムプロパティで設定および保存できます。 * hovertree.com */ private static class integercache {static final int low = -128;静的な最終int high;静的最終整数キャッシュ[]; static {//高い値は、プロパティint h = 127で構成できます。 string integercachehighpropvalue = sun.misc.vm.getSavedProperty( "java.lang.integer.integercache.high"); if(integercachehighpropvalue!= null){try {int i = parseint(integercachehighpropvalue); i = math.max(i、127); //最大配列サイズはinteger.max_value h = math.min(i、integer.max_value-(-low)-1); } catch(numberformatexception nfe){//プロパティをintに解析できない場合は、無視してください。 }} high = h; cache = new Integer [(high -low) + 1]; int j = low; for(int k = 0; k <cache.length; k ++)cache [k] = new Integer(j ++); //範囲[-128、127]は内面化する必要があります(JLS7 5.1.7)Assert IntegerCache.high> = 127; } private integercache(){}}簡単に言えば、リテラル値が-128〜127の場合、新しい整数オブジェクトは新しいものではありませんが、定数プールの整数オブジェクトは直接参照されます。したがって、上記のインタビューの質問におけるF1 == F2の結果は真実であり、F3 == F4の結果は偽です。インタビューの質問が簡単になればなるほど、謎が大きくなり、インタビュアーはかなりのスキルを持つ必要があります。
8。&&&の違いは何ですか?
回答: (1)ビットワイズと(2)論理的および。 &&演算子は短絡と操作です。論理と短絡の違いは非常に大きいですが、両方とも演算子の左右のブール値が式全体の値に忠実であることを要求しています。 &&は短絡操作と呼ばれます。なぜなら、&&の左側の式の値がfalseの場合、右側の式が直接短絡され、操作は実行されないためです。多くの場合、&&&&の代わりに使用する必要がある場合があります。たとえば、ユーザー名がnullではなく、空の文字列ではないことを確認する場合、username!= null &&!username.equals( "")として記述する必要があります。 2つの順序は交換できず、最初の条件が真でない場合、文字列の比較はまったく実行できないため、nullpointerexceptionが生成されるため、使用できません。注:論理または演算子(|)と短絡または演算子(||)の違いにも同じことが言えます。
追加: JavaScriptに精通している場合は、短絡コンピューティングの力をさらに感じることができる場合があります。 JavaScriptのマスターになりたい場合は、短絡コンピューティングをプレイすることから始めます。
9.メモリ内のスタック、ヒープ、静的ストレージ領域の使用について説明します。
回答:通常、基本データ型の変数、オブジェクト参照、および関数呼び出しのオンサイトストレージはすべて、メモリ内のスタックスペースを使用します。新しいキーワードとコンストラクターを介して作成されたオブジェクトは、ヒープスペースに配置されます。 100、「Hello」、定数など、プログラム内のリテラルは、静的ストレージエリアに配置されます。スタックスペースは最速で動作しますが、非常に小さいです。通常、多数のオブジェクトがヒープスペースに配置され、ハードディスク上の仮想メモリを含むメモリ全体をヒープスペースとして使用できます。
string str = new String( "hello");
上記のステートメントでは、STRがスタックに配置され、新しいで作成された文字列オブジェクトがヒープに配置され、文字通りの「Hello」が静的ストレージエリアに配置されます。
サプリメント: Javaの新しいバージョンは、「エスケープ分析」と呼ばれるテクノロジーを使用して、オブジェクトの運用パフォーマンスを改善するためにいくつかのローカルオブジェクトをスタックに配置できます。
10。Math.Round(11.5)は何に等しいですか? Math.Round(-11.5)は何に等しいですか?
回答: Math.Round(11.5)の返品値は12で、Math.Round(-11.5)の返品値は-11です。丸めの原則は、パラメーターに0.5を追加してから丸めます。
11。swtichはバイトに作用し、長い弦に作用できますか?
回答:初期のJDKでは、スイッチ(EXPR)で、EXPRはバイト、ショート、チャー、およびINTである可能性があります。バージョン1.5から始めて、JavaはEnumタイプ(列挙)を導入し、ExprはJDKのバージョン1.7から、および文字列(文字列)から始めることもできます。長いタイプは許可されていません。
12。最も効率的な方法を使用して、2回8を計算しますか?
回答: 2 << 3(左側の3ビットを移動することは、2を3のパワーに掛けるのと同等であり、右側の3ビットを3ビットに移動することは、2で3のパワーに分割することに相当します)。
サプリメント:書いたクラスのハッシュコードメソッドを書き換えると、以下に示すコードが表示される場合があります。実際、なぜこのような乗算を使用してハッシュコード(ハッシュコード)を生成する理由をよく理解していません。なぜこの数字はプライム番号であり、なぜ番号31が選択されているのですか?最初の2つの質問への回答をBaiduできます。 31を選択します。これは、乗算の代わりにシフトおよび減算操作を使用できるため、パフォーマンスが向上するためです。これについて言えば、31 * num <==>(num << 5) - num、左5ビットのシフトは2を2枚(32)に乗算し(32)、それ自体を差し引くことは31を掛けることに相当します。すべてのVMはこの最適化を自動的に完了できます。
パッケージcom.loonstudio;パブリッククラスの電話{private intエリアコード;プライベート文字列のプレフィックス。プライベートストリングリネ。 @override public int hashcode(){final int prime = 31; int result = 1; result = prime * result +エリアコード; result = prime * result +((linenumber == null)?0:linenumber.hashcode()); result = prime * result +((prefix == null)?0:prefix.hashcode());返品結果; } @Override public boolean equals(object obj){if(this == obj)return true; if(obj == null)falseを返します。 if(getClass()!= obj.getClass())falseを返します。 PhoneNumber other =(PhoneNumber)obj; if(aregcode!= other.areocode)falseを返します。 if(lineNumber == null){if(other.lineNumber!= null)falseを返します。 } else if(!linenumber.equals(other.lineNumber))falseを返します。 if(prefix == null){if(other.prefix!= null)falseを返します。 } else if(!prefix.equals(other.prefix))falseを返します。 trueを返します。 }} //なぜhovertree.comについて尋ねるのか13。配列に長さ()メソッドはありますか?文字列に長さ()メソッドはありますか?
回答:配列には長さ()メソッドはありませんが、長さの属性があります。文字列には長さ()メソッドがあります。 JavaScriptでは、文字列の長さを取得することは、長さ属性を介して取得されます。これはJavaと簡単に混同されます。
14。Javaでは、現在の複数のネストされたループから抜け出す方法は?
回答:最も外側のループの前にAなどのマークを追加し、Break Aを使用します。複数のループを壊すことができます。 (Javaはタグ付きのブレークと継続的なステートメントをサポートしており、それらの機能はCおよびC ++のGOTOステートメントに少し似ていますが、GOTOを回避するのと同じように、プログラムをよりエレガントにすることはなく、しばしば反対の効果を持たないため、タグ付きの休憩を避けて継続する必要があります。
15.コンストラクターはオーバーライドできますか?
回答:コンストラクターを継承することはできないため、書き直すことはできませんが、過負荷になります。
16. 2つのオブジェクトには同じ値(x.equals(y)== true)がありますが、異なるハッシュコードを持つことができます。これは正しいですか?
回答:いいえ、2つのオブジェクトxとyがx.equals(y)== trueを満たしている場合、ハッシュコードは同じでなければなりません。 Javaは、次のようにeqAulsメソッドとハッシュコード法を規定しています。(1)2つのオブジェクトが同じである場合(メソッドはtrueを返します)、ハッシュコード値は同じでなければなりません。 (2)2つのオブジェクトのハッシュコードが同じ場合、必ずしも同じではありません。もちろん、必要に応じて行う必要はありませんが、上記の原則に違反すると、コンテナを使用すると同じオブジェクトがセットコレクションに表示される可能性があり、新しい要素を追加する効率が大幅に削減されます(ハッシュストレージを使用して、ハッシュコードで頻繁に競合することでアクセスパフォーマンスが急激に減少します)。
サプリメント:多くのJavaプログラムは、平等とハッシュコードの方法について知っていますが、多くの人はそれを知っています。 Joshua Blochの傑作「効果的なJava」(多くのソフトウェア会社、「効果的なJava」、「Javaプログラミング思考」、「リファクタリング:既存のコードの品質の向上」は、Javaプログラマーによる必見です。 true)、および対称性(x.equals(y)はtrue、y.equals(x)もtrueを返す必要があります(x.equals(y)およびy.equals(z)もtrueを返す必要があります)と一貫性(xとyが参照されるオブジェクト情報が変更されていない場合、x. nul nul nul nul nul x.高品質の等しい方法を実装するためのトリックには、次のものが含まれます。1。==演算子を使用して、「パラメーターがこのオブジェクトへの参照であるかどうか」を確認します。 2。「パラメーターが正しいタイプであるかどうか」を確認するには、オペレーターのインスタンスを使用します。 3。クラスの重要な属性については、オブジェクトに渡された属性がそれを一致させるかどうかを確認します。 4. Equals Methodを書いた後、対称性、トランサイト性、一貫性を満たすかどうかを自問してください。 5.書き換えの場合は、常にハッシュコードを書き直します。 6. equalsメソッドパラメーターのオブジェクトを他のタイプに置き換えないでください。また、書き換え時の@Overrideアノテーションを忘れないでください。
17。文字列クラスを継承できますか?
回答:文字列クラスは最終クラスであり、継承することはできません。
サプリメント:継承文字列はそれ自体が間違った動作です。文字列タイプを再利用する最良の方法は、継承(IS-A)ではなく関連性(HAS-A)です。
18.オブジェクトがメソッドのパラメーターとして渡されると、このメソッドはオブジェクトのプロパティを変更し、変更された結果を返すことができます。それで、それはここのバリューパスですか、それとも参照パスですか?
回答:値転送です。 Javaプログラミング言語は、値でパラメーターのみを渡します。オブジェクトインスタンスがパラメーターとしてメソッドに渡されると、パラメーターの値はオブジェクトへの参照です。コールプロセス中にオブジェクトのプロパティを変更できますが、オブジェクトへの参照は変更されません。 C ++およびC#では、参照を渡すか、パラメーターを転送することにより、合格したパラメーターの値を変更できます。
サプリメント: Java 8では改善されていないJavaで参照を渡さないことは本当に不便です。この方法では、Javaで書かれたコードに多数のラッパークラスが表示されます(メソッド呼び出しを介してラッパークラスに変更する必要がある参照を配置し、ラッパーオブジェクトをメソッドに渡す必要があります)。このアプローチは、特にCやC ++からJavaプログラマーに耐え難いものに変身する開発者にとって、コードが肥大化するだけです。
19.文字列とstringbuilderとstringbufferの違いは何ですか?
回答: Javaプラットフォームには、文字列とStringBuffer/StringBuilderの2種類の文字列を提供します。これは、文字列を保存および操作できます。文字列は読み取り専用文字列です。つまり、文字列で参照される文字列のコンテンツを変更できません。 StringBufferおよびStringBuilderクラスで表される文字列オブジェクトは、直接変更できます。 StringBuilderはJDK 1.5に導入されました。 StringBufferの方法とまったく同じです。違いは、すべての側面が同期によって変更されていないため、StringBufferよりもわずかに効率的であるため、単一のスレッド環境で使用されることです。
サプリメント1:インタビューの質問があります:StringBuffer/StringBuilderオブジェクトの追加方法を呼び出すよりも、文字列連結を使用するよりも優れている状況はありますか?接続の後に取得された文字列が既に静的ストレージ領域に存在する場合、StringBuffer/StringBuilderの追加方法よりも +を使用することは、 + string concatenationよりも優れています。
サプリメント2:以下はインタビューの質問でもあり、プログラムの出力に正しい答えを出すことができるかどうかを確認するように依頼します。
パッケージcom.lovo; // hovertree.compublic class stringequaltestについて質問する理由文字列b = new String( "Programming");文字列c = "program" + "ming"; System.out.println(a == b); System.out.println(a == c); System.out.println(A.Equals(b)); System.out.println(A.Equals(c)); system.out.println(a.intern()== b.intern()); }}
20。過負荷とオーバーライドの違い。過負荷のメソッドは、返品タイプに基づいて区別できますか?
回答:両方のメソッドのオーバーロードと書き換えは、多型を実装する方法です。違いは、前者が時間多型をコンパイルするのに対し、後者はランタイム多型を実装することです。クラスで過負荷が発生します。同じ名前のメソッドに異なるパラメーターリスト(異なるパラメータータイプ、異なるパラメーター数、またはその両方)がある場合、それは過負荷と見なされます。サブクラスと親クラスの間で書き換えが発生します。書き換えには、サブクラスの書き換えメソッドと親クラスは、親クラスの書き換えメソッドと同じ返品タイプを持つことが必要です。これは、親クラスの書き換えメソッドよりも優れたアクセスであり、親クラスの書き換え方法(Rischerの代替原則)よりも多くの例外を宣言することはできません。過負荷には、返品タイプの特別な要件はありません。
サプリメント: Huaweiはかつてインタビューの質問で質問しました:なぜ返品タイプに基づいて過負荷を区別できないのか、答えを伝えます!
21. JVMロードクラスファイルの原理とメカニズムを説明してください。
回答: JVMのクラスのロードは、クラスローダー(クラスローダー)とそのサブクラスによって実装されます。 Javaのクラスローダーは、重要なJavaランタイムシステムコンポーネントであり、実行時にクラスファイルのクラスを見つけて読み込むことを担当しています。
補充:
1. Javaのクロスプラットフォームの性質により、コンパイルされたJavaソースプログラムは実行可能なプログラムではなく、1つ以上のクラスファイルです。 Javaプログラムがクラスを使用する必要がある場合、JVMはクラスのロード、接続(検証、準備、解析)、および初期化されることを保証します。クラスの読み込みとは、クラス.classファイルからメモリにデータを読み取ることを指します。通常、バイト配列を作成して.classファイルに読み取り、ロードされたクラスに対応するクラスオブジェクトを生成します。読み込みが完了した後、クラスオブジェクトはまだ不完全であるため、現時点ではクラスは利用できません。クラスがロードされると、接続ステージに入ります。この段階には、検証、準備(静的変数にメモリの割り当て、デフォルトの初期値の設定)、および解析(シンボル参照を直接参照に置き換える)の3つのステップが含まれます。最後に、JVMは次のようなクラスを初期化します。1。クラスに直接の親クラスがあり、クラスが初期化されていない場合、親クラスは最初に初期化されます。 2.クラスに初期化ステートメントがある場合、これらの初期化ステートメントが順番に実行されます。
2。クラスの読み込みは、ルートローダー(ブートストラップ)、拡張ローダー(拡張子)、システムローダー(システム)、ユーザー定義のクラスローダー(java.lang.classloaderのサブクラス)を含むクラスローダーによって行われます。 JDK 1.2から、クラスロードプロセスは父親の委任メカニズム(PDM)を採用しています。 PDMは、Javaプラットフォームのセキュリティを確実に保証します。このメカニズムでは、JVM独自のブートストラップはルートローダーであり、他のローダーは1つの親クラスローダーのみを持っています。クラスの読み込みは、最初に親クラスローダーのロードを要求し、親クラスローダーは無力である場合にのみサブクラスローダーによってロードされます。 JVMは、JavaプログラムへのBootstrapへの参照を提供していません。いくつかのクラスローダーの指示は次のとおりです。
a)ブートストラップ:一般的にローカルコードを使用して実装されており、JVM(Rt.jar)の基本的なコアクラスライブラリをロードする責任があります。
b)拡張: java.ext.dirsシステムプロパティで指定されたディレクトリからクラスライブラリをロードし、その親ローダーはブートストラップです。
c)システム:アプリケーションクラスローダーとも呼ばれ、その親クラスは拡張機能です。最も広く使用されているクラスローダーです。環境変数クラスパスまたはシステム属性java.class.pathによって指定されたディレクトリからクラスを記録し、ユーザー定義ローダーのデフォルトの親ローダーです。
22。漢字は文字型変数に保存できますか?なぜ?
回答: Javaで使用されるエンコードがUnicodeであるため、Charタイプは漢字を保存できます(特定のエンコードは選択されておらず、文字が唯一の統一された方法である文字セット番号で直接使用されます)。 Charタイプは2バイト(16ビット)を占めるため、中国人を置くのは問題ありません。
サプリメント: Unicodeを使用すると、文字がJVMの内外で異なる症状があり、両方ともJVM内にユニコードであることがわかります。この文字がJVMから外側に転送される場合(たとえば、ファイルシステムに保存されています)、エンコード変換が必要です。したがって、Javaにはバイトストリームと文字ストリーム、および入力StreamReaderやOutputStreamReaderなどの文字ストリームとバイトストリーム間を変換する変換ストリームがあります。これらの2つのクラスは、バイトストリームと文字ストリーム間のアダプタークラスであり、変換をエンコードするタスクを引き受けます。 Cプログラマーの場合、そのようなエンコード変換を完了するために、彼らはおそらく、達成するために組合(組合/コミュニティ)共有記憶の特性に依存しています。
23.抽象クラスとインターフェイスの類似点と相違点は何ですか?
回答:抽象クラスとインターフェイスの両方をインスタンス化することはできませんが、抽象クラスとインターフェイスタイプへの参照を定義できます。クラスが抽象クラスを継承するか、インターフェイスを実装する場合、その中にすべての抽象的なメソッドを実装する必要があります。そうしないと、クラスを抽象クラスとして宣言する必要があります。コンストラクターは抽象クラスで定義できるため、インターフェイスは抽象クラスよりも抽象的です。抽象的なメソッドと具体的な方法がありますが、コンストラクターはインターフェイスで定義できず、それらのすべての方法は抽象的な方法です。抽象クラスのメンバーは、プライベート、デフォルト、保護された、公開されますが、インターフェイスのメンバーはすべて公開されています。メンバー変数は抽象クラスで定義できますが、インターフェイスで定義されているメンバー変数は実際には定数です。抽象的なメソッドを備えたクラスは、抽象クラスとして宣言する必要があり、抽象クラスには必ずしも抽象的なメソッドがあるとは限りません。
24.静的ネストされたクラスと内部クラス(内部クラス)の違いは何ですか?
回答:静的ネストされたクラスは、静的と宣言された内部クラスであり、外部クラスインスタンスに依存せずにインスタンス化できます。以下に示すように、外部クラスがインスタンス化された後、通常の内部クラスをインスタンス化する必要があり、構文は非常に奇妙に見えます。
パッケージcom.lovo; / ** *ポーカークラス(ポーカーのデッキ)なぜhovertree.comについて尋ねるのか * */ public class poker {private static string [] suites = {"spade"、 "rose"、 "草の花"、 "cube"}; private static int [] faces = {1、2、3、4、5、6、7、8、9、10、11、12、13};プライベートカード[]カード; / *** constructor**/ public poker(){cards = new Card [52]; for(int i = 0; i <suites.length; i ++){for(int j = 0; j <faces.length; j ++){cards [i * 13+j] = new Card(suites [i]、faces [j]); }}} / ** * shuffle(rondance of of ord of ord of of of of of of of of of of of of of of of shuffle(){for(int i = 0、len = cards.length; i <len; i ++){int index =(int)(math.random() * len);カードtemp = card [index];カード[index] =カード[i];カード[i] = temp; }} / ***カード取引* @paramインデックス取引の位置** / publicカード取引(int index){return cards [index]; } / ** *カードクラス(ポーカーの一部) * [内部クラス] * @author luo hao * * / public class card {private string suite; //プライベートINTフェイスにスーツ。 //ポイントパブリックカード(String Suite、int face){this.suite = suite; this.face = face; } @Override public string toString(){string facestr = ""; switch(face){case 1:facestr = "a";壊す;ケース11:FACESTER = "J";壊す;ケース12:FACESTER = "Q";壊す;ケース13:FACESTER = "K";壊す;デフォルト:FACESTER = STRING.VALUEOF(FACE); } suite + facestrを返します。 }}}テストクラス:
パッケージcom.lovo; class pokertest {public static void main(string [] args){poker poker = new poker(); poker.shuffle(); // shuffle poker.card c1 = poker.deal(0); //最初のカードを提供します。 //自分でカードを作成しますsystem.out.println(c1); //最初のsystem.out.println(c2); //印刷:レッドハートA}} // hovertree.comについて尋ねる理由25。Javaにメモリリークがありますか?簡単に説明してください。
回答:理論的には、Javaにはゴミ収集メカニズム(GC)があるため、Javaにはメモリリークがありません(これが、Javaがサーバー側のプログラミングで広く使用されている重要な理由でもあります)。ただし、実際の開発では、役に立たないがアクセスしやすいオブジェクトが存在する可能性があり、これらのオブジェクトはGCによってリサイクルできず、メモリリークが発生します。例としては、Hibernateのセッション(レベル1キャッシュ)のオブジェクトは永続的であり、ガベージコレクターはこれらのオブジェクトをリサイクルしないが、これらのオブジェクトには役に立たないガベージオブジェクトがある可能性があることです。次の例は、Javaのメモリリークも示しています。
パッケージcom.lovo; // hovertree.comについて尋ねる理由java.util.arrays; java.util.emptystackexcectionをインポートします。パブリッククラスMyStack <T> {private t []要素。プライベートINTサイズ= 0;プライベート静的最終int init_capacity = 16; public mystack(){elements =(t [])new object [init_capacity]; } public void push(t elem){ensurecapacity();要素[size ++] = elem; } public T pop() { if(size == 0) throw new EmptyStackException(); return elements[--size]; } private void ensureCapacity() { if(elements.length == size) { elements = Arrays.copyOf(elements, 2 * size + 1); }}}上面的代码实现了一个栈(先进后出(FILO))结构,乍看之下似乎没有什么明显的问题,它甚至可以通过你编写的各种单元测试。然而其中的pop方法却存在内存泄露的问题,当我们用pop方法弹出栈中的对象时,该对象不会被当作垃圾回收,即使使用栈的程序不再引用这些对象,因为栈内部维护着对这些对象的过期引用(obsolete reference)。在支持垃圾回收的语言中,内存泄露是很隐蔽的,这种内存泄露其实就是无意识的对象保持。如果一个对象引用被无意识的保留起来了,那么垃圾回收器不会处理这个对象,也不会处理该对象引用的其他对象,即使这样的对象只有少数几个,也可能会导致很多的对象被排除在垃圾回收之外,从而对性能造成重大影响,极端情况下会引发Disk Paging(物理内存与硬盘的虚拟内存交换数据),甚至造成OutOfMemoryError。
26、抽象的(abstract)方法是否可同时是静态的(static),是否可同时是本地方法(native),是否可同时被synchronized修饰?
答:都不能。抽象方法需要子类重写,而静态的方法是无法被重写的,因此二者是矛盾的。本地方法是由本地代码(如C代码)实现的方法,而抽象方法是没有实现的,也是矛盾的。synchronized和方法的实现细节有关,抽象方法不涉及实现细节,因此也是相互矛盾的。
27、静态变量和实例变量的区别?
答:静态变量是被static修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝;实例变量必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。静态变量可以实现让多个对象共享内存。在Java开发中,上下文类和工具类中通常会有大量的静态成员。
28、是否可以从一个静态(static)方法内部发出对非静态(non-static)方法的调用?
答:不可以,静态方法只能访问静态成员,因为非静态方法的调用要先创建对象,因此在调用静态方法时可能对象并没有被初始化。
29、如何实现对象克隆?
答:有两种方式:
1.实现Cloneable接口并重写Object类中的clone()方法;
2. Implement the Serializable interface, and implement cloning through object serialization and deserialization, which can realize true deep cloning.コードは次のとおりです。
package com.lovo; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class MyUtil { private MyUtil() { throw new AssertionError(); } public static <T> T clone(T obj) throws Exception { ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bout); oos.writeObject(obj); ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bin); return (T) ois.readObject(); // 说明:调用ByteArrayInputStream或ByteArrayOutputStream对象的close方法没有任何意义// 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源} } //何问起hovertree.com下面是测试代码:
package com.lovo; import java.io.Serializable; /** * Human* @author Luo Hao* */ class Person implements Serializable { private static final long serialVersionUID = -9102017020286042305L;プライベート文字列名; // Name private int age; // Age private Car car; // Car public Person(String name, int age, Car car) { this.name = name; this.age = age; this.car = car; } public string getname(){return name; } public void setName(String name) { this.name = name; } public int getage(){return age; } public void setAge(int age) { this.age = age; } public Car getCar() { return car; } public void setCar(Car car) { this.car = car; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + ", car=" + car + "]"; } } /** * Car class* @author Luo Hao* */ class Car implements Serializable { private static final long serialVersionUID = -5713945027627603702L; private String brand; // Brand private int maxSpeed; // Top speed public Car(String brand, int maxSpeed) { this.brand = brand; this.maxSpeed = maxSpeed; } public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } public int getMaxSpeed() { return maxSpeed; } public void setMaxSpeed(int maxSpeed) { this.maxSpeed = maxSpeed; } @Override public String toString() { return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed + "]"; } } //Why ask about hovertree.comclass CloneTest { public static void main(String[] args) { try { Person p1 = new Person("Hao LUO", 33, new Car("Benz", 300)); Person p2 = MyUtil.clone(p1); // Deep cloning p2.getCar().setBrand("BYD"); // Modify the brand attributes of the cloned Person object p2-associated car object// The original Person object p1-associated car will not be affected in any way// Because when cloning Person object, the car object associated with it is also cloned System.out.println(p1); } catch (Exception e) { e.printStackTrace(); }}}注意:基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object类的clone方法克隆对象。
30、GC 是什么?为什么要有GC?
答: GC是垃圾收集的意思,内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法。Java程序员不用担心内存管理,因为垃圾收集器会自动进行管理。要请求垃圾收集,可以调用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,但JVM可以屏蔽掉显示的垃圾回收调用。
垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。在Java诞生初期,垃圾回收是Java最大的亮点之一,因为服务器端的编程需要有效的防止内存泄露问题,然而时过境迁,如今Java的垃圾回收机制已经成为被诟病的东西。移动智能终端用户通常觉得iOS的系统比Android系统有更好的用户体验,其中一个深层次的原因就在于Android系统中垃圾回收的不可预知性。
补充:垃圾回收机制有很多种,包括:分代复制垃圾回收、标记垃圾回收、增量垃圾回收等方式。标准的Java进程既有栈又有堆。栈保存了原始型局部变量,堆保存了要创建的对象。Java平台对堆内存回收和再利用的基本算法被称为标记和清除,但是Java对其进行了改进,采用“分代式垃圾收集”。这种方法会跟Java对象的生命周期将堆内存划分为不同的区域,在垃圾收集过程中,可能会将对象移动到不同区域:
伊甸园(Eden):这是对象最初诞生的区域,并且对大多数对象来说,这里是它们唯一存在过的区域。
幸存者乐园(Survivor):从伊甸园幸存下来的对象会被挪到这里。
终身颐养园(Tenured):这是足够老的幸存对象的归宿。年轻代收集(Minor-GC)过程是不会触及这个地方的。当年轻代收集不能把对象放进终身颐养园时,就会触发一次完全收集(Major-GC),这里可能还会牵扯到压缩,以便为大对象腾出足够的空间。
与垃圾回收相关的JVM参数:
-Xms / -Xmx --- 堆的初始大小/ 堆的最大大小
-Xmn --- 堆中年轻代的大小
-XX:-DisableExplicitGC --- 让System.gc()不产生任何作用
-XX:+PrintGCDetail --- 打印GC的细节
-XX:+PrintGCDateStamps --- 打印GC操作的时间戳
31、String s=new String(“xyz”);创建了几个字符串对象?
答:两个对象,一个是静态存储区的"xyz",一个是用new创建在堆上的对象。
32、接口是否可继承(extends)接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承具体类(concrete class)?
答:接口可以继承接口。抽象类可以实现(implements)接口,抽象类可继承具体类,但前提是具体类必须有明确的构造函数。
33、一个“.java”源文件中是否可以包含多个类(不是内部类)?有什么限制?
答:可以,但一个源文件中最多只能有一个公开类(public class)而且文件名必须和公开类的类名完全保持一致。
34、Anonymous Inner Class(匿名内部类)是否可以继承其它类?是否可以实现接口?
答:可以继承其他类或实现其他接口,在Swing编程中常用此方式来实现事件监听和回调。
35. Can an inner class refer to its members that contain the class (external class)?制限はありますか?
答:一个内部类对象可以访问创建它的外部类对象的成员,包括私有成员。
36、Java 中的final关键字有哪些用法?
答: (1)修饰类:表示该类不能被继承;(2)修饰方法:表示方法不能被重写;(3)修饰变量:表示变量只能一次赋值以后值不能被修改(常量)。
37、指出下面程序的运行结果:
class A{ static{ System.out.print("1"); } public A(){ System.out.print("2"); } } class B extends A{ static{ System.out.print("a"); } public B(){ System.out.print("b"); } } //Why ask about hovertree.compublic class Hello{ public static void main(String[] args){ A ab = new B(); ab = new B(); }}答:执行结果:1a2b2b。创建对象时构造器的调用顺序是:先初始化静态成员,然后调用父类构造器,再初始化非静态成员,最后调用自身构造器。
38、数据类型之间的转换:
1)如何将字符串转换为基本数据类型?
2)如何将基本数据类型转换为字符串?
答え:
1)调用基本数据类型对应的包装类中的方法parseXXX(String)或valueOf(String)即可返回相应基本类型;
2)一种方法是将基本数据类型与空字符串(””)连接(+)即可获得其所对应的字符串;另一种方法是调用String 类中的valueOf(…)方法返回相应字符串
39、如何实现字符串的反转及替换?
答:方法很多,可以自己写实现也可以使用String或StringBuffer / StringBuilder中的方法。有一道很常见的面试题是用递归实现字符串反转,代码如下所示:
public static String reverse(String originStr) { if(originStr == null || originStr.length() <= 1) return originStr; return reverse(originStr.substring(1)) + originStr.charAt(0); } //何问起hovertree.com40、怎样将GB2312编码的字符串转换为ISO-8859-1编码的字符串?
答:代码如下所示:
String s1 = "你好";String s2 = newString(s1.getBytes("GB2312"), "ISO-8859-1");41、日期和时间:
1)如何取得年月日、小时分钟秒?
2)如何取得从1970年1月1日0时0分0秒到现在的毫秒数?
3)如何取得某月的最后一天?
4)如何格式化日期?
答:操作方法如下所示:
1)创建java.util.Calendar 实例,调用其get()方法传入不同的参数即可获得参数所对应的值
2)以下方法均可获得该毫秒数:
Calendar.getInstance().getTimeInMillis(); System.currentTimeMillis(); //何问起hovertree.com
3)示例代码如下:
Calendar time = Calendar.getInstance(); time.getActualMaximum(Calendar.DAY_OF_MONTH); //何问起hovertree.com
4)利用java.text.DataFormat 的子类(如SimpleDateFormat类)中的format(Date)方法可将日期格式化。
42、打印昨天的当前时刻。
答え:
public class YesterdayCurrent { public static void main(String[] args){ Calendar cal = Calendar.getInstance(); cal.add(Calendar.DATE, -1); System.out.println(cal.getTime()); } } //何问起hovertree.com43、比较一下Java 和JavaSciprt。
答: JavaScript 与Java是两个公司开发的不同的两个产品。Java 是原Sun 公司推出的面向对象的程序设计语言,特别适合于互联网应用程序开发;而JavaScript是Netscape公司的产品,为了扩展Netscape浏览器的功能而开发的一种可以嵌入Web页面中运行的基于对象和事件驱动的解释性语言,它的前身是LiveScript;而Java 的前身是Oak语言。
下面对两种语言间的异同作如下比较:
1)基于对象和面向对象:Java是一种真正的面向对象的语言,即使是开发简单的程序,必须设计对象;JavaScript是种脚本语言,它可以用来制作与网络无关的,与用户交互作用的复杂软件。它是一种基于对象(Object-Based)和事件驱动(Event-Driven)的编程语言。因而它本身提供了非常丰富的内部对象供设计人员使用;
2)解释和编译:Java 的源代码在执行之前,必须经过编译;JavaScript 是一种解释性编程语言,其源代码不需经过编译,由浏览器解释执行;
3)强类型变量和类型弱变量:Java采用强类型变量检查,即所有变量在编译之前必须作声明;JavaScript中变量声明,采用其弱类型。即变量在使用前不需作声明,而是解释器在运行时检查其数据类型;
4)代码格式不一样。
补充:上面列出的四点是原来所谓的标准答案中给出的。其实Java和JavaScript最重要的区别是一个是静态语言,一个是动态语言。目前的编程语言的发展趋势是函数式语言和动态语言。在Java中类(class)是一等公民,而JavaScript中函数(function)是一等公民。对于这种问题,在面试时还是用自己的语言回答会更加靠谱。
44、什么时候用assert?
答: assertion(断言)在软件开发中是一种常用的调试方式,很多开发语言中都支持这种机制。一般来说,assertion用于保证程序最基本、关键的正确性。assertion检查通常在开发和测试时开启。为了提高性能,在软件发布后, assertion检查通常是关闭的。在实现中,断言是一个包含布尔表达式的语句,在执行这个语句时假定该表达式为true;如果表达式计算为false,那么系统会报告一个AssertionError。
断言用于调试目的:
assert(a > 0); // throws an AssertionError if a <= 0
断言可以有两种形式:
assert Expression1;
assert Expression1 : Expression2 ;
Expression1 应该总是产生一个布尔值。
Expression2 可以是得出一个值的任意表达式;这个值用于生成显示更多调试信息的字符串消息。
断言在默认情况下是禁用的,要在编译时启用断言,需使用source 1.4 标记:
javac -source 1.4 Test.java
要在运行时启用断言,可使用-enableassertions 或者-ea 标记。
要在运行时选择禁用断言,可使用-da 或者-disableassertions 标记。
要在系统类中启用断言,可使用-esa 或者-dsa 标记。还可以在包的基础上启用或者禁用断言。可以在预计正常情况下不会到达的任何位置上放置断言。断言可以用于验证传递给私有方法的参数。不过,断言不应该用于验证传递给公有方法的参数,因为不管是否启用了断言,公有方法都必须检查其参数。不过,既可以在公有方法中,也可以在非公有方法中利用断言测试后置条件。另外,断言不应该以任何方式改变程序的状态。
45、Error 和Exception 有什么区别?
答: Error 表示系统级的错误和程序不必处理的异常,是恢复不是不可能但很困难的情况下的一种严重问题;比如内存溢出,不可能指望程序能处理这样的情况;Exception 表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题;也就是说,它表示如果程序运行正常,从不会发生的情况。
Supplement: During the interview with Motorola in 2005, I asked a question like "If a process reports a stack overflow run-time error, what's the most possible cause?", giving four options a. lack of memory; b。 write on an invalid memory space; c。 recursive function calling; d。 array index out of boundary. Java programs may also encounter StackOverflowError when running. This is an error that cannot be recovered, so I can only re-modify the code. The answer to this interview question is c. If you write recursion that cannot converge quickly, it is very likely to cause a stack overflow error, as shown below:
package com.lovo; public class StackOverflowErrorTest { public static void main(String[] args) { main(null); } } //何问起hovertree.com因此,用递归编写程序时一定要牢记两点:1. 递归公式;2. 收敛条件(什么时候就不再递归而是回溯了)。
46、try{}里有一个return语句,那么紧跟在这个try后的finally{}里的code会不会被执行,什么时候被执行,在return前还是后?
答:会执行,在方法返回调用者前执行。Java允许在finally中改变返回值的做法是不好的,因为如果存在finally代码块,try中的return语句不会立马返回调用者,而是记录下返回值待finally代码块执行完毕之后再向调用者返回其值,然后如果在finally中修改了返回值,这会对程序造成很大的困扰,C#中就从语法上规定不能做这样的事。
47、Java 语言如何进行异常处理,关键字:throws、throw、try、catch、finally分别如何使用?
答: Java 通过面向对象的方法进行异常处理,把各种不同的异常进行分类,并提供了良好的接口。在Java 中,每个异常都是一个对象,它是Throwable 类或其子类的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并进行处理。Java 的异常处理是通过5 个关键词来实现的:try、catch、throw、throws和finally。一般情况下是用try来执行一段程序,如果出现异常,系统会抛出(throw)一个异常,这时候你可以通过它的类型来捕捉(catch)它,或最后(finally)由缺省处理器来处理;try用来指定一块预防所有“异常”的程序;catch 子句紧跟在try块后面,用来指定你想要捕捉的“异常”的类型;throw 语句用来明确地抛出一个“异常”;throws用来标明一个成员函数可能抛出的各种“异常”;finally 为确保一段代码不管发生什么“异常”都被执行一段代码;可以在一个成员函数调用的外面写一个try语句,在这个成员函数内部写另一个try语句保护其他代码。每当遇到一个try 语句,“异常”的框架就放到栈上面,直到所有的try语句都完成。如果下一级的try语句没有对某种“异常”进行处理,栈就会展开,直到遇到有处理这种“异常”的try 语句。
48、运行时异常与受检异常有何异同?
答:异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就不会发生。受检异常跟程序运行的上下文环境有关,即使程序设计无误,仍然可能因使用的问题而引发。Java编译器要求方法必须声明抛出可能发生的受检异常,但是并不要求必须声明抛出未被捕获的运行时异常。异常和继承一样,是面向对象程序设计中经常被滥用的东西,神作《Effective Java》中对异常的使用给出了以下指导原则:
不要将异常处理用于正常的控制流(设计良好的API不应该强迫它的调用者为了正常的控制流而使用异常)
对可以恢复的情况使用受检异常,对编程错误使用运行时异常避免不必要的使用受检异常(可以通过一些状态检测手段来避免异常的发生)
优先使用标准的异常每个方法抛出的异常都要有文档保持异常的原子性不要在catch中忽略掉捕获到的异常
49、列出一些你常见的运行时异常?
答え:
ArithmeticException(算术异常)
ClassCastException (类转换异常)
IllegalArgumentException (非法参数异常)
IndexOutOfBoundsException (下表越界异常)
NullPointerException (空指针异常)
SecurityException (安全异常)
50、final, finally, finalize 的区别?
答: final:修饰符(关键字)有三种用法:如果一个类被声明为final,意味着它不能再派生出新的子类,即不能被继承,因此它和abstract是反义词。将变量声明为final,可以保证它们在使用中不被改变,被声明为final 的变量必须在声明时给定初值,而在以后的引用中只能读取不可修改。被声明为final 的方法也同样只能使用,不能在子类中被重写。finally:通常放在try…catch的后面构造总是执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要JVM不关闭都能执行,可以将释放外部资源的代码写在finally块中。finalize:Object类中定义的方法,Java中允许使用finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize() 方法可以整理系统资源或者执行其他清理工作。
上記はこの記事のすべての内容です。 I hope it will be helpful to everyone in the Java interview, and I hope everyone will support Wulin.com more.