2011年2月22日火曜日

EnumerableモジュールとEnumeratorモジュール

rubyを使うとかなり頻繁に利用するのがEnumerableオブジェクト(イテレータ)である。
意識して使ってないけど、配列のeachメソッドやループのtimesなどがこれに該当する。

Enumerableオブジェクト

組み込みオブジェクトのArrayやHashがincludeしているモジュール。
Enumerableをincludeして、eachメソッドを定義してあげれば独自モジュールでもEnumerableが提供する様々なメソッドを利用出来る。

Arrayを使った場合のコード

array = [1, 2, 3]

# eachでループ
array.each {|x|
  puts x
}

# 添字付きでループ
array.each_with_index {|x, i|
  puts "#{i} = #{x}"
}
出力結果はこんな感じ
1
2
3
0 = 1
1 = 2
2 = 3

独自クラスの場合

class Hoge
  include Enumerable

  def initialize(num)
    @num = num
  end

  def each
    @num.each {|num|
      # prefixにhoge_を付加する。
      yield "hoge_#{num}"
    }
  end
end
出力結果はこんな感じ
Enumerableで定義されてるgrepもちゃんと使えてるのが分かる。
each start...
hoge_1
hoge_2
hoge_3
hoge_4
grep start...
hoge_2
hoge_3

ruby始めた頃は、each_with_indexなんて知らなくて、こんなループをかいてた・・・。
まぁ、動くから間違ってはないけど直感的ではないよね。一瞬なにがしたいんだ?って思ってしまう。
array.count.times {|i|
  puts "array[#{i}] = [#{array[i]}]"
}

Enumeratorオブエクト

each以外のメソッドでもEnumerableを使うためのラッパークラス
Enumeratorはイミュータブルなので、生成したあとの状態変更はできない。
なので、配列でパラメータ渡しをするのではなくEnumeratorを指定したほうが良い。

# eachメソッドを定義しているクラスのto_enumを呼び出すと、Enumeratorを生成できる。
enum = array.to_enum
enum.each {|x|
  puts x
}

# enumメソッドを定義していないクラスの場合
class Hoge
  def initialize(num)
    @num = num
  end

  def hoge
    @num.each {|num|
      yield "hoge_#{num}"
    }
  end
end

hoge = Hoge.new([1, 2, 3, 4])

# enum_forメソッドに、eachと同義のhogeメソッドを指定して Enumeratorを生成する。
enum = hoge.enum_for(:hoge)
puts "each start..."
enum.each {|x|
  puts x
}
puts "grep start..."
enum.grep(/^.*[23]$/) {|x|
  puts x
}
実行結果はこんな感じ

hogeメソッドしか持っていなくても、Enumerableのメソッドを利用できている。
each start...
hoge_1
hoge_2
hoge_3
hoge_4
grep start...
hoge_2
hoge_3

2011年2月20日日曜日

JDK7のAutoCloseable

JDK7からAutoCloseableインタフェースなるものが追加されてます。
このインタフェースを実装しているクラスの場合、JDK6以前のようにリソース解放処理をfainally句で行う必要がなくなります。

また、ThrowableにaddSuppressedメソッドが追加されていてtry句で発生した例外に対して、
リソース解消処理(JDK6までの、finally句でのリソース解放処理)で発生した例外を追加してくれています。

コードサンプル

public class Test {

    public static void main(String[] args) throws FileNotFoundException {
        // try句で、リソースを解放する必要のあるオブジェクトを生成する。
        // (対象のオブジェクトは、java.lang.AutoCloseableの実装クラスのみ)
        try (FileInputStream fis = new FileInputStream("C:\\work\\java\\idea_project\\src\\Test.java")) {
            fis.available();
        } catch (IOException e) {
            throw new RuntimeException("main failed.", e);
        }
    }
}

2011年2月19日土曜日

JDK7の複数例外の捕捉

JDK7の新機能の1つである、複数例外の捕捉をためしてみた。
public class Test {
    public static void main(String[] args) {
        try {
            m1();
            m2();
        // 複数例外を補足する場合は、パイプ(|)で
        // 対象の例外クラスをつなげてあげる。
        } catch (Hoge1Exception | Hoge2Exception e) {
            e.printStackTrace();
        }
    }

    private static void m1() throws Hoge1Exception {
        throw new Hoge1Exception();
    }

    private static void m2() throws Hoge2Exception {
        throw new Hoge2Exception();
    }

    private static class Hoge1Exception extends Exception {

    }

    private static class Hoge2Exception extends Exception {

    }
}
JDK6までだとこんな感じになってしまうんだよね。
try {
    // 処理
} catch (Hoge1Exception e) {
    e.printStackTrace();
} catch (Hoge2Exception e) {
    e.printStackTrace();
}

Console2をインストールしてみた

こちらのサイトでConsole2なるものを知ったので、インストールしてみた。
http://d.hatena.ne.jp/jyukutyo/20110126/1296136550
個人的には、かなり気に入ったので自宅と会社の両方にインストールして使ってみてます。

通常だと日本語の表示がおかしくなるけど、以下の方法で対応できる。

Console2を起動する

「Console2のメニュー」 → 「View」 → 「Console」を選択して起動する。

フォントをMSゴシックに変更する

Consoleウィンドウのアイコン(左上のアイコン)をクリック → 「プロパティ」を選択する。
(ショートカットキーだと、Alt + Spaceでいける。)

起動されたプロパティ画面で、フォントをMSゴシックに変更する。(下の画像のように)





















Console2を再起動する

Consoleウィンドウ閉じると、Console2も落ちちゃうので起動しなおしてあげる。
そうすると、こんな感じに日本語も表示される。

















Console2の設定

「メニュー」 → 「Edit」 → 「Settings」からいろいろ設定できる。

2011年2月14日月曜日

【MySql】DatabaseMetaDataからテーブルの情報取得

DatabaseMetaDataからデータベースの情報を抜き出す方法

カラム名の取得

テーブルに対応するカラムのリストを定義順に取得するコード。

Oracleだと、スキーマ名やテーブル名は大文字じゃないとNGだけど、MySqlだと小文字じゃないとダメらしい。
ずっと大文字で指定してて、データ取得できずにかなりはまった・・・。
Map<Integer, String> columns = new TreeMap<Integer, String>();
try {
    DatabaseMetaData metaData = con.getMetaData();
    // testスキーマのtest_tableのカラム情報を取得
    ResultSet resultSet = metaData.getColumns(
            con.getCatalog(), schema, table, null);
    try {
        while (resultSet.next()) {
            columns.put(resultSet.getInt("ORDINAL_POSITION"),
                    resultSet.getString("COLUMN_NAME"));
        }
    } finally {
        resultSet.close();
    }
} catch (SQLException e) {
    throw new RuntimeException(
            String.format("エラー. テーブル名 = [%s]",
                    table), e);
}

主キーの取得

テーブルに対応する主キー情報を取得するコード。

MySqlは、主キーが定義されていないテーブルがあるとSQLExceptionを投げてくれる。
0件取得でいいのに・・・。
Map<Integer, String> primaryKeys = new TreeMap<Integer, String>();
try {
    DatabaseMetaData metaData = con.getMetaData();
    // testスキーマのtest_tableの主キー情報を取得
    ResultSet resultSet = metaData.getPrimaryKeys(
            con.getCatalog(), "test", "test_table");
    try {
        while (resultSet.next()) {
            primaryKeys.put(resultSet.getInt("KEY_SEQ"),
                    resultSet.getString("COLUMN_NAME"));
        }
    } finally {
        resultSet.close();
    }
} catch (SQLException e) {
    // NOP
}

2011年2月12日土曜日

scala

プログラミングScalaが翻訳されて発売されてたのか。
scala勉強してみたいし、買ってみよう。

2011年2月7日月曜日

【Oracle】PreparedStatementの入力パラメータに変数名を指定する

JDBCのAPIを使用した場合、PreparedStatementの入力パラメータは「?」で指定する必要があり、非常に分かりづらい。カラム数の多いinsert文とか見ると何番目が、どのカラムかを即理解するなんてまず不可能だと思うし。
この問題を解決する機能を、Oracleの拡張機能で用意してくれています。
この機能を使うと、入力パラメータを「?」で定義して位置で紐付けるのではなく、変数名で紐付ける事ができるようになります。

実際に使ってみると、こんな感じのコードになる。個人的には、JDBCのAPIにこの機能ほしいと思ってしまいます。

// 入力パラメータは、":" + 変数名として定義する。
// prepareStatementの戻りを、OraclePreparedStatementにキャストする。
// Oracleの拡張機能を使うのでキャストしてあげないといけない。
OraclePreparedStatement statement = (OraclePreparedStatement) connection.prepareStatement( 
        insert into test (c1, c2, c3) values (:c1, :c2, :c3)"); 
try { 
    // setStringを使用するのではなく、Oracle拡張機能のsetStringAtNameを使う。 
    // 第一引数には、位置ではなく変数名(SQL文で定義した変数名(:を除いた物))を設定する。 
    statement.setStringAtName("c1", "1"); 
    statement.setStringAtName("c2", "あ"); 
    // 数値型の場合には、setIntAtNameを使用する。
    statement.setIntAtName("c3", 12345); 
    statement.executeUpdate(); 
} finally { 
    statement.close(); 
}

※setメソッドは、java.sql.PreparedStatementに用意されているsetXXXに対応するsetXXXAtNameがあります。IDEを使えば保管できるので何を使えばいいかはまず迷うことはないと思います。

2011年2月4日金曜日

Oracle11gからPL/SQLでのSEQUENCE採番が便利に

Oracle11gからは、PL/SQLでSEQUENCEオブジェクトを使って採番する際に、dual表を使ってSELECT文を実行する必要がなくなった。

実際のコードは、こんな感じに、採番した値をダイレクトに変数に代入できるようになった。
なんか、なんの違和感もない直感的なコードになってる気がする。
  1  declare
  2    num pls_integer;
  3  begin
  4    num := test_seq.nextval;
  5    dbms_output.put_line(num);
  6* end;
SQL>
SQL> /
2

PL/SQL procedure successfully completed.

ちなみに、これがOracle10gまでのコード。
やっぱり、select文って必要ないよな。
  1  declare
  2    num pls_integer;
  3  begin
  4    select test_seq.nextval into num from dual;
  5    dbms_output.put_line(num);
  6* end;
SQL>
SQL> /
3

PL/SQL procedure successfully completed.

Oracle10gで、11gの新機能を使ってみようとすると当然エラーとなる。
  1  declare
  2    num pls_integer;
  3  begin
  4    num := test_seq.nextval;
  5    dbms_output.put_line(num);
  6* end;
SQL> /
  num := test_seq.nextval;
                  *
ERROR at line 4:
ORA-06550: line 4, column 19:
PLS-00357: Table,View Or Sequence reference 'TEST_SEQ.NEXTVAL' not allowed in
this context
ORA-06550: line 4, column 3:
PL/SQL: Statement ignored