Groovy Primitive Data Types vs Objects
A message I posted on the Grails User List spawned another pretty interesting conversation that I would like to share with you. The original question had to do with a declaring a boolean property. Say we have a user domain object and we want to store a bit flag describing whether or not the user was active.
class User {
String first
String last
boolean active1
boolean active2 = Boolean.TRUE
Boolean active3
Boolean active4 = Boolean.FALSE
static constraints = {
}
}
Here are a couple ways that we could define this active flag. You will notice the 1st 2 use lowercase boolean, this means that its a primitive type. The 2nd two use the Boolean class to define its type. The odd 2 also show an easy way to setup a default value for the property.
So the question came up when is it appropriate to use the primitive data type (lowercase) and when should we use the Class type. Burt Beckwith actually gave a response on the list and I would like to share his response here.
The core problem is that primitives can't be null. Groovy fakes that out with autoboxing, but in Java (which is what Hibernate assumes we're using) that's not realistic. If you store a null value in a boolean or numeric column, you can't store that in a boolean/int/long/etc field. It's not correct to convert a null boolean to false, or a null number to 0, since those might be valid values. Null means that no value or choice has been made yet.
For example consider an "int yearsOfExperience" field in a domain class. 0 is a valid value, but even without an initializer the domain class instance will be initialized to 0 by the compiler (both in Java and Groovy). So if the user enters nothing, there will be a value set and you miss your chance to reject the submission. If it's "Integer yearsOfExperience" it's not initialized, and since there's a default not-null constraint on all fields, you'll see that the user didn't make a choice and you can reject. They can then choose 0 or whatever, but now you have a way to ensure that they enter valid values.
And in fact even if you try to make a primitive nullable in GORM, Hibernate won't let you. As an example create this domain class in a test app and run "grails schema-export":
class Thing { String name int foo boolean bar static constraints = { name nullable: true foo nullable: true bar nullable: true } }
Looking at the generated DDL in target/ddl.sql you'll see that the String column is nullable, but the others aren't. So it works the way you want, but the domain class and database are inconsistent.
create table thing ( id bigint generated by default as identity, version bigint not null, bar boolean not null, foo integer not null, name varchar(255), primary key (id) );
Burt
A big thank you to Burt for clearing that up for me. So in the case of our bit flag on our user object we can simply use a primitive type and set the default to TRUE. Here is a quick list of primitive types (and their defaults) and their wrapper objects. The only caveat to this list is String, which is actually an object.
Data Type | Default Value (for fields) | Wrapper Type |
---|---|---|
byte | 0 | Byte |
short | 0 | Short |
int | 0 | Integer |
long | 0L | Long |
float | 0.0f | Float |
double | 0.0d | Double |
char | ‘\u0000’ | Character |
boolean | false | Boolean |
String (or any object) | null | n/a |