Static Fields & Methods
Table of Contents
Introduction
In a previous chapter we investigated instance fields and methods and how they related to class definitions and objects. We learned that every instance of an object has its own corresponding fields tied to that instance which is distinct from another instance. Java provides the direct opposite of instance fields with static fields.
What Does static Mean?
static is a reserved keyword which can be prepended to either a variable or a method in a class definition. Per the Java Language Specification for static fields:
If a field is declared static, there exists exactly one incarnation of the field, no matter how many instances (possibly zero) of the class may eventually be created. A static field, sometimes called a class variable, is incarnated when the class is initialized.
This means that static fields are tied to an entire class. They are not associated with a specific instance of a class (i.e. an object). The same goes for static methods:
A method that is declared static is called a class method.
A class method is always invoked without reference to a particular object.
Both static methods and static fields belong to the class itself and not a given instance of that class.
We can modify the BankAccount class from the instance chapter to include a static field which tracks the unique ID of each bank account created:
1: public class BankAccount { 2: private static int UID = 0; 3: private String owner; 4: private double balance; 5: private int uid; 6: 7: BankAccount(String owner, double initialBalance){ 8: this.owner = owner; 9: this.balance = initialBalance; 10: this.uid = BankAccount.UID; 11: BankAccount.UID += 1; 12: } 13: 14: void deposit(double amount){ 15: balance = balance + amount; 16: } 17: 18: void withdraw(double amount){ 19: balance = balance - amount; 20: } 21: 22: void printBalance(){ 23: System.out.printf("(%d) %s's balance: $%.2f%n", uid, owner, balance); 24: } 25: 26: static int getNumAccountsCreated(){ 27: return BankAccount.UID; 28: } 29: }
We can then use the above class definition like so:
1: public class UsingStaticAccount{ 2: public static void main(String[] args){ 3: String[] names = {"Alan", "Barbara", "Clive", "Denise", "Eddie", "Fran"}; 4: BankAccount[] accs = new BankAccount[names.length]; 5: for(String n : names){ 6: int i = BankAccount.getNumAccountsCreated(); 7: accs[i] = new BankAccount(n, 1000); 8: } 9: for(BankAccount acc : accs){ 10: acc.printBalance(); 11: } 12: System.out.printf("We created %d accounts!%n", BankAccount.getNumAccountsCreated()); 13: } 14: }
After compiling and running the code we get:
josephraskind@stargazer:/tmp/static$ javac UsingStaticAccount.java; java UsingStaticAccount (0) Alan's balance: $1000.00 (1) Barbara's balance: $1000.00 (2) Clive's balance: $1000.00 (3) Denise's balance: $1000.00 (4) Eddie's balance: $1000.00 (5) Fran's balance: $1000.00 We created 6 accounts!
That's a lot to process!
Let's go through this step by step. First, we modified BankAccount to include a new static field UID, a new instance field uid, and a new static method getNumAccountsCreated. Next, the constructor was modified to set the uid of the new instance wiht the value of UID. Because UID is private it can only be referenced in the context of the BankAccount definition. Furthermore, because UID is static, the value is "shared" between all instances (at the class level). This is why each constructor invocation is able to use the value of UID to set the value of their internal uids. We can also see that the printBalance method was modified to include a reference to the uid. Each printout of uid in UsingStaticAccount is different because uid is an instance field. Finally, getNumAccountsCreated returns the value of the UID static field—it is allowed to do so because it is defined as static in its method declaration.
UID was referenced using the class name paired with the . notation. BankAccount.UID retrieved the static UID value. Again, static members are not tied to an instance, but rather the class, therefore I need the class name when referencing a static member.
Since static methods are not tied to a particular instance they cannot reference instance members. For example, if I created a new, static function called printStaticBalance:
1: static void printStaticBalance{ 2: System.out.printf("(%d) %s's balance: $%.2f%n", uid, owner, balance); 3: }
it would not compile! uid, owner, and balance only make semantic sense when referring to a particular instance of BankAccount. However, it is possible for instance methods to reference static members.
Many beginner Java programmers do not recognize this when they start programming. Oftentimes they will make the mistake of calling instance methods from a static context and get frustrated with the compiler when it points out that is illegal in the language. Always double check that you are in the proper method context when invocation errors crop up.
Exercises
- Compile and run Listing 2. Then create a second program that instantiates three
BankAccountobjects and prints the result ofBankAccount.getNumAccountsCreated()once after all three have been created. What value does it return? Now callgetNumAccountsCreatedbefore creating any accounts and print that too. What does this confirm about when the static field is initialized and how it accumulates across all instantiations? The chapter states that static members are accessed via the class name rather than an instance variable. Attempt to access
getNumAccountsCreatedthrough an instance variable instead:BankAccount acc = new BankAccount("Alice", 1000.00); System.out.println(acc.getNumAccountsCreated());
Does it compile? Does it run? What warning or note does the compiler give you and why does it discourage this style even if it technically works?
- It turns out that
public static void mainmust be static because the JVM calls it before any objects exist. Try removing thestatickeyword from amainmethod and attempt to run it. What error do you get? In your own words explain why the JVM cannot call a non-staticmainmethod at program startup. - Add a
static final double INTEREST_RATE = 0.05field toBankAccountrepresenting a fixed annual interest rate shared by all accounts. Then add an instance methodapplyInterestthat multipliesbalanceby(1 + INTEREST_RATE). Test it on two accounts and verify that changingINTEREST_RATEto0.10updates the behavior for all accounts without modifying any instance. Why isfinalan appropriate qualifier for this field? - The chapter states that instance methods can reference static members but static methods cannot reference instance members. Write a small class that demonstrates both directions: one instance method that reads a static field, and one static method that attempts to read an instance field. Compile both and record what happens. Explain in your own words why the restriction only applies in one direction.
- The chapter shows
UIDis declaredprivate static. Changeprivatetopublicand recompile. Now attempt to reset the counter fromUsingStaticAccountwithBankAccount.UID = 0after creating a few accounts. What happens to subsequentuidvalues? What does this demonstrate about whyUIDshould remainprivate? - Write a class
Counterwith a singleprivate static int count = 0field, a constructor that incrementscount, and a static methodgetCountthat returns it. InstantiateCounterobjects in a loop and verifygetCountreflects the total number of instances created. Then write a second class in the same program that also instantiatesCounterobjects. DoesgetCountreflect instantiations from both classes? Explain why. - The chapter shows that
BankAccount.UIDpersists across all instances created during a program run. Consider what happens if you wanted to reset the UID counter — for example when writing tests. Add astatic void resetUIDmethod toBankAccountthat resetsUIDto0. Write a program that creates three accounts, resets the counter, then creates three more. Print all six accounts'uidvalues. What do you observe and what does this suggest about the risks of mutable static state?