ページ

2014年3月13日

javaのiteratorとかiterable


※ とあるところに書いたものの転載です。
※ Java8の話はまた、あとで


こんなコードよく書くよね。


BufferedReader reader = new BufferedReader(new FileReader("myfile.txt"));
String line = null;
while ((line = reader.readLine()) != null) {
     System.out.println(line);
}

まあ、でも、ループの条件式とのとことか、微妙でわかりにくいし、変数のスコープも大きいよね。と言うか、どっかのスクリプト言語みたいに


BufferedReader reader = new BufferedReader(new FileReader("myfile.txt"));
for (String line: reader) {
     System.out.println(line);
}

みたいに書きたくない?
ということで、そこら辺のもの。拡張forループとか言っても、結局iterator使ってグルグルイテレートしているだけ。なんで、イテレートできるようにしてあげたら上みたいな感じで書けるはず。ちなみに、拡張forループ使わずに

for (Iterator iter = reader.iterator(); iter.hasNext();) {
    String line = iter.next();

みたいな書き方は全く嬉しくないので、forループが拡張されたからやりたくなることですね。
さて、実行時にbufferedReaderにメソッドを追加するとかは危険なので今回はBufferedReaderを拡張したIterableReaderを作っちゃいます。

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

public class A {
    public static class IterableReader extends BufferedReader implements Iterable {
        public IterableReader(Reader in) {
            super(in);
        }
        public IterableReader(Reader in, int sz) {
            super(in, sz);
        }
        public Iterator iterator() {
            try {
                final String l = readLine();
                return new Iterator() {
                    String last = l;
                    public boolean hasNext() {
                        return last != null;
                    }
   
                    public String next() {
                        String result = last;
                        if (last != null) {
                            try {
                                last = readLine();
                            } catch (IOException e) {
                                throw new RuntimeException(e);
                            }
                        }
                        return result;
                    }

                    public void remove() {
                    }
                };
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

        public static void main(String[] argv) {
        try (IterableReader reader = new IterableReader(new FileReader(argv[0]))) {
            for (String line: reader) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
       }

}

Iterableを実装したクラスを作ってあげて、その中でIteratorを返せばいいだけ。ちなみに、無名クラスにもコンストラクタ、書きたい…。
IOExceptionを投げられないので、RuntimeExceptionで代用しているのは許してください。

try-with-resource statementはjava7からだったのか…

※ とあるところで書いたことの転載です

普段、Java7で生活しているとJava6だとめんどうだなー、って感じることがありますよね。みんな早くJava7とかJava8に移行したほうがいいよね。

って言うことで、IO関係の処理ってcloseメソッドってIOExceptionがつきまっとっていて、うざいよね。みんなこんな感じで書いていると思うんだ。

InputStream io = null;

try {
    io = new FileInputStream("somefile")
    いろんな処理
} catch (IOException e) {
    例外処理的な何か
} finally {
    if (io != null) {
        try {
            io.close();
        } catch (IOException e) {
            // なんもしない
       }
    }
}

沢山の行数が必要ですね。Javaはなんと生産性の高いことか!じゃあ、Java7だとどうなるのか、って言うと、

try (InputStream io = new FileInputStream("somefile")) {
     いろんな処理
} catch (IOException e) {
    例外処理的な何か
}

はい、おしまい。余分なことが抜けてスッキリして、必要最小限のことに
CloseableかAutoCloseableのインターフェースを継承している人なら、tryから抜けてfinallyに入る直前に後始末をしてくれます。ちょーべんりー。プログラマは怠惰なものさ、ってきっとATEの誰かが言っています。
自動でクローズされなくても、スコープがtry節の中に限定されるだけでもスッキリします。(変数はなるべく局所化したほうがいい)

でも、プロダクトでJava7に上げてもらえないんですぅ。そんなあなたにlombok。この子さえいれば、さらにハッピーに。


@Cleanup InputStream io = new FileInputStream("somefile")
いろんな処理


@Cleanupは http://projectlombok.org/features/Cleanup.html
もう、魔法ですね。ただしlombokはバイトコードをいじるので、導入には許可をとってからにしましょう。一度、この子に触れると、普通のJavaは書けない体になってしまうので、気をつけましょう。

ちなみに、上の奴はさらに短く…

@Cleanup val io = new FileInputStream("somefile")
いろんな処理

どきどきJavaの国際化

※ とあるところでかいたものの転載


それでは、それでは。

変数名とかに英語表記ではなく、ローマ字で記述しているコードって沢山あるじゃないですか?アリエルは開発フレームワークなので、Javaのコードにはローマ字表記はコーディング規約として許していませんが、その上で作るアプリケーション(JavaScriptで記述)はちょっとカオスなところがあって、変数名にローマ字表記されているところもあります。

あっ、今回もJavaのお題です。僕はJavaScriptは嫌いなので…。

例えばこんな感じ

  public class MyClass {
      private int hensu = 1;

      public int add(int v) {
          hensu += v;
          return hensu;
     }
          
      public static void main(String[] argv) {
          MyClass obj = new MyClass();
          System.out.println(obj.add(10));
      }
  }

英語でもそうですけど、ローマ字でも読みにくいですよね。日本人なら日本語(漢字とかひらがな、カタカナ)ですよね。
ということで、変数名も日本語にしちゃいましょう!


  public class MyClass {
      private int 変数 = 1;

      public int add(int v) {
          変数 += v;
          return 変数;
     }
          
      public static void main(String[] argv) {
          MyClass obj = new MyClass();
          System.out.println(obj.add(10));
      }  }

ほら、読みやすくなった。じゃあ、メソッドも日本語化したほうがわかりやすくなるんじゃない?


  public class MyClass {
      private int 変数 = 1;

      public int 加算(int 入力値) {
          変数 += 入力値;
          return 変数;
      }

      public static void main(String[] argv) {
          MyClass インスタンス = new MyClass();
          System.out.println(インスタンス.加算(10));
      }
  }

ほら、スッキリした。クラス名も日本語に…。えーと、僕は日本語のファイル名があまり好きではないので、それはしません。インナークラスだったら許せます。なので、


  public class MyClass {
      static class 算術クラス {
          private int 変数 = 1;
         
          public int 加算(int 入力値) {
              変数 += 入力値;
              return 変数;
          }
      }

      public static void main(String[] コマンドライン引数) {
          算術クラス インスタンス = new 算術クラス();
          System.out.println(インスタンス.加算(10));
      }
  }


ほら、すごーく分かりやすい。Javaって素敵。でも、悲しいことが2つあります。1つ目はIMEを切り替えながら使わないといけないこと。これは、慣れの問題なのでがんばりましょう。もうひとつは、エディタによっては、全角空白が入り込むと、見つけるのがちょっと大変になることかな。

そう言えば、アリエルもJavaのテストコードに日本語のメソッド名を使っているところがあったような気がする。
ちなみに、pythonで同じようなことをした時のもの。
  http://blog.liris.org/2012/02/python3.html


※ 今回はネタでした。
※ 次回はlombokかもしれない。でも、スレッドのお話かもしれない。グローバル変数についてのお話かもしれない。

javaのEnumSet

とあるところで書いたものの転載 

EnumSetは、ビット集合を扱っていないとよくわかってもらえないんじゃないかと思ってるんですが、EnumSetについてのお話です。
Cの世代のビット演算大好きなおっさんの話は書かないので、そっちは自分で調べてください。それから、メモリの話もしないです。

それから、これを読むときはカービーダンスの樫木先生のしゃべり方を思い出しながら読んでね。

-----------------

さて、コードの中を見ていると、こんな感じでbooleanで設定のon/offを制御しているところが沢山あるんだよ。

class MySomeClass {
    private boolean opt_1 = false;
    private boolean opt_2 = false;
    private boolean opt_3 = false;
    private boolean opt_4 = false;
     :
}

多分、コードを書いている人はこんなの沢山見ているはずだよ。前回のenumの場合、オプションでとりうる値が複数あって、その中の一個を指定できるってことだったんだよね。でも、今回みたいに、booleanで複数のオプションがあって、その組み合わせがある場合、enumだけだと制御できないよね。

で、そんな組み合わせを簡単に扱えるようにするのがEnumSetなんだ。って言っても、わかんないよね。
コードで見ていったほうがいいかな。

オプション関係は一つのまとまりなので、まとまりはまとまりとしてわかりやすくしたいよね。で、enumでオプション関係をきり出しちゃいます。

class MySomeClass {
   static enum MyOpt { opt_1, opt_2, opt_3, opt4 };
}

それで、実際の値を保持する部分はEnumSetに指定するよ。

class MySomeClass {
   public static enum MyOpt { opt_1, opt_2, opt_3, opt_4 };
   private EnumSet opt = EnumSet.noneOf(MyOpt.class);
}

こんな感じで、4つあったオプションのフィールドが一個にまとまっちゃいました。
実際に使うときは、普通のSetと同じように扱えるよ。

  opt.add(MyOpt.opt_1)

って感じでオプションをセットできるよ。

  opt.contains(MyOpt.opt_2)

のようにして、値がセットされているかどうか判定できるよ。使うときにbooleanそのものよりもちょっと面倒だけどね。

値を削除するのはremoveだよ。


もうちょっと別の使い方は、メソッドの引数がbooleanの羅列で動作を変える場合だよね。
こんなメソッド、気がついたらよくできちゃっているのよね。

public void func(boolean opt1, boolean opt2, boolean opt3, boolean opt4) {
   :
}

で、さらに今回opt5が必要になった・・・。悪夢だよ。そんなら

public void func(EnumSet opt) {
   :
}

ほら、スッキリした。使う方は、メソッドのマニュアルを見ながら、オプションの意味を確認していたのが、enumの値を見ればオプションの意味をしることができるよ。
最後に呼び出し元を見てみよう。

func(true, false, false, true);

  ↓

func(EnumSet,of(MyOpt.opt1, MyOpt.opt4));

どっちが分かりやすい?




うーん、やっぱりビットのお話から入ったほうが良かった?

javaのenum

とあるところで書いたものの転載


昔のコード

  int sex = 0; // 1: male, 2: female, 0: other

数字の意味が思い出せない年寄りのために、昔のコードをちょっとだけリファクタリング

  public static final int SEX_MALE = 1;
  public static final int SEX_FEMALE = 2;
  public static final int SEX_OTHER = 0;

  int sex = SEX_OTHER;

でも、こんなコードが何処かで…

  this.sex = 3;

maleとfemaleを足しあわせたものはOTHERじゃなくって、3という発明をしちゃった開発者が…。そんな発明は素敵だけど、もう、させないぞ。

  public enum Sex {
      MALE(1),
      FEMALE(2),
      OTHER(0);

     private int v;
    
     private Sex(int v) {
         this.v = v;
     }
   
     public int getIntValue() {
        return v;
     }
  }

  Sex sex = Sex.OTHER;

※ public enum sex {OTHER, MALE, FEMALE}でもいいけど、特別な値もいれられるために、あえて上のように書いています。

これで、MALEとFEMALEを足しあわせたものは、勝手に作れなくなったよ。


でも、formがポストしてくるのはfemaleとかmaleとかの文字列です。どうやって、区別するんですか?

  if (value.equals("male")) {
     sex = Sex.MALE;
  } else if (value.equals("female")) {
    sex = Sex.FEMALE;
  } else {
    sex = Sex.OTHER;
  }

嘘です。こんなコード、書いていたら、アリエルのこわーい中山さんという人に袋たたきに会います。

  sex = Sex.valueOf(value.toUpperCase());


でも、フォームの場所によっては数字で来るんです。
じゃあ、Sexがもっといろいろできるようにしたちゃえ。

   public enum Sex {
     : 中略
     public Sex valueOf(int v) {
        for (Sex s: Sex.values()) {
            if (s.getIntValue() == v) {
                return s;
            }
         }
         return OTHER;
     }
   }

でも、変な値が来たら無条件にOTHERにするのはどうも・・・。ということで

     public Sex valueOf(int v) {
        for (Sex s: Sex.values()) {
            if (s.getIntValue() == v) {
                return s;
            }
         }
         throws IllegalArgumentException("sex : " + v);
     }