Processingで学ぶ 実践的プログラミング専門課程

第14回クラスはプログラミングを楽にする道具(2)

導入

前回からオブジェクト指向の学習に入り、インスタンスを作らないで使えるクラスの便利さを学びました。今回はインスタンスを作って使うクラスの便利さの例を紹介します。プログラミングの学習は、いかに楽をするかを追求することだと思います。ある課題について楽にこなす技術があれば、より難しい課題に取り組むことが可能になります。積極的に楽をする方法を学びましょう。

展開

配列の扱いを便利にするArrayListクラス

この連載中で紹介した便利なクラスの代表はArrayListです。ArrayListは配列を便利に取り扱えるような工夫が詰め込まれたクラスです。ArrayListを使う最も大きなメリットは、要素数を実行時に自在に変更できることです。取り扱うデータの量があらかじめ分からない場合、配列を使うならば、予想される最大限の領域を確保しておくことになります。必要なければその領域を使わないか、実行時に要素数を指定するコードを書くことになります。

コード例を示します。もし、同じ配列名で要素数を変更する必要が生じると、これだけ面倒だということと、ほぼ同じことをArrayListですればこんなに楽だということを感じてください。

ArrayAndArrayList.pde
println("配列の宣言 ");
int a[] = {1,2,3,4,5};
println("aの要素の値を出力");
for ( int n : a ){
  println(n);
}
println("aの要素数は" + a.length);
println("aよりも1つ大きい配列bを宣言");
int b[] = new int[a.length+1];
println("bにaの要素を代入");
for ( int i = 0; i<a.length; i++){
  b[i]=a[i];
}
println("新しい要素に値を代入");
b[a.length]=6;
println("aを宣言しなおす");
a = new int[a.length+1];
println("bの要素の値をaに代入する");
for ( int i = 0; i<a.length; i++){
  a[i]=b[i];
}
println("aの要素の値を出力");
for ( int n : a ){
  println(n);
}
println("//----------");

println("ArrayListを使ってみよう");
println("必要なパッケージを用意");
import java.util.Arrays;
println("ArrayListを宣言");
ArrayList<String> A = new ArrayList(Arrays.asList("Tim Bray",
              "Brian Kernighan",
              "Jon Bentley",
              "Karl Fogel"));
println("現在のArrayListの各要素");
println(A.toString());
println("要素を追加・削除します");
A.add(1,"Eliotte Rusty Harold"); // 2番目位置に要素を挿入します。
A.remove(2); // 3番目の要素を削除します。
println("変更後のArrayListの要素");
println(A.toString());

[注意] 配列のコピーは配列クラスのcloneメソッドやarraycopyメソッド、System.arraycopyメソッドがありますが、これらは各要素への参照をコピーするもので、コピー元の要素の値を変更すると、コピー先の値もそれに追随して変わります。このようなコピーをシャロー・コピー(Shallow Copy)と呼びます。
これに対してサンプルsketchのようにfor文で地道に代入する方法をディープ・コピー(Deep Copy)と呼びます。これは、コピー先の配列に対して元の配列の値を代入していますから、元の配列の要素の値を変更しても、コピー先の配列の要素の値は変化しません。
この違いは多次元配列のコピーで顕在化します。1次元配列のコピーではclonearraycopyもディープコピーされることを筆者は確かめています。

[作業]1次元配列と2次元配列を作成し、それぞれclonearraycopyでコピーしてください。そして、元の配列の要素の値を変更し、コピー先の配列の要素の値がそれに追随して変わることを確認しましょう。

ファイル入出力を楽にするPrintWriterクラスとBufferedReaderクラス

Processingでテキストデータをまとめて読み書きする分にはloadStringssaveStringsといったメソッドを使うのが一番です。

UsageOf_saveStringsAnd_loadStrings.pde
String hisWords 
    = "Bad programmers worry about the code. "
    + "Good programmers worry about data structures "
    + "and their relationships.(Linus Torvalds)";
String[] list = split(hisWords, ' ');
saveStrings("LinusSaids.txt", list);

String words[] = loadStrings("LinusSaids.txt");
println("He said");
for (String val: words) {
  print(val + " ");
}
println();

しかし、これらのメソッドは入力があるたびに保存、必要に応じて一行ずつ読み出しといった動作が必要な場合に対応しません。そのような場合には、PrintWriterクラスやBufferedReaderクラスのインスタンスを用います。

ProcessingのAPIリファレンスを参照してください。

これらの便利なクラスを用いれば、ファイルの入出力が簡単に、そして安全に実行できます。それぞれのリファレンスに掲載されているサンプルを参照しましょう。注意点は、例外処理を記述する必要があることです。せっかくProcessingを使っているのですから、例外処理に煩わされたくありませんが、これらのクラスを使う場合は仕方ありません。

サンプルコードを読むと分かるように、これらのクラスを利用することで、データが発生するごとにファイルに記録するといった操作が可能になります。すべてのデータをプログラムが最後まで保持しておこうとすると、大変なメモリ容量が必要な場合があるでしょう。メモリを食いつぶしてソフトがクラッシュするという事態を防ぐことができます。短所として考えられるのは、データが発生する度に保存しますから、動作速度に影響があるでしょう。ファイルアクセスはソフトウエアの中で負荷の大きい処理の代表です。メモリに余裕があるか、それほど大容量のデータを取り扱わないならば、まとめて読み込み・書き込みを行うのも選択肢です。

演習

演習1(難易度:easy)

ArrayListインスタンスのコピーにおいて、シャロー・コピーやディープ・コピーといった違いは起こるのでしょうか。調査してまとめてください。

演習2(難易度:easy)

PrintWriterとBufferedReaderそれぞれのリファレンスに掲載されているサンプルコードを連結しましょう。ディスプレイウインドウ上を動くマウスポインタの軌跡を黒い点として描画し、座標値を記録したテキストファイルを作成します。何かキーが押されたら記録を中断・終了します。座標が保存されたテキストファイルを読み込み、ディスプレイウインドウに赤い点を描き移動軌跡を再現してください。ファイル名はSaveAndLoad.pdeとしてください。

まとめ

  • インスタンスを作成して使うクラスの便利さを紹介しました。

学習の確認

それぞれの項目で、Aを選択できなければ、本文や演習にもう一度取り組みましょう。

  1. ArrayListクラスを使うと楽になる意味が理解できましたか?
    1. 理解できた。気持ちよく納得した。
    2. 理解できた。しかし、今ひとつスッキリしない。
    3. 理解できない。
  2. ファイル操作用のクラスの便利さが理解できましたか?

    1. 理解できた。気持ちよく納得した。
    2. 理解できた。しかし、今ひとつスッキリしない。
    3. 理解できない。

参考文献

  • 『本格学習Java入門[改訂新版]』⁠佐々木整 著、技術評論社
    • Java言語の基本的な文法が良くまとめられています。また、大変広い範囲をカバーしているので、はじめてJava言語に触る方が、Javaがどんな言語なのかを知るために役立つ資料になるでしょう。

演習解答

  1. 違いが起こる。

    次のsketchを実行すると分かります。値を格納しているインスタンスはnamesです。インスタンスprogrammersnamesへの参照を持っているだけで、programmersだけのための記憶領域は確保していません。11行目でインスタンスnamesのアドレスを受け取っていると考えてください。このようなコピーでは、namesの要素への変更はprogrammersの要素に影響し、逆もまた然りです。値を記憶している領域を共有しているのですから当然です。

    ぞれぞれ独立した記憶領域を持つArrayListインスタンスにしたければ、次のように操作する必要があります。新しく宣言したArrayListインスタンスへコピー元のインスタンスの要素値を追加するのです。こうすることでnamesprogrammersでそれぞれ別の記憶領域に値を記憶させておくことができます。一方に加えた変更は他方へ影響を与えません。

  2. SaveAndLoad.pde

おすすめ記事

記事・ニュース一覧