Java.use(better);

Java.use(better);


Episode#03
拡張可能な for 文(2)-- for と別れる50の方法


《関連記事》

■ 階層構造を持つデータ

ファイルシステムを利用して、任意のフォルダーを頂点とする情報を体系的に検索/管理できると便利です。フォルダーの中には、ファイルのほかに別のフォルダーがあり、その中にまたファイルがあるという、再帰的な階層構造をしています。

□ リスト形式で表示する

 ̄まず、指定したフォルダー(ディレクトリー)を頂点として、その直下にあるファイル/フォルダーの名前を「リスト形式」で列挙します。たとえば、

// case #4: Java - リスト形式で表示する
    String pathname = "java/api/org/omg/CORBA_2_3";
    for (File e: new ExFile(pathname)) {
      System.out.println(e.getName());
    }

これを実行すると、次の結果が得られます。

$ java apple.pie._Tips
class-use
ORB.html
ORB.html
package-frame.html
package-summary.html
package-tree.html
package-use.html
portable
class-use
Delegate.html
InputStream.html
ObjectImpl.html
(省略)
$ 

 ̄インターフェース Iterable に準拠するクラスは、次のように実現します。

class ExFile extends File implements Iterable {
  Stack stack = new Stack();
  File cur = null;
  public ExFile(String pathname) {
    super(pathname);
    insertAll(listFiles());
  }
  public Iterator iterator() {
    return new Iterator() {
      public boolean hasNext() {
        if (stack.empty()) return false;
        cur = stack.pop();
        if (cur.isDirectory()) {
          insertAll(cur.listFiles());
        }
        return true;
      }
      public File next() { return cur; }
      public void remove() {}
    };
  }
  private void insertAll(File[] list) {
    int index = stack.size();
    for (File e: list) {
      stack.add(index, e);
    }
  }
}

 ̄スタックに要素を積むときには、後で取り出しやすいように逆順にします。しかし、要素を逆順に並べるのは面倒です。そこで、メソッド insertAll では、要素を挿入する位置 index を固定したまま、要素を末尾に追加します。すると、逆順に並べたのと同じ結果になります。

 ̄拡張 for 文の対象には、インターフェース Iterable に準拠する「任意のオブジェクト」を指定できます。それには、メソッド iterator を再定義する必要があります。さらに、インターフェース Iterator に準拠するメソッド hasNext/next/remove を再定義すると、拡張 for 文を介して、オブジェクトに最適な制御構造を利用できます。

 ̄メソッド hasNext では、次の要素が存在するかを判定します。スタックから要素 cur を取り出して、それがディレクトリー isDirectory なら、その中に含まれる一連のファイル listFiles をスタックに積みます。

 ̄メソッド next では、スタックから取り出した要素 cur を返します。

□ ツリー形式で表示する

 ̄次に、同じ情報を「ツリー形式」で列挙します。たとえば、

// case #5: Java - ツリー形式で表示する
    String pathname = "java/api/org/omg/CORBA_2_3";
    System.out.println(pathname);
    for (Node e: new ExFile2(pathname)) {
      System.out.println(e);
    }

これを実行すると、次の結果が得られます。

$ java apple.pie._Tips
java/api/org/omg/CORBA_2_3
+ class-use
  . ORB.html
. ORB.html
. package-frame.html
. package-summary.html
. package-tree.html
. package-use.html
+ portable
  + class-use
    . Delegate.html
    . InputStream.html
    . ObjectImpl.html
(省略)
$ 

ここで「+」に続くのはフォルダー「.」に続くのはファイルです。また、先行するインデントが階層(深さ/高さ)に相当します。

 ̄インターフェース Iterable に準拠するクラスは、次のように実現します。

class ExFile2 extends File implements Iterable {
  List seq = new Vector();
  public ExFile2(String pathname) {
    super(pathname);
    walk(seq, new File(pathname), 0);
  }
  public Iterator iterator() {
    return seq.iterator();
  }
  private void walk(List seq, File file, int depth) {
    for (File e: file.listFiles()) {
      if (e.isDirectory()) {
        seq.add(new Node(e, "+", depth));
        this.walk(seq, e, depth+1);
      } else {
        seq.add(new Node(e, ".", depth));
      }
    }
  }
}

 ̄メソッド walk では、指定した file を基点に、親から子へと移動 walk しながら、ファイル/フォルダーが混在する階層構造(ツリー)を巡回します。各要素 Node を seq の末尾に追加するときに、末端の葉にある(子を持たない)ファイルと、その途中の節にある(子を持つ)フォルダーとでは、対処が異なります。また、親から子へと移動するときには、実引数の値を増加 depth+1 させて、現在の位置(深さ)を記録します。

 ̄ここで注目に値するのは「再帰的なデータ構造に呼応する、再帰的なメソッド呼び出し」です。同じ制御構造を何度も記述するのは面倒なだけでなく、コードが煩雑で見通しも悪くなります。この問題を解決するのが、次のメソッドです。

 ̄メソッド iterator を再定義すると、拡張 for 文を介して、再帰的なデータ構造に依存せずに、各要素を巡回できます。

 ↑ TOP

》作業中です《

update*13/01/12 19:16:58