<<前ページ目次次ページ>>
3.文字列 その1
〜 java.lang.String 〜
 文字列処理はStringというクラスも用意されていて、あまり難しく考えなくてもよさそうです。でもあなどるなかれひとつ間違えると、大きなミスに繋がったり、膨大な無駄を生み出したりします。ここで基本を押さえておきましょう。
オブジェクトの亡霊
 基本データ型のchar型の変数には1文字しか入りません。
   char c = ‘a’;
 名前や住所など複数文字のデータ、文字列データは記憶できません。ではchar型の配列はどうでしょう。
   char[ ] name = new char[20];
おお、20文字入れられるではないですか!3文字目を取り出すにはname[2](添字は0から始まります。)でOK。と、ここまではいいのですが、例えば実際何文字入っているか、2文字目から6文字目を取り出したい、さらに同姓同名か他の名前と比較したい、という場合など、一々自分でプログラミングしなければなりません。
 やはり標準ライブラリのjava.lang.Stringというクラスがいいでしょう。読み物Java(基礎編)第11章でいくつか紹介していますが、文字列に便利なメソッドがそれはたくさん用意されています。
 クラスStringのインスタンスは、Unicode文字の並びが入ります。そしてラッパークラス同様、インスタンスが一旦生成されるとそこに格納された文字列は変更できません。

 次の一連の処理で何が出力されるか考えて見ましょう。
                        String s = "abc";
                        String t = s.toUpperCase();
                        System.out.println(s + " " + t);

                        s = s.toUpperCase();
                        System.out.println(s); 
 まず1行目。

 これでクラスStringの変数sが宣言され、「abc」の入った、クラスStringのインスタンスが生成されて、その参照値がsに代入されます。"abc"は文字列リテラルなので、正確にはコンパイル時にインスタンスは生成されています。
 文字列をデータとして持つインスタンスであることを示すために、図の左側では二重の箱で示してありますが、めんどうなので右のように枠の線が少し太い文字列の入った箱で表すことにします。ただしこれはchar型の配列ではないことに注意しましょう。読み物Java基礎編の第11章の表ではこの太枠も省略してありますので、ご注意ください。

 では2行目。

 次もクラスStringの変数tの宣言です。ここで代入されるのは、変数sの「abc」(めんどうなので文字列sと呼びましょう。)を大文字に変換したABCです。クラスStringには文字列を扱うメソッドがたくさん用意されています。ここではその中のtoUpperCaseという、大文字に変換するメソッドを使っています。
 toUpperCaseの解説には、このメソッドの宣言の最初の部分が出ています。
    public String toUpperCase()
これで引数なし、戻り値はStringのインスタンスの参照値であることがわかります。
 sに対しこのメソッドが呼ばれると、まず@にあるように、文字列sを大文字に変換したStringのインスタンスが、新たに生成されます。気をつけてほしいのはsの文字列の中身そのもの(図の赤い部分)が大文字になるのではないということです。別のインスタンスが生成されるのです。toUpperCaseの戻り値は新しいインスタンスの参照値です。だからAにあるように変数tは「ABC」のインスタンスを指すようになります。最初の出力は
   abc ABC
となります。

 さあ最後の4行目です。

 まず新たに大文字のインスタンスが生成される@はさっきと同じです。ところがこの参照を、今度は変数sに代入しなおしています。Aのようになり、「abc」の入ったインスタンスへの参照は切れてしまいます。出力は
   ABC
になります。あれぇ、文字列の変更はできないんじゃなかったっけ?そう、Stringのインスタンスの中身は変更できません。しかし、変数sの中身は変更できます。sに入っている参照値が変わっただけで、ピンクの箱の中は変わっていませんよね。

 さてこの図のピンクの箱は一体どうなってしまうのでしょうか。どの変数からも参照されず、今後プログラムで利用されることのない、亡霊のようなインスタンスになってしまいました。前章でやったラッパークラスでもこんなインスタンスが出てきましたね。無駄にメモリの一部を占有しているのです。なにやら物悲しい気持ちになりますが、放っておいてください。Javaには、ガベージコレクションという機能があり、このような将来のないオブジェクト(クラスのインスタンスや配列)を自動的に消し去ってくれるのです。ただしガベージコレクションは、優先順位の低いスレッドで行われるので、メモリ中のゴミが増え過ぎるとメモリ不足が起こるので気をつけましょう。ガベージはgarbageで、くず、生ゴミ、残飯といった意味があります。

 文字列の変更が頻繁に行われる場合、Stringクラスを使っていると効率が悪くなります。なぜなら見てきたように、新たなインスタンスの生成がしょっちゅう行われることになり、これは他の処理に比べ時間のかかる重い処理です。またガベージコレクションの対象となるようなインスタンスができやすく、メモリの無駄遣いになってしまいます。そういう時は文字列の変更が可能なStringBufferクラス(次の章で登場します。)を使いましょう。 


見てるだけー

 クラスStringでは、新しいインスタンスを作るのに時間がかかり、インスタンスの亡霊はできてしまうし、いいことないじゃないかぁ...待ってください。それは文字列自体をいろいろ変更したい場合に起こるので、例えば文字列を比較する場合などは問題ありません。
 文字列の中身の比較は、メソッドequalsで行います。==で比較すると、変数が同じ参照値を持った場合のみtrueになります。後で出てくるように、二つのString変数が同じ「abc」を保持していても、別領域にインスタンスが取られていればfalseになってしまいます。

   String name1, name2;
       :
   name1とname2に名前が入る
   if(name1.equals(name2)) {
      同姓同名の処理
   }
という感じです。

 また文字列の頭や尻尾のみの比較startsWithendsWithもあります。
   String tel = "01234567";
   System.out.println(tel.startsWith("03") + " " + tel.endsWith("567"));
 これで
   false true
が出力されます。

 先頭から1文字ずつ見ていくにはcharAtが便利です。先頭文字の位置が0であることに注意しましょう。
   String str = "abcde";
   for(int i = 0; i < str.length(); i++) {
      System.out.print(str.charAt(i) + "*");
   }
 これで
   a*b*c*d*e*
が出力されます。


コンストラクタ

 クラスStringのコンストラクタはいろいろなものがあります。いくつか取り上げ、インスタンスと変数の関係を図で確認しておきましょう。いちいちなぜ?と思われるかもしれませんが、文字列を効率よく処理するために、この辺の基礎知識を知っておいた方がいいでしょう。

 最初は文字列リテラルを代入するもの。これは特別の形になります。文字列リテラルはコンパイル時にインスタンスが生成され、同じ文字列についてはひとつしか生成されません。下の場合二箇所に"abc"がありますが、同じインスタンスになります。図のように変数s1とs2は同じインスタンスを参照するので、下の出力は「true」になります。
                        String s1 = "abc", s2 = "abc";
                        System.out.println(s1 == s2); 


 次はコンストラクタの実引数に文字列リテラルを指定した場合です。
                        String s1 = "abc", s2 = new String("abc");
                        System.out.println((s1 == s2) + " " + s1.equals(s2));

 変数s1については同様ですが、s2については、@でインスタンスのコピーが行われ、この参照値が入ります。同じ文字列のインスタンスが二つできてしまいました。インスタンスの生成には単純な演算や代入より時間が掛かります。また上と比べてメモリの無駄遣いにもなっています。このような処理が繰り返し行われると、効率が悪くなってしまいます。

 出力は、「s1 == s2」はfalse、「s1.equals(s2)」はtrueなので
   false true
となります。参照型の変数を「==」で比較すると、同じインスタンスを参照している場合のみ「true」になるのでしたね。またクラスStringのメソッドequalsは、文字列の中身を比較するので、インスタンスのある場所は関係しません。「==」とequalsの違いに気をつけましょう。

 ところで余談ですが、不肖私、上の出力処理で「s1 == s2」の周りの括弧を忘れてしまいました。falseがひとつ出力され首を傾げました。演算子の優先順位には気をつけましょう。「==」は低いです。

 次はchar型の配列から作る場合です。
                        char[] c = { 'a', 'b', 'c' };
                        String s1 = "abc", s2 = new String(c);
                        System.out.println((s1 == s2) + " " + s1.equals(s2));

上と同じようになりますが、@では、配列cの内容を元にインスタンスが生成されます。出力も
   false true
になります。


 次のs2はs1を元にインスタンスを生成しています。
                        String s1 = "abc", s2 = new String(s1);
                        System.out.println((s1 == s2) + " " + s1.equals(s2));
これも上と同様ですね。出力も
   false true
になります。


 では次はどうでしょう。s2はコンストラクタを呼んでいるわけではないのですが...ついでです。
                        String s1 = "abc", s2 = s1;
                        System.out.println((s1 == s2) + " " + s1.equals(s2));

 s2にはs1に入っている参照値、つまり赤いインスタンスの参照が入るので、図のようになります。出力は
   true true
になります。


クラスメソッドもあります

 Stringクラスには、valueOfというクラスメソッドがあります。引数には、基本データ型とObject型が指定できます。すべて引数を文字列に変換したものが求められます。

   String s = String.valueOf(123);

でsには文字列123が入ります。またおなじみの

   System.out.println(123);

は123がint型の定数なので、

   String.valueOf(123)

が出力されます。またこれは

   Integer.toString(123)

と同じになります。
<<前ページ目次次ページ>>
Copyright (c) 2004-2005 Nagi Imai All Rights Reserved..
セブンアンドワイ