MyStringArrayListの結果確認。

練習問題19-3で、作成したMyStringArrayListの結果確認のため、MyStringArrayListにtoString()メソッドを追加し、UseMyStringArrayListクラスを作成しました。
まず、MyStringArrayListクラスのtoString()メソッド。

  public String toString() {
    int size = size();
    String s = "サイズ = " + size + ", 長さ = " + ar.length + ", 値 = [";
    if (size != 0) { s = s + ar[0]; }
    for (int i = 1; i < size; i++) s = s + " " + ar[i];
    s = s + "]";
    return s;
  }

次に、UseMyStringArrayListクラス。

public class UseMyStringArrayList {
  public static void main(String[] args) {
    MyStringArrayList list = new MyStringArrayList();
    System.out.println(list);
    list.add("1番目");
    list.add("2番目");
    list.add("3番目");
    list.add("4番目");
    System.out.println(list);
    list.add("5番目");
    System.out.println(list);
  }
}

実行結果は、こんな感じになります。

E:\study\19>javac MyStringArrayList.java UseMyStringArrayList.java
E:\study\19>java UseMyStringArrayList
サイズ = 0, 長さ = 4, 値 = []
サイズ = 4, 長さ = 8, 値 = [1番目 2番目 3番目 4番目]
サイズ = 5, 長さ = 8, 値 = [1番目 2番目 3番目 4番目 5番目]

...あれ、サイズ4の時はまだ長さ4になってないとおかしいですね。MyStringArrayListを見直して...。

E:\study\19>java UseMyStringArrayList
サイズ = 0, 長さ = 4, 値 = []
サイズ = 4, 長さ = 4, 値 = [1番目 2番目 3番目 4番目]
サイズ = 5, 長さ = 8, 値 = [1番目 2番目 3番目 4番目 5番目]

うん、OK。確認重要ということですね。

Set、Map、Map.EntryのtoString();

ArrayListのtoString();」を試したついでに、Set、Map、Map.EntryのtoString();も確認しておきます。

import java.util.*;

public class HashTest {
  public static void main(String[] args) {
    Set<String> set = new HashSet<String>();
    set.add("Alice");
    set.add("Bob");
    set.add("Chris");
    Map<String,String> map = new HashMap<String,String>();
    map.put("Alice", "Girl");
    map.put("Bob",   "Boy");
    map.put("Chris", "Man");
    System.out.println("Set<String>             : " + set);
    System.out.println("Map<String,String>      : " + map);
    for (Map.Entry<String,String> entry : map.entrySet()) {
      System.out.println("MapEntry<String,String> : " + entry);
      break;
    }
  }
}
E:\study\19>java HashTest
Set<String>             : [Bob, Chris, Alice]
Map<String,String>      : {Bob=Boy, Chris=Man, Alice=Girl}
MapEntry<String,String> : Bob=Boy

うん、見れば分かる感じですね。

ArrayListのtoString();

リスト19-10でLinkedListインスタンスをSysytem.out.printlnの引数にして、次のような出力を得ています。

[Bob, Chris, Diana, Elmo]

もしかして、「Aliceは何人いる?」や「バイバイ、Alice」でforループを使って出力していた部分も、ListArrayですが同じようにできるのでしょうか?
実験実験。

import java.util.*;

public class ArrayListTest4 {
  public static void main(String[] args) {
    ArrayList<String> strList = new ArrayList<String>();
    strList.add("Alice");
    strList.add("Bob");
    strList.add("Chris");
    ArrayList<Integer> intList = new ArrayList<Integer>();
    intList.add(1);
    intList.add(2);
    intList.add(3);
    System.out.println("ArrayList<String>  : " + strList);
    System.out.println("ArrayList<Integer> : " + intList);
  }
}
E:\study\19>java ArrayListTest4
ArrayList<String>  : [Alice, Bob, Chris]
ArrayList<Integer> : [1, 2, 3]

できますね。toString、素晴らしいなぁ。

バイバイ、Alice。

Aliceは何人いる?」ですが、よく考えたら保留するほどのことでもないですね。きっと、removeの引数がStringの"Alice"であれば"Alice"が消えて、intの5であれば5番目が消えるのでしょう。やってみます。

//  list.remove("Alice");
    list.remove(5);
E:\study\19>java ArrayListTest
add Alice and 4   : Alice Bob Chris Diana Elmo
add another Alice : Alice Bob Chris Diana Elmo Alice
remove Alice      : Alice Bob Chris Diana Elmo

「じゃあリストの中身がintの時は?」と思ったのだけど、これも簡単。参照型ではないintのArrayListは作れなくて、代わりにラッパーのIntegerを指定するように、とのことなのだから、Integerオブジェクトを与えればその値が消えて、int値を与えればそのインデックスの番号が消えるのでしょう。やってみます。

    list.remove(6);
    list.remove(new Integer(6));
E:\study\19>java ArrayListTest3
add 3, 6, 9, 6, 12, 6, 15 : 3 6 9 6 12 6 15
remove 6                  : 3 6 9 6 12 6
remove Integer(6)         : 3 9 6 12 6

「remove 6」とした際にはインデックスが6の値、つまり最後にあった「15」が消えています。次に「remove Integer(6)」とした時には、最初に出てくる、インデックスでは1の位置にあった「6」が消えています。

Aliceは何人いる?

ArrayListからの削除と確認の項に、次のようなコードがあります。

    // AliceとBobとElmoを削除
    list.remove("Alice");

Aliceが二人いたら、一人だけ消えるのでしょうか?それとも二人とも消えるのでしょうか?一人だけの時は、どちらが消えるのでしょうか?
ということで、例によって実験。

import java.util.*;

public class ArrayListTest {
  public static void main(String[] args) {
    ArrayList<String> list = new ArrayList<String>();
    
    System.out.print("add Alice and 4   : ");
    list.add("Alice");
    list.add("Bob");
    list.add("Chris");
    list.add("Diana");
    list.add("Elmo");
    for (int i = 0; i < list.size(); i++) System.out.print(list.get(i) + " ");
    System.out.println("");
    
    System.out.print("add another Alice : ");
    list.add("Alice");
    for (int i = 0; i < list.size(); i++) System.out.print(list.get(i) + " ");
    System.out.println("");
    
    System.out.print("remove Alice      : ");
    list.remove("Alice");
    for (int i = 0; i < list.size(); i++) System.out.print(list.get(i) + " ");
    System.out.println("");
  }
}
E:\study\19>javac ArrayListTest.java
E:\study\19>java ArrayListTest
add Alice and 4   : Alice Bob Chris Diana Elmo
add another Alice : Alice Bob Chris Diana Elmo Alice
remove Alice      : Bob Chris Diana Elmo Alice

最初に見つかったAliceにバイバイするみたいですね。他のAliceは、まだ残っていました。
じゃあ、2番目のAlice、あるいは最後のAliceにバイバイするにはどうすれば良いでしょう。ここでは、ひとまず保留します。

genericsをJava1.4でコンパイルした際のエラー。

本日の作業環境はJava1.4.2_11なので、genericsを試そうとしたらコンパイル・エラー。こんなエラーになります。

E:\STUDY\19>javac GcArray3.java
GcArray3.java:4: <identifier> がありません。
  static ArrayList<int[]> list = new ArrayList<int[]>();
                  ^

業務用PCで、業務で使うパッケージがJava1.5未対応だからなのですが...しかたない、最終章まで来てですが、このPCにもJava1.5を入れて、PATHを変えて使い分けることにします。

文字コードの変換をしながら読み込み

ファイルから"EUC-JP"の文字データを読み込むと、文字が化けているように思われたので、対応方法を調査。まず、以下を手がかりにしました。

文字ストリームを扱うReaderやWriterを使えば、入出力を行うと同時に、文字コードの変換も行うことができるのです。

文字ストリームを扱うReaderとして、InputStreamReaderが挙げられているので、これとFileReaderを組み合わせることを考えました。InputStreamReaderのドキュメントを見てみると...「直系の既知のサブクラス:FileReader」となってます。じゃあなぜ?
しかしよく考えてみると、内部ではUnicodeで処理されるJavaですが、MakeHtmlなどはシフトJISのデータ・ファイルを読ませているのにちゃんと処理されています。そこで、今度は次の部分を手がかりに。

    InputStreamReader reader = new InputStreamReader(stream, charset);
ここで、streamはバイトストリームであり、charsetは"Shift_JIS"や"EUC-JP"など文字のエンコードを指定する文字列です。charsetを省略すると、プラットホーム依存(ネイティブ)のエンコード方法になります。

シフトJISはネイティブ・エンコードだから変換が働くけど、EUC-JPはそうではないからうまくいかない、と仮定。まずFileReaderはInputStreamReaderからgetEncodingというメソッドを継承しているとのことなので、実行してみます。

import java.io.*;
public class TellEncode {
	public static void main(String[] args) throws FileNotFoundException, IOException {
		FileReader reader = new FileReader("TellEncode.java");
		System.out.println("現在のエンコーディングは " + reader.getEncoding() + " です。");
		reader.close();
	}
}
E:\STUDY\18>java TellEncode
現在のエンコーディングは windows-31j です。

windows-31jShift_JISを拡張した「Windows-31J」と呼ばれるWindows標準文字セット(@IT)ですね。ここまではOKとして、次にエンコーディングの指定。
FileReaderのドキュメントを見るとcharsetを指定できるコンストラクタの記法も、charset指定用のメソッドも見当たりません。できないはずはないだろうという思いと、FileReaderはInputStreamReaderのサブクラスなんだから、ということで、InputStreamReaderでの指定方法を真似てみました。

E:\STUDY\18>javac MakeHtml2.java
MakeHtml2.java:14: シンボルを解決できません。
シンボル: コンストラクタ FileReader (java.lang.String,java.lang.String)
場所    : java.io.FileReader の クラス
      BufferedReader reader = new BufferedReader(new FileReader(sourcefile, "EUC-JP"));
                                                 ^

だめだそうです。この方向はあきらめて、ファイルからの入力をInputStreamReaderにかけることを検討。再度InputStreamReaderのドキュメントを開いて、コンストラクタを確認します。

InputStreamReader(InputStream in) 
InputStreamReader(InputStream in, Charset cs) 
InputStreamReader(InputStream in, CharsetDecoder dec) 
InputStreamReader(InputStream in, String charsetName) 

いずれにしても、引数にFileReaderは使えず、InputStreamクラスじゃなきゃいけない様子。今度はInputStreamのドキュメントを開くと、「直系の既知のサブクラス」に「FileInputStream」というのがありますね。これかな?
FileInputStreamのコンストラクタを確認し、真似てみます。

//    BufferedReader reader = new BufferedReader(new FileReader(sourcefile));
//    BufferedReader reader = new BufferedReader(new FileReader(sourcefile, "EUC-JP"));
      BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(sourcefile), "EUC-JP"));
E:\STUDY\18>javac MakeHtml2.java
E:\STUDY\18>java MakeHtml2 MakeHtml.txt MakeHtml.htm
E:\STUDY\18>

コンパイルも通って、実行結果もOK。EUCコードのMakeHtml.txtを処理させましたが、MakeHtml.htmにはちゃんとHTMLへの変換が済んだ状態で、シフトJIS(というより、おそらくwindows-31Jなのでしょうね)で出力されています。
何とか調べられましたが、ドキュメントを調べるって厄介です。それに、本当にこのやり方が妥当だったのか、ということは疑問として残ります。