『java解惑』読書ノート3——もっと文字列の謎

7772 ワード

1.文字列置換:
質問:
次のプログラムでは、クラスのフルパス名の「.」「/」に置き換えます.コードは次のとおりです.
package com.javapuzzlers;

public class Test {

	public static void main(String[] args){
		System.out.println(Test.class.getName().replaceAll(".", "/") + ".class");
	}
}
本来所望の出力の結果はcom/javapuzzlers/testである.classですが、プログラムが実行する本当の出力は/////////////////////.です.class.
理由:
String.replaceAllメソッドは、文字シーケンスの文字列定数ではなく、正規表現を最初のパラメータとして受け入れます.正規表現"."任意の単一文字に一致するため、クラス名の各文字はスラッシュに置き換えられます.つまり、予想外の結果です.
結論:
この問題を解決するには2つの方法があります.
方法1:
正規表現の句点の前にエスケープ用のバックスラッシュ「」を追加します.バックスラッシュは正規表現でエスケープ文字の先頭を表すため、バックスラッシュ自体も別のバックスラッシュを使用してエスケープする必要があります.コードは次のとおりです.
package com.javapuzzlers;

public class Test {

	public static void main(String[] args){
		System.out.println(Test.class.getName().replaceAll("\\.", "/") + ".class");
	}
}
メソッド2:
JDK 5はその後javaを導入する.util.regex.Pattern.quoteメソッドは、パラメータとして文字列を受け入れ、必要なエスケープ文字を追加して、入力した文字列に正確に一致する正規表現文字列を返します.コードは次のとおりです.
package com.javapuzzlers;

import java.util.regex.Pattern;

public class Test {

	public static void main(String[] args){
		System.out.println(Test.class.getName().replaceAll(Pattern.quote("."), "/") + ".class");
	}
}

2.プラットフォーム間の文字列置換:
質問:
1つ目の例ではクラス名の全パスの句点をUnix/Linuxファイルパススラッシュに置き換えるが、Windowsオペレーティングシステムではファイルの区切りがスラッシュであるため、上記のプログラムはプラットフォーム間性を持たず、JDKでのjavaである.io.File.separatorは、オペレーティングシステムのプラットフォームに関するファイルパス区切り記号(Unix/Linuxではスラッシュ、Windowsではスラッシュ)を指定するための共通ドメインとして定義されているので、javaを使用するプログラムを修正します.io.File.separatorがプラットフォームにまたがるプログラムを書くには、次のようにします.
package com.javapuzzlers;

import java.io.File;
import java.util.regex.Pattern;

public class Test {

	public static void main(String[] args){
		System.out.println(Test.class.getName().replaceAll(Pattern.quote("."), File.separator) + ".class");
	}
}
テストを経て、プログラムを変更してUnix/Linuxプラットフォームで正常に運行し、com/javapuzzlers/testを印刷した.class、WindowsプラットフォームではcomjavapuzzlersTestを出力することを期待していました.classですが、次の運転時異常を報告します.
Exception in thread "main"java.lang.StringIndexOutOfBoundsException: String index out of range: 1 at java.lang.String.charAt(String.java:658) at java.util.regex.Matcher.appendReplacement(Matcher.java:762) at java.util.regex.Matcher.replaceAll(Matcher.java:906) at java.lang.String.replaceAll(String.java:2162) at com.javapuzzlers.Test.main(Test.java:9)
分析:
Windowsプラットフォームで実行時異常が発生したのはStringのためである.replaceAllメソッドの2番目のパラメータは普通の文字列ではなく、javaで代替文字列です.util.regex仕様では、代替文字列に現れる反スラッシュが後続する文字をエスケープし、字面の意味で処理されると規定されている.
JDK 5の後、この問題を解決する方法は2つあります.
方法1:
Javaを使用します.util.regex.Matcher.quoteReplacementメソッドは、文字列を対応する代替文字列に変換します.コードは次のとおりです.
package com.javapuzzlers;

import java.io.File;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Test {

	public static void main(String[] args){
		System.out.println(Test.class.getName().replaceAll(Pattern.quote("."), Matcher.quoteReplacement(File.separator)) + ".class");
	}
}
メソッド2:
Stringを使用します.Stringの代わりにreplaceAllメソッドは、2つのメソッドの機能が同じです.異なる点は、replaceメソッドが受け入れる2つのパラメータが正規表現ではなく文字列として処理される点です.コードは次のとおりです.
package com.javapuzzlers;

import java.io.File;

public class Test {

	public static void main(String[] args){
		System.out.println(Test.class.getName().replace(".", File.separator) + ".class");
	}
}

JDK 5以前の旧バージョンJDKを使用する場合はStringを使用することができる.replace(char,char)メソッドはこの問題を解決し、コードは以下の通りである.
package com.javapuzzlers;

import java.io.File;

public class Test {

	public static void main(String[] args){
		System.out.println(Test.class.getName().replace('.', File.separatorChar) + ".class");
	}
}

3.別の奇妙な記号:
質問:
次のプログラムはコンパイルによって、結果を出力できますか?
public class Test {

	public static void main(String[] args){
		System.out.print("iexplore:");
		http://www.google.com
		System.out.println(":maximize");
	}
}
はプログラムを変更して正常にコンパイルすることができて、出力の結果は:iexplore::maximizeです.
理由:
どう見てもプログラムに奇妙な言葉が追加されています」http://www.google.comこのURLの前の部分「http:」はjava言語に内蔵された記号(goto文がなく、ジャンプ位置を識別する記号)とされ、後ろの部分は単行注釈とされていることが多く考えられます.
結論:
多くの人はjavaプログラミングでラベルに遭遇することはめったにありませんが、このようなjava構文特性を覚えておいてください.プログラムをより理解しやすいようにするには、プログラムをフォーマットし、ラベルと注釈を分けたほうがいいです.コードは以下の通りです.
public class Test {

    public static void main(String[] args) {
        System.out.print("iexplore:");
    http:   // www.google.com
        System.out.println(":maximize");
    }
}

4.文字列の結合:
質問:
次のプログラムの印刷出力結果を当ててみてください.
import java.util.Random;

public class Test {
    
    private static Random rnd = new Random();

    public static void main(String[] args) {
        StringBuffer word = null;
        switch(rnd.nextInt(2)){
            case 1: word = new StringBuffer('P');
            case 2: word = new StringBuffer('G');
            default: word = new StringBuffer('M');
        }
        word.append('a');
        word.append('i');
        word.append('n');
        System.out.println(word);
    }
}

プログラムが複数回実行されている可能性があり、出力Pain,Gain,Mainを等しい確率でそれぞれ印刷している可能性があり、タスクswitchのcaseが貫通している可能性もあるため、プログラムは総出力Mainを印刷しなければならないと考えられています.
プログラムの実際の実行結果はいつも奇妙なainである.
理由:
このような驚くべき実行結果が現れたのは、プログラムを変更して合計3つのバグがあり、この3つのバグがたまたま1つに集まって驚くべき結果を引き起こしたからだ.
1つ目のバグ:選択された擬似乱数により、switch文はその3つのケースのうちの2つしか達成できない、Random.nextInt(int)の仕様記述は、0(含む)から指定する値(含まない)までの間に均等に分布する擬似乱数のint値を返すため、Random.nextInt(2)の値は0と1しか取ることができず、2になることは不可能であるため、switchの2分岐は永遠に実行することができず、2に達するには擬似乱数をRandomに変更する必要がある.nextInt(3).
2番目のバグ:switchのcase文にはbreakがないため、word=new StringBuffer('M')が常に実行されるdefault文にドリルスルー順で実行されます.前のプログラム割り当てを上書きします.
3番目のbug:StringBufferにはStringBuffer(char)コンストラクション関数はありません.StringBufferには次の3つのコンストラクション関数しかありません.
(1).デフォルトのパラメータなしコンストラクション関数:StringBuffer()
(2).文字列の初期バッファの内容を指定するコンストラクション関数:StringBuffer(String);
(3).文字列の初期バッファの初期容量を指定するコンストラクション関数:StringBuffer(int);
word=new StringBuffer('M');すると、コンパイラは、文字Mの自動タイプをint値77に変換して3番目のコンストラクタを選択するので、プログラムの総印刷出力ainを変更するのも理解しにくくなります.
結論:
上記の手順を修正するには、3つの方法があります.
方法1、プログラムバグを修正する:
import java.util.Random;

public class Test {
    
    private static Random rnd = new Random();

    public static void main(String[] args) {
        StringBuffer word = null;
        switch(rnd.nextInt(3)){
            case 1: word = new StringBuffer("P");
            break;
            case 2: word = new StringBuffer("G");
            break;
            default: word = new StringBuffer("M");
            break;
        }
        word.append('a');
        word.append('i');
        word.append('n');
        System.out.println(word);
    }
}
方法2、簡略版:
import java.util.Random;

public class Test {
    
    private static Random rnd = new Random();

    public static void main(String[] args) {
        System.out.println("PGM".charAt(rnd.nextInt(3)) + "ain");
    }
}
方法3:
import java.util.Random;

public class Test {
    
    private static Random rnd = new Random();

    public static void main(String[] args) {
        String[] a = {"Main", "Pain", "Gain"};
        System.out.println(randomElement(a));
    }
    
    private static String randomElement(String[] a){
        return a[rnd.nextInt(a.length)];
    }
}