Print Friendly, PDF & Email

この記事では、弊社の新人エンジニア研修の参考にJava8を解説します。

前回は配列の作成と使用について解説しました。

今回は文字と文字列の扱いについて解説します。

1.文字と文字列の違い

まれに混同される方がいますが、Javaにおいて文字と文字列は別物です。

以下のサンプルプログラムを見てください。

package chap07;

public class Example01 {

    public static void main(String[] args) {
        char c1 = 'あ';
        String str1 = "あ";

        System.out.println(c1);
        System.out.println(str1);
    }
}

<結果>

<結果を表示>


どちらも結果は同じですが、c1にはひらがな「あ」の文字コードが、str1には文字列「あ」への参照が入っています。

文字はプリミティブ型、文字列は参照型なのでした。

試しに以下のようなプログラムを実行すると

package chap07;

public class Example02 {

    public static void main(String[] args) {
        char c1 = 'あ';
        int a = c1;
        System.out.println(a);
    }
}

<結果を表示>

12354

この結果「12354」は、「あ」の文字コードUTF-8を10進数で表現したものです。

文字列も内部的には文字の配列として扱われているということはエンジニアの嗜みとして知っておきましょう。

package chap07;

public class Example03 {

    public static void main(String[] args) {
        char[] charstr = {'H', 'e', 'l', 'l', 'o'};
        String str = new String(charstr);
        System.out.println(str);
    }
}

IDEを使ってJavaのソースコードを見る事ができます。

具体的な方法は講師から説明があります。

ここで講師と一緒にJavaのソースコードツアーに行ってみましょう。

Stringクラスのソースコードを見ると113行目あたりに以下の記述があります。

    /** The value is used for character storage. */
    private final char value[];

このようにchar型の配列です。

※ちなみに、フィールドがprivate final宣言されているのは、後で見るように文字列が一度作成したら中身を変えられない、ということを意味しています。

2.Stringクラス

String(標準API) は java.langパッケージに含まれるクラスです。

そのためimport文なしでいきなりソースコード中にStringと記述できるのでした。

Stringは次のようにnew演算子とString()というコンストラクタを使ってインスタンス化することもできます。

※new演算子とコンストラクタについては9.インスタンスの活用のところで詳しく学びます。

package chap07;

public class Example04 {

    public static void main(String[] args) {

        String str = new String("Hello");
        System.out.println(str);
    }
}

しかし、文字列はとても頻繁に使いますので、以下のように簡単にインスタンス化する方法も用意されています。

package chap07;

public class Example05 {

    public static void main(String[] args) {

        String str2 = "Hello";
        System.out.println(str2);
    }
}

※しかもこの方法は同じインスタンスを使い回すという点でパフォーマンスも優れています。

つまり、Stringは、new演算子を使わなくてもインスタンスを作れる特殊なクラスです。

ただし、実はこの二つのインスタンスの作り方では微妙な違いがあります。

3.equalsメソッド

以下のサンプルプログラムを見てください。

package chap07;

public class Example06 {

    public static void main(String[] args) {
        String str1 = new String("Hello");
        String str2 = new String("Hello");
        System.out.println(str1 == str2);

        String str3 = "Hello";
        String str4 = "Hello";
        System.out.println(str3 == str4);
    }
}

<結果を表示>

false
true

ここで、str1~4は参照です。

参照が指し示しているのは"Hello"が格納されている"メモリのありか"です。

str1とstr2は異なる2つのインスタンスが作られています。

ですから、それぞれが格納されているメモリのありかも違っていて、その結果上記のfalseが表示されたのでした。

==は参照の比較
==は参照の比較

対して、str3とstr4では同じ1つのインスタンスを参照しています。

実は、str4を=演算子でインスタンス化したとき、メモリの中を検索して、同じ文字列"Hello"があれば、それを再利用しているのです。

その結果、trueが表示されたのでした。

しろ、Javaの前身はOakという家電組み込み用のプログラミング言語だったのでメモリの節約を考慮したのです。

同じ文字列であれば再利用する
同じ文字列であれば再利用する

そうすると気をつけなければならないことがあります。

例えば、ログインのシステムを考えてみましょう。

ユーザーが画面から入力したIDとデータベースに格納されているIDを照合するといった処理を考えます。

その際、2つのユーザーIDは別々のヒープ領域に格納されています。

どのようにして同じであるという判断をしたら良いでしょうか?

==で比較するのではなく、Stringクラスのequalsメソッドを使います。

以下のサンプルコードを見てください。

package chap07;

public class Example07 {

    public static void main(String[] args) {
        String str1 = new String("Hello");
        String str2 = new String("Hello");
        System.out.println(str1 == str2);
        System.out.println(str1.equals(str2));
    }
}

<結果を表示>

false
true

2つの文字列が同じであるかどうかを調べるにはequalsメソッドを使う
2つの文字列が同じであるかどうかを調べるにはequalsメソッドを使う

この後、研修が進むと、Webシステムでログインのシステムを作ります。

その時のために2つの文字列が同一の文字列であるか確かめるためにはequalsメソッドを使わなければならないということを理解してください。

4.イミュータブル

もう少し、Stringクラスの話を続けたいと思います。

実は、Stringクラスのインスタンスは一度作ったら中身を変えられないのです。

以下のサンプルプログラムを見てください。

package chap07;

public class Example08 {

    public static void main(String[] args) {
        String str1 = "Hello";
        str1 = str1 + " World";
        System.out.println(str1);
    }
}

<結果を表示>

Hello World

このとき、表示結果には影響ありませんが、7行目でstr1という変数が一旦捨てられて、同じ名前のstr1という変数が作り直されているのです。

Stringクラスのインスタンスは一度作ったら中身を変えられないのです。

このような性質を不変性:イミュータブル(immutable)といいます。
※ミュータント(mutant)は変異種という意味の名詞ですが、そこに否定の"im"がついて形容詞になっているのですね。

それを証明する次のようなサンプルコードを見てください。

package chap07;

public class Example09 {

    public static void main(String[] args) {
        String str1 = "Hello"; //①
        String str2 = str1; //②
        str1 = "World"; //③

        System.out.println(str1 == str2);
    }
}

<結果を表示>

false

もし③でstr1に”World”を代入した時点で参照はそのままで参照が指し示す値だけを上書きしたとしたら、trueとなるはずです。

ですが”World”を代入した時点で(名前は同じ)新たな参照が作成されたため、str1とstr2の参照値は異なりfalseとなった訳です。

Stringクラスがイミュータブルであるということ
Stringクラスがイミュータブルであるということ

上記のヒープ領域の"Hello"を"aloha"とか"Hey"に変更はできないのです。

これがString型がイミュ―タブル(不変)であるということです。

Javaの設計者がStringをイミュ―タブルにした理由はいつくかありますが、一つはマルチスレッド対応です。

プログラムを複数の処理の流れに分けることをマルチスレッドといいます。

英語のthreadには「」という意味があります。

ある処理(thread)が文字列を処理している途中で別の処理(thread)がその文字列を書き換えてしまうとまずいのですね。

そのため文字列はイミュ―タブルなのです。

(さらに近年は様々なクラスをイミュータブルで作成することが多くなってきています。)

さて、理由はさておき実務において、このことはどのような問題があるでしょうか?

例えば、大量の文字列の結合を繰り返す場合に、その都度新しいインスタンスを生成するためパフォーマンスが悪化します。

何千何万回と文字列を結合するような場合には、Stringクラスを「+」で結合するのではなく、StringBuilderクラスのappendメソッドを使うことをお勧めいたします。

時間が許せば問題集で確認しましょう。

5.Stringクラスの便利なメソッド

Stringクラスには、(charのようなプリミティブ型とは違い)文字列を扱うための便利なメソッドが用意されています

そのほんの一部を紹介します。

以下のプログラムでは「新人エンジニアのためのJava研修」という文字列を使って文字列の文字数を数えたり、任意の文字列の出現位置を調べたり、任意の文字列が含まれるか調べたり、任意の文字列を置き換えたりといったことをしています。

package chap07;

public class Example10 {

    public static void main(String[] args) {
        String str = "新人エンジニアのためのJava研修";

        System.out.println(str.length() + "\n");

        System.out.println(str.indexOf("Java"));
        System.out.println(str.indexOf("Python") + "\n");

        System.out.println(str.contains("研修"));
        System.out.println(str.contains("Python") + "\n");

        String str2 = str.replace("エンジニア", "SE");
        System.out.println(str2);

    }
}

<結果を表示>

17

 

11
-1

true
false

新人SEのためのJava研修

lengthは、「長さ」という意味なのでlengthメソッドは文字数を返します。

indexOfは、文字列を配列としてみたときの添え字(index)の値のうち、実引数の文字列が最初に現れた添え字を返します。

配列の復習にもなりますが、添字の数え方は以下の通り0始まりです。

文字列も添字は0始まり
文字列も添字は0始まり

containsは、「含む」という意味、replaceは、「置き換え」という意味ですので文字通りですね。

他にも色々便利なメソッドがありますからStringの標準APIを探検してみてください。

また、後半のシステム開発演習では大きな数値を扱うとき、3桁カンマで表示するようにお願いすることが多いのですが、それは、このようにして実現できます。

package chap07;

public class Example11 {

    public static void main(String[] args) {
        int price = 123456789;
        System.out.println(String.format("%,d", price));
    }
}

<結果を表示>

123,456,789

業務システムでは金額を扱うことが多いですからStringクラスのformatメソッドは重要です。

3桁カンマ以外の書式についてはクラスFormatter(標準API)を調べてください。

例えば、円周率の少数点第3位四捨五入2位表示であれば、

System.out.printf("%.2f", Math.PI);

のように書くことができます。

ここで、注目していただきたいのは、メソッドの呼び出し方です。

String.format("%,d", price)

Stringは先頭文字が大文字になっています。

これは、クラスですね。

クラスに属するメソッドということでクラスメソッドまたは、スタティック(static)メソッドと呼ばれます。

staticは静的という意味で、反意語はDynamic(動的)です。

staticはクラスにあらかじめ用意してあるメソッドという意味です。

動的に作り出したインスタンスが持つメソッドではないという意味です。

クラスメソッドは、クラス名.メソッド名という形で呼び出すことができます。

一方、

str.length()

のようにインスタンスを作ってから、その個々のインスタンスのメソッドを呼び出すのをインスタンスメソッドといいます。

インスタンスメソッドは、変数名.メソッド名という形で呼び出します。

クラスは先頭が大文字ですから見分け方は簡単ですね。

使い分けはベテランでも迷うところですから今は気にしなくて大丈夫です。

インスタンスメソッドとクラスメソッドの違い
インスタンスメソッドとクラスメソッドの違い

ここでは、なぜ、クラスメソッドとインスタンスメソッドがあるのかを考察してみましょう。

str.length()は、str(その中身は"新人エンジニアのためのJava研修")自身が持つ文字列の長さということですから、インスタンスメソッドがふさわしいのです。

インスタンスが変われば文字列の長さも変わりますね。

オブジェクト指向には責務という考え方があります。

文字列の文字数は誰(どのインスタンス)が知っているべきかというのが責務の例です。

この場合は、個々の文字列が自分の文字数を知っているべきです。

オブジェクト指向を一言で片付けると「自分のことは自分でしよう」という考え方といえます。

一方、String.format()はintを整形してString(123,456,789)を得るメソッドです。

この処理にはStringのインスタンスが作られる必然性がありません。

最終的な結果として表示される(123,456,789)だけがStringのインスタンスなのです。

そのため、String.format()はインスタンスではなくクラスに属すると考えて、クラスメソッドであるべきなのです。

intに対応するStringを生成するのは、個々のインスタンスには関係のない決まりきった内容の処理だからです。

ただし、クラスメソッドは次回のテーマになっていますのでそこでも詳しく学ぶとしましょう。

6.エスケープシーケンス

例えばHello と World の間に改行を入れたい場合はどうしたらいいでしょうか?

以下のように、改行を入れたいところには、“¥n”を入れる必要があります。

package chap07;

public class Example13 {

    public static void main(String[] args) {
        System.out.println("Hello\nWorld");
    }
}

<結果を表示>

Hello
World

特別な記号や出力方法を制御するためには「¥」記号を使います

この ¥nなどの文字を、「文字本来の意味から逃がした文字列」という意味で、エスケープシーケンス(escape sequence)と呼びます。

この¥記号は日本語版のフォントを使用しているWindows環境の場合です。

それ以外の環境では\記号を使います。

※この本書もソースコードの表記は\記号になっているかと思います。

もともと英語圏では\記号を使うのですが、日本語圏では金額表示に¥記号を使う必要があったため、まだ、使える文字数に制限があった時代、Windowsの前身のOSであるMS-DOS時代から、日本では\記号を¥記号に置き換えて表示しているという経緯があります。

\記号を¥記号に置き換えるのが一番影響が少ないと考えられたからのようです。

実際には影響大ありですが。

当社の新人研修で使用する可能性のあるエスケープシーケンスを紹介します。

 エスケープシーケンス意味
\n改行
\t水平タブ
\\\
4(※)\''
\""

※フォントの問題で同じに見えないかもしれませんが同じ文字です。

覚える必要はありませんが、上手く表示できない文字があった時に、ここに戻れるようにしておきましょう。

以下サンプルコードです。

package chap07;

public class Example14 {

    public static void main(String[] args) {
        String str1 = "\"special\"price:";
        String str2 = "\\3000";
        System.out.print(str1);
        System.out.print(str2);
    }
}

<結果を表示>

"special"price:\3000

7.null

ヒープ領域に確保された文字列のための領域を開放するにはどうしたらいいでしょうか?

参照型にnullを代入することで、その参照はインスタンスを参照しなくなります。

nullとは「何も示さない」という意味の特別な意味を持った予約語です。

ちなみに、Javaの場合nullは、数値の0、空文字””やスペース” ”とは全く別物として扱われます。

Javaにはガーベージコレクションという機構があり、どこからも参照されなくなった文字列は消去されます。

以下のサンプルコードを見てください。

package chap07;

public class Example15 {

    public static void main(String[] args) {
        String str1 = new String("ABC");
        String str2 = str1;
        str1 = null;
        System.out.println(str2);
        str2 = null;
        System.out.println(str2);
    }
}

<結果を表示>

ABC
null

"ABC"という文字列は1つですが、この文字列を指し示す参照は当初"str1"と"str2"の2つがあります。

しかし、nullを代入することによってこれら2つの参照はどこも指さなくなりました。

そして、どこからも参照されなくなった時に、この文字列はガーベージコレクションの対象になります。

nullとは
nullとは

JVMがガーベージコレクションを走らせるのですが、いつ、ガーベージコレクトをするのかをプログラマーの側で指示することはできません。

あくまでプログラマーができるのはガーベージコレクトを依頼することだけです。

今回は文字と文字列の扱いについて見てきました。

次回は、今回も少し出てきたクラスメソッドについて学びましょう。

<まとめ:隣の人に正しく説明できたらチェックを付けましょう>

□2つの文字列が同一の文字列であるか確かめるためにはequalsメソッドを使わなければならない

□Stringクラスには、charのようなプリミティブ型とは違い文字列を扱うための便利なメソッドが用意されている

□クラスメソッドは、クラス名.メソッド名という形で呼び出すことができ、インスタンスメソッドは、変数名.メソッド名という形で呼び出すことができる

□特別な記号や出力方法を制御するためには「¥」記号を使ってエスケープシーケンスを作る

□参照型にnullを代入することで、その参照はインスタンスを参照しなくなり、どこからも参照されなくなったインスタンスはいずれメモリから消去される

まとめができたら、アウトプットとして演習問題にチャレンジしましょう。

【今回の復習Youtube】

002-Javaの基本-文字列型の変数と出力

003-Javaの基本-文字列の連結

029-オブジェクト指向の基本-文字列の比較

030-オブジェクト指向の基本-null

031-オブジェクト指向の基本-参照の考え方

053-APIの利用-文字数を取得する

054-APIの利用-部分的な文字列を取り出す

055-APIの利用-書式を変更する

056-APIの利用-文字列から数値に変換する

057-APIの利用-正規表現

JavaSE8の解説に戻る