Implementing operator overloading in Java

Một phần của tài liệu Making java groovy (Trang 94 - 98)

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 develop- ers, 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 opera- tor by implementing the appropriate method in your Groovy class. What isn’t neces- sarily 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).

2 Incidentally, 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 opera- tor overriding. Effectively they’re the same thing here, so I’ll use the terms interchangeably.

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.

public class Department { private int id;

private String name;

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

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);

return this;

}

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

Groovy

Groovy uses methods for operators

Java ClassAwith plus()method A1 + A2 + A3 + ...

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.

Employees indexed by ID

Business methods to add and remove Employees Overriding operator

methods

69 Implementing operator overloading in Java

public Department minus(Employee e) { layOff(e);

return this;

}

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 oper- ator methods.

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

3 As 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.

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

operator methods

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 associ- ated 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 imple- ment array-like access through an index by implementing getAt, for example. Pre- and 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 advan- tage of them either way.

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

71 Making Java library classes better: the Groovy JDK

Một phần của tài liệu Making java groovy (Trang 94 - 98)

Tải bản đầy đủ (PDF)

(369 trang)