便利なコードパーツ集!

やまろうのプログラミングTips

Java

[Java] 汎用的なソート機能、java.util.Arrays.sortを自分で実装するとリフレクションがよく分かる

投稿日:2016年12月6日 更新日:

2003/04/05

どうもー、新天地で心機一転
でおなじみのやまろうでぃーす。

4月から派遣先が変わりました。読者の皆さんは職業が
ソフトウェア技術者の方もいれば学生さんや主婦(いるのか?)の方も
いらっしゃると思いますが、ソフトウェア技術者って、A社っていう
会社に就職してもA社のビルで働くことってあまりないんですよ。
まぁ、大手企業に就職した人は別ですけどね。一個の大きい仕事を
大企業様(例えばIBMとか日立とかNECとか)が請け負ったとしたら、
そのプロジェクトの人員を全て自社の人間で賄うわけではなく、
子会社とか協力会社から人を集めるわけです。そしてプロジェクトが
終了するとチームは解散して、メンバーはそれぞれ新しい場所へと
向かうわけです。

で、僕の勤めてる会社
ってのは協力会社にあたるんですな。で、今までやってた国の金融機関の
Web開発がそろそろ終了するのですよ。1年半でした。
本格的な開発は今回が初めてだったので、とてもためになりました。
自分の力が初めて評価してもらえて、嬉しかったし自信になりました。
そんなわけで名残惜しいのですが、旅立たねば!

出会いと別れの繰り返し、ソフトウェア技術者って、
そんなさすらいのお仕事なのです。(あっそ)
新しい現場のお仕事のことは次回お話しするということで、お楽しみに!

では本題、今回はファイルの閉じ忘れを防ぐ方法をご紹介します。C++の
場合、デストラクタという、オブジェクトが消滅するときの
イベントハンドラがあるのでそこにファイルを閉じる処理を書いて
おけばいいんですけど、Javaにはデストラクタはありません。
それと似たものでfinalizeメソッドってのがあって、ここに終了処理
を書くことが出来るんですけど。書いてしまうとガベージコレクション
のメモリ回収の効率が落ちてしまうので、推奨されていません。
具体的にいうとfinalizeはオブジェクトが消滅する時に呼ばれます。
Javaでオブジェクトが消滅する時ってのは、ガベージコレクションが
メモリを回収する時です。ただし、finalizeに処理が実装されていると
ガベージコレクションがメモリを回収使用とした時にfinalizeを呼出し、
実際にメモリを開放するのは次回のガベージコレクションの時になります。
ということでfinalizeを実装するのはよろしくないというわけです。

そこで、オープンしたファイルを覚えておいて、処理を終えたら
自動的にファイルをクローズするフレームワークを考えました。
フレームワークって言っても一つのクラスですけどね。一つのクラス
であっても処理の流れ(枠組み)を定義しているんだから、フレームワーク
ってことで。今流行ってるしね、フレームワーク!

んじゃ、ソース行ってみよう!!

package fileframework;

import java.util.ArrayList;
import java.io.*;

// 自動的にファイルをクローズするフレームワークを利用するサンプル
class FileProcess extends FileHandler {                           //(1)
  private static final int FILE_COUNT = 100;

  public static void main(String[] args)  throws Exception {
    FileProcess obj = new FileProcess();
    obj.process(args);                                           //(2)
  }

  //親クラスの抽象メソッドをオーバーライドしている。
  protected void doProcess(String[] args) throws IOException{    //(3)
    PrintWriter out1 = getWriter(args[0]);                       //(3.1)
    out1.println("やまろうのJavaなわけって");
    out1.println("すごく役に立つよね!");
    out1.println("友達にも購読勧めよっと!");
    
    PrintWriter out2 = getWriter(args[1]);
    out2.println("やまろうのJavaなわけって");
    out2.println("そのままコンパイルして実行できるのが");
    out2.println("すごいよね!!");

    PrintWriter out3 = getWriter(args[2]);
    out3.println("やまろうって");
    out3.println("抱かれたい男No1だよね。");
    out3.println("んなアホな?!");
  }
}

// 自動的にファイルをクローズするフレームワーク
abstract class FileHandler {
  private ArrayList writerList;

  private ArrayList readerList;

  /**
   * メイン処理
   * @param args
  */
  protected abstract void doProcess(String[] args) throws IOException;

  /**
   * 入出力ストリームを格納するリストを生成し、
   * メイン処理(サブクラスで実装)を呼び出す。
   * メイン処理終了後、開かれたファイルを全て閉じる。
   * @param args
  */
  public void process(String[] args)
    throws IOException {
      
    writerList = new ArrayList();                 //(4)
    readerList = new ArrayList();                 //(5)
    
    doProcess(args);                              //(6)

    int size = readerList.size();                 //(7)
    for (int i = 0; i < size; i++) {
      ((Reader)readerList.get(i)).close();
    }

    size = writerList.size();
    for (int i = 0; i < size; i++) {
      ((Writer)writerList.get(i)).close();
    }
  }

  /**
   * 入力ストリームを生成して返す。返す前にリストに登録して覚えておく。
   * @param filePath
   * @return BufferedReader
   */
  protected BufferedReader getReader(String filePath)
    throws IOException {

    BufferedReader in = new BufferedReader(new FileReader(filePath));
    readerList.add(in);
    return in;
  }

  /**
   * 出力ストリームを生成して返す。返す前にリストに登録して覚えておく。
   * @param filePath
   * @return PrintWriter
   */
  protected PrintWriter getWriter(String filePath)                     //(8)
    throws IOException {
      
    PrintWriter out
      = new PrintWriter(new BufferedWriter(new FileWriter(filePath))); //(9)
    writerList.add(out);                                               //(10)
    return out;
  }
  
}

んでは、説明しますぜ。
FileProcessというコマンドライン引数で渡された3つのファイルに
テキストを出力するクラスです。
(1)のclass FileProcess extends FileHandler {
で、クラスの宣言、FileHandlerクラスを継承してます。こいつが
自動的にファイルをクローズするフレームワークでございます。
ファイルのオープンの機能の提供と、メイン処理の呼び出し、
メイン処理後にファイルを自動的に閉じてくれるわけです。

処理を追ってみよう。void mainから始まって、(2)で、FileHandlerの
proccesを呼び出す。すると(4)(5)で、開いたファイルを覚えておくための
リストを生成し、(6)で、doProcessを呼び出す。このメソッドは
抽象メソッドだ。FileHandlerクラスにはdoProcessの宣言だけで
実体がない。子クラスのdoProcessが呼び出される。つまり(3)に処理が
跳ぶ。(3.1)で、親クラスのgetWriterメソッドを使って出力ストリームを取得。
そして、出力をしてるというわけ。

getWriterメソッドでは(9)でストリームを生成して(内部でファイルを開いてる)
(10)で、リストに追加しています。開いたファイルを覚えておくためです。

で、doProcessが終了して、(7)以降で、覚えておいたファイルを閉じてる
というわけです。

それでは実行しましょう。

このメルマガをFileHandler.javaという名前で保存してください。
javac -d . FileHandler.java
java fileframework.FileProcess file1.txt file2.txt file3.txt

実行結果

file1.txt file2.txt file3.txtが出来上がってて、
中身はイタイ文章が書き込まれてるはずです。

そういや、getReader()使ってねーや、
入力ファイル作るのめんどいし、まいっか。

いかがだったでしょうか?
単純な例ですが、THEオブジェクト指向プログラミングって感じでしょ!
実は今回作った親クラスの中で、処理の流れを定義して、その一部を
抽象メソッドにして子クラスがその一部を実装するというのは、
デザインパターンのTemplate Methodというパターンを使っています。
雛形メソッド。FileHandlerクラスのprocessメソッドが
Template Method(雛形メソッド)です。リストを作ってdoProcessを呼んで
開いたファイルを全て閉じるということをしている。だけれども
doProcessで実際に何をするかは実装していない。リストを作った後、
何やるかは知らないけどdoProcessというのを呼び出すという処理の流れを
定義しているのです。だから雛形メソッドなのです。

オブジェクト指向ってのは処理の流れ、
呼び出す側のコードまでも共通化するんだなぁと。

僕は日経ソフトウェアでTemplate Methodパターンの記事を読んだときに、
「オブジェクト指向ってこういうことかぁ!!!」と衝撃を受けました。
なんちゅうか、開眼したというか感動したんだよね。読者の皆さんは
いかがかしら?今回のメルマガ読んでオブジェクト指向を開眼した、なーんて
人がいたら嬉しく思います。

では最後にJDKへのダメだし!(7)以降で、
((Reader)readerList.get(i)).close();
((Writer)writerList.get(i)).close();
とやってるとこがありますが、両方ともclose()を呼んでるんですよね。
もしも、WriterやReaderクラスが
public abstract class Reader {
public abstract class Writer {
じゃなくて、
public abstract class Reader implements Closable {
public abstract class Writer implements Closable {
となっていて、
public interface Closable {
void close();
}
という風に作っていてくれていたら、
readerList、writerListと2種類のリストを用意せずに済んで
((Reader)readerList.get(i)).close();
((Writer)writerList.get(i)).close();
じゃなく、
((Closable)ioList.get(i)).close();
ってな風に書けるのになぁと。ポリモーフィズムの恩恵を授かれるのになぁと。
ということで、読者の方でSun Microsystemsにお勤めの方が
いらしたら、設計者に言っといてよ!(偉そう)

んじゃぁねぇーーーーーー!!
やまろう

スポンサーリンク

-Java

Copyright© やまろうのプログラミングTips , 2024 AllRights Reserved Powered by AFFINGER4.