第三回ありえる社内勉強会 「いわががのLombok」
-
Upload
yoshiaki-iwanaga -
Category
Technology
-
view
9.204 -
download
2
Transcript of 第三回ありえる社内勉強会 「いわががのLombok」
第3回 ありえる社内勉強会「いわががのLombok」
お前だれよ?
twitter: @kiris いわなが?いわがが?
Lombokって何?
http://projectlombok.org Javaの冗長性を排除する為
のライブラリ 「赤唐辛子」の意味 v0.10.4 MIT license
created by Roel Spilker
Reinier Zwitserloot
Javaの冗長性って?
こういうのとか
class Data { private int value;
public int getValue() { return value; } public void setValue(int value) { this.value = value; }}
Javaの冗長性って?
後、こういうのとか…
InputStream in = new InputStream(args[0]);try { ...} finally { If (in != null) in.close();}
Javaの冗長性って?
他にも、こういうのとか…
Map<String, List<String>> map = new HashMap<String, List<String>>();
...
for(Map.Entry<String, List<String>> entry : map) { ...}
Javaの冗長性って?
……
class MyClass { private static Log log = LogFactory.getLog(MyClass.class);
private final String name;
public MyClass(String name) { if (name == null) { throw new NullPointerException(); } this.name = name; }
@Override public int toString() { return “MyClass(name=”+ this.name +“)”; }
@Override public boolean equals(Object other) {
}
Javaの冗長性って?
こうならない為のLombok!続きはWebで!!
冗長の何がいけないの?
生産性が下がる コード量が増えて読みづらくなる バグが入り込む可能性がある 死にたくなる
Lombokの導入
Lombokを入手する
Download lombok.jar http://projectlombok.org/download.html
Maven or Ivy http://projectlombok.org/mavenrepo/index.html
Lombokを使う
Javac Classpathに追加
GWT java -javaagent:lombok.jar=ECJ
Play Framework https://github.com/aaronfreeman/play-lombok#readme
ECJ java -javaagent:lombok.jar=ECJ \
-Xbootclasspath/p:lombok.jar -jar ecj.jar -cp lombok.jar
LombokをIDEでも使う
Eclipse, NetBeans なんかに対応 IDEA IntelliJはまだ未対応
java -jar lombok.jar
Lombokを試してみる
@Data
@Dataの主な機能 全てのフィールドのgetter / setter の生成 toString, equals, hashCodeの生成 finalフィールドを引数にしたコンストラクタの生成
import lombok.Datapublic @Data class DataExample { private final String name; private int count; private List<Object> list;}
結果の確認(delombok)
変換後のコードを出力 java -jar lombok.jar delombok -p ${src}
ファイルとして保存 java -jar lombok.jar delombok -d ${output} ${src}
Ant <delombok verbose="true" encoding="UTF-8" to="$
{output}" from="${src}" /> Maven
https://github.com/awhitford/lombok.maven
@Data(変換後)
public class DataExample { private final String name; private int count; private List<Object> list;
public DataExample(String name) { this.name = name; }
public String getName() { return name; } public int getCount() { return count; } public void setCount(int count) { this.count = count; } @Override public String toString() { ... } @Override public boolean equals(Object other) { ... } @Override public int hashCode() { … }}
Eclipseからも即時反映
その場でアウトラインや補完候補に表示されます
他の機能
@Getter / @Setter
@Getter(lazy=true)
@ToString
@EqualsAndHashCode
@NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor
@Data
@Cleanup
@Synchronized
@SneakyThrows
@Log
val
@Delegate
http://projectlombok.org/features/index.html
@Getter / @Setter
Getter / Setterの自動生成 @Dataよりも優先
public class GetterSetterExample { @Getter @Setter private String name; @Getter(AccessLevel.PROTECTED) private int age;}
@Getter / @Setter(変換後)
Getter / Setterの自動生成 @Dataよりも優先
public class GetterSetterExample { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } protected int getAge() { return age; }}
@Getter(lazy=true)
いわゆるメモ化 サブルーチン(関数)呼び出しの結果を保持し、再利用するこ
とで、そのサブルーチンの呼び出し毎の再計算を防ぐpublic class GetterLazyExample { @Getter(lazy=true) private final double[] cached = expensive();
private double[] expensive() { ... }}
@Getter(lazy=true)(変換後)
いわゆるメモ化 サブルーチン(関数)呼び出しの結果を保持し、再利用するこ
とで、そのサブルーチンの呼び出し毎の再計算を防ぐpublic class GetterLazyExample { public double[] getCached() { // 本当はthread-safe if (!this.$lombok$lazy1i) { this.$lombok$lazy1v = expensive(); this.$lombok$lazy1i = true; } return this.$lombok$lazy1v; }
private double[] expensive() { ... }}
@Cleanup
リソースの片付けを自動で行なう
public static void main(String[] args) throws IOException { @Cleanup InputStream in = new FileInputStream(args[0]); @Cleanup("release") MyResource resource = new MyResource(); ...}
@Cleanup(変換後)
リソースの片付けを自動で行なう
public static void main(String[] args) throws IOException { InputStream in = new FileInputStream(args[0]); try { MyResource resource = new MyResource(); try { ... } finally { if (resource != null) resource.release(); } } finally { if (in != null) in.close(); }}
@Synchronized
this以外のロックオブジェクトで排他
public class SynchronizedExample { private final Object readLock = new Object();
@Synchronized private int foo() { return 1; } @Syncrhonized("readLock") private int bar() { return 2; }}
@Synchronized(変換後)
this以外のロックオブジェクトで排他
public class SynchronizedExample { private final Object $lock = new Object(); private final Object readLock = new Object();
private int foo() { synchronized($lock) { return 1; } }
private int bar() { synchronized(readLock) { return 2; } }}
val
ローカル変数の型宣言を省略
public static void main(String[] args) { val map = new HashMap<String, List<String>>(); ... for(val entry : map.entrySet()) { ... }}
val(変換後)
ローカル変数の型宣言を省略
public static void main(String[] args) { final Map<String, List<String>> map = new HashMap<String, List<String>>();
...
for(final Map.Entry<String, List<String>> entry : map.entrySet()) { ... }}
etc
@ToString toStringの生成
@EqualsAndHashCode equalsとhachCodeの生成
@NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor コンストラクタの生成
@SneakyThrows チェック例外を非チェック例外のようにthrowする
@Delegate 移譲処理の生成
http://projectlombok.org/features/index.html
Lombokのメリット
コードの冗長性の排除 生産性が上がる コードの見通しを良くなる バグを埋め込む可能性を減らす 心の平穏
Lombokのデメリット
魔法に見える(not WYSIWYG) Lombokのバグに悩まされる可能性がある リファクタリング機能との衝突 デバッグがややこしくなる
ここまでのまとめ
LombokはJavaの冗長性を排除する Lombokの導入はとても簡単 魔法には代償をともなう
Break time
Lombokの仕組み
ソースコード生成?バイトコード生成?
いいえ、AST変換です
JavaのASTを直接生成・変換してます ソースコード生成と違ってコードが膨れあがりません バイトコード生成と違って同じコンパイル単位のクラスか
らも可視的です
Lombokが保持するAST
JavacとECJの二つのASTを別々に保持 AnnotationHandlerも各AST毎に実装する必要がある 二つのASTを統合するためのプロジェクトも進行中
https://github.com/rzwitserloot/lombok.ast
Lombokの処理の流れ
エントリーポイント
lombok.javac.apt.Processor implements javax.annotation.processing.Processor
lombok.eclipse.TransformEclipseAST EclipseのParserにパッチを当てて実行
https://github.com/rzwitserloot/lombok.patcher OSGi ClassLoaderに注入されて実行される
AnnotationHandlerの読み込み
プラグイン形式の読み込み @ProviderFor(JavacAnnotationHandler.class)
used SPI(http://code.google.com/p/spi/) Service Provider Interfaceのwrapper
ASTの探索
ASTをトラバースしてアノテーションを探索 アノテーションが見付かったら、
対応するAnnotationHandlerのhandleを実行するAnnotationVisitor
Implements JavacASTVisitor 独自のVisitorも定義可能
@ProviderFor(JavacASTVisitor.class) HandleVal
ASTの変換
各AnnotationHandlerや各ASTVisitorで 変換にはJavacなどの非公開APIを直接使用
com.sun.tools.javac.tree org.eclipse.jdt.internal
Lombokを拡張する
Lombokを拡張するには?
Lombokは外からの拡張を意識して作っているわけではない Lombok本体を模範することで拡張することは出来る
@Perf
メソッドの実行時間を出力
public class PerfExample { @Perf void foo() { ... }}
@Perf(変換後)
メソッドの実行時間を出力
public class PerfExample { void foo() { long $start = System.nanoTime(); try { … } finally { System.out.println(“PerfExample.foo = ”+ System.nanoTime() - $start)); } }}
プロジェクトの作り方
prototype: https://github.com/alexruiz/dw-lombok プロジェクト名などを置換 Ivyの設定を一部変更
ECJのjarが取得出来なかった Lombokの最新(0.10.4)を使いたかった
アノテーションの定義
トリガーとなるPerfアノテーションを作成する
@Target({ElementType.METHOD})@Retention(RetentionPolicy.SOURCE)public @interface Perf {}
AnnotationHandlerの作成
Javac用とECJ用の二つのAnnotationHandlerを作成する
// for javacpackage localhost.javac.handlers;
@ProviderFor(JavacAnnotationHandler.class)public class HandlePerf extends JavacAnnotationHandler<Perf> { @Override public void handle(AnnotationValues<Perf> annotation, JCAnnotation ast, JavacNode annotationNode) { ... }}
AST変換処理の実装
愚直にASTを作るだけの簡単なお仕事
TreeMaker maker = methodNode.getTreeMaker();
// long $start = long t1 = System.nanoTime();Name startName = methodNode.toName("$start");JCExpression nanoTimeMethod = chainDotsString(methodNode, "System.nanoTime");JCExpression nanoTimeApply = maker.Apply(List.<JCExpression>nil(), nanoTimeMethod, List.<JCExpression>nil());
JCVariableDecl startDef = setGeneratedBy(maker.VarDef(maker.Modifiers(0), startName, maker.TypeIdent(getCtcInt(TypeTags.class, "LONG")), nanoTimeApply), ast);
テスト
本体が用意しているテスト・インフラがそのまま使える
@RunWith(DirectoryRunner.class)public class TestWithEcj implements TestParams { @Override public Compiler getCompiler() { return ECJ; } @Override public boolean printErrors() { return true; }
@Override public File getBeforeDirectory() { return new File("test/transform/resource/before"); } @Override public File getAfterDirectory() { return new File("test/transform/resource/after-ecj"); } ...}
拡張されたLombokの実行
jar化してClasspathに追加すれば良い$ ant dist$ javac -cp “.:lib/build/lombok.jar:dist/lombok-perf.jar”\Example.java$ java -cp “.”Example
感想
変換処理はわりと愚直にAST作るだけの簡単なお仕事 本体のコードこそが最高のサンプル
https://github.com/rzwitserloot/lombok/tree/master/src/core/lombok/javac/handlers https://github.com/rzwitserloot/lombok/tree/master/src/core/lombok/eclipse/handlers
まとめ
Lombokは皆さんのJava嫌いをちょっとだけ癒してくれます
きっとScalaプログラマにもなれないJavaプログラマの皆さん
Lombokを手にいれてみませんか?