Java 参照型(Stringクラス)

Javaでは、オブジェクト参照は、メモリに格納されているオブジェクトへの参照です。オブジェクト自体ではなく、オブジェクトを参照する値です。

Java 参照型とプリミティブ型の違い

Javaではプリミティブ型は言語に組み込まれている基本的なデータ型(int, double, charなど)です。これらの型はオブジェクトへの参照ではなく、単一の値を表します。一方、オブジェクト参照はメモリに格納されているオブジェクトへの参照です。

ほとんどのプログラミング言語では、オブジェクトは参照型です。つまり、オブジェクトを作成して変数に割り当てると、変数にはオブジェクト自体ではなく、メモリ内のオブジェクトの場所への参照が含まれます。それはプリミティブ型とは異なり、オブジェクトは大量のメモリを必要とする可能性があるからです。これは、変数に割り当てられたメモリに直接格納される整数やブール値などのプリミティブ型とは異なります。

参照型を使用すると、変数ごとにオブジェクトの個別のコピーを作成するのではなく、同じオブジェクトを参照する複数の変数を持つことができるため、メモリ効率が向上します。ただし、どの変数が参照で、どの変数が実際のオブジェクトであるかを追跡する必要があるため、オブジェクトと変数の間の関係を理解するのが難しくなる可能性もあります。

public class Main {
    public static class Person {
        String name;
        int age;

        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }

    public static void main(String[] args) {
        Person person1 = new Person("Alice", 30);
        Person person2 = person1;

        // person1とperson2の両方がメモリ内の同じPersonオブジェクトを参照します。
        // このPersonオブジェクトの名前はAliceで年齢は30です。
        person2.name = "Bob";

        // person1とperson2の両方が参照するオブジェクトの名前がBobに変更されました。
        System.out.println(person1.name); // Bob
        System.out.println(person2.name); // Bob
    }
}

Java 文字列型(Stringクラス)

Javaでは、文字列は文字の列を表す不変のオブジェクトです。文字列はオブジェクトであり、整数やブール値のようなプリミティブなデータ型ではなく、参照型と呼ばれます。

Javaでは、文字列を表現するためにStringクラスが使用されます。Stringクラスはjava.langパッケージの一部で、すべてのJavaプログラムに自動的にインポートされるため、自分でインポートする必要はありません。

Java 文字列(Stringクラス)の作成方法

Javaでは、Stringオブジェクトを作成する方法として、文字列リテラルを使用する方法と、newキーワードを使用する方法の2つがあります。では、文字列リテラルを使ってStringオブジェクトを作成する方法から見ていきましょう。

String s1 = "Hello";

これは、JavaでStringオブジェクトを作成する最も一般的な方法です。

文字列リテラルを宣言すると、JVMはヒープ領域の中にあるコンスタントプールと呼ばれる特別なメモリ領域にオブジェクトを作成します。コンスタントプールとは定数やシンボルなどを保存する領域のことでヒープ領域内にあります。

文字列リテラルを作成する度に、JVMはこのコンスタントプールをチェックします。もし、文字列がすでにプールに存在していれば、そのインスタンスへの参照を使いまわすことで不必要なオブジェクトの生成を避けます。

JVMの動作は以下のようになります。

  1. パーマネント領域にStringクラスをロードする
  2. スタック領域に変数s1を用意する
  3. コンスタントプールに同じ文字列を持つインスタンスがないかチェックする。
  4. 存在しない場合は"Hello"のインスタンスを作成しそのアドレスをスタック領域にある変数に保存する

それでは、newキーワードを使用してStringオブジェクトを作成する方法を見てみましょう。

String s1 = new String("Hello");

この場合のJVMの動作は以下になります。

  1. パーマネント領域にStringクラスをロード(すでにロードされていたら省略)
  2. スタック領域に変数を用意する
  3. コンスタントプールに”Hello”と同じ文字列がないかチェックし、無ければインスタンスを作成する。
  4. new Stringでヒープ領域内に”Hello”という値を持つインスタンスを作成する。
  5. インスタンスの参照をスタック領域の変数に代入する。

同じ文字列がすでにコンスタントプールに存在する場合でも、実行されるたびに新しいStringオブジェクトをヒープ領域に作成してしまうため、文字列リテラルを使ってStringオブジェクトを作成するよりも、少し遅くメモリ効率も悪くなります。

一般に、文字列の相互作用を利用し、パフォーマンスを向上させるためには、可能な限り文字列リテラルを使用することをお勧めします。

Java 文字列のメソッド

Stringクラスは、文字列を扱うのに便利なメソッドをたくさん持っています。たとえば、length()メソッドで文字列の長さを決定したり、charAt()メソッドで文字列内の特定のインデックスにある文字を取得したり、substring()メソッドで文字列の特定の部分を取り出したりすることができます。

String s = "Hello, World!";
int length = s.length(); // 13
char c = s.charAt(0); // H
String sub = s.substring(7, 12); // World

Java toLowerCase()メソッドとtoUpperCase()メソッド

toLowerCase()メソッドとtoUpperCase()メソッドを使用すると、それぞれ文字列をすべて小文字、すべて大文字に変換することができます。

public class Main {
    public static void main(String[] args) {
        String s = "Hello, World!";
        String s2 = s.toLowerCase();
        System.out.println(s2); // "hello, world!"

        String s3 = s.toUpperCase();
        System.out.println(s3); // "HELLO
    }
}

Java codePointAt()メソッド

Javaでは、codePointAt()メソッドを使用して、String内の文字のUnicodeコードポイント値を取得することができます。

codePointAt()メソッドはStringクラスおよび、Characterクラスのstaticメソッドとして定義されており、指定されたインデックスにある文字のUnicodeコードポイントを返します。以下は、codePintAt()メソッドの使用例です。

class Main{
    public static void main(String[] args){
        String s = "Hello, World!";
        System.out.println(s.charAt(7));
        System.out.println(s.codePointAt(7)); // Stringクラス
        System.out.println(Character.codePointAt(s, 7)); // Characterクラス
    }
}

Note: Unicode規格では、いくつかの文字は2つ以上のchar値で表現されます。これらの文字は補助文字と呼ばれます。

Javaでは、charデータ型は16ビットの符号なし整数で、0から65535までの任意のUnicodeコードポイント値を表すことができます。しかし、いくつかのUnicodeコードポイント値はこの範囲外であり、1つのchar値で表すことができません。これらのコードポイント値を補助コードポイントと呼びます。

Javaで補足コードポイントを表現するには、charデータ型とサロゲートペア機構を組み合わせて使用することができます。サロゲートペアとは、1つの補足コードポイントを表す2つのchar値の組のことです。最初のchar値を高位サロゲートと呼び、2番目のchar値を低位サロゲートと呼びます。

たとえば、UnicodeコードU+1F600は、1個の文字値で表現できる範囲外にあります。このコードポイントをJavaで表現するには、次のようなサロゲートペアを使用します。

char highSurrogate = '\uD83D';
char lowSurrogate = '\uDE00';
char[] ch = new char[]{highSurrogate, lowSurrogate};
System.out.println(ch);
System.out.println(new String(Character.toChars(0x1F600)));

これら2つのchar値は合わせて補助コードポイントU+1F600を表します。

Java 文字列の不変性

Stringクラスは不変であり、一度Stringオブジェクトを作成すると、その値を変更することはできません。もし文字列を変更したい場合は、変更後の値を持つ新しいStringオブジェクトを作成しなければなりません。

JavaでStringオブジェクトの値を変更する場合、変更後の値を持つ新しいStringオブジェクトを作成する必要があります。

String s1 = "Hello";
String s2 = s1.replace('H', 'h');

この例では、replace()メソッドを使用して、元のStringオブジェクトs1のコピーでありながら、文字Hを文字hに置き換えた新しいStringオブジェクトを作成しています。元のStringオブジェクトs1は変更されず、新しいStringオブジェクトs2が変更された値を持ちます。

Java 文字列の比較

先に述べたように、Javaでは文字列リテラルを宣言するとコンスタントプールに文字列リテラルのインスタンスが作成され、その参照がスタック領域に保存された変数へ代入されます。二つの文字列を作成した場合の文字列の比較するコードを見てみましょう。

class Main{
    public static void main(String[] args){
        // コンスタントプールに生成される
        String s1 = "Hello";
        String s2 = "Hello";
        // ヒープ領域に生成される
        String s3 = new String("Hello");
        String s4 = new String("Hello");

        System.out.println(s1 == s2);
        System.out.println(s1.equals(s2));
        System.out.println(s1 == s3);
        System.out.println(s1.equals(s3));
        System.out.println(s3 == s4);
        System.out.println(s3.equals(s4));
    }
}

文字列リテラルで宣言した二つのStringオブジェクトは、コンスタントプール内の同じインスタンスを参照しているので、s1 == s2はtrueです。一方 new Stringした場合は、新しいインスタンスをヒープ領域に作成しその参照を比較するのでs3 == s4はfalseになります。この場合はequals()メソッドを使用するとtrueになります。

スタックやヒープについては、CS基礎/中級/スコープで詳しく学習できます。

equals()メソッドは、JavaのすべてのクラスのスーパークラスであるObjectクラスで定義されているので、Stringオブジェクトを含むすべてのオブジェクトで利用可能です。異なる参照の等価判定をするにはequals()メソッドを使うほうが良いでしょう。

文字列の比較で特殊な例を見てみましょう。

class Main{
    public static void main(String[] args){
        String s1 = "Hello World!";

        String s2 = "Hello ";
        final String s3 = "World!";

        // コンスタントプールの文字列連結
        String s4 = "Hello " + "World!";
        System.out.println(s1 == s4); // true
        System.out.println(s1.equals(s4)); // true

        // 変数とコンスタントプールの連結
        String s5 = s2 + "World!";
        System.out.println(s1 == s5); // false
        System.out.println(s1.equals(s5)); // true

        // コンスタントプールとfinalの連結
        String s6 = "Hello " + s3;
        System.out.println(s1 == s6); // true
        System.out.println(s1.equals(s6)); // true
    }
}

Java 文字列の連結

先に述べたように、JavaではStringクラスは不変であり、一度Stringオブジェクトを作成すると、その値を変更することはできません。つまり、2つの文字列を+演算子で連結すると、両方の文字列の内容がコピーされて、連結結果を含む新しい3番目の文字列オブジェクトが作成されます。

以下は、この動作の例です。

String s1 = "Hello";
String s2 = "World";
String s3 = s1 + ", " + s2 + "!";

この例では、+演算子を使って、文字列s1, s2, および他のいくつかの文字列を連結して、新しい文字列s3を作成しています。Stringクラスは不変なので、これはs1とs2の内容がコピーされて新しい文字列s3が作成されることを意味します。

Note: Javaでは、文字列と数値の足し算ができます。演算子を使うと、2つの文字列を連結(足し算)したり、文字列と数値を足したりすることができます。

 int x = 5;
 String y = "10";

 String result = x + y; // "510"

これは、+演算子がオーバーロードされ、数値に対しては加算を、文字列に対しては連結を行うようになったためです。オペランドの一方が文字列で、もう一方が数値の場合、数値は自動的に文字列に変換され、両者は連結されます。

多くの文字列を連結する場合、連結のたびに新しい文字列オブジェクトを作成しなければならないので、少し効率が悪いかもしれません。そのような場合は、StringBuilderオブジェクトを使った方が効率的です。StringBuilderオブジェクトはミュータブルで、多数の文字列を連結する場合にはより効率的です。

Java StringBuilderクラス

StringBuilderクラスは、Javaのミュータブルな文字列です。これはStringクラスと似ていますが、mutableであるため、作成後に内容を変更することができます。

StringBuilderクラスの主な利点の1つは、多数の文字列を連結する際に効率的であることです。演算子を使って文字列を連結する場合、連結のたびに新しい文字列オブジェクトが作成されます。これは、多数の文字列を連結する場合、連結のたびに新しい文字列オブジェクトを作成する必要があるため、非効率的な場合があります。

一方、StringBuilderクラスは、文字を格納するための内部バッファを使用し、必要に応じて大きくすることができるため、多数の文字列を連結する場合にはより効率的です。つまり、StringBuilderクラスを使えば、大量の中間文字列オブジェクトを作成することなく、大量の文字列を連結することができるのです。

以下は、StringBuilderクラスを使って文字列を連結する例です。

public class Main {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        sb.append("Hello");
        sb.append(", ");
        sb.append("World");
        sb.append("!");
        String s = sb.toString();
        System.out.println(s);
    }
}

この例では、StringBuilderオブジェクトsbを作成して、文字列 "Hello", ",", "World", "!"を連結するのに使っています。append()メソッドは、各文字列を StringBuilderオブジェクトに追加するために使用されます。最後に、toString()メソッドが呼び出され、StringBuilderオブジェクトがStringオブジェクトに変換されています。

また、StringBuilderクラスを使用して、insert()やdelete()メソッドで既存の文字列を変更することができます。

StringBuilder sb = new StringBuilder("Hello, World!");
sb.insert(5, "there ");
sb.delete(11, 13);
String s = sb.toString();

この例では、insert()メソッドを用いて、インデックス5のStringBuilderオブジェクトに文字列thereを挿入し、delete()メソッドを用いて、インデックス11と12の文字を削除しています。

この記事を書いた人

著者の画像

Jeffry Alvarado

Ex-Facebook Engineer 大学ではコンピュータサイエンスを専攻し、在学中に複数のインターンシップを経験。コンピュータサイエンスが学習できるプラットフォームRecursionを創業し、CTOとしてカリキュラム作成、ソフトウェア開発を担当。


ツイート