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.

  1. 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
    }
  2. 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
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.251.169