1. 概要

Groovy は、動的で強力なJVM言語であり、クロージャー特性などの多数の機能を備えています。

このチュートリアルでは、Groovyでのメタプログラミングの概念について説明します。

2. メタプログラミングとは何ですか?

メタプログラミングは、メタデータを使用して自分自身または別のプログラムを変更するプログラムを作成するプログラミング手法です。

Groovyでは、実行時とコンパイル時の両方でメタプログラミングを実行できます。 今後は、両方の手法のいくつかの注目すべき機能について説明します。

3. ランタイムメタプログラミング

ランタイムメタプログラミングにより、クラスの既存のプロパティとメソッドを変更できます。 また、新しいプロパティとメソッドを添付することもできます。 すべて実行時に。

Groovyは、実行時にクラスの動作を変更するのに役立ついくつかのメソッドとプロパティを提供します。

3.1. propertyMissing

Groovyクラスの未定義のプロパティにアクセスしようとすると、 MissingPropertyExceptionがスローされます。例外を回避するために、GroovyはpropertyMissingメソッドを提供します。

まず、いくつかのプロパティを持つEmployeeクラスを作成しましょう。

class Employee {
    String firstName
    String lastName  
    int age
}

次に、 Employee オブジェクトを作成し、未定義のプロパティ addressを表示しようとします。その結果、 MissingPropertyException がスローされます。

Employee emp = new Employee(firstName: "Norman", lastName: "Lewis")
println emp.address
groovy.lang.MissingPropertyException: No such property: 
address for class: com.baeldung.metaprogramming.Employee

Groovyは、欠落しているプロパティ要求をキャッチするためのpropertyMissingメソッドを提供します。 したがって、回避することができます MissingPropertyException 実行時。

欠落しているプロパティのgetterメソッド呼び出しをキャッチするために、プロパティ名の単一の引数を使用してそれを定義します。

def propertyMissing(String propertyName) {
    "property '$propertyName' is not available"
}
assert emp.address == "property 'address' is not available"

また、同じメソッドにプロパティの値として2番目の引数を指定して、欠落しているプロパティのsetterメソッド呼び出しをキャッチすることもできます。

def propertyMissing(String propertyName, propertyValue) { 
    println "cannot set $propertyValue - property '$propertyName' is not available" 
}

3.2. methodMissing

methodMissing メソッドは、propertyMissingに似ています。 ただし、 methodMissing は、欠落しているメソッドの呼び出しをインターセプトするため、MissingMethodExceptionを回避します。

EmployeeオブジェクトでgetFullNameメソッドを呼び出してみましょう。 getFullName が欠落しているため、実行時にMissingMethodExceptionがスローされます。

try {
    emp.getFullName()
} catch (MissingMethodException e) {
    println "method is not defined"
}

したがって、メソッド呼び出しを try-catch でラップする代わりに、methodMissingを定義できます。

def methodMissing(String methodName, def methodArgs) {
    "method '$methodName' is not defined"
}
assert emp.getFullName() == "method 'getFullName' is not defined"

3.3. ExpandoMetaClass

Groovyは、すべてのクラスでmetaClassプロパティを提供します。 The metaClass プロパティは、のインスタンスを参照します ExpandoMetaClass

ExpandoMetaClass クラスは、実行時に既存のクラスを変換するためのさまざまな方法を提供します。 たとえば、プロパティ、メソッド、またはコンストラクターを追加できます。

まず、 metaClass プロパティを使用して、不足しているaddressプロパティをEmployeeクラスに追加しましょう。

Employee.metaClass.address = ""
Employee emp = new Employee(firstName: "Norman", lastName: "Lewis", address: "US")
assert emp.address == "US"

さらに進んで、実行時に不足しているgetFullNameメソッドをEmployeeクラスオブジェクトに追加しましょう。

emp.metaClass.getFullName = {
    "$lastName, $firstName"
}
assert emp.getFullName() == "Lewis, Norman"

同様に、実行時にEmployeeクラスにコンストラクターを追加できます。

Employee.metaClass.constructor = { String firstName -> 
    new Employee(firstName: firstName) 
}
Employee norman = new Employee("Norman")
assert norman.firstName == "Norman"
assert norman.lastName == null

同様に、metaClass.static。を使用してstaticメソッドを追加できます。

metaClass プロパティは、ユーザー定義クラスだけでなく、実行時に既存のJavaクラスを変更するのにも便利です。

たとえば、CapitalizeメソッドをStringクラスに追加しましょう。

String.metaClass.capitalize = { String str ->
    str.substring(0, 1).toUpperCase() + str.substring(1)
}
assert "norman".capitalize() == "Norman"

3.4. 拡張機能

拡張機能は、実行時にクラスにメソッドを追加し、グローバルにアクセスできるようにすることができます。

拡張機能で定義されたメソッドは、 self クラスオブジェクトを最初の引数として、常に静的である必要があります。

たとえば、 BasicExtension クラスを記述して、getYearOfBirthメソッドをEmployeeクラスに追加しましょう。

class BasicExtensions {
    static int getYearOfBirth(Employee self) {
        return Year.now().value - self.age
    }
}

BasicExtension を有効にするには、プロジェクトの META-INF /servicesディレクトリに構成ファイルを追加する必要があります。

それでは、org.codehaus.groovy.runtime.ExtensionModuleファイルを次の構成で追加しましょう。

moduleName=core-groovy-2 
moduleVersion=1.0-SNAPSHOT 
extensionClasses=com.baeldung.metaprogramming.extension.BasicExtensions

Employeeクラスに追加されたgetYearOfBirthメソッドを確認してみましょう。

def age = 28
def expectedYearOfBirth = Year.now() - age
Employee emp = new Employee(age: age)
assert emp.getYearOfBirth() == expectedYearOfBirth.value

同様に、クラスに static メソッドを追加するには、別の拡張クラスを定義する必要があります。

たとえば、 StaticEmployeeExtension クラスを定義して、staticメソッドgetDefaultObjEmployeeクラスに追加しましょう。

class StaticEmployeeExtension {
    static Employee getDefaultObj(Employee self) {
        return new Employee(firstName: "firstName", lastName: "lastName", age: 20)
    }
}

次に、 ExtensionModule ファイルに次の構成を追加して、StaticEmployeeExtensionを有効にします。

staticExtensionClasses=com.baeldung.metaprogramming.extension.StaticEmployeeExtension

ここで必要なのは、Employeeクラスでstatic getDefaultObjメソッドをテストすることだけです。

assert Employee.getDefaultObj().firstName == "firstName"
assert Employee.getDefaultObj().lastName == "lastName"
assert Employee.getDefaultObj().age == 20

同様に、拡張機能を使用して、IntegerLongなどのプリコンパイルされたJavaクラスにメソッドを追加できます。

public static void printCounter(Integer self) {
    while (self > 0) {
        println self
        self--
    }
    return self
}
assert 5.printCounter() == 0
public static Long square(Long self) {
    return self*self
}
assert 40l.square() == 1600l

4. コンパイル時のメタプログラミング

特定のアノテーションを使用すると、コンパイル時にクラス構造を簡単に変更できます。 つまり、アノテーションを使用して、コンパイル時にクラスの抽象構文ツリーを変更できます

ボイラープレートコードを減らすためにGroovyで非常に便利な注釈のいくつかについて説明しましょう。 それらの多くは、groovy.transformパッケージで入手できます。

注意深く分析すると、いくつかの注釈がJavaの ProjectLombokと同様の機能を提供していることがわかります。

4.1. @ToString

@ToString アノテーションは、コンパイル時にtoStringメソッドのデフォルトの実装をクラスに追加します。 必要なのは、クラスにアノテーションを追加することだけです。

たとえば、@ToStringアノテーションをEmployeeクラスに追加しましょう。

@ToString
class Employee {
    long id
    String firstName
    String lastName
    int age
}

次に、 Employee クラスのオブジェクトを作成し、toStringメソッドによって返される文字列を確認します。

Employee employee = new Employee()
employee.id = 1
employee.firstName = "norman"
employee.lastName = "lewis"
employee.age = 28

assert employee.toString() == "com.baeldung.metaprogramming.Employee(1, norman, lewis, 28)"

excludes include includePackage ignoreNullsなどのパラメーターを@ToStringで宣言して変更することもできます。出力文字列。

たとえば、Employeeオブジェクトの文字列からidpackageを除外しましょう。

@ToString(includePackage=false, excludes=['id'])
assert employee.toString() == "Employee(norman, lewis, 28)"

4.2. @TupleConstructor

Groovyで@TupleConstructorを使用して、パラメーター化されたコンストラクターをクラスに追加します。 このアノテーションは、各プロパティのパラメーターを持つコンストラクターを作成します。

たとえば、@TupleConstructorEmployeeクラスに追加しましょう。

@TupleConstructor 
class Employee { 
    long id 
    String firstName 
    String lastName 
    int age 
}

これで、クラスで定義されたプロパティの順序でパラメーターを渡すEmployeeオブジェクトを作成できます。

Employee norman = new Employee(1, "norman", "lewis", 28)
assert norman.toString() == "Employee(norman, lewis, 28)"

オブジェクトの作成中にプロパティに値を指定しない場合、Groovyはデフォルト値を考慮します。

Employee snape = new Employee(2, "snape")
assert snape.toString() == "Employee(snape, null, 0)"

@ToString と同様に、 excleds include includeSuperPropertiesなどのパラメーターを@TupleConstructorで宣言できます。必要に応じて、関連するコンストラクターの動作を変更します。

4.3. @EqualsAndHashCode

@EqualsAndHashCode を使用して、コンパイル時にequalsおよびhashCodeメソッドのデフォルトの実装を生成できます。

@EqualsAndHashCodeEmployee クラスに追加して、その動作を確認しましょう。

Employee normanCopy = new Employee(1, "norman", "lewis", 28)

assert norman == normanCopy
assert norman.hashCode() == normanCopy.hashCode()

4.4. @Canonical

@Canonicalは、@ ToString、@ TupleConstructor、および@EqualsAndHashCodeアノテーションの組み合わせです

追加するだけで、3つすべてをGroovyクラスに簡単に含めることができます。 また、3つのアノテーションすべての特定のパラメーターのいずれかを使用して@Canonicalを宣言できます。

4.5. @AutoClone

Cloneable インターフェースを実装するための迅速で信頼性の高い方法は、@AutoCloneアノテーションを追加することです。

@AutoCloneEmployeeクラスに追加した後、cloneメソッドを確認してみましょう。

try {
    Employee norman = new Employee(1, "norman", "lewis", 28)
    def normanCopy = norman.clone()
    assert norman == normanCopy
} catch (CloneNotSupportedException e) {
    e.printStackTrace()
}

4.6. @ Log、@ Commons、@ Log4j、@ Log4j2、および @Slf4jによるロギングサポート

Groovyクラスにロギングサポートを追加するには、groovy.util.loggingパッケージで利用可能なアノテーションを追加するだけです。

Employeeクラスに@Logアノテーションを追加して、JDKが提供するロギングを有効にしましょう。 その後、logEmpメソッドを追加します。

def logEmp() {
    log.info "Employee: $lastName, $firstName is of $age years age"
}

EmployeeオブジェクトでlogEmpメソッドを呼び出すと、コンソールにログが表示されます。

Employee employee = new Employee(1, "Norman", "Lewis", 28)
employee.logEmp()
INFO: Employee: Lewis, Norman is of 28 years age

同様に、 @Commons アノテーションを使用して、ApacheCommonsロギングサポートを追加できます。 @ Log4j は、Apache Log4j 1.xロギングのサポートに使用でき、 @ Log4j2 は、 Apache Log4j2.xに使用できます。 最後に、 @ Slf4j を使用して、 Simple Logging Facade forJavaサポートを追加します。

5. 結論

このチュートリアルでは、Groovyでのメタプログラミングの概念について説明しました。

その過程で、実行時とコンパイル時の両方で注目すべきメタプログラミング機能がいくつか見られました。

同時に、Groovyで利用できる追加の便利なアノテーションを調べて、よりクリーンで動的なコードを作成しました。

いつものように、この記事のコード実装はGitHubから入手できます。