
これを取る:
import groovy.transform.Canonical import groovy.transform.TupleConstructor @Canonical @TupleConstructor class Person { int id String firstName String lastName Date birthdate }
コンパイルすると、バイトコードでこれに類似したものが得られます。
100行を超えるHellのJavaボイラープレート
import java.util.Date; import java.util.Map; public class Person { private int id; private String firstName; private String lastName; private Date birthdate; // @TupleConstructor- public Person(Map parameters){ this.id = (int) parameters.get("id"); this.firstName = (String) parameters.get("firstName"); this.lastName = (String) parameters.get("lastName"); this.birthdate = (Date) parameters.get("birthdate"); } public Person(int id, String firstName, String lastName, Date birthdate) { this.id = id; this.firstName = firstName; this.lastName = lastName; this.birthdate =birthdate; } public Person(int id, String firstName, String lastName) { this(id, firstName, lastName, null); } public Person(int id, String firstName) { this(id, firstName, null, null); } public Person(int id) { this(id, null, null, null); } public Person() { this(0, null, null, null); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; if (id != person.id) return false; if (birthdate != null ? !birthdate.equals(person.birthdate) : person.birthdate != null) return false; if (firstName != null ? !firstName.equals(person.firstName) : person.firstName != null) return false; if (lastName != null ? !lastName.equals(person.lastName) : person.lastName != null) return false; return true; } @Override public int hashCode() { int result = id; result = 31 * result + (firstName != null ? firstName.hashCode() : 0); result = 31 * result + (lastName != null ? lastName.hashCode() : 0); result = 31 * result + (birthdate != null ? birthdate.hashCode() : 0); return result; } @Override public String toString() { return "Person{" + "id=" + id + ", firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + ", birthdate=" + birthdate + '}'; } public int getId() { return this.id; } public void setId(int paramInt) { this.id = paramInt; } public String getFirstName() { return this.firstName; } public void setFirstName(String paramString) { this.firstName = paramString; } public String getLastName() { return this.lastName; } public void setLastName(String paramString) { this.lastName = paramString; } public Date getBirthdate() { return this.birthdate; } public void setBirthdate(Date paramDate) { this.birthdate = paramDate; } }
まあ、はい、いいです。 しかし、このボイラープレート全体を生成して非表示にする優れたIDEの能力は言うまでもなく、 Lombokにはユニークなものはありません。
なぜGroovyなのか、なぜAST変換なのか?
この記事では、JavaプロジェクトでGroovy AST変換を使用する理由を簡単に実証し、(再び)今日のGroovyでのAST変換を説明します。 理由がすでにわかっていて、「方法と内容」だけが必要な場合は、「AST変換の概要」まで大胆にスクロールしてください。
それでは、なぜロンボクではなくAST変換なのでしょうか?
まず、AST変換を使用するために、Groovyを知ったり、Groovyを作成したり、実行時にGroovyを実行したりする必要はありません。 変換はソートのコンパイル中に発生し、Groovyは1つのjarによって依存関係のリストに追加されます。 それだけです
したがって、AST変換は、Groovyをプロジェクトに「ドラッグ」するための優れた方法です。「ほら、ボス、大丈夫、これはボイラーボイラーを処理するための単なるライブラリです!」 そしてもちろん、Gradleでのビルドの背後にあるSpockでのテストにより、実際のGroovyがコードに表示されます(動的、機能的、エレガント)。 AST変換は最初のステップにすぎません。
さらに、AST変換はLombokよりもはるかに拡張性が高く、強力で多用途です。
最後になりましたが、AST変換は、Eclipseだけでなく、Groovy対応のIDEでも完全にサポートされています。
LombokとAST変換のフロントエンドの比較は明らかにこの記事の範囲を超えているので、そこで停止しましょう。
バイトコードの生成はソースコードの生成よりも大きな利点があることは当然のように思えます(そして、腹を立てないようにエディターで「崩壊」します)-生成はコンパイル中に行われ、「サポート」する必要はありません。 1つの例は、IntelliJ IDEAがハッシュコードと同等を完全に生成することです。 新しいフィールドを追加するとき、ペンを使用してこれら2つのメソッドを消去し、再度生成します。 ふう。
Java開発者とGroovy開発者の両方にとってのAST変換の利点については、他にも多くのことが述べられていますが、その考えが明確であることを願っています。 練習に移りましょう。
AST変換の概要
もちろん、Groovyで最も重要な利点の1つは、 メタプログラミングです。 コンパイル時と実行時の2つのタイプがあります。
ランタイムメタプログラミングはおおよそ「ああ、存在しないメソッドを呼び出しましたか?」 怖くはありません。このメソッドを呼び出したときに考えていたことに基づいてスレッドを作成します。」 これには多くの例があります-ほぼすべてのGroovyライブラリーは、ビルダー、スラーパー、Grails、Ratpack、Gradleなど、すべてのものに基づいています。 しかし、今はそれについてではありません(これについて知りたい場合は、投稿の最後にある厚かましいPRの段落1を参照してください)。
次に、コンパイル中のメタプログラミングについて説明します。つまり、単純にコードにメタプログラミングを記述し、バイトコードに別のメタコードを取得する方法(まあ、または余分なもの)について説明します。
変換から始めましょう。これは、Groovy自体にフラッシュされており、注釈やその他の付加物はありません。
私たちは書きます:
class Person { String name }
出力は、すべてのフィールドが
private
(この場合は
name
)であるバイトコードであり、すべてのゲッターとセッターが書き込まれます(まあ、この場合は
getName()
と
setName(String name)
ですが、アイデアは明確です)。
この美しい小さなことは、コンパイル時のメタプログラミングの完全な例です。
ボイラープレートからのこの小さな配達を見て、素晴らしい男のダンノ・フェリンはこう言いました。「しかし、ゲッターとセッターを除いて、まだ多くのボイラープレートがあり、それらはすべて同じではありません! それでAST変換が生まれました(最初の、奇妙なことに、
@Bindable
。ただし、どのくらいのコードがスローされるかを見てみると、奇妙ではないかもしれません)。
AST変換は、Groovyのコンパイル中にオンザフライで抽象構文ツリーを変更する注釈のコレクションです。 ゲッターとセッターの追加は、アノテーションを追加しなくても常に機能する組み込みのAST変換であると言えます。 残りはオンデマンドでのみ含まれます。
私たちが持っているものを見てみましょう:
- そのため、アノテーションのパイオニアである
@Bindable
と彼女の友人である@Vetoable
は、ゲッターとセッターを実際のプロパティに変換し、リスナーをフックし、変更を聞いて元に戻し、禁止する機能を備えています。 -
@Category
と@Mixin
という非常にファッショナブルな言葉は、あるクラスの性質を別のクラスに追加します。 さて、 不純物 ! -
@Delegate
は、デリゲートが持つすべてのメソッドを追加し、当然、デリゲーションによって実装しますか?
私たちは書きます:
class Event { String name @Delegate Date date }
準備ができた委任を取得しますimport java.util.Date; public class Event { private String name; private Date date; public String getName() { return this.name; } public void setName(String name) { this.name = name; } public Date getDate() { return this.date; } public void setDate(Date paramDate) { this.date = paramDate; } public boolean after(Date otherDate) { return date.after(otherDate); } public boolean before(Date otherDate) { return date.before(otherDate); } public long getTime() { return date.getTime(); } public void setTime(long timestamp) { date.setTime(timestamp); } }
-
@Immutable
は、特にクラスを不変にします。
- セッターは
ReadOnlyPropertyException
スローします - クラスは
final
- フィールドは
private
かつfinal
なります - すべてのフィールドを持つコンストラクターが表示されます:パラメーターとマップの両方に(最初の例のように)
- 可変コンポーネントをバックアップするコードが表示されます
-
equals
、hashcode
およびtoString
が表示されます
- セッターは
- ボイラープレートを備えた同様のファイターの束があります:
@InheritConstructors
はスーパークラスからすべてのコンストラクターを追加し、@TupleConstructor
はキーがフィールド名で値が値であるマップコンストラクターを追加します(この@AutoClone
最初の例を@AutoClone
)、@AutoClone
および@AutoExternalize
add適切なメソッド、および@Canonical
は「正しいJavaクラス」を実行します。パラメーターなしのコンストラクター、すべてのパラメーター(行とマップの両方)を受け入れるコンストラクター、およびequals
、hashCode
およびtoString
ます。 まあ、@Immutable
ように、変更可能です-最初の例でも見ました。 - 別のファッショナブルな用語!
@Lazy
は、必要に応じてソフト参照でラップされた遅延初期化フィールドを(要求に応じて)作成します -
@Newify
使用すると、コンストラクターの名前の代わりにnew
メソッドを使用して(Rubyのように)、逆にコンストラクターの名前のみでnew
せずに(Pythonのように)オブジェクトを作成できます。 ここでは、おそらく、例は痛くない:
@Newify rubyLikeNew() { assert Integer.new(42) == 42 }
または
@Newify([Tree, Leaf]) buildTree() { Tree(Tree(Leaf(1), Leaf(2)), Leaf(3)) }
最後の例では、new
を使用せずにTree
とLeaf
を作成します。 Javaの同等物と比較してください:
public Tree buildTree() { return new Tree(new Tree(new Leaf(1), new Leaf(2)), new Leaf(3)); }
- そして、ここに長年の不正の修正があります。Grooveでは、デフォルトですべてのフィールドが
public
です。package
作り方@PackageScope
変換を通じて! - シングルトンをパターンと見なすかアンチパターンと見なすかに関係なく、それを書く必要がある場合があります。 さて、またはクラスに
@Singleton
を置くだけで、ダブルロックチェック付きの遅延初期化シングルトンの準備ができました。 - #razborpoletovy Andreyは、Groove 2.2 @Memoizedに含まれている素晴らしいメソッドを作成しました。これはメソッドの結果を記憶し、再度呼び出された場合、すぐに結果を返します(そしてパラメーターは重要です)
- そして最後に-
@NotYetImplemented
という注釈逸話@NotYetImplemented
テストの結果を反転します:合格するはずの結果、およびその逆です。def true=false //happy debugging
思い出させるという事実に加えて、このことはTDDに役立ちます-まだ登録されていないものを含むすべてのメソッドに対してテストをスローし、@NotYetImplemented
を使用し@NotYetImplemented
。 したがって、これらのテストが失敗しても、残りのテストは妨げられません。
そして、それだけではありません! アーチの重要な
@CompileStatic
、
@Field
、および
@Field
性に苦しむことに耐える一連の注釈もありますが、それでも別の時間です(まあ、または投稿の最後に厚かましいPRのポイント1を参照してください)。
PSこれが何であるかを知ったので、新しいAST変換をどのように 、そしてどのように書くかについての 2つの興味深い記事があります 。 下のPRのパラグラフ2の同じものを参照してください。
そして今、2点からの無作法なPR: