1. 序章

Stringクラスは、Javaで最も広く使用されているクラスの1つであり、言語設計者はこれを特別に扱う必要があります。 この特別な振る舞いは、Javaインタビューで最もホットなトピックの1つになっています。

このチュートリアルでは、Stringに関する最も一般的なインタビューの質問をいくつか取り上げます。

2. 文字列基本

このセクションは、Stringの内部構造とメモリに関する質問で構成されています。

Q1。 Javaの文字列とは何ですか?

Javaでは、 String は、 byte 値(またはJDK9より前のchar 値)の配列によって内部的に表されます。

Java 8までのバージョンでは、StringはUnicode文字の不変の配列で構成されていました。 ただし、ほとんどの文字は、16ビット(char サイズ)ではなく、8ビット(1 バイト)で表現できます。

メモリ消費とパフォーマンスを改善するために、Java9はコンパクトストリングを導入しました。 これは、 String に1バイト文字しか含まれていない場合、Latin-1エンコーディングを使用して表されることを意味します。 String に少なくとも1つのマルチバイト文字が含まれている場合、UTF-16エンコーディングを使用して1文字あたり2バイトとして表されます。

CおよびC++では、 String も文字の配列ですが、Javaでは、独自のAPIを備えた別個のオブジェクトです。

Q2。 Javaで文字列オブジェクトを作成するにはどうすればよいですか

java.lang.String は、Stringを作成するための13の異なる方法を定義します。 ただし、一般的には2つあります。

  • String リテラルを介して:
    String s = "abc";
  • new キーワードを使用して:
    String s = new String("abc");

Javaのすべての文字列リテラルはStringクラスのインスタンスです。

Q3。 String はプリミティブ型ですか、それとも派生型ですか?

String は、状態と動作があるため、派生型です。 たとえば、 substring() indexOf() equals()、などのプリミティブにはないメソッドがあります。

しかし、私たち全員がそれを頻繁に使用するので、それはそれを原始的なように感じさせるいくつかの特別な特徴を持っています:

  • 文字列はプリミティブのようにコールスタックに格納されませんが、文字列プールと呼ばれる特別なメモリ領域に格納されます。
  • プリミティブのように、文字列に対して+演算子を使用できます
  • また、プリミティブのように、新しいキーワードなしで文字列のインスタンスを作成できます

Q4。 文字列が不変である利点は何ですか?

JamesGoslingによるインタビューによると、文字列はパフォーマンスとセキュリティを向上させるために不変です。

そして実際には、不変の文字列を持つことにはいくつかの利点があります。

  • 文字列プールは、一度作成された文字列が変更されない場合にのみ可能です。再利用されることになっているためです。
  • コードは文字列を別のメソッドに安全に渡すことができ、そのメソッドでは変更できないことがわかっています。
  • 不変にこのクラスを自動的にスレッドセーフにします
  • このクラスはスレッドセーフであるため、共通データを同期する必要はありません。これによりパフォーマンスが向上します。
  • それらは変更されないことが保証されているため、それらのハッシュコードは簡単にキャッシュできます

Q5。 文字列はどのようにメモリに保存されますか?

JVM仕様によれば、 String リテラルは、JVMのメソッド領域から割り当てられるランタイム定数プールに格納されます。

メソッド領域は論理的にヒープメモリの一部ですが、仕様では場所、メモリサイズ、またはガベージコレクションポリシーを指定していません。実装固有の場合があります。

クラスまたはインターフェースのこのランタイム定数プールは、クラスまたはインターフェースがJVMによって作成されるときに構築されます。

Q6。 インターン文字列はJavaのガベージコレクションに適格ですか?

はい。プログラムからの参照がない場合、文字列プール内のすべてのStringがガベージコレクションの対象になります。

Q7。 文字列定数プールとは何ですか?

文字列プールは、String定数プールまたはStringインターンプールとも呼ばれ、JVMがStringを格納する特別なメモリ領域です。 ]インスタンス。

割り当てられる文字列の頻度と数を減らすことで、アプリケーションのパフォーマンスを最適化します

  • JVMは、特定のStringのコピーを1つだけプールに格納します
  • 新しいStringを作成するとき、JVMはプール内で同じ値を持つStringを検索します
  • 見つかった場合、JVMは追加のメモリを割り当てずにそのStringへの参照を返します
  • 見つからない場合、JVMはそれをプールに追加(インターン)し、その参照を返します

Q8。 文字列はスレッドセーフですか? どのように?

文字列は不変であるため、実際には完全にスレッドセーフです。 不変であるクラスは、インスタンスが複数のスレッド間で変更されないことが保証されるため、スレッドセーフの対象となります。

たとえば、スレッドが文字列の値を変更すると、既存の String が変更されるのではなく、新しいStringが作成されます。

Q9。 ロケールを指定することが重要な文字列操作はどれですか?

Locale クラスを使用すると、文化的なロケールを区別したり、コンテンツを適切にフォーマットしたりできます。

String クラスに関しては、 format で文字列をレンダリングする場合、または小文字または大文字の文字列をレンダリングする場合に必要です。

実際、これを忘れると、移植性、セキュリティ、および使いやすさの問題が発生する可能性があります。

Q10。 文字列の基になる文字エンコードとは何ですか?

Java8までのバージョンのStringのJavadocsによると、文字列は内部でUTF-16形式で格納されます。

charデータ型およびjava.lang.Characterオブジェクトも、文字を固定幅の16ビットエンティティとして定義した元のUnicode仕様に基づいています。

JDK 9以降、1バイト文字のみを含む文字列 Latin-1 エンコーディングを使用し、少なくとも1つのマルチバイト文字を含む文字列はUTF-を使用します。 16エンコーディング。

3. String API

このセクションでは、 StringAPIに関連するいくつかの質問について説明します。

Q11。 Javaで2つの文字列を比較するにはどうすればよいですか? str1 == str2str1.equals(str2)の違いは何ですか?

文字列比較するには、equal to演算子(==)を使用する方法と、 equals()メソッドを使用する方法の2つがあります。

どちらも互いにまったく異なります。

  • 演算子(str1 == str2)参照の同等性をチェックします
  • メソッド(str1.equals(str2))は字句の同等性をチェックします

ただし、2つの文字列が字句的に等しい場合、 str1.intern()== str2.intern()trueです。

通常、2つの Strings のコンテンツを比較するには、常にString.equalsを使用する必要があります。

Q12。 Javaで文字列を分割するにはどうすればよいですか?

String クラス自体は、正規表現の区切り文字を受け入れる String#splitメソッドを提供します。 String[]配列を返します。

String[] parts = "john,peter,mary".split(",");
assertEquals(new String[] { "john", "peter", "mary" }, parts);

splitのトリッキーな点の1つは、空の文字列を分割するときに、空でない配列を取得する可能性があることです。

assertEquals(new String[] { "" }, "".split(","));

もちろん、スプリットは、Java文字列スプリットする多くの方法の1つにすぎません。

Q13。 Stringjoinerとは何ですか?

StringJoiner は、Java 8で導入されたクラスで、個別の文字列を1つに結合します。たとえば、は色のリストを取得し、それらをコンマ区切りの文字列として返します。 区切り文字、接頭辞、接尾辞を指定できます。

StringJoiner joiner = new StringJoiner(",", "[", "]");
joiner.add("Red")
  .add("Green")
  .add("Blue");

assertEquals("[Red,Green,Blue]", joiner.toString());

Q14。 String、Stringbuffer、Stringbuilderの違いは?

文字列は不変です。 この意味はその値を変更または変更しようとすると、Javaはまったく新しいものを作成します弦。 

たとえば、文字列 str1 を作成した後で追加すると、次のようになります。

String str1 = "abc";
str1 = str1 + "def";

次に、JVMは、 str1 を変更する代わりに、まったく新しいStringを作成します。

ただし、ほとんどの単純なケースでは、コンパイラは内部で StringBuilder を使用し、上記のコードを最適化します。

ただし、ループなどのより複雑なコードの場合、まったく新しい文字列が作成され、パフォーマンスが低下します。 ここで、StringBuilderStringBufferが役立ちます。

JavaStringBuilderとStringBufferはどちらも、文字の可変シーケンスを保持するオブジェクトを作成します。 StringBufferは同期されているため、スレッドセーフですが、StringBuilderはそうではありません。

StringBuffer での追加の同期は通常不要であるため、StringBuilder。を選択することでパフォーマンスを向上させることができます。

Q15。 文字列よりもChar[]配列にパスワードを保存する方が安全なのはなぜですか?

文字列は不変であるため、変更できません。 この動作により、コンテンツの上書き、変更、またはゼロ化が防止され、機密情報の保存に文字列が不適切になります。

文字列の内容を削除するには、ガベージコレクターに依存する必要があります。 さらに、Javaバージョン6以下では、文字列はPermGenに格納されていました。つまり、 String が作成されると、ガベージコレクションは行われませんでした。

char [] 配列を使用することで、その情報を完全に制御できます。 ガベージコレクタに依存することなく、変更または完全にワイプできます。

Stringchar[] を使用しても、情報は完全には保護されません。 これは、悪意のあるユーザーが機密情報にアクセスする機会を減らすための追加の手段にすぎません。

Q16。 Stringのintern()メソッドは何をしますか?

メソッドintern()は、ヒープ内の String オブジェクトの正確なコピーを作成し、JVMが維持するString定数プールに格納します。

Javaは、文字列リテラルを使用して作成されたすべての文字列を自動的にインターンしますが、new演算子( String str = new String( “abc”)など)を使用して String を作成すると、Javaは次のように追加します。他のオブジェクトと同じように、それをヒープに追加します。

intern()メソッドを呼び出して、JVMに文字列プールにまだ存在しない場合は追加するように指示し、そのインターンされた文字列の参照を返すことができます。

String s1 = "Baeldung";
String s2 = new String("Baeldung");
String s3 = new String("Baeldung").intern();

assertThat(s1 == s2).isFalse();
assertThat(s1 == s3).isTrue();

Q17。 Javaで文字列を整数に、整数を文字列に変換するにはどうすればよいですか?

文字列を整数変換する最も簡単な方法は、 Integer# parseIntを使用することです。

int num = Integer.parseInt("22");

逆の場合は、 Integer# toStringを使用できます。

String s = Integer.toString(num);

Q18。 String.format()とは何ですか?どのように使用できますか?

String#format は、指定されたフォーマット文字列と引数を使用してフォーマットされた文字列を返します。

String title = "Baeldung"; 
String formatted = String.format("Title is %s", title);
assertEquals("Title is Baeldung", formatted);

また、オペレーティングシステムのデフォルトを受け入れるだけで問題がない場合を除き、ユーザーのロケールを指定することも忘れないでください。

Locale usersLocale = Locale.ITALY;
assertEquals("1.024",
  String.format(usersLocale, "There are %,d shirts to choose from. Good luck.", 1024))

Q19。 文字列を大文字と小文字に変換するにはどうすればよいですか?

String は、ケーシングを大文字に変更するための String#toUpperCaseを暗黙的に提供します。

ただし、Javadocは、正確性を確保するために、ユーザーの L ocaleを指定する必要があることを通知しています。

String s = "Welcome to Baeldung!";
assertEquals("WELCOME TO BAELDUNG!", s.toUpperCase(Locale.US));

同様に、小文字に変換するには、 String#toLowerCaseがあります。

String s = "Welcome to Baeldung!";
assertEquals("welcome to baeldung!", s.toLowerCase(Locale.UK));

Q20。 文字列から文字配列を取得するにはどうすればよいですか?

StringtoCharArrayを提供します。これは、JDK9より前の内部 char 配列のコピーを返します( Stringを新しいに変換します)。 ] JDK9+のchar配列):

char[] hello = "hello".toCharArray();
assertArrayEquals(new String[] { 'h', 'e', 'l', 'l', 'o' }, hello);

Q21。 Java文字列をバイト配列に変換するにはどうすればよいですか?

デフォルトでは、メソッド String#getBytes()は、プラットフォームのデフォルトの文字セットを使用して文字列をバイト配列にエンコードします。

APIでは文字セットを指定する必要はありませんが、セキュリティと移植性を確保するために必要があります

byte[] byteArray2 = "efgh".getBytes(StandardCharsets.US_ASCII);
byte[] byteArray3 = "ijkl".getBytes("UTF-8");

4. 文字列ベースのアルゴリズム

このセクションでは、Stringに関連するプログラミングの質問について説明します。

Q22。 2つの文字列がJavaのアナグラムであるかどうかを確認するにはどうすればよいですか?

アナグラムは、「car」や「arc」など、特定の単語の文字を並べ替えて形成された単語です。

まず、両方のStringsが同じ長さであるかどうかを確認します。

次に、それらをchar []配列に変換し、並べ替えてから、等しいかどうかをチェックします

Q23。 文字列内の特定の文字の出現回数をどのようにカウントできますか?

Java 8は、次のような集約タスクを実際に簡素化します。

long count = "hello".chars().filter(ch -> (char)ch == 'l').count();
assertEquals(2, count);

また、ループ、再帰、正規表現、外部ライブラリなど、lのカウントするための優れた方法が他にもいくつかあります。

Q24。 Javaで文字列を反転するにはどうすればよいですか?

これを行うには多くの方法がありますが、最も簡単なアプローチは、 StringBuilder (または StringBuffer )のreverseメソッドを使用することです。

String reversed = new StringBuilder("baeldung").reverse().toString();
assertEquals("gnudleab", reversed);

Q25。 文字列が回文であるかどうかを確認するにはどうすればよいですか?

回文は、「マダム」、「レーダー」、「レベル」など、前方と同じように後方に読み取る文字のシーケンスです。

文字列がパリンドロームであるかどうかを確認するには、指定された文字列を1つのループで一度に1文字ずつ前後に繰り返します。 ループは最初の不一致で終了します。

5. 結論

この記事では、最も一般的なStringインタビューの質問をいくつか取り上げました。

ここで使用されているすべてのコードサンプルは、GitHubで入手できます。