こんにちは、みなさん!プログラミングには落とし穴がたくさんあります。そして、つまずいたり、ぶつかったりしないトピックはほとんどありません。これは特に初心者に当てはまります。これを減らす唯一の方法は勉強することです。特に、これは最も基本的なトピックの詳細な分析に当てはまります。現在も、Java 開発者インタビューから得た250 以上の質問の分析を続けていますが、これらの質問には基本的なトピックが十分に含まれています。このリストには、一般的なトピックを別の角度から見ることを可能にする非標準的な質問も含まれていることに注意してください。
62. 文字列プールとは何ですか?なぜ必要ですか?
Java のメモリ (ヒープ、後で説明します) には、文字列プールまたは文字列プールという領域があります。文字列値を保存するように設計されています。つまり、二重引用符などを使用して特定の文字列を作成すると、次のようになります。String str = "Hello world";
文字列プールに指定された値があるかどうかのチェックが行われます。そうである場合、str変数にはプール内のその値への参照が割り当てられます。そうでない場合は、新しい値がプール内に作成され、その値への参照がstr変数に割り当てられます。例を見てみましょう:
String firstStr = "Hello world";
String secondStr = "Hello world";
System.out.println(firstStr == secondStr);
true が 画面に表示されます。== は参照を比較することを覚えています。これは、これら 2 つの参照が文字列プールの同じ値を参照することを意味します。これは、メモリ内にString型の同一のオブジェクトが多数生成されないようにするために行われます。覚えているとおり、Stringは不変クラスであり、同じ値への参照が多数ある場合でも問題はありません。1 か所の値を変更しても、他の複数のリンクを同時に変更することは不可能になりました。 ただし、newを使用して文字列を作成すると、次のようになります。
String str = new String("Hello world");
この文字列値を保存する別のオブジェクトがメモリ内に作成されます (文字列プールにそのような値がすでにあるかどうかは関係ありません)。確認として:
String firstStr = new String("Hello world");
String secondStr = "Hello world";
String thirdStr = new String("Hello world");
System.out.println(firstStr == secondStr);
System.out.println(firstStr == thirdStr);
2 つのfalse 値が 得られます。これは、ここでは 3 つの異なる値が参照されていることを意味します。実際、二重引用符を使用して文字列を作成することをお勧めするのはこのためです。ただし、 newを使用してオブジェクトを作成するときに、文字列プールに値を追加 (または参照を取得) できます。これを行うには、文字列クラスのメソッドintern()を使用します。このメソッドは、文字列プールに値を強制的に作成するか、値が既に格納されている場合はその値へのリンクを取得します。以下に例を示します。
String firstStr = new String("Hello world").intern();
String secondStr = "Hello world";
String thirdStr = new String("Hello world").intern();
System.out.println(firstStr == secondStr);
System.out.println(firstStr == thirdStr);
System.out.println(secondStr == thirdStr);
その結果、コンソールに3 つのtrue値が表示されます。これは、3 つの変数すべてが同じ文字列を参照していることを意味します。
63. 文字列プールではどのような GOF パターンが使用されますか?
GOFパターンは文字列プールではっきりと見えます- flyweight、別名セトラーと呼ばれます。ここで別のテンプレートを見つけた場合は、コメントで共有してください。さて、軽量テンプレートについて話しましょう。ライトウェイトとは、プログラム内のさまざまな場所で一意のインスタンスとして存在するオブジェクトが、実際にはそうではない構造的な設計パターンです。ライトウェイトでは、各オブジェクトに同じデータを保存するのではなく、オブジェクトの共有状態をオブジェクト間で共有することでメモリを節約します。本質を理解するために、最も単純な例を見てみましょう。従業員インターフェースがあるとします。public interface Employee {
void work();
}
そして、いくつかの実装があります。たとえば、弁護士:
public class Lawyer implements Employee {
public Lawyer() {
System.out.println("Юрист взят в штат.");
}
@Override
public void work() {
System.out.println("Решение юридических вопросов...");
}
}
そして会計士はこう言いました。
public class Accountant implements Employee{
public Accountant() {
System.out.println("Бухгалтер взят в штат.");
}
@Override
public void work() {
System.out.println("Ведение бухгалтерского отчёта....");
}
}
メソッドは非常に条件付きです。必要なのは、メソッドが実行されることを確認することだけです。同じ状況がコンストラクターにも当てはまります。コンソール出力のおかげで、新しいオブジェクトがいつ作成されるかがわかります。また、従業員部門もあり、その任務は要求された従業員を発行することです。その従業員がそこにいない場合は、その従業員を雇用し、要求に応じて発行します。
public class StaffDepartment {
private Map<String, Employee> currentEmployees = new HashMap<>();
public Employee receiveEmployee(String type) throws Exception {
Employee result;
if (currentEmployees.containsKey(type)) {
result = currentEmployees.get(type);
} else {
switch (type) {
case "Бухгалтер":
result = new Accountant();
currentEmployees.put(type, result);
break;
case "Юрист":
result = new Lawyer();
currentEmployees.put(type, result);
break;
default:
throw new Exception("Данный сотрудник в штате не предусмотрен!");
}
}
return result;
}
}
つまり、ロジックは単純です。指定されたユニットが存在する場合はそれを返し、存在しない場合はユニットを作成し、ストレージ (キャッシュのようなもの) に配置して返します。それでは、すべてがどのように機能するかを見てみましょう。
public static void main(String[] args) throws Exception {
StaffDepartment staffDepartment = new StaffDepartment();
Employee empl1 = staffDepartment.receiveEmployee("Юрист");
empl1.work();
Employee empl2 = staffDepartment.receiveEmployee("Бухгалтер");
empl2.work();
Employee empl3 = staffDepartment.receiveEmployee("Юрист");
empl1.work();
Employee empl4 = staffDepartment.receiveEmployee("Бухгалтер");
empl2.work();
Employee empl5 = staffDepartment.receiveEmployee("Юрист");
empl1.work();
Employee empl6 = staffDepartment.receiveEmployee("Бухгалтер");
empl2.work();
Employee empl7 = staffDepartment.receiveEmployee("Юрист");
empl1.work();
Employee empl8 = staffDepartment.receiveEmployee("Бухгалтер");
empl2.work();
Employee empl9 = staffDepartment.receiveEmployee("Юрист");
empl1.work();
Employee empl10 = staffDepartment.receiveEmployee("Бухгалтер");
empl2.work();
}
それに応じて、コンソールには次の出力が表示されます。
弁護士が雇われました。法的問題の解決... 会計士が雇われました。会計報告書の維持.... 法的問題の解決.... 会計報告書の維持.... 法的問題の解決... 会計報告書の維持.... 法的問題の解決... 会計報告書の維持....法的問題の解決.. 会計報告書の管理...
ご覧のとおり、作成されたオブジェクトは 2 つだけで、何度も再利用されました。この例は非常に単純ですが、このテンプレートを使用するとリソースがどのように節約されるかを明確に示しています。お気づきのとおり、このパターンのロジックは保険プールのロジックと非常によく似ています。GOFパターンの種類について詳しくは、この記事をご覧ください。
64. 文字列を複数の部分に分割するにはどうすればよいですか? 対応するコードの例を提供してください
明らかに、この質問は分割メソッドに関するものです。Stringクラスには、このメソッドの 2 つのバリエーションがあります。String split(String regex);
そして
String split(String regex);
regexは行区切り文字であり、文字列を文字列の配列に分割する正規表現です。次に例を示します。
String str = "Hello, world it's Amigo!";
String[] arr = str.split("\\s");
for (String s : arr) {
System.out.println(s);
}
以下がコンソールに出力されます。
こんにちは、世界よ、アミーゴです!
つまり、文字列値は文字列の配列に分割され、区切り文字はスペースでした (区切りには、スペース以外の正規表現"\\s"と単なる文字列式" "を使用できます)。2 番目のオーバーロードされたメソッドには、追加の引数limitがあります。 limit — 結果として得られる配列の最大許容値。つまり、文字列がすでに最大許容数の部分文字列に分割されている場合、それ以上の分割は行われず、最後の要素には分割されていない可能性のある文字列の「残り」が含まれます。例:
String str = "Hello, world it's Amigo!";
String[] arr = str.split(" ", 2);
for (String s : arr) {
System.out.println(s);
}
コンソール出力:
こんにちは、世界よ、アミーゴです!
見てわかるように、limit = 2制約がなければ、配列の最後の要素は 3 つの部分文字列に分割される可能性があります。
65. パスワードを保存する場合、文字列よりも文字配列の方が優れているのはなぜですか?
パスワードを保存するときに文字列よりも配列を優先する理由はいくつかあります。 1. 文字列プールと文字列の不変性。 配列 ( char[] ) を使用する場合、データを使い終わった後に明示的にデータを消去できます。また、配列は必要に応じて書き換えることができ、ガベージ コレクションの前であっても、有効なパスワードはシステムのどこにも存在しません (いくつかのセルを無効な値に変更するだけで十分です)。同時に、Stringは不変クラスです。つまり、値を変更したい場合は、新しい値を取得しますが、古い値は文字列プールに残ります。パスワードの文字列値を削除したい場合、これは非常に困難な作業となる可能性があります。これは、文字列プールから値を削除するためにガベージ コレクターが必要であり、この文字列値がしばらくそこに残る可能性が高いためです。長い間。つまり、この状況では、データ ストレージのセキュリティの点で Stringはchar配列よりも劣ります。2.文字列値が誤ってコンソール (またはログ) に出力された場合、値自体が表示されます。String password = "password";
System.out.println("Пароль - " + password);
コンソール出力:
パスワード
同時に、誤って配列をコンソールに出力した場合は、次のようになります。
char[] arr = new char[]{'p','a','s','s','w','o','r','d'};
System.out.println("Пароль - " + arr);
コンソールには理解不能なゴブルディグックが表示されます。
パスワード - [C@7f31245a]
実際には gobbledygook ではありませんが、 [Cはクラス名であり、 char配列、 @は区切り文字、その後に続く 7f31245aは 16 進数のハッシュコードです。 3. 公式文書である Java Cryptography Architecture Guide では、パスワードを String ではなく char[] に保存することについて明示的に 言及しています。ただし、ここで注意点があります。Stringオブジェクトは不変です。つまり、Stringオブジェクトの内容を使用後に変更 (上書き) したり null にしたりできるように定義されたメソッドはありません。この機能により、Stringオブジェクトはユーザー パスワードなどの機密情報の保存には適さなくなります。代わりに、常に機密のセキュリティ情報を収集し、文字配列に保存する必要があります。」
列挙型
66. JavaのEnumについて簡単に説明してください
Enumは列挙であり、共通の型によって結合された文字列定数のセットです。キーワード-enumを通じて宣言されます。以下はenumを使用した例です- 特定の学校での有効な役割:public enum Role {
STUDENT,
TEACHER,
DIRECTOR,
SECURITY_GUARD
}
大文字で書かれた単語は、new演算子を使用せずに簡略化された方法で宣言されたものと同じ列挙定数です。列挙型を使用すると、(特定の値のリストしか存在できないため) 名前付けの際のエラーや混乱を避けるのに役立つため、作業が大幅に簡素化されます。個人的には、 Switch のロジック設計に使用すると非常に便利だと感じています。
67. Enumはインターフェースを実装できますか?
はい。結局のところ、列挙は単なる受動的なコレクション (ロールなど) 以上のものを表す必要があります。Java では、いくつかの機能を備えたより複雑なオブジェクトを表すことができるため、追加の機能を追加する必要がある場合があります。これにより、実装されたインターフェイスのタイプが必要な場所でenum値を置き換えることにより、ポリモーフィズムの機能を使用することもできます。68. Enumはクラスを拡張できますか?
いいえ、できません。列挙型はジェネリック クラスEnum <T>のデフォルトのサブクラスであるためです。ここで、T はジェネリック列挙型を表します。これは、Java 言語のすべての列挙型に共通の基本クラスにすぎません。enumから class への変換は、コンパイル時に Java コンパイラーによって行われます。この拡張機能はコード内で明示的に示されていませんが、常に目に見えないように存在します。69. オブジェクトインスタンスなしでEnumを作成することは可能ですか?
私としては、質問が少し変というか、よく理解できませんでした。私には2つの解釈があります: 1. 値のない列挙型は存在できますか- はい、もちろんそれは空のクラスのようなものになります - 無意味です:public enum Role {
}
そしてこう呼びかけます:
var s = Role.values();
System.out.println(s);
コンソールに次の内容が表示されます。
[Lflyweight.Role;@9f70c54
( Role値の空の配列) 2. new演算子を使用せずに列挙型を作成することは可能ですか- もちろん、はい。上で述べたように、 enum 値 (列挙) は静的な値であるため、これらにnew演算子を使用する必要はありません。
70. Enum の toString() メソッドをオーバーライドできますか?
はい、もちろん、toString()メソッドをオーバーライドして、 toStringメソッドを呼び出すとき(たとえば、コンソールやログへの出力のために列挙型を通常の文字列に変換するとき) に列挙型を表示する特定の方法を定義できます。public enum Role {
STUDENT,
TEACHER,
DIRECTOR,
SECURITY_GUARD;
@Override
public String toString() {
return "Выбрана роль - " + super.toString();
}
}
今日はここまで、次のパートまで!
GO TO FULL VERSION