その3: C言語欠点からのJava 文字列管理

C言語は、文字列型の基本型が存在しません。文字列は、charの1byteを指し示すポインタで管理しました。 ((char*)型のポインタで管理していました。)
C言語の文字列管理参考⇒(確認後はブラウザの戻る操作で戻ってください
byte単位で表現できる場合は扱いやすいのですが、2byte文字を含む場合に扱い難くなります。
例えば日本語の文字は2byte使うShift JIS系の文字列を扱えるコンパイラにおいて、
char *s="あいうabc"と宣言した時、 sが指し示す位置から始まるbyte列は次のようになります。

abc
'0x082' '0x0A0' '0x082' '0x0A2' '0x082' '0x0A4' '0x61' '0x62' '0x63' '0x0'

ここで、2文字目のを出力する際、 putchar(a[1]);としても'0x0A0'の出力で文字化けになるでしょう。
正しくを表示させるには、
putchar(a[2]);putchar(a[3]);の連続2byte出力させる実行が必要です。
しかも、文字列管理方法は処理系依存なので、OSによって異なるbyte列になり、扱い難いのです。

対してJavaでは

文字列型のStringクラスや文字のchar型がが存在します。
  (char型は基本型ですが、文字列のString型は基本型ではありません。参照型です。)
そして文字の内部コードはUnicodeの2byteを使うことに決まっているので、比較的簡単に取り扱えます。 例えば、
String s = "あうabc";と変数sが用意されるとき、先頭を0、1と数えた1番目の の文字を出力するには、次のようにcharAtメソッドで指定の位置にある文字(char型)を 取り出せます。
char c = s.charAt(1); 
System.out.print(c); 

また文字列定数がStringクラスのオブジェクトなので、その機能のcharAtメソッドが使ます。
よって、以下で同じようにが出力できます。
System.out.print( "あいうabc".charAt(1) ); 

そしてJavaでは、+演算子で文字列の連結ができます。
例えば、次のコードの出力結果で、 a+b+c=12 が得られます。

		int v1 = 3;
		int v2 = 5;
		int v3 = 4;
		System.out.print("a+b+c=" + (v1 + v2 + v3));

これは+の演算子の機能で、一方のオペランドがStringであるとき他方をStringに変換して連結した文字列 を演算子の結果にするからです。つまり、(v1 + v2 + v3)の演算結果の12が連結されたわけです。

これを、System.out.print("a+b+c=" + v1 + v2 + v3)としてしまうと、 a+b+c=354の結果になってしまいます。
これは+演算は左結合なので、始めに左の"a+b+c=" + v1が演算されて、"a+b+c=3"の 演算結果の文字列が得られ、それが次の+演算のオペランドになっていくからです。

なお、改行("\n")を伴う表示は、printlnを使います。(print lineの意味です。)つまり以下の2行で、同じ出力が得られます。
System.out.print("a+b+c=" + (v1 + v2 + v3) + "\n");
System.out.println("a+b+c=" + (v1 + v2 + v3));

 

文字列操作メソッド

よく使われる文字列操作は、連結、文字数取得、文字列の検索、部分文字列の抽出、等価チェック、大小の比較、数値変換などが挙げられます。 C言語の文字列管理参考⇒(確認後はブラウザの戻る操作で戻ってください

JavaのString型は、参照型 if( str == "abc")〜 str の参照内容が"abc"と等しいか判定ができません。
参照型はC言語のポインタと同じように、 データの中身をチェックするのではなく、『同じ記憶域を指し示すか?』 の判定だからです。
Javaでは、equalsメソッドを使って if( str.equals("abc") == true)〜と書きます
Javaでは次のようなメソッドがよく使われます。なお、strは、文字列オブジェクトで表記する意味です。

操作 メソッドや操作

連結

+演算子

String s = str + "\r\n";

文字数取得

str.length()

String s = "あいうabc";
System.out.print( s.strlen() );
//6を出力

指定位置の文字取得

str.charAt(添え字)

String s = "あいうabc";
System.out.print( s.charAt(2) );
//を出力

文字列の検索

str.indexOf(検索文字列)

String s = "あabc45bce";
System.out.print( s.indexOf("bc") );
//2を出力

str.indexOf(検索文字列,
 検索開始位置)

String s = "あabc45bce";
System.out.print( s.indexOf("bc",3) );
//3の位置以降より探して6を出力

str.lastIndexOf(検索文字列,
 検索開始位置)

String s = "あabc45bce";
System.out.print( s.lastIndexOf("bc",5) );
//5の位置の後ろから探して2を出力

部分文字列
の抽出

str.substring(先頭位置)

String s = "あabc45bce";
System.out.print( s.substring(3) );
//c45bceを出力

str.substring(先頭位置,
 終了位置)

String s = "あabc45bce";
System.out.print( s.substring(3, 6) );
//c45を出力(6の位置のbがないことに注意)

str.trim()

String s = "   abc   ";
System.out.print( s.trim() );
//前後の空白文字が除かれてabcを出力

等価チェック

str.equals(文字列)

String s = "あab";
System.out.print( s.equals("あab") );
//trueを出力

str.startsWith(文字列)

String s = "あabc45bce";
System.out.print( s.startsWith("あab") );
//先頭部分が引数と合ってtrueを出力

str.endsWith(文字列)

String s = "あabc45bce";
System.out.print( s.endsWith("5bce") );
//最後が引数と合ってtrueを出力

大小の比較

str.compareTo(文字列)

String s = "あab";
System.out.print( s.compareTo("あcb") );
//辞書的に引数の方が後で-2を出力
//等しいなら0で、sが大きいなら正の整数です

str.compareToIgnoreCase(文字列)

String s = "あa5";
System.out.println( s.compareToIgnoreCase( "あA5" ));
//大小を無視すると等しいので0を出力
System.out.println( s.compareToIgnoreCase( "あA1" ));
//引数の小さいで4を出力

数値へ変換

Integer.parseInt(10進文字列)

int data = Integer.parseInt("5432");
System.out.print( data );
//5432を出力

Integer.parseInt(文字列 , 基数)

int data = Integer.parseInt("FF" , 16);
System.out.println( data );
//16進文字列を変換して255を出力
data = Integer.parseInt("1010" , 2);
System.out.println( data );
//2進文字列を変換して10を出力

Double.parseDouble(文字列)

double data = Double.parseDouble("12.3145");
System.out.println( data );
//12.3145を出力

数値から
 文字列へ

Integer.toString(数値,基数)

String data = Integer.toString(18,2);
System.out.print( data );
//10010を出力

数値への変換 では、各基本型に対応するクラスが用意されているので、それらのクラスを使います。 int型ではIntegerクラス、double型ではDoubleクラスです。
この変換において、実行エラーが起きる場合があります。
次のコードでは、コンパイルエラーはありませんが、実行時にエラーが発生します。以下のその例も示します。

public class Test
{
	public static void main(String[] arg)
	{
		double data = Double.parseDouble("12.314X");	// 数字でないXの文字を含む(これがエラー要因)
		System.out.println(data);
	}
}
Exception in thread "main" java.lang.NumberFormatException: For input string: "12.314X"
        at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1224)
        at java.lang.Double.parseDouble(Double.java:510)
        at Test.main(Test.java:7)

上記実行エラーで、NumberFormatExceptionの部分が、発生した例外クラスの名前で、 この名前からエラーの理由を推測します。(数を表現する書式エラーとなります)

またこのエラーの発生箇所が、FloatingDecimal.javaの1224行目です。 このFloatingDecimal.javaは、JDKの開発用ディレクトリ内に存在するJava APIのソースです。 (Java開発キットの中には、全てのクラスのソースが存在し、圧縮されたsrc.zip内にあります。)
エラー発生はここですが、その箇所を呼び出す命令が、Double.javaの510行目で、 それを呼び出す命令が、Test.javaの7行目と分かります。
このようにして、エラーの発生源から問題点を推測して直していきます。

文字列の内容を考察する。

次のプログラムで、文字列内の個々の文字を取り出して、コードを確認する。 これで、Javaの文字が16ビットのユニコードで管理されていることが確認できる。

	str="12AB あい";
	for(int i=0; i < str.length(); i++){
		int c = (int)str.charAt(i);
		System.out.print(Integer.toString(c, 16) + " ");
	}
31 32 41 42 20 3042 3044 


この文字列を外部(例えばファイル)で使うような場合、各種文字セットが存在します。
例えば UTF-8の文字セットのバイナリ列が必要な場合は、次のように配列に変換してます。 その確認例を示します。

	str="12AB あい";
	byte []a = str.getBytes("utf-8");
	for(int i=0; i < a.length; i++){
		System.out.print(Integer.toString(a[i] & 0x0ff, 16) + " ");
	}
31 32 41 42 20 e3 81 82 e3 81 84 

windows-31jの文字セットのバイナリ列の確認例を示します。

	str="12AB あい";
	byte []a = str.getBytes("windows-31j");
	for(int i=0; i < a.length; i++){
		System.out.print(Integer.toString(a[i] & 0x0ff, 16) + " ");
	}
31 32 41 42 20 82 a0 82 a2