79. Class Methods and Variables
In the preceding chapters we looked at instance properties/fields (variables) and instance methods (including constructors) - the components of a class that pertain to individual instantiations of the class. This ensures that each instance works against its own state and doesn’t interfere with other instances. There are many times, however, when we want a class to provide a method that isn’t bound to an instance or when we want a field/property that is used across instances. This is where class methods and class variables are utilised.
So, when to use class methods and variables? There’s a number of answers to this but the following three are the main ones:
- When a single property is best shared across all instances
- This is often the case with configuration properties
- When a method isn’t really specific to an instance
- The method sits nicely with the class’s concepts but doesn’t need to relate directly to each instance
- When the class is really just a library of methods and constants
- Sometimes you just need a set of utility methods
It’s important to note a few things about class methods:
- They can’t access
thisas there’s no underlying instance - They can’t use instance (member) variables
Declaring a field/property or method as static is easy: you just prepend the static modifier. Lets look at an example:
import groovy.json.JsonSlurper
class Configuration {
static String databaseName = ''
static String databasePassword = ''
static String logFile = ''
static loadConfig() {
//This is the config file:
File file = new File('config.json')
//We use JsonSlurper to read a JSON file:
JsonSlurper slurper = new JsonSlurper()
//Now parse the config file
def config = slurper.parse(file)
//We can access the config file elements using dot-point notation:
databaseName = config.database
databasePassword = config.password
logFile = config.log
}
}
Configuration.loadConfig()
println """\
System database: ${Configuration.databaseName}
System database password: ${Configuration.databasePassword}
Log file: ${Configuration.logFile}
"""
The code above provides a common use of static variables as it reads in configuration from a source and makes that
configuration available to the rest of the system. In this case I have chosen to use JSON
notation to store the configuration and the file config.json appears thus:
{
"database": "CorporateData",
"password": "password",
"log": "/tmp/log.txt"
}
In the Configuration class you can see three class variables: databaseName, databasePassword and logFile. These
are accessed via the class, and not an instance, by using the <class name>.<variable name> form: Configuration.databaseName.
I’ve also defined the class method loadConfig and this is accessed through Configuration.loadConfig. You’ll see that
I don’t have to instantiate Configuration by calling new nor do I assign Configuration to a variable - I just access
Configurations’s class members when I need them.
I’ve used the String type for the static variables in Configuration but, should I want to use dynamic types, I just
use static <varName> and don’t need def:
static databaseName = ''
static databasePassword = ''
static logFile = ''
Whilst the Configuration class only defines class methods and variables, classes can have a mix of these elements. The
next example is more complex:
@Grab('org.javamoney:moneta:1.0')
import groovy.transform.ToString
import org.javamoney.moneta.Money
@ToString(includeNames = true)
class SilverCreditCard {
String cardNumber
String cardHolderName
Money balance = Money.of(0, currency)
static String currency = 'AUD'
static Money creditLimit = Money.of(5_000, currency)
static SilverCreditCard applyForAccount(String applicantName, Money totalAss\
ets, Money totalDebts) {
if (totalAssets.subtract(totalDebts).subtract(creditLimit).isPositive())\
{
// TODO: Create a new record in the database etc
return new SilverCreditCard(cardNumber: '0000 1111 2222 3333', cardH\
olderName: applicantName)
} else {
// TODO: Throw an exception - don't just return null
return null
}
}
static SilverCreditCard loadAccount(String cardNumber) {
// TODO: Lookup the number in our database
new SilverCreditCard(cardNumber: cardNumber, cardHolderName: 'Fred Nurk'\
, balance: Money.of(100, currency))
}
Money deposit(Money amt) {
//TODO: Implement
}
Money withdrawl(Money amt) {
//TODO: Implement
}
}
SilverCreditCard yourCard = SilverCreditCard.loadAccount('1234 5678 9876 5432')
println yourCard
SilverCreditCard myCard = SilverCreditCard.applyForAccount('Jim Smith',
Money.of(20_000, SilverCreditCard.currency),
Money.of(10_000, SilverCreditCard.currency))
println myCard
Running this code will cause an error to be displayed but don’t panic - it’s just saying that I haven’t provided a configuration for the Java money classes. It’s all cool and fine for this example.
In the code above I have described a credit card account - the SilverCreditCard. This class uses class
variables to describe the policy for the credit card product (bank speak) and class methods for accessing
individual accounts (after all, you don’t want people to just new CreditCard do you?):
- Class variables:
-
currencyis the currency used by all Silver Credit Cards -
creditLimitis the maximum limit allowed for this type of account
-
- Class methods:
-
applyForAccountis for new customers requesting an account - it would return an instance ofSilverCreditCardbeing the newly created account -
loadAccountis for existing customers wanting to access their account - it would return an instance ofSilverCreditCardwith information loaded from a database
-
The instance elements represent a single customer’s SilverCreditCard account and aid in tracking their account:
- Instance properties:
-
cardNumberis the unique number for the account -
cardHolderNameis the name of the card holder -
balanceis how much they’ve spent on their credit card
-
- Instance methods:
-
depositandwithdrawalwould let the person use their account
-
Static initializer blocks
Classes don’t have a constructor-style approach that you can use to prepare the class variables for use. However, there is a static form of the intializer block:
import groovy.json.JsonSlurper
class Configuration {
static String databaseName = ''
static String databasePassword = ''
static String logFile = ''
static {
//This is the config file:
File file = new File('config.json')
//We use JsonSlurper to read a JSON file:
JsonSlurper slurper = new JsonSlurper()
//Now parse the config file
def config = slurper.parse(file)
//We can access the config file elements using dot-point notation:
databaseName = config.database
databasePassword = config.password
logFile = config.log
}
}
println """\
System database: ${Configuration.databaseName}
System database password: ${Configuration.databasePassword}
Log file: ${Configuration.logFile}
"""
You’ll see that I’ve just moved the earlier loadConfig class method into a static initializer block (static { }).
This is probably a good idea as the initializer block is acted on before the class variables are accessed,
allowing me to make sure that the configuration is ready to go rather than relying on other developers to call loadConfig.
Additionally, the initializer block will only be called once so the config file is only read once
- much more efficient than if loadConfig is called over and over by other code.
- More on this in Access Modifiers↩