- Groovy 2 Cookbook
- Andrey Adamovich Luciano Fiandesio
- 1293字
- 2021-07-23 15:57:24
Writing less verbose Java Beans with Groovy Beans
Java Beans are very popular among Java developers. A Java Bean is essentially data with little or no behavior (depending on the implementation). Formally, a Java Bean is a Java class that is serializable, has a public no-argument constructor, private fields, and getter and setter methods for accessing the fields. Often, POJO (Plain Old Java Object) and a Java Bean are used interchangeably.
In this recipe, we will show how Groovy can save you from a lot of typing by offering several features for bean class creation built into the language and API.
Getting ready
A typical Java Bean looks like the following code snippet:
public class Student implements Serializable { private Long id; private String name; private String lastName; // more attributes public Student() { // NO-ARGS CONSTRUCTOR } // more constructors public void setId(final Long id) { this.id = id; } public Long getId() { return id; } public void setName(final String name) { this.name = name; } public Long getName() { return name; } }
This is quite a lot of code for a simple object without any business logic. The bean is instantiated as with any other Java class, and properties are set either through setters or one of the constructors.
Student student = new Student(); student.setId(23892); student.setName("Charlie");
How to do it...
Let's check out how Groovy simplifies the writing of such objects with Groovy Beans.
- Groovy makes the creation of a Java Bean way less verbose. Let's convert the above Java class
Student
into a Groovy Bean:class Student { Long id String name String lastName }
- Yes. That's it. The bean only has to declare the properties. Both getters and setters and constructors are created at compilation time (through an AST transformation).
How it works...
Now let's check out how to interact with our newly created Groovy Bean:
def student = new Student() student.setName('Charlie') student.lastName = 'Parker' assert student.name == 'Charlie' assert student.lastName == 'Parker' def student2 = new Student( id: 100, name: 'Jack', lastName: 'Shepard' ) assert student2.name == 'Jack' assert student2.id == 100 assert student2.lastName == 'Shepard'
Mutators (or setters) can be invoked by either using the familiar Java syntax student.setName("John")
or by a handy shortcut student.name = 'John
' (note that the shortcut doesn't use the actual field, but the generated mutator). The Groovy Bean has also a constructor that uses a Map
for quick initialization. The Map
parameters can be set in any order and do not need to set all the properties of the bean:
def customer2 = new Customer(id:100) def customer3 = new Customer(id:100, lastName:'Shepard')
A requirement that is often needed is that a bean has one or more fields that are read-only. To define a field as immutable, add the final
keyword to it and add an explicit constructor:
class Student { final Long id String name String lastName Student(Long id) { this.id = id } } def c = new Student(100)
Unfortunately, the explicit constructor replaces the Groovy-generated one so that named parameters constructors are no longer available. Another way to make a Groovy Bean completely immutable is to use the @Immutable
AST transformation:
import groovy.transform.Immutable @Immutable class Student { String name, lastName Long id } def student = new Student( lastName: 'Hogan', id: 200, name: 'Mark' ) student.name = 'John'
Executing the previous code yields the following exception:
groovy.lang.ReadOnlyPropertyException: Cannot set read-only property: name for class: Customer
The @Immutable
annotation makes the class final, all the fields become final, and also the default equals
, hashCode
, and toString
methods are provided based on the property values. Furthermore, along with the standard Map constructor, a tuple-style constructor is provided which allows you to set properties in the same order as they are defined.
def c1 = new Student('Mark', 'Hogan', 100)
There's more...
We just encountered the @Immutable
annotation that conveniently adds a number of default methods to the Groovy Bean. But what if we want to automatically generate these rather common methods and have the class mutable? Groovy has a number of AST transformations (based on annotations) that just do that.
The first annotation that we look at is @ToString
, used to add a toString
method to a bean.
import groovy.transform.ToString @ToString class Student { String name } def s = new Student(name:'John') assert s.toString() == 'Student(John)'
To include the field names in the output of toString
add the includeNames=true
attribute.
@ToString(includeNames = true) class Student { ... }
To exclude some fields from the computation of the String
, use the excludes
attribute.
@ToString(includeNames = true, excludes = 'lastName,age') class Student { ... }
The toString
method is conventionally accompanied by two other methods, hashCode
and equals
. The first method is important to the performance of hash tables and other data structures that store objects in groups (buckets) based on their computed hash values. The equals
method checks if the object passed to it as an argument is equal to the object on which this method is invoked.
To implement both methods on a Groovy Bean, simply add the @EqualsAndHashCode
annotation to it:
import groovy.transform.EqualsAndHashCode @EqualsAndHashCode class Student { String name String lastName } def s1 = new Student( name: 'John', lastName: 'Ross' ) def s2 = new Student( name: 'Rob', lastName: 'Bell' ) assert !s1.equals(s2) def copyOfS2 = new Student( name: 'Rob', lastName: 'Bell' ) Set students = [:] as Set students.add c1 students.add c2 students.add copyOfC2 assert users.size() == 2
Similar to the @ToString
annotation, the excludes
property can be used to exclude properties from the computation. If the bean extends from a second bean, the property callSuper
set to true can be used to include the properties of the superclass.
The @TupleConstructor
annotation deals with Groovy Bean constructors and creates constructors that do not require named parameters (Java-style constructors). For each property in the bean, a parameter with a default value is created in the constructor in the same order as the properties are declared. As the constructor is using default values, we don't have to set all the properties when we build the bean.
import groovy.transform.TupleConstructor @TupleConstructor class Student { String name String lastName Long age List favouriteSubjects } def s1 = new Student('Mike','Wells',20,['math','phisics']) def s2 = new Student('Joe','Garland',22) assert s1.name == 'Mike' assert !s2.favouriteSubjects
The @TupleConstructor
annotation has also some additional attributes that can be set to modify the behavior of the generated constructor. The force=true
property instructs the annotation to generate a constructor even if a different constructor is already defined. Naturally, the property will fail in the case of a constructor conflict.
If the class annotated with @TupleConstructor
extends another class and we wish to include the properties or fields of the superclass, we can use the attributes includeSuperProperties
and includeSuperFields
. Finally, the callSuper=true
attribute instructs the annotation to create a code in the constructor to call the super constructor of the superclass with the properties.
A Groovy Bean can be annotated with more than one of the previous annotations:
@EqualsAndHashCode @ToString class Person { def name, address, pets }
Furthermore, all the annotations we discussed in this recipe can be combined in a single annotation, @Canonical
. The @Canonical
annotation instructs the compiler to execute an AST transformation which adds positional constructors, hashCode
, equals
, and a pretty print toString
to a class.
The @Canonical
annotation has only two parameters, includes
and excludes
, to select which properties to include or exclude in the computation. Since @Canonical
doesn't take any additional parameters and uses some sensible defaults, how can we customize certain aspects of the code generation? For example, if you want to use @Canonical
but customize the @ToString
behavior, then annotate the class with both @Canonical
and @ToString
. The @ToString
definition and parameters take precedence over @Canonical
.
@Canonical @ToString(excludes='age') class Person { String name int age }
- The Complete Rust Programming Reference Guide
- C++程序設計(第3版)
- Microsoft Application Virtualization Cookbook
- PostgreSQL Cookbook
- 高級語言程序設計(C語言版):基于計算思維能力培養
- Node.js Design Patterns
- Azure Serverless Computing Cookbook
- Visual Basic程序設計(第三版)
- QlikView Unlocked
- 透視C#核心技術:系統架構及移動端開發
- 優化驅動的設計方法
- Android 5從入門到精通
- Java Web入門很輕松(微課超值版)
- 現代JavaScript編程:經典范例與實踐技巧
- MySQL數據庫應用技術及實戰