Inspired by a post by Jos in the Java forum, I have put together a little article on class initializers.
Initializers are indeed underutilized by most Java programmers. Once the Java programmer knows about the constructor he/she feels they have found a good place to put code that must be run before an object is created and sometimes don't try to look for more places to put pre-object creation code. The other places to put pre-object creation code are initializers.
There are two types of initializers that can be used in classes. Static initializers and instance initializers.
Static initializers.
Static initializers are the first bit of code that is run once when the class is being loaded. Since they are static, they are not tied to any particular instance but rather are shared among all objects of their class. An instance initializer block is thus executed only once (during class loading). After that, creation of objects in the program do not trigger execution of the static initializer block again. Another consequence of being static is that you can only use static initializers to initialize static variables. This makes sense because these are the only ones available during class loading. Enough chattering, let's dive into an example:
A scenario
The scenario is a Utility class that gives information about products in a database. The class works on a list of all the products in the database by loading them into an arraylist once when the class is loaded and then referring to that arraylist on all subsequent inquiries on products. The exists method for example, does not need to connect to the database but just queries the products ArrayList.
Disclaimer
Please note that this example is for illustration purposes only. This approach is only helpful in a read only database system because if a product is added into the database during this program's execution after the ProductUtils class has been loaded, then that product will not be available in the list of products created at class loading. There are workarounds of course but we digress ...
A static initializer block is simply denoted by a pair of opening and closing braces preceeded by the static keyword with the initialization code placed inside the braces
Expand|Select|Wrap|Line Numbers
- static {
- //initialization code goes here.
- }
Expand|Select|Wrap|Line Numbers
- import java.sql.Connection;
- import java.sql.DriverManager;
- import java.sql.ResultSet;
- import java.sql.Statement;
- import java.util.ArrayList;
- import java.util.List;
- class ProductUtils {
- static List<Product> products = new ArrayList();
- static {
- try {
- Class.forName("com.mysql.jdbc.Driver");
- Connection con = DriverManager.getConnection("jdbc:mysql://localhost/test","r035198x ", "acomplexpassword");
- Statement st = con.createStatement();
- ResultSet rs = st.executeQuery("select * from Products");
- while(rs.next()) {
- products.add(new Product(rs.getInt(1), rs.getString(2)));
- System.out.println("product added");
- }
- }
- catch(Exception e) {
- e.printStackTrace();
- }
- }
- public static void main(String[] args) {
- }
- boolean exists (Product product) {
- return products.contains(product);
- }
- }
Output with a blank main
Running this program with the proper configurations (database created and connector set in the class path correctly) prints product added for each product that is added to the ArrayList of products. Note that I have deliberately left the main method blank but the program connects to the database, loads objects into an arraylist and prints some stuff to the console. The static initializer block is responsible for all this. The main method is innocent.
This should make it clear that an instance of a class is not required for a static initializer block to be executed. Static initializers can be as sophisticated as you wish, just keep in mind that they are executed during class loading and therefore can only access other statics.
The more the merrier?
You can have as many static initializers as you wish in your class. They will be executed in the order that they appear in your code (from the top of the file to the bottom). To make your code easy to read, you should put different tasks in different initializer blocks so that each block performs one logical operation.
Use
Use static initializers to perform operations that must be done once before all objects of a class are created. Doing these operations in a static initializer ensures that they are done once and therefore can speed up your program because it doesn't have to do them again every time an object of that class is created. More specifically, use static initializers for operations that must be done during class loading of that class by the JVM. The example above also contains a common use of static initializers.
A common use
The specs for the Driver interface say
When a Driver class is loaded, it should create an instance of itself and register it with the DriverManager. This means that a user can load and register a driver by callingDatabase driver writers are being told here that when their drivers are loaded, an object must be created and registered with the driver manager.
Class.forName("foo.bah.Driver")
The MySQL driver that was used to connect to the database in the example above had to obey this rule. The call to DriverManager.registerDriver() was done in a static initializer of the MySQL driver class which looks something like:
Expand|Select|Wrap|Line Numbers
- static {
- try {
- java.sql.DriverManager.registerDriver(new Driver());
- } catch (java.sql.SQLException E) {
- throw new RuntimeException("Can't register driver!");
- }
- if (DEBUG) {
- Debug.trace("ALL");
- }
- }
This is helpful since loading a driver now just translates to loading the driver class for that driver.
Instance initializers
The difference between static initializers and instance initializers is in the time that they are executed. While static initializers are executed once when a class is loaded, instance initializers are run once before an object is created. This changes matters somewhat. Instance initializers now have access to non-static variables in a class including the this object.
Instance initializers in named classes should be used to initialize instance variables which cannot be initialized using one line of code but should be initialized the same way for all the constructors in that class. Otherwise if the variable should be initialized differently depending on the constructor that was invoked, then it should be initialized in the appropriate constructors.
Expand|Select|Wrap|Line Numbers
- class Cat {
- String name;
- int numOfLegs;
- int numOfEars;
- {
- numOfLegs = 2;
- numOfEars = 2;
- }
- Cat(String name) {
- this.name = name;
- }
- public String toString() {
- return name + " has " + numOfLegs + " legs and " + numOfEars + " ears";
- }
- public static void main(String[] args) {
- System.out.println(new Cat("Mary").toString());
- }
- }
Called before constructors
Instance initializers are always called before a constructor is called (note that the compiler actually generates <init> methods for every constructor in a class and these are the ones called when a constructor is invoked). As with static initializers, multiple initializers are executed in their textual order.
Instance initializers in anonymous classes
When used with anonymous classes, initializers can make cumbersome code more elegant. Generally you want to isolate code that does different operations. Specifically you don't want to mix code that builds a structure (e.g list, map or more exotic structures) with code that manipulates that structure. Suppose you want to build a mapping between Integers and Strings so that 1 maps to “Sunday”, 2 maps to “Monday” etc.
You could build the map as
Expand|Select|Wrap|Line Numbers
- Map<Integer, String> days = new HashMap();
- days.put(1, “Sunday”);
- days.put(2, “Monday”);
- //etc
If an anonymous class and initializer is used, this can be done as
Expand|Select|Wrap|Line Numbers
- Map<Integer, String> days = new HashMap() {
- {
- put(1, “Sunday”);
- put(2, “Monday”);
- //etc
- }
- };
Initializers and exceptions
Static initializers cannot throw checked exceptions (it is a compilation error if they do).
An instance variable initializer in a named class can only throw checked exceptions if that exception or one of its supertypes is explicitly declared in the throws clause of each constructor of its class and the class has at least one explicitly defined constructor.(JLS Third Edition[8.3.2]).
Expand|Select|Wrap|Line Numbers
- static {
- if(true) {
- throw new Exception();
- }
- }
while
Expand|Select|Wrap|Line Numbers
- public class Sorry {
- String sorry;
- public Sorry() throws Exception{}
- {
- if(true) {
- throw new Exception();
- }
- }
- }
Instance variable initializers in anonymous classes, however, are allowed to throw any checked exceptions.
Expand|Select|Wrap|Line Numbers
- import java.io.IOException;
- public class Sorry {
- String sorry;
- public Sorry() {}
- public static void main(String[] args) throws IOException{
- System.out.print( new Sorry() {
- {
- if(true) {
- throw new IOException("Sorry");
- }
- }
- }.toString());
- }
- public String toString() {
- return sorry;
- }
- }
In case you are wondering about the name of the class, a friend of mine wrote a Sorry class to apologize 5 000 times to his girlfriend through email and I just stole that class and changed it a bit to suit more serious purposes.
I hope you have now some understanding on initializers and know when to use which one to better your programs in both speed and maintanability.