Lombokアノテーションのメモ

Javaコンパイル時に定型コードを自動生成してくれるLombokをいろいろ触ってみたのでメモする。

@NonNull

メソッドの引数に付与してnullチェックを生成する。 メソッドの先頭でnullかどうか確認し、nullの場合はNullPointerExceptionをthrowする。コンストラクタの場合はsuper()this()の呼び出しの後にチェックされる。

生成前

    public void hoge(@NonNull Object fuga){
        System.out.println(fuga);
    }

生成後

    public void hoge(@NonNull Object fuga) {
        if (fuga == null) {
            throw new NullPointerException("fuga is marked @NonNull but is null");
        } else {
            System.out.println(fuga);
        }
    }

@Cleanup

ローカル変数の宣言に付与して、スコープを抜ける前にオブジェクトのクリーンアップ処理を追加する。

具体的にはtry-finally節の追加とfinally節中にclose()メソッドの呼び出しを追加する。@Cleanupの引数にメソッド名を文字列で指定して、close()メソッド以外を呼ぶこともできる。

生成前

    public void hoge() throws IOException {
        @Cleanup BufferedReader reader = new BufferedReader(new FileReader("testfile.txt"));
        System.out.println(reader.readLine());
    }

生成後

    public void hoge() throws IOException {
        BufferedReader reader = new BufferedReader(new FileReader("testfile.txt"));
        try {
            System.out.println(reader.readLine());
        } finally {
            if (Collections.singletonList(reader).get(0) != null) {
                reader.close();
            }
        }
    }

@Getter/@Setter

フィールドに付与してgetterとsetterを生成する。メソッド名はフィールド名の先頭を大文字にしてget/setをつけたものが生成されるが、フィールドの型がbooleanの場合、getterメソッド名はgetではなくisをつけた名前になる。

可視性はデフォルトでpublicになるが、@Getter/@Setterの引数にAccessLevel.*を指定すると変更することができる。

クラス定義にアノテーションを付与した場合、インスタンスフィールド全てが生成対象になる。 生成したくないフィールドがある場合、AccessLevel.NONEをフィールドに付与すると生成されなくなる。

生成前

    @Setter
    @Getter
    private int hoge;

    @Getter(AccessLevel.PACKAGE)
    private boolean fuga;

生成後

    private int hoge;
    private boolean fuga;

    public void setHoge(int hoge) {
        this.hoge = hoge;
    }

    public int getHoge() {
        return this.hoge;
    }

    boolean isFuga() {
        return this.fuga;
    }

@ToString

クラス定義に付与して、toString()メソッドの実装を生成する。 デフォルトでは、クラス名(フィールド名1=値, フィールド名2=値,...)形式の文字列を返す。アノテーションの引数にincludeFieldNames=falseを追加するとフィールド名を取り除くことができ、callSuper=trueを指定すると親クラスのtoString()メソッドの結果を含めることができる。

デフォルトではインスタンスフィールド全てが文字列の生成対象となる。除外したい場合はフィールドに@ToString\.Excludeアノテーションを付与する。もしくはクラス定義の@ToStringonlyExplicitlyIncluded=trueを指定して表示したいフィールドのみ@ToString\.Includeを付与することで、表示するフィールドを制御できる。

引数なしのインスタンスメソッドに@ToString\.Includeを付与することで、メソッドの実行結果も生成文字列に含めることもできる。

生成前

    @ToString(callSuper=true)
    public class Sandbox {
        private int hoge;
        private boolean fuga;
    }

生成後

    public String toString() {
        return "Sandbox(super=" + super.toString() + ", hoge=" + this.hoge + ", fuga=" + this.fuga + ")";
    }

@EqualsAndHashCode

equals()hashCode()メソッドを生成する。

生成前

@EqualsAndHashCode
public class Sandbox {
    @NonNull private Integer hoge;
    private boolean fuga;
}

生成後

長いので省略。equals()はフィールド同士をequals()メソッドで比較する。

コンストラクタ生成(@NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor)

@NoArgsConstructor

引数なしのコンストラクタを生成する。 未初期化のfinalフィールドを持つ場合コンパイル時エラーとなるが、force=trueを指定した場合は0かfalseかnullで初期化される。@NonNullを付与したフィールドがあった場合でもnullチェック処理は生成されないので注意が必要。

生成前
@NoArgsConstructor()
public class Sandbox {
    @NonNull private Integer hoge;
    private boolean fuga;
}
生成後
public class Sandbox {
    @NonNull
    private Integer hoge;
    private boolean fuga;

    public Sandbox() {
    }
}

@RequiredArgsConstructor

すべての初期化されていないfinalフィールド・@NonNullが付与されたフィールドを引数に持つコンストラクタを生成する。引数の順序はクラス内の出現順と同じ。 @NonNullのnullチェックは生成される。

生成前
@RequiredArgsConstructor()
public class Sandbox {
    @NonNull private Integer hoge;
    private boolean fuga;
}
生成後
public class Sandbox {
    @NonNull
    private Integer hoge;
    private boolean fuga;

    public Sandbox(@NonNull Integer hoge) {
        if (hoge == null) {
            throw new NullPointerException("hoge is marked @NonNull but is null");
        } else {
            this.hoge = hoge;
        }
    }
}

@AllArgsConstructor

全てのフィールドを引数に持つコンストラクタを生成する。 @NonNullのnullチェックは生成される。

生成前
@AllArgsConstructor()
public class Sandbox {
    @NonNull private Integer hoge;
    private boolean fuga;
}
生成後
public class Sandbox {
    @NonNull
    private Integer hoge;
    private boolean fuga;

    public Sandbox(@NonNull Integer hoge, boolean fuga) {
        if (hoge == null) {
            throw new NullPointerException("hoge is marked @NonNull but is null");
        } else {
            this.hoge = hoge;
            this.fuga = fuga;
        }
    }
}

@Data

@ToString, @EqualsAndHashCode, @Getter, @Setter, @RequiredArgsConstructorを指定した場合と同じ。

生成前

@Data
public class Sandbox {
    @NonNull private Integer hoge;
    private boolean fuga;
}

生成後

長いので省略。生成されたメソッドは次の通り。

  • @RequiredArgsConstructor
    • public Sandbox(@NonNull Integer hoge)
  • @Getter/@Setter
    • public Integer getHoge()
    • public boolean isFuga()
    • public void setHoge(@NonNull Integer hoge)
    • public void setFuga(boolean fuga)
  • @EqualsAndHashCode
    • public boolean equals(Object o)
    • protected boolean canEqual(Object other)
    • public int hashCode()
  • @ToString
    • public String toString()

@Value

@Dataをimmutableにしたもの。すべてのフィールドはprivate finalに設定され、Setterは生成されない。 クラスにもfinalが設定されるが@NonFinalを指定していると設定されない。 生成されるメソッドが先に定義されていた場合は、メソッドが生成されないだけでエラーは発生しない。

生成前

@Value
public class Sandbox {
    @NonNull private Integer hoge;
    private boolean fuga;
}

生成後

こちらも省略。

@Builder

クラス定義に付与してBuilderクラスを生成する。生成するコードは次の通り。

  • Innerクラスの${クラス名}Builderを生成する

  • Builderインスタンスを返すbuilder()メソッドを生成する。

  • ${クラス名}Builderに次のメソッド・フィールドを生成する
    • 親クラスが持つフィールドのうち、非staticかつ非finalなものをprivateで定義する
    • Setterっぽいメソッドを定義する(thisを返す)
    • Builderインスタンスから親クラスのインスタンスを返すbuild()メソッドを定義する
    • toString()メソッドを定義する

生成前

@Builder
public class Sandbox {
    @NonNull private Integer hoge;
    private boolean fuga;
}

生成後

public class Sandbox {
    @NonNull
    private Integer hoge;
    private boolean fuga;

    Sandbox(@NonNull Integer hoge, boolean fuga) {
        if (hoge == null) {
            throw new NullPointerException("hoge is marked @NonNull but is null");
        } else {
            this.hoge = hoge;
            this.fuga = fuga;
        }
    }

    public static Sandbox.SandboxBuilder builder() {
        return new Sandbox.SandboxBuilder();
    }

    public static class SandboxBuilder {
        private Integer hoge;
        private boolean fuga;

        SandboxBuilder() {
        }

        public Sandbox.SandboxBuilder hoge(Integer hoge) {
            this.hoge = hoge;
            return this;
        }

        public Sandbox.SandboxBuilder fuga(boolean fuga) {
            this.fuga = fuga;
            return this;
        }

        public Sandbox build() {
            return new Sandbox(this.hoge, this.fuga);
        }

        public String toString() {
            return "Sandbox.SandboxBuilder(hoge=" + this.hoge + ", fuga=" + this.fuga + ")";
        }
    }
}

@Synchronized

メソッドに付与して、指定したオブジェクトでロックするsynchronizedブロックを追加する。 デフォルトの場合、インスタンスメソッドは$lockフィールド、クラスメソッドは$Lockクラスフィールドが使用される。 存在しない場合はprivateで作成される。

別のフィールドでロックしたい場合は@Synchronizedの引数にフィールド名を文字列で指定する。

生成前

    @Synchronized
    public void hoge(){
        System.out.println("hoge");
    }

    @Synchronized
    public static void fuga(){
        System.out.println("fuga");
    }

生成後

    private final Object $lock = new Object[0];
    private static final Object $LOCK = new Object[0];

    public void hoge() {
        synchronized(this.$lock) {
            System.out.println("hoge");
        }
    }

    public static void fuga() {
        synchronized($LOCK) {
            System.out.println("fuga");
        }
    }

@Log

クラス定義に付与すると、static finalなlogフィールドを生成し指定したロガーで初期化する。各ロガー実装に応じたアノテーションが用意されている。

生成前

@Log
public class Sandbox {
    @NonNull private Integer hoge;
    private boolean fuga;
}

生成後

    private static final Logger log = Logger.getLogger(Sandbox.class.getName());

参考

rbenv, ruby-build導入メモ

複数のバーションのrubyを切り替えられるようにするrbenvとruby-buildをインストールしてみたのでメモします。

まずは、homebrewでコマンド本体をインストールします。

% brew install rbenv ruby-build

インストール途中にシェルの設定が書かれているのでこの通り追加します。PATHの設定、補完関数の追加、rbenvコマンドの再定義などをしているようです。
2行目を~/.zshrcに追加すればいいのですが、zshの場合は$(rbenv init -)を$(rbenv init - zsh)に変えないとエラーが出るので注意です。

To enable shims and autocompletion add to your profile:
  if which rbenv > /dev/null; then eval "$(rbenv init -)"; fi

設定を読み直してエラーを吐かなければOKです。

% source .zshrc

次にRubyのビルドのための準備です。OSX標準のopensslとreadlineはアレらしいのでhomebrewからインストールします。

% brew install openssl
% brew link openssl
% brew install readline
% brew link readline

installサブコマンドを-l付きで実行するとインストール可能なrubyのバージョンが表示されるので、ここから選択します。JRubyも入っているんですね。

% rbenv install -l
Available versions:
(以下長いので適当に抽出)
  1.8.6-p420
  1.8.7-p249
  1.8.7-p302
  1.8.7-p370
  1.9.3-p286
  jruby-1.7.0
  maglev-1.0.0
  rbx-1.2.4
  rbx-2.0.0-dev
  ree-1.8.7-2012.02

先ほどインストールしたopensslとreadlineにリンクさせる為にオプションを付けてインストールコマンドを叩きます。

% CONFIGURE_OPTS="--with-readline-dir=$(brew --prefix readline) --with-openssl-dir=$(brew --prefix openssl)" rbenv install 1.9.3-p286

rbenvのrubyの切り替え方法は以下の3つがあります。

  • global そのユーザで有効
  • local そのディレクトリで有効
  • shell そのシェルで有効

今回は普段使うバージョンの選択なのでglobalサブコマンドでバージョンを指定します。 

% rbenv global 1.9.3-p286
% ruby --version
ruby 1.9.3p286 (2012-10-12 revision 37165) [x86_64-darwin12.2.1]

1.9.3p286を使うことができています。

仕組みはこちらで解説されていますが、~/.rbenv/versions以下に各バージョンのバイナリをインストールし、~/.rbenv/shims/に使用するバージョンのシンボリックリンクをおいてPATHを通しているようです。

またgemsetは上の3つの切り替え方法とは関係なく、現在選択しているrubyのバージョンに紐付いているようです。gemで実行コマンドをインストールするような操作をした場合、rbenv側で対応するシンボリックリンクを作成しなければならないため、コマンド実行前に

% rbenv rehash

を実行する一手間が必要になります。