第三回ありえる社内勉強会 「いわががのLombok」

Preview:

Citation preview

第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を手にいれてみませんか?

Recommended