Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Kenneth A. Kousen - Making Java Groovy - 2014.pdf
Скачиваний:
47
Добавлен:
19.03.2016
Размер:
15.36 Mб
Скачать

Using Groovy features in Java

This chapter covers

Basic code-level simplifications

Useful AST transformations

XML processing

In chapter 1 I reviewed many of Java’s arguable weaknesses and drawbacks and suggested ways that Groovy might help ameliorate them. Because that chapter was intended to be introductory I only suggested how Groovy can help, without showing a lot of code examples. Now that I’ve established how easy it is to add Groovy classes to Java applications, when is it helpful to do so? What features, if any, does Groovy bring to Java systems that make them easier to develop?

A guide to the techniques covered in this chapter is shown in figure 4.1. I’ll review several Groovy advantages, like POGOs, operator overloading, the Groovy JDK, AST transformations, and how to use Groovy to work with XML and JSON data. To start, I’ll show that from Groovy code POJOs can be treated as though they were POGOs.

64

www.it-ebooks.info

 

Treating POJOs like POGOs

65

POGOs

Operator

AST

 

overloading

transformations

 

 

 

Groovy

 

 

 

features

 

 

 

Groovy

XML parsers

JSON parsers

 

JDK

and builders

and builders

 

Figure 4.1 Groovy features that can be added to Java classes

4.1Treating POJOs like POGOs

POGOs have more capabilities than POJOs. For example, all POGOs have a map-based constructor that’s very convenient for setting properties. The interesting thing is that even if a class is written in Java, many of the same conveniences apply as long as it’s accessed from Groovy.

Consider a simple POJO representing a person, possibly created as part of a domain model in Java, shown in the next listing. To keep it simple I’ll only include an ID and a name. I’ll put in a toString override as well but won’t include the inevitable equals and hashCode overrides.

Listing 4.1 A simple POJO representing a person

public class Person { private int id; private String name;

public Person() {}

public Person(int id, String name) { this.id = id;

this.name = name;

}

public void setId(int id) { this.id = id; } public int getId() { return id; }

public void setName(String name) { this.name = name; } public String getName() { return name; }

@Override

public String toString() {

return "Person [id=" + id + ", name=" + name + "]";

}

}

Any typical Java persistence layer has dozens of classes just like this, which map to relational database tables (figure 4.2).

If I instantiate this class from Groovy I can use a map-based1 constructor to do so, even though the Java version already specifies two constructors and neither is the one

1The term map-based refers to the fact that the attributes are set using the key-value notation used in Groovy maps. The constructor doesn’t actually use a map to do its job.

www.it-ebooks.info

66

CHAPTER 4 Using Groovy features in Java

new A(prop1:..., Groovy adds map-based constructor

prop2:...)

Groovy

Java Class A with typical constructors

Figure 4.2 Groovy adds a map-based constructor to Java classes, regardless of what constructors are already included.

I want. The following Groovy script creates some Person instances using three different mechanisms, none of which appear in the Java class:

def buffy = new Person(name:'Buffy') assert buffy.id == 0

assert buffy.name == 'Buffy'

def faith = new Person(name:'Faith',id:1) assert faith.id == 1

assert faith.name == 'Faith'

def willow = [name:'Willow',id:2] as Person assert willow.getId() == 2

assert willow.getName() == 'Willow'

The instances buffy and faith are created using the map-based constructor, first setting only the name, and then setting both the name and the id. I’m then able to verify, using Groovy’s built-in assert method (omitting its optional parentheses), that the person’s properties are set correctly.

Incidentally, all the assert statements that seem to be accessing private properties of the class directly really aren’t. Groovy goes through the getter and setter methods provided in the Java class when it looks like properties are being accessed or assigned. I can prove this by modifying the implementation of the getter method to return more than just the name:

public String getName() {

return "from getter: " + name;

}

Now I have to modify each of the asserts to include the string "from getter: " for them to still return true.

The third person, willow, is constructed using the as operator in Groovy. This operator has several uses, one of which is to coerce a map into an object as shown here. In this case the operator instantiates a person and supplies the map as properties for the resulting instance.

Moving on, I can also add the person instances to a Groovy collection, which isn’t all that surprising but has some nice additional benefits. For example, Groovy collections support operator overloading, making it easy to add additional persons and have additional methods for searching:

www.it-ebooks.info

Implementing operator overloading in Java

67

def slayers = [buffy, faith]

assert ['Buffy','Faith'] == slayers*.name assert slayers.class == java.util.ArrayList

def characters = slayers + willow

assert ['Buffy','Faith','Willow'] == characters*.name

def doubles = characters.findAll { it.name =~ /([a-z])\1/ } assert ['Buffy','Willow'] == doubles*.name

Groovy has a native syntax for collections, which simplifies Java code. Putting the references inside square brackets creates an instance of the java.util.ArrayList class and adds each element to the collection. Then, in the assert statement, I used the socalled “spread-dot” operator to extract the name property from each instance and return a list of the results (in other words, the spread-dot operator behaves the same way collect does). By the way, I restored the getName method to its original form, which returns just the attribute value.

I was able to use operator overloading to add willow to the slayers collection, resulting in the characters collection. Finally, I took advantage of the fact that in Groovy, the java.util.Collection interface has been augmented to have a findAll method that returns all instances in the collection matching the condition in the provided closure. In this case the closure contains a regular expression that matches any repeated lowercase letter.

Many existing Java applications have extensive domain models. As you can see, Groovy code can work with them directly, even treating them as POGOs and giving you a poor-man’s search capability.

Now to demonstrate a capability Groovy can add to Java that Java doesn’t even support: operator overloading.

4.2Implementing operator overloading in Java

So far I’ve used the fact that both the + and – operators have been overloaded in the String class. The overloaded + operator in String should be familiar to Java developers, because it’s the only overloaded operator in all of Java; it does concatenation for strings and addition for numerical values. Java developers can’t overload operators however they want.

That’s different in Groovy. In Groovy all operators are represented by methods, like the plus method for + or the minus method for—. You can overload2 any operator by implementing the appropriate method in your Groovy class. What isn’t necessarily obvious, though, is that you can implement the correct method in a Java class, too, and if an instance of that class is used in Groovy code, the operator will work there as well (see figure 4.3).

2Incidentally, changing the behavior of operators this way is normally called operator overloading, because the same operator has different behavior in different classes. Arguably, though, what I’m actually doing is operator overriding. Effectively they’re the same thing here, so I’ll use the terms interchangeably.

www.it-ebooks.info

68

CHAPTER 4 Using Groovy features in Java

Groovy uses methods for operators

A1 + A2 + A3 + ...

Groovy

Java Class A with plus() method

Figure 4.3 Groovy operators are implemented as methods, so if the Java class contains the right methods, Groovy scripts can use the associated operators on their instances.

To demonstrate this I’ll create a Java class that wraps a map. A Department contains a collection of Employee instances and will have a hire method to add them and a layOff method to remove them (hopefully not very often). I’ll implement operator overloading through three methods: plus, minus, and leftShift. Intuitively, plus will add a new employee, minus will remove an existing employee, and leftShift will be an alternative way to add. All three methods will allow chaining, meaning that they’ll return the modified Department instance.

Here’s the Employee class, which is just the Person POJO by another name:

public class Employee { private int id; private String name;

public String getName() { return name; }

public void setName(String name) { this.name = name; } public int getId() { return id; }

public void setId(int id) { this.id = id; }

}

Now for the Department class, shown in the following listing, which maintains the employee collection in a Map keyed to the employee id values.

Listing 4.2 A Department with a map of Employees and operator overriding

public class Department { private int id; private String name;

private Map<Integer, Employee> empMap = new HashMap<Integer, Employee>();

Employees indexed by ID

public int getId() { return id; }

public void setId(int id) { this.id = id; } public String getName() { return name; }

public void setName(String name) { this.name = name; }

public Collection<Employee> getEmployees() { return empMap.values(); }

public void hire(Employee e) { empMap.put(e.getId(), e); } public void layOff(Employee e) { empMap.remove(e.getId()); }

public Department plus(Employee e) {

hire(e);

Overriding operator

return this;

methods

}

Business methods to add and remove Employees

www.it-ebooks.info

Implementing operator overloading in Java

69

public Department minus(Employee e) {

 

 

 

 

 

 

layOff(e);

 

Overriding

 

return this;

 

operator

 

}

 

methods

 

public Department leftShift(Employee e) { hire(e);

return this;

}

}

By the way, notice that the plus method doesn’t add two Department instances; rather, it adds an Employee to a Department. Groovy only cares about the name of the method for the operator.3

To test this I’ll use the Spock testing framework. As in chapter 1, I’ll present the test without going into much detail about the Spock framework itself, which I’ll deal with in chapter 6. Fortunately, Spock tests are easy to read even if you don’t know the details. The next listing shows a Spock test that’s focused on just the operator methods.

Listing 4.3 A Spock test to check the operator overloading methods in a Java class

class DepartmentTest extends Specification { private Department dept;

def setup() { dept = new Department(name:'IT') }

def "add employee to dept should increase total by 1"() { given: Employee fred = new Employee(name:'Fred',id:1)

when: dept = dept + fred

then:

dept.employees.size() == old(dept.employees.size()) + 1

}

def "add two employees via chained plus"() { given:

Employee fred = new Employee(name:'Fred',id:1) Employee barney = new Employee(name:'Barney',id:2)

when:

dept = dept + fred + barney

then: dept.employees.size() == 2

}

def "subtract emp from dept should decrease by 1"() { given:

Employee fred = new Employee(name:'Fred',id:1) dept.hire fred

3As an example from the Groovy JDK, the java.util.Date class has a plus method that takes an integer representing the number of days. See also the multiply method in Collection that takes an integer.

www.it-ebooks.info

70

CHAPTER 4 Using Groovy features in Java

when:

dept = dept - fred

then:

dept.employees.size() == old(dept.employees.size()) - 1

}

def "remove two employees via chained minus"() { given:

Employee fred = new Employee(name:'Fred',id:1) Employee barney = new Employee(name:'Barney',id:2) dept.hire fred; dept.hire barney

when: dept = dept - fred - barney

then: dept.employees.size() == 0

}

def "left shift should increase employee total by 1"() { given:

Employee fred = new Employee(name:'Fred',id:1)

when:

dept = dept << fred

then:

dept.employees.size() == old(dept.employees.size()) + 1

}

def "add two employees via chained left shift"() { given:

Employee fred = new Employee(name:'Fred',id:1) Employee barney = new Employee(name:'Barney',id:2)

when:

dept = dept << fred << barney

then: dept.employees.size() == 2

}

}

The Spock test is written in Groovy, so I can use +, –, and << and know that the associated methods will be used, even though they’re implemented in a Java class.

The list of operators that can be overridden in Groovy includes plus, minus, and leftShift, as shown in the listing, and many others as well. You can implement array-like access through an index by implementing getAt, for example. Preand post-increment are implemented through the next and previous methods, respectively. The spaceship operator, <=>, is implemented through compareTo. You can even override the dot operator, believe it or not. The cool part is that you can implement these methods in either POJOs or POGOs, and Groovy will take advantage of them either way.

The next feature of Groovy that simplifies Java is one I’ve taken advantage of several times already: the Groovy JDK.

www.it-ebooks.info

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