19 Dec 2014, 12:18

Java の Map で byte 配列をキーにするときの注意点

はじめに

Java で HashMap のキーに byte[] 配列を利用したら, key を put しても containsKey で key がないよといわれてしまった.

static void testbyteMap () {
    Map<byte[], Integer> map = new HashMap<byte[], Integer>();
    byte[] key = {1,2,3};
    byte[] key2 = {4,5,6};
    byte[] key3 = {1,2,3};

    map.put (key,1);
    map.put (key2,2);

    System.out.println (map.containsKey (key));
    System.out.println (map.containsKey (key2));
    System.out.println (map.containsKey (key3));
}

結果

true
true
false

調査

どうも配列を入れても, うまく検出できないようだ.

同一オブジェクトだと, 大丈夫だが, 値が同じでも違うオブジェクトだとだめ.

static void testIntMap () {
    Map<int[], Integer> map = new HashMap<int[], Integer>();
    int[] key = {1,2,3};
    int[] key2 = {4,5,6};;
    int[] key3 = {1,2,3};;

    map.put (key,1);
    map.put (key2,2);

    System.out.println (map.containsKey (key));
    System.out.println (map.containsKey (key2));
    System.out.println (map.containsKey (key3));
}

解決策

原因は, byte[] が 大小比較できないから.

Stack Overflow によると

  • byte[] を String に変換
  • byte[] をを List<Byte>に変換
  • equals と hashmap を実装した ラッパーデータ型を作成

String 変換を試す

一番お手軽なのは, String 変換か?

static void testStringMap () throws UnsupportedEncodingException {
    Map<String, Integer> map = new HashMap<String, Integer>();
    byte[] key = {1,2,3};
    byte[] key2 = {4,5,6};
    byte[] key3 = {1,2,3};

    String keyStr = new String (key, "UTF-8");
    String keyStr2 = new String (key2, "UTF-8");
    String keyStr3 = new String (key3, "UTF-8");        

    map.put (keyStr,1);
    map.put (keyStr2,2);

    System.out.println (map.containsKey (keyStr));
    System.out.println (map.containsKey (keyStr2));
    System.out.println (map.containsKey (keyStr3));
}

static void testStringMap2 () throws UnsupportedEncodingException {
    Map<String, Integer> map = new HashMap<String, Integer>();
    byte[] key = {1,2,3};
    byte[] key2 = {4,5,6};
    byte[] key3 = {1,2,3};

    String keyStr = Arrays.toString (key);
    String keyStr2 = Arrays.toString (key2);
    String keyStr3 = Arrays.toString (key3);        

    map.put (keyStr,1);
    map.put (keyStr2,2);

    System.out.println (map.containsKey (keyStr));
    System.out.println (map.containsKey (keyStr2));
    System.out.println (map.containsKey (keyStr3));
}

結果

これで OK.

true
true
true

Special Thanks

18 Dec 2014, 15:44

Java での再帰処理で Stack Overflow を回避するためのエセ方法

はじめに

最近, haskell の勉強をしている関係上, Java でも再帰が使いたい.

しかし, Java で 再帰処理を書くと, いつか必ず StackOverflowError がでて プログラムが強制終了してしまう.

回避方法がないものか, 調べてみた.

検証

コード

public class RecursiveSample {
    public static void main (String args[]) {
        recursiveProcedure (1);
    }

    static void recursiveProcedure (int i) {
        try {
            System.out.println (i);
            recursiveProcedure (i+1);
        }
        catch (Throwable e) {
      System.out.println ("Error " + e.getMessage ());
      e.printStackTrace ();
        }
    }
}

実行結果

9917
9918
9919Error null
java.lang.StackOverflowError
at java.util.concurrent.locks.AbstractOwnableSynchronizer.<init>(AbstractOwnableSynchronizer.java:59)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.<init>(AbstractQueuedSynchronizer.java:299)
at java.util.concurrent.locks.ReentrantLock$Sync.<init>(ReentrantLock.java:119)
at java.util.concurrent.locks.ReentrantLock$NonfairSync.<init>(ReentrantLock.java:203)
at java.util.concurrent.locks.ReentrantLock.<init>(ReentrantLock.java:262)
at java.util.concurrent.ConcurrentHashMap$Segment.<init>(ConcurrentHashMap.java:425)
at java.util.concurrent.ConcurrentHashMap.ensureSegment (ConcurrentHashMap.java:749)
at java.util.concurrent.ConcurrentHashMap.putIfAbsent (ConcurrentHashMap.java:1149)
at java.lang.ClassLoader.getClassLoadingLock (ClassLoader.java:464)
at java.lang.ClassLoader.loadClass (ClassLoader.java:405)
at java.lang.ClassLoader.loadClass (ClassLoader.java:412)
at sun.misc.Launcher$AppClassLoader.loadClass (Launcher.java:308)
at java.lang.ClassLoader.loadClass (ClassLoader.java:358)
at RecursiveSample.recursiveProcedure (RecursiveSample.java:9)
at RecursiveSample.recursiveProcedure (RecursiveSample.java:9)
at RecursiveSample.recursiveProcedure (RecursiveSample.java:9)

所感

あちゃー.

再帰階層に上限をつけてみる

問題は, 深すぎる再帰呼び出しにある. ある程度深くなったら, 一旦関数呼び出しから戻って, 再会してみる.

コード

  public class RecursiveSample {
    static int recursiveMax;
    static int recursiveCount;

    public static void main (String args[]) {

        try {
            while (true) {
                    recursiveMax += 10000;
                    recursiveCount = recursiveProcedure2 (recursiveCount);
            }
        } catch (RecursiveEnd e) {
            System.out.println ("End");
        }

    }

    static int recursiveProcedure2 (int recursiveCount) throws RecursiveEnd {
        if (recursiveCount == 50000) throw new RecursiveEnd ();

        if (recursiveCount < recursiveMax) {
            System.out.println (recursiveCount);
            return recursiveProcedure2 (recursiveCount+1);
        }
        else {
            return recursiveCount;
        }
    }
}

class RecursiveEnd extends Exception {
}

所感

loop を結局つかっていて, かっこわるい… でも一応再帰も使えている.

終了条件にマッチしたら例外を発生させて, コンテキストから飛び出すところがミソ.

17 Dec 2014, 16:20

Java で log4j の使ってみた (Eclipse, Emacs Viewerも)

はじめに

仕事で log4j を利用しているので, Eclipse での使い方を少し調べてみた.

環境

  • log4j 2.3
  • Eclipse 4.4
  • Java 1.7

log4j とは

Java で, ログを残すための便利で有名なライブラリ.

install

以下から最新版をダウンロード.

設定

適当なところに解凍後, クラスパスを通す.

Eclipse だと,

  • ツールバー > ウィンドウ > 設定
  • Java > ビルド・パス > ユーザライブラリ
  • 新規 -> Log4j と入力
  • 外部 Jar 追加 を選択
    • 以下を登録
      • log4j-core-2.x.jar
      • log4j-api-2.x.jar
      • log4j-1.x-api-2.x.jar

続いて, プロジェクトを右クリックして,

  • プロバティ > Java ビルド・パス > ライブラリータブ
  • ライブラリー追加 > ユーザライブラリ > 次へ
  • 上で作成した Log4j を追加.

Hello log4j

標準出力に log を出すことを目指す.

log4j2.xml を作成.

以下を参考に logger.xml を作成して, src 配下に配置.

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
  <Appenders>
    <Console name="Console" target="SYSTEM_OUT">
      <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
    </Console>
  </Appenders>
  <Loggers>
    <Root level="trace">
      <AppenderRef ref="Console"/>
    </Root>
  </Loggers>
</Configuration>

<Root level=“xxxx”>を調整することで, ログレベルを調整できる.

ログレベル

Wikipedia から引用. 以下のようにログのレベルがある.


Fatal 致命的なエラー. プログラムの異常終了 ERROR エラー. 予期しないその他の実行時エラー WARN 警告. INFO 情報. 実行時の何らかの注目すべき事象 DEBUG デバッグ用の情報 Trace トレース情報. DEBUG よりも詳細な情報


コードを作成

サンプルコード作成.

package sample;
import org.apache.log4j.Logger;

public class LoggerSample {
        public static void main (String[] args) {
        Logger logger = Logger.getLogger (LoggerSample.class.getName ());

        logger.trace ("Hello trace");
        logger.debug ("Hello debug");
        logger.info ("Hello info");
        logger.warn ("Hello warning");
        logger.error ("Hello error");
        logger.fatal ("Hello fatal");
    }

出力結果

23:57:03.111 [main] TRACE sample.LoggerSample - Hello trace
23:57:03.112 [main] DEBUG sample.LoggerSample - Hello debug
23:57:03.112 [main] INFO  sample.LoggerSample - Hello info
23:57:03.112 [main] WARN  sample.LoggerSample - Hello warning
23:57:03.112 [main] ERROR sample.LoggerSample - Hello error
23:57:03.112 [main] FATAL sample.LoggerSample - Hello fatal

Eclipse Plugin

Eclipse で log4j のログを表示させるプラグインはいくつかあるみたい.

JLV

JLV を試す. ためそうと思ったらこれは, log4j 1.x 用だったので, 1.x を入れる.

以下にしたがって設定.

カラフルに色が表示される. 検索機能も便利.

Emacs Lisp

こんなのみつけた.

text におとした log をみるモード. 自動で更新されるのもよい.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" >

  <appender name="file" class="org.apache.log4j.FileAppender">
     <param name="File" value="./sample.log" />
     <param name="Append" value="true" />
     <param name="Encoding" value="Shift_JIS" />
     <layout class="org.apache.log4j.PatternLayout">
        <param name="ConversionPattern" value="%m%n" />
     </layout>
  </appender>

  <root>
    <appender-ref ref="file"/>
  </root>
</log4j:configuration>

helm-swoop とかとくみあわせると, 抜群の検索効果.

JTags と組み合わせると, なんと Log からソースへジャンプできる.

BookMark

14 Dec 2014, 08:51

データフロー変数 (Oz) で実現する Producer-Consumer Pattern

はじめに

以下の記事の続編です.

前回は, Java の 共有メモリモデルを利用して Producer-Consumer Pattern を実装した.

今回は, Oz のもつ決定性データフローモデルを利用して実装してみる.

決定性データフローモデル

データフロー変数をもつモデル.

データフロー変数

<div class="outline-text-3" id="text-unnumbered-3">
  <p>
    変数に値が束縛されるまでプログラムの実行を待ち合わせるような宣言的変数.
  </p>

  <p>
    あるスレッドがデータフロー変数を利用しようとしたとき, その変数に値が束縛されていない場合は, 別のスレッドが束縛するまで待ち合わせを行う.
  </p>

  <p>
    束縛されたときの実行を データフロー実行 という.
  </p>
</div>

実行例

Java

<div class="outline-text-3" id="text-unnumbered-5">
  <p>
    まずは, Java でのサンプル. 平行性を実現するためには, キューを共有する.
  </p>

  <p>
    結構コードがながくなってしまった&#x2026;
  </p>

  <p>
    [sourcecode language=&#8221;java&#8221; title=&#8221;&#8221;]<br /> import java.util.concurrent.LinkedBlockingQueue;<br /> import java.util.concurrent.BlockingQueue;<br /> import java.util.LinkedList;<br /> import java.util.List;
  </p>

  <p>
    public class ProducerConsumerPattern {<br /> public static void main (String args[]) {<br /> BlockingQueue<List<Integer>> queue = new LinkedBlockingQueue<List<Integer>>();
  </p>

  <p>
    Producer producer = new Producer (queue, 10);<br /> Consumer consumer = new Consumer (queue);
  </p>

  <p>
    producer.start ();<br /> consumer.start ();<br /> }<br /> }
  </p>

  <p>
    class Producer extends Thread {<br /> BlockingQueue<List<Integer>> queue;<br /> List<Integer> list;<br /> int limit;
  </p>

  <p>
    public Producer (BlockingQueue<List<Integer>> queue, int limit) {<br /> this.queue = queue;<br /> this.limit = limit;<br /> list = new LinkedList<Integer>();<br /> }
  </p>

  <p>
    public void run () {<br /> try {<br /> for (int i=1; i <= limit; i++) {<br /> System.out.println (i);<br /> sleep (1000);<br /> list.add (i);<br /> }<br /> queue.put (list);<br /> }<br /> catch (Exception e) {}<br /> }<br /> }
  </p>

  <p>
    class Consumer extends Thread {<br /> BlockingQueue<List<Integer>> queue;<br /> Integer sum;<br /> public Consumer (BlockingQueue<List<Integer>> queue) {<br /> this.queue = queue;<br /> this.sum = 0;<br /> }
  </p>

  <p>
    public void run () {<br /> try {<br /> List<Integer> list = queue.take ();<br /> for (Integer i: list) {<br /> sum += i;<br /> }<br /> System.out.println (sum);<br /> }<br /> catch (Exception e) {}<br /> }<br /> }<br /> [/sourcecode]
  </p>
</div>

Oz

<div class="outline-text-3" id="text-unnumbered-6">
  <p>
    つづいて, データフロー変数をサポートする Oz.
  </p>

  <p>
    とてもシンプルにかつ安全に書くことができる. データフロー変数の未来を感じることができるコード.
  </p>

  <p>
    [sourcecode language=&#8221;ruby&#8221; title=&#8221;&#8221;]<br /> declare<br /> fun {Producer N}<br /> fun {Producer1 X N}<br /> {Delay 1000}<br /> if X < N+1 then<br /> {Show X}<br /> X|{Producer1 X+1 N}<br /> else nil<br /> end<br /> end<br /> in<br /> {Producer1 1 N}<br /> end
  </p>

  <p>
    fun {Consumer S}<br /> fun {Sum S Acc}<br /> case S of X|Xr then {Sum Xr Acc+X}<br /> [] nil then Acc<br /> end<br /> end<br /> in<br /> {Sum S 0}<br /> end
  </p>

  <p>
    local Xs Ys S in<br /> thread Xs = {Producer 10} end<br /> thread<br /> Ys = {Consumer Xs}<br /> {Show Ys}<br /> end<br /> end<br /> [/sourcecode]
  </p>
</div>

13 Dec 2014, 03:54

Adapter, Facade, Proxy パターンの違いのメモ

はじめに

Gof のデザインパターンで Adapter, Facade, Proxy があり, 違いがわからなかったので, 整理してみた.

まずは定義から

Adapter

インタフェースを変換することにより, インタフェースに互換性がない クラス同士を接続する.

既存のクラスに対して修正を加えることなく, インタフェースを変更することができる.

継承を利用する場合と委譲を利用する場合がある.

Facade

複数のクラス群からなるサブシステムにアクセスするための, インタフェースを提供する.

異なるサブシステムを単純な操作だけを持った Facade クラスで結び, サブシステム間の独立性を高める事を目的とする.

facade とは, 正面という意味.

Proxy

オブジェクトへのアクセスをフックするための代理オブジェクトを提供する.

Proxy は英語で代理人.

ラッパー

ラッパーという概念がある.

あるクラスや関数, データ型などが提供する機能やデータを含み, 別の形で提供するもののこと.

どれもラッパーと言える.

オブジェクト思考のこころより

オブジェクト思考のこころという本に, Adapter と Facade の比較表がある.

以下, Proxy パターンも交えて整理すると,

  • Facade はインタフェースを簡素化する
  • Adapter は既存インタフェースを他のインタフェースに変換する
  • Proxy はインタフェースを変更せずに機能追加する.

                                                    Facade   Adapter   Proxy
    

    既存クラスがある? ○ ○ ○ インタフェースを再設計する? × ○ × ボリモーフィズムによるオブジェクトの振る舞いが必要? × ○ × より簡素なインタフェースが必要? ○ × ×

コードでの例

class Target {
    void printInt (int i) {
        System.out.println (i);
    }

    void printLong (long l) {
        System.out.println (l);
    }
}

class Adapter {
    Target target;
    Adapter (Target target) {
        this.target = target;
    }

    void printInt (Integer i) {
        target.printInt (i);
    }

    void printLong (Long l) {
        target.printLong (l);
    }

}

class Facade {
    Target target;
    Facade (Target target) {
        this.target = target;
    }

    void print (long l) {
        target.printLong (l);
    }
}

class Proxy {
    Target target;
    int intCount = 0;
    int intCache= 0;
    long longCount = 0;
    long longCache = 0; 

    Proxy (Target target) {
        this.target = target;
    }

    void printInt (Integer i) {
        target.printInt (i);
        intCount++;
        intCache = i;
    }

    void printLong (Long l) {
        target.printLong (l);
        longCount++;
        longCache = l;
    }
}

12 Dec 2014, 15:59

ConcurrentModificationException が Java で発生したときの対処方法

はじめに

ConcurrentModificationException が Java で発生したときの対処方法.

以下のようなコードを実行すると, 例外発生.

import java.util.Set;
import java.util.HashSet;

public class ConcurrentModification {
    public static void main (String[] args) {
        Set<Integer> set = new HashSet<Integer>();
        for (int i = 0; i < 5; i++)
            set.add (i);

        for (Integer i: set) {
            if (i == 3) {
                set.remove (i);
            }
        }
    }
}
Exception in thread "main" java.util.ConcurrentModificationException
   at java.util.HashMap$HashIterator.nextEntry (HashMap.java:922)
   at java.util.HashMap$KeyIterator.next (HashMap.java:956)
   at ConcurrentModification.main (ConcurrentModification.java:10)

原因は, iterater で for 文を回している時に, 要素を削除しようとしたから.

回避方法

その 1: イテレータを利用しない

イテレータなんてつかってかっこつけているのが悪い. Index で for 文をまわす

for (int i=0; i < set.size (); i++) {
    if (i == 3) {
        set.remove (i);
    }
}

その 2 Concurrent ライブラリを利用する

ConcurrentHashMap を利用する. ただし, ConcurrentHashSet はない…以下のように対応

Set<Integer> set = Collections.newSetFromMap (new ConcurrentHashMap<Integer, Boolean>());

その 3 コレクションをコピーして回す

すこし冗長か?

Set<Integer> set = new HashSet<Integer>();
for (int i = 0; i < 5; i++)
    set.add (i);

Set<Integer> set2 = new HashSet<Integer>();
set2.addAll (set);

for (Integer i: set2) {
    if (i == 3) {
        set.remove (i);
    }
}

おわりに

やりかたはいろいろある. 1 がいいかな…

BookMark

11 Dec 2014, 15:30

JUnit のテストケースをステートフルで利用する

はじめに

JUnit で, 結合テスト (Integration test), シナリオテストを書きたい.

トランザクションごとに長いテストを書く必要がある.

複数のトランザクションを順に処理していったときに, オブジェクトの状態の変化を検証したい.

JUnit はテストケースが独立

はじめ, トランザクションごとにテストケースを書いていたが, うまく動かない.. オブジェクトの状態が初期化されてしまう.

これは, JUnit の設計思想だった.

つまり, 各テストケースは独立だということ. テストで使用するオブジェクトはテストケース内で生成されて, テストケース内で消滅する.

たとえば

たとえば, このテストは失敗する. number は 0 が入っている.

import static org.junit.Assert.*;

import org.junit.Test;

public class MemoryTest {

    static int number;

    @Test
    public void test () {
        number = 1;
    }

    @Test
    public void test2 () {
        assertEquals (1, number);
    }
}

static を利用する

テスト間で状態を引き継ぐためには, 変数に static 修飾子をつける.

こうすると, 各テストケースの独立を破ることができる.

package test;

import static org.junit.Assert.*;

import org.junit.Test;

public class MemoryTest {

    static int number;
    static Foo foo;
    static Foo bar; 
    static Foo pee;     

    @Test
    public void test () {
        number = 1;
        foo = new Foo (1);
        bar = new Foo (foo);
        pee = new Foo ();       
    }

    @Test
    public void test2 () {
        assertEquals (1, number);
        assertEquals (1, foo.i);
        assertEquals (1, bar.foo.i);        
        assertEquals (2, pee.foo.i);
        foo.plus (3);
    }

    @Test
    public void test3 () {
        assertEquals (4, foo.i);
    }

}

class Foo {
    public int i;
    public Foo foo;

    public Foo (int i) {
        this.i = i;
    }

    public Foo () {
        this.foo = new Foo (2);
    }

    public Foo (Foo foo) {
        this.foo = foo;
    }

    public void plus (int i){
        this.i += i;
    }
}

テストの実行順序を制御する

JUnit のテストが実行される順番はランダム.

これだと, ステートフルなテストには不向きだ.

テストの実行順序を指定するには, 以下の方法がある

  • @FixMethodOrder (MethodSorters.NAME_ASCENDING) をクラスの頭に設定
  • メソッド名を 実行したいものから abc 順に変更.
import org.junit.Test;
import org.junit.runners.MethodSorters;
import org.junit.FixMethodOrder;

@FixMethodOrder (MethodSorters.NAME_ASCENDING)
public class MemoryTest {

    static int number;
    static Foo foo;
    static Foo bar; 
    static Foo pee;     

    @Test
    public void test () {
        number = 1;
        foo = new Foo (1);
        bar = new Foo (foo);
        pee = new Foo ();       
    }

    @Test
    public void test2 () {
        assertEquals (1, number);
        assertEquals (1, foo.i);
        assertEquals (1, bar.foo.i);        
        assertEquals (2, pee.foo.i);
        foo.plus (3);
    }

    @Test
    public void test3 () {
        assertEquals (4, foo.i);
    }

}

10 Dec 2014, 15:56

Factory Method と Abstract Factory の違いを順に理解する

はじめに

デザインパターンにでてくる Factory Method と Abstract Factory.

なんだか, いつになっても違いが分からない… というわけで一旦整理してみることにした.

能書き

まずは, 一般的な説明をネットからひろう.

Factory の原則

生成と実装を分離することで, プログラムはシンプルになる.

  • 生成パラメータの指定方法をシンプルに
  • 生成後の管理をシンプルに
  • 生成するオブジェクトの指定方法をシンプルに

特定のケースで特定のオブジェクトを生成するのは手続き思考的.

2 つをわけて考えることで設計に集中.

  • 動作方法
  • 生成,管理方法

Factory Method

オブジェクトの生成を行う時のインタフェースを規定して, インスタンス化するクラスを決定するのはサブクラスに任せる.

factoryMethod の中でオブジェクトの生成をすることで, 生成を生成オブジェクト (メソッド) 内にカプセル化.

Abstract Factory

関連するオブジェクト群を, その具象クラスを明確にせずに生成するための インタフェースを提供する.

関連するインスタンス群を生成するための API を集約することによって, 複数のモジュール群の再利用を効率化することを目的とする.

実装は意識せずに, インタフェース (API) のみで, 抽象的な部品をつくりあげる.

Factory Method 自体のカプセル化. マルチ Factory Method. Factory Methods.

両者の違い

Factory Method

ファクトリのクライアントとなるオブジェクトが, ファクトリオブジェクトにインスタンスの生成を委譲する.

  • 親クラスである Creator クラスが子クラスである

ConcreteCreator クラスにオブジェクトの生成を委ねる

  • Creator クラスと ConcreteCreator クラスとの関連である.
  • [オブジェクト生成] の抽象化にポイントを置いたパターン

Abstract Factory

親クラスであるファクトリが, 実際のオブジェクトの生成をサブクラスに委譲する

  • Client のインスタンスが ConcreteFactory のインスタンスにオブジェクトの生成を委ねる
  • オブジェクト同士の関連
  • [関連するオブジェクト群をまとめて生成するための手順] の抽象化

コードから理解する

能書きはいくら読んでもわからない.

というわけで, コードから理解する.

Factory Method

パターン未適用

まずは基本から. if-else が汚いことがよくわかる.

public class FactoryMethodSample {
    enum Type { FIRST, SECOND }

    public static void main (String[] args) {
        Type type;
      Alphabet alphabet;

        type = Type.FIRST;
        if (type == Type.FIRST) {
            alphabet = new A ();
        }
        else {
            alphabet = new B ();
        }
        alphabet.whoAreYou ();

        type = Type.SECOND;
        if (type == Type.FIRST) {
            alphabet = new A ();
        }
        else {
            alphabet = new B ();
        }
        alphabet.whoAreYou ();
    }
}

abstract class  Alphabet {
    abstract void whoAreYou ();
}

class A extends  Alphabet {
    void whoAreYou () { System.out.println ("I'm A");}
}

class B extends  Alphabet {
    void whoAreYou () { System.out.println ("I'm B");}
}

static ファクトリーメソッド適用

Factory の原則にしたがうと, 生成部分と振る舞いをわけることがシンプルなコードへの第一歩.

ということで, 生成部分をサブメソッドに抽出する.

これを, Effective Java では, static ファクトリーメソッドといっている.

public class StaticFactoryMethodSample {
    enum Type { FIRST,SECOND }

    public static void main (String[] args) {
        Type type;
        Alphabet alphabet;

        type = Type.FIRST;
        alphabet = factoryMethod (type);
        alphabet.whoAreYou ();

        type = Type.SECOND;
        alphabet = factoryMethod (type);
        alphabet.whoAreYou ();
    }

    static Alphabet factoryMethod (Type type) {
        if (type == Type.FIRST) {
            return new A ();
        }
        else {
            return new B ();
        }
    }
}

abstract class Alphabet {
    abstract void whoAreYou ();
}

class A extends Alphabet {
    void whoAreYou () { System.out.println ("I'm A");}
}

class B extends Alphabet {
    void whoAreYou () { System.out.println ("I'm B");}
}

ファクトリーメソッド パターン適用

そして, これがファクトリーメソッド パターン適用版. static ファクトリメソッドをオブジェクトに抽出.

抽象クラスに生成メソッドを定義して, サブクラスで実装する.

if-else 文が消滅しているところに注目.

public class FactoryMethodSample {
    public static void main (String[] args) {
        Creator creator;
        Alphabet alphabet;

        creator = new CreatorA ();
        alphabet = creator.factoryMethod ();
        alphabet.whoAreYou ();

        creator = new CreatorB ();
        alphabet = creator.factoryMethod ();
        alphabet.whoAreYou ();
    }
}

abstract class Creator {
    abstract Alphabet factoryMethod ();
}

class CreatorA extends Creator {
    Alphabet factoryMethod () {
        return new A ();
    }
}

class CreatorB extends Creator {
    Alphabet factoryMethod () {
        return new B ();
    }
}

abstract class Alphabet {
    abstract void whoAreYou ();
}

class A extends Alphabet {
    void whoAreYou () { System.out.println ("I'm A");}
}

class B extends Alphabet {
    void whoAreYou () { System.out.println ("I'm B");}
}

Abstract Factory

つぎに, Abstract Factory は Factory Method のカプセル化に過ぎないことを示す.

Factory Method を発展させたのが, Abstract Factory.

ファクトリメソッドのソースに Number という概念を加える. Alphabet と Number には関係がある.

Abstract Factory は 関連ある複数のオブジェクトの生成のための API をひとつのオブジェクトに集約する.

パターン未適用

まずは, 汚いコードから.

public class AbstractFactorySample {
    enum Type { FIRST,SECOND }

    public static void main (String[] args) {
        Type type;
        Alphabet alphabet;
        Number number;

        type = Type.FIRST;
        if (type == Type.FIRST) {
            alphabet = new A ();
        }
        else {
            alphabet = new B ();
        }

        if (type == Type.FIRST) {
            number = new One ();
        }
        else {
            number = new Twe ();
        }
        alphabet.whoAreYou ();
        number.whoAreYou ();

        type = Type.SECOND;
        if (type == Type.FIRST) {
            alphabet = new A ();
        }
        else {
            alphabet = new B ();
        }

        if (type == Type.FIRST) {
            number = new One ();
        }
        else {
            number = new Twe ();
        }
        alphabet.whoAreYou ();
        number.whoAreYou ();
    }
}

abstract class Alphabet {
    abstract void whoAreYou ();
}

class A extends Alphabet {
    void whoAreYou () { System.out.println ("I'm A");}
}

class B extends Alphabet {
    void whoAreYou () { System.out.println ("I'm B");}
}

abstract class Number {
    abstract void whoAreYou ();
}

class One extends Number {
    void whoAreYou () { System.out.println ("I'm 1");}
}

class Twe extends Number {
    void whoAreYou () { System.out.println ("I'm 2");}
}

ファクトリメソッドパターン適用

ここで, まずはファクトリメソッドを適用して整理する.

public class AbstractFactorySample2 {

    public static void main (String[] args) {
        AlphabetCreator alphabetCreator;
        NumberCreator numberCreator;        
        Alphabet alphabet;
        Number number;

        alphabetCreator = new CreatorA ();
        numberCreator = new CreatorOne ();
        alphabet = alphabetCreator.factoryMethod ();
        number = numberCreator.factoryMethod ();
        alphabet.whoAreYou ();
        number.whoAreYou ();

        alphabetCreator = new CreatorB ();
        numberCreator = new CreatorTwe ();
        alphabet = alphabetCreator.factoryMethod ();
        number = numberCreator.factoryMethod ();
        alphabet.whoAreYou ();
        number.whoAreYou ();
    }
}

abstract class AlphabetCreator {
    abstract Alphabet factoryMethod ();
}

class CreatorA extends AlphabetCreator {
    Alphabet factoryMethod () {
        return new A ();
    }
}

class CreatorB extends AlphabetCreator {
    Alphabet factoryMethod () {
        return new B ();
    }
}

abstract class NumberCreator {
    abstract Number factoryMethod ();
}

class CreatorOne extends NumberCreator {
    Number factoryMethod () {
        return new One ();
    }
}

class CreatorTwe extends NumberCreator {
    Number factoryMethod () {
        return new Twe ();
    }
}

abstract class Alphabet {
    abstract void whoAreYou ();
}

class A extends Alphabet {
    void whoAreYou () { System.out.println ("I'm A");}
}

class B extends Alphabet {
    void whoAreYou () { System.out.println ("I'm B");}
}

abstract class Number {
    abstract void whoAreYou ();
}

class One extends Number {
    void whoAreYou () { System.out.println ("I'm 1");}
}

class Twe extends Number {
    void whoAreYou () { System.out.println ("I'm 2");}
}

Type と if-else 文が取り除かれてすっきり. しかし, まだ冗長なところがある.

そこで, alphabetCreator と NumberCreator をひとつにまとめる.

Abstract Factory パターン適用

public class AbstractFactorySample3 {

    public static void main (String[] args) {
        Creator creator;
        Alphabet alphabet;
        Number number;

        creator = new FirstCreator ();
        alphabet = creator.alphabetFactoryMethod ();
        number = creator.numberFactoryMethod ();
        alphabet.whoAreYou ();
        number.whoAreYou ();

        creator = new SecondCreator ();
        alphabet = creator.alphabetFactoryMethod ();
        number = creator.numberFactoryMethod ();
        alphabet.whoAreYou ();
        number.whoAreYou ();
    }
}

abstract class Creator {
    abstract Alphabet alphabetFactoryMethod ();
    abstract Number numberFactoryMethod (); 
}

class FirstCreator extends Creator {
    Alphabet alphabetFactoryMethod () {
        return new A ();
    }
    Number numberFactoryMethod () {
        return new One ();
    }
}

class SecondCreator extends Creator {
    Alphabet alphabetFactoryMethod () {
        return new B ();
    }
    Number numberFactoryMethod () {
        return new Twe ();
    }
}

abstract class Alphabet {
    abstract void whoAreYou ();
}

class A extends Alphabet {
    void whoAreYou () { System.out.println ("I'm A");}
}

class B extends Alphabet {
    void whoAreYou () { System.out.println ("I'm B");}
}

abstract class Number {
    abstract void whoAreYou ();
}

class One extends Number {
    void whoAreYou () { System.out.println ("I'm 1");}
}

class Twe extends Number {
    void whoAreYou () { System.out.println ("I'm 2");}
}

おわりに

本をよんでも分かりにくいことは, より単純な例に落とし込めば自分でも理解できる.

Abstract Factroy と Factory メソッドの関係が分かって, スッキリ.

09 Dec 2014, 15:52

Java に Pair はないの?

はじめに

2 つの Key をもつ Map を利用したい.

たしか, C++ には Pair があった. Java にはないの?

結論

ない.

じゃあどうするか?

自分で作成するしかない!

class Pair<F, S> {
    public final F first;
    public final S second;

    Pair (F first, S second) {
        this.first = first;
        this.second = second;
    }
}

しかし, これでは 2 つの Key をもつ Map としてうまく動作しない.

import java.util.Map;
import java.util.HashMap;

public class PairSample {
    public static void main (String[] args) {
        Map<Pair<Integer,Integer>, String> map = new HashMap<Pair<Integer,Integer>, String>();

        Pair pair = new Pair (1,2);
        Pair pair2 = new Pair (1,2);        
        map.put (pair, "a");

        if (map.containsKey (pair2)) {
            System.out.println ("equal");
        }
        else {
            System.out.println ("not equal");           
        }
    }
}

秘密は, equals と hashCode にあった.

同一性と同値性

2 つのオブジェクトが同じ時, それらは同一性をもつという. hashCode () メソッドで検証する.

2 つのオブジェクトが保持する属性が同じとき, それらは同値性をもつという. equals () メソッドで検証する.

equals, hashCode はともに Object 型のメソッド.

以下のページが図つきでわかりやすい.

Map で二つのオブジェクトが同値だと判断するときは, equals メソッドをも ちいている.なので, このメソッドをオーバーロードして独自定義する必要がある.

実装例

今回やりたいことは, 同値性の確認なので, hashCode はなくてもいい.

ドキュメントによると, Hashcode があったほうが, HashMap の性能が上がるらしい.

@Override
public boolean equals (Object obj) {
    if (! (obj instanceof Pair))
        return false;
    Pair pair = (Pair) obj;
    return (first.equals (pair.first) && second.equals (pair.second));
}

@Override
public int hashCode () {
    return first.hashCode () ^ second.hashCode ();
}

Special Thanks

08 Dec 2014, 15:49

状態ありはプロトタイプパターンで, 状態なしはファクトリメソッドで実装 (Java)

はじめに

だんだん, タイトルが毎回同じになってきた.

今回はプロトタイプパターンの実装を Java で実施してみた.

Prototype パターン

生成するオブジェクトの原型をコピーして新しいオブジェクトを生成する.

Abstract Factory と似ている.

  • new でオブジェクトを生成すれば Abstract Factory./ Factory Method.
  • clone をつかう場合の Prototype.

複製を作成するためのメソッドを用意する. といういたって単純なもの.

プロトタイプ が複製を担当し, それ以外の生成における操作をクライアントが 担っている.

Map にテンプレートを登録しておいて, 利用するときに複製する. バイナリデータをマップにいれておいて, キーとなる名前をつけて管理する,など.

(実際に仕事では, バイナリのパケットをテンプレートから生成する処理につかった)

メリット

インスタンスのコンストラクタ引数で差分を渡すことで, クラスの数をかなり減らすことができる.

Java には, Clonable インタフェースがある.

利用シーン

  • Abstract Factory パターンでなされるように,

クライアント・アプリケーションにおいて オブジェクトの生成者をサブクラスにすることを回避する

  • 標準的な方法 (例えば’new’) で新しいオブジェクトを作ることによる

固有のコストが所与のアプリケーションにとって高すぎる時にそれを回避する.

サンプルコード

import java.util.HashMap;
import java.util.Map;

public class PrototypeSample {
    public static void main (String args[]) {
        printerFacotry factory = new printerFacotry ();
        Printer printer;

        printer = factory.create ("type a");
        printer.printMessage ();
        printer = factory.create ("type b");
        printer.printMessage ();
        printer = factory.create ("type c");
        printer.printMessage ();
    }
}

class Printer implements Cloneable {
    String str;

    public Printer (String str) {
        this.str = str;
    }

    public void printMessage () {
        System.out.println (str);
    }

    @Override
    public Printer clone () {
        Printer cloned = null;
        try {
            cloned = (Printer) super.clone ();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace ();
        }
        return cloned;
    }
}

class printerFacotry {
    Map<String, Printer> protoMap; 

    public printerFacotry (){
        protoMap = new HashMap<String, Printer>();
        protoMap.put ("type a", new Printer ("a"));
        protoMap.put ("type b", new Printer ("b"));
        protoMap.put ("type c", new Printer ("c"));
    }

    public Printer create (String type) {
        return protoMap.get (type).clone ();
    }
}

状態をもたないならば, プロトタイプは不要

無名クラスをクローンできるのかと思ったが, できなかった. そもそも, 無名クラスは状態を持たないので, クローンする必要がなかった.

この比較から以下のことが分かる.

  • 状態をもつオブジェクトをコピーするのはプロトタイプパターンが有用.
  • 状態をもたないオブジェクトは new で生成する ファクトリメソッドパターンが有用.

クロージャをわたす

Map のなかにクロージャを入れて, 好きな時に取り出すようにした.

これはけっこういいパターンかもしれない. 個人的に気に入った.

import java.util.HashMap;
import java.util.Map;

public class PrototypeSample {
    public static void main (String args[]) {
        printerFacotry factory = new printerFacotry ();
        Printer printer;

        printer = factory.create ("type a");
        printer.printMessage ();
        printer = factory.create ("type b");
        printer.printMessage ();
        printer = factory.create ("type c");
        printer.printMessage ();
    }
}

interface Printer {
    public void printMessage ();
}

class printerFacotry {
    Map<String, Printer> protoMap; 

    public printerFacotry (){
        protoMap = new HashMap<String, Printer>();
        protoMap.put ("type a", new Printer (){
            public void printMessage () { System.out.println ("a"); }
        });
        protoMap.put ("type b", new Printer (){
            public void printMessage () { System.out.println ("b"); }
        });
        protoMap.put ("type c", new Printer (){
            public void printMessage () { System.out.println ("c"); }
        });
    }

    public Printer create (String type) {
        return protoMap.get (type);
    }
}

クロージャ + 引数

さらに改良.

クロージャを Map に保存しておいて, 呼び出し時に外部から引数を与えるようにした.

これで, さらにメソッドが柔軟になった.

interface Printer {
    public void printMessage (String str);

}

class printerFacotry {
    Map<String, Printer> protoMap; 
    public printerFacotry (){
        protoMap = new HashMap<String, Printer>();
        protoMap.put ("type a", new Printer (){
            public void printMessage (String str) { System.out.println ("**"+str+"**"); }
        });
        protoMap.put ("type b", new Printer (){
            public void printMessage (String str) { System.out.println ("++"+str+"++"); }
        });
        protoMap.put ("type c", new Printer (){
            public void printMessage (String str) { System.out.println ("=="+str+"=="); }
        });
    }

    public Printer create (String type) {
        return protoMap.get (type);
    }
}

もはや, プロトタイプパターンの記事ではなくなっているが….