Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Andrey Adamovich - Groovy 2 Cookbook - 2013.pdf
Скачиваний:
44
Добавлен:
19.03.2016
Размер:
26.28 Mб
Скачать

Using Groovy Language Features

There's more...

Extension methods existed in Groovy before v2.0. Most of the magical GDK functionality (for example, additional methods available on the java.lang.String or java.io.File classes) is implemented in that way. But since Groovy 2.0, creating and packaging your own extension modules became possible.

Extension modules also work if you use the @Grab annotations to append classpath dependencies to your scripts (see the Simplifying dependency management with Grape recipe in Chapter 2, Using Groovy Ecosystem).

You can also append static methods to the classes. The mechanism works in the exactly same way with the only small exception that you need to use staticExtensionClasses in the module descriptor to refer to the class implementing those extensions. Also, if your module defines both static and non-static extension points, then those should be located in different classes.

That's not the only way to extend existing classes. Another approach is to use metaclass facilities at runtime. This technique is discussed in more detail in Chapter 9, Metaprogramming and DSLs in Groovy.

See also

ff Groovy's User Guide pages about extension modules: http://docs.codehaus. org/display/GROOVY/Creating+an+extension+module

Defining type-checking rules for dynamic code

The defining characteristic of Groovy is probably the dynamic nature of the language. Dynamic languages possess the capacity to extend a program at runtime, including changing types, behaviors, and object structures. With these languages, the things that static languages do at compile time can be done at runtime; we can even execute program statements that are created on the fly at runtime. Another trait of Groovy, typical of other dynamic languages such as Ruby or Python, is dynamic typing. With dynamic typing, the language automatically tests properties when expressions are being (dynamically) evaluated (that is, at runtime).

In this recipe, we will present how Groovy can be instructed to apply static typing checks to code elements to warn you about potential violations at compiletime; that is, before code is executed.

126

www.it-ebooks.info

Chapter 3

Getting ready

Let's look at some script as follows:

def name = 'john' printn naame

The Groovy compiler groovyc is perfectly happy to compile the previous code. Can you spot the error?

But, if we try to execute the code we get, not surprisingly, the following exception:

Caught: groovy.lang.MissingPropertyException: No such property: naame

for class: DynamicTypingExample

Dynamic typing is a very controversial topic. Proponents of static typing argue that the benefits of static typing encompass:

1.Easier to spot programming mistakes (for example, preventing assigning a Boolean to an integer).

2.Self-documenting code thanks to type signatures.

3.The compiler has more opportunities to optimize the code (for example, replacing a virtual call with a direct one when the receiver's type is statically known).

4.More efficient development tools (for example, autocompletion of members of a class).

Defenders of dynamic typing return fire by claiming that static typing is inflexible and has a negative impact on prototyping systems with volatile or unknown requirements. Furthermore, writing code in dynamic languages requires more discipline than writing in statically typed languages. This is often translated into a greater awareness for testing—and unit testing in particular—among developers who decide to produce code with dynamic languages.

It is worth noting that Groovy also supports optional static typing, even though the name is slightly misleading. In Groovy, we can declare a variable's type as follows:

int var = 10

or

String computeFrequency(List<String> listOfWords) { }

Still, the compiler would not catch any compilation error if we assigned the wrong type to a typed variable:

int var = "Hello, I'm not a number!"

127

www.it-ebooks.info

Using Groovy Language Features

Groovy has optional typing mostly for Java compatibility and to support better code readability.

To mitigate some of the criticism inherent to dynamic typing, the Groovy team has introduced static type-checking since v2.0 of the language. Static type-checking enables the verification of the proper type and ensures that the methods we call and properties we access are valid for the type at compile time, hence decreasing the number of bugs pushed to the runtime.

How to do it...

Forcing the compiler to enable static type-checking it is simply a matter of annotating the code that we want to be checked with the @groovy.transform.TypeChecked annotation. The annotation can be placed on classes or individual methods:

1.Create a new Groovy file (with the .groovy extension) with the following code:

def upperInteger(myInteger) { println myInteger.toUpperCase()

}

upperInteger(10)

2.From the shell, compile the code using the Groovy compiler groovyc. The compiler shouldn't report any error.

3.Now, run the script using groovy. The runtime should throw an exception: caught: groovy.lang.MissingMethodException:

No signature of method: java.lang.Integer.toUpperCase() is applicable for argument types: () values: []

4.Add the type-checking annotation to the upperInteger function:

@groovy.transform.TypeChecked def upperInteger(myInteger) {

println myInteger.toUpperCase()

}

5.Compile the code again with groovyc. This time, the compiler reports the type error:

DynamicTypingExample.groovy: 3: [Static type checking] - Cannot find matching method java.lang.Object#toUpperCase(). Please check if the declared type is right

and if the method exists. @ line 3, column 11.

println myInteger.toUpperCase()

^

128

www.it-ebooks.info

Chapter 3

How it works...

If we place the annotation on a class, then type-checking is performed on all the methods, closures, and inner classes in the class. If we place it on a method, the type-checking is performed only on the members of the target method, including closures.

Static type-checking is implemented through an AST Transformation (see more on AST Transformations in Chapter 9, Metaprogramming and DSLs in Groovy) and can be used to enforce proper typing for a number of different cases. The following list shows a collection of bugs that would not be caught by the compiler but are caught by the TypeChecked annotation.

1.Variable names:

@TypeChecked

def variableName() { def name = 'hello' println naame

}

The variable named name is misspelled in the second line. The compiler outputs:

MyClass.groovy: 7: [Static type checking] - The variable [naame] is undeclared..

2.GStrings:

@TypeChecked def gstring() {

def name = 'hello'

println "hello, this is $naame"

}

Similar to the previous bug, the compiler complains about the undeclared variable naame.

3.Collections:

@TypeChecked

def collections() { List myList = []

myList = "I'm a String"

}

Here, the compiler detects that we are assigning a String to a List and throws an error: [Static type checking] - Cannot assign value of type java. lang.String to variable of type java.util.List.

129

www.it-ebooks.info

Using Groovy Language Features

4.Collection type:

@TypeChecked

def moreCollections() { int[] array = new int[2] array[0] = 100

array[1] = '100'

}

Similarly to the previous example, the compiler detects the invalid assignment of a String to an element of an array of ints.

5.Return type:

@TypeChecked

int getSalary() {

'fired on ' + new Date().format('yyyy-MM-dd')

}

Return types are also verified by the static type-checking algorithm. In this example, the compiler throws the following error:

[Static type checking] - Cannot return value of type java.lang. String on method returning type int

6.Return type propagation:

boolean test() { true

}

@TypeChecked void m() {

int x = 1 + test()

}

Wrong return types are also detected when the type is propagated from another method. In the previous example, the compiler fails with:

Static type checking] - Cannot find matching method int#plus(void). Please check if the declared type is right and if the method exists

7.Generics:

@TypeChecked def generics() {

List<Integer> aList = ['a','b']

}

Generics are supported as well. We are not allowed to assign the wrong type to a typed List.

130

www.it-ebooks.info

Chapter 3

There's more...

As you may have suspected, static type-checking doesn't play very nice with a dynamic language. Metaprogramming capabilities are effectively shut down when using the @TypeChecked annotation on a class or method.

Here is an example that uses metaprogramming to inject a new method into the class String

(the Dynamically extending classes with new methods recipe in Chapter 9, Metaprogramming and DSLs in Groovy has more information about method injections):

class Sample { def test() {

String.metaClass.hello = { "Hello, $delegate" } println 'some String'.hello()

}

}

new Sample().test()

When executed, this script yields the following output:

Hello, some String

If we add the @TypeChecked annotation to the class and try to run the script, we get the following error from the compiler:

[Static type checking] -

Cannot find matching method java.lang.String#hello().

Please check if the declared type is right and if the method exists.

The reason for the error is that the compiler doesn't see the new dynamic method and therefore fails. Another case where static type-checking is too picky is when using implicit parameters in closures. Let's take an example:

class Sample { def test() {

['one','two','three'].collect { println it.toUpperCase() }

}

}

new Sample().test()

This example, when executed, prints the following output:

ONE

TWO

THREE

131

www.it-ebooks.info

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]