Port of schuchert.wikispaces.com


Java_5_Variable_Arguments

Java_5_Variable_Arguments

Java 5 Variable Arguments

This article uses Log4J as a vehicle to describe Java 5 Variable Augments through a concrete example.

There are 4 versions of the same program:

Version 1

Here is a typical example of using Log4J with a simple utility class that:

Main.java

01: package varargs.v1;
02: 
03: import org.apache.log4j.Logger;
04: 
05: public final class Main {
06:     private Main() {
07:         // all static methods, do not instantiate me
08:     }
09: 
10:     public static final String FILE_NAME = "SomeFileName";
11:     public static final String ERROR_CONTACT = "someemail@palcetocontact.com";
12: 
13:     public static void main(final String[] args) {
14:         final Logger myLogger = LoggingConfiguration.getLoggerFor(Main.class);
15: 
16:         myLogger.debug("Problem with file: " + FILE_NAME + ". Please contact: " + ERROR_CONTACT);
17:     }
18: }

Interesting Lines

Line Description
14 Use a utility class to retrieve a logger for my class.
16 Use the logger.

So what is wrong with this example? Specifically, something is missing. In a real application, line 16 should be wrapped in some conditional logic. Don’t worry if you’re not sure about this, it’s coming up.

Version 2

This version only introduced the use of a new utility method on the String class. Instead of writing the following:

   System.out.println("You have scored: " + points + ", and made it to level: " + level);

You can use the new method String.format and C-style format strings:

    String.format("You have scored: %d, and made it to level: %d", points, level);

Note that String.format is an example of a method whose parameters are defined using variable arguments.

Here’s the code for version 2:

Main.java

01: package varargs.v2;
02: 
03: import org.apache.log4j.Logger;
04: 
05: public final class Main {
06:     private Main() {
07:         // all static methods, do not instantiate me
08:     }
09: 
10:     public static final String FILE_NAME = "SomeFileName";
11:     public static final String ERROR_CONTACT = "someemail@palcetocontact.com";
12: 
13:     public static void main(final String[] args) {
14:         final Logger myLogger = LoggingConfiguration.getLoggerFor(Main.class);
15: 
16:         myLogger.debug(String.format("Problem with file: %s. Please contact: %s", FILE_NAME,
17:                 ERROR_CONTACT));
18:     }
19: }

Interesting Lines

Line Description
16 - 17 Use the String.format method to produce the same output as before.

Version 3

Version 3 adds the missing element mentioned in version 1. Namely, in both version 1 and version 2, we are formatting a String, performing string concatenation, allocating memory, etc. when it might not be necessary.

The logger might not be displaying debug-level information. If it is not, then we have wasted time and memory on String manipulation. This is a typical problem with logging. It may not seem like much but from my experience it is a big deal. In a large system, you’ll end up garbage collecting more often than necessary.

There’s an “easy” fix. You can ask the logger if the level you’re going to output is currently enabled. If it is not, then you don’t to the work:

   if(myLogger.isDebugEnabled()) {
      myLogger.debug(String.format("Problem with file: %s. Please contact: %s", FILE_NAME, ERROR_CONTACT));
   }

It is ugly but it makes a big difference.

Here’s the code modified to take this into consideration:

Main.java

01: package varargs.v3;
02: 
03: import org.apache.log4j.Logger;
04: 
05: public final class Main {
06:     private Main() {
07:         // all static methods, do not instantiate me
08:     }
09: 
10:     public static final String FILE_NAME = "SomeFileName";
11:     public static final String ERROR_CONTACT = "someemail@palcetocontact.com";
12: 
13:     public static void main(final String[] args) {
14:         final Logger myLogger = LoggingConfiguration.getLoggerFor(Main.class);
15: 
16:         if (myLogger.isDebugEnabled()) {
17:             myLogger.debug(String.format("Problem with file: %s. Please contact: %s", FILE_NAME,
18:                     ERROR_CONTACT));
19:         }
20:     }
21: }

Interesting Lines

Line Description
16 - 19 Now we are wrapping our calls to the logger just in case we are not currently printing debugging information.

This is ugly, error prone and a pain, right? But it really does make a big difference. What if you could make this happen automagically?

Version 4

This version has the advantages of versions 1 and 2 in terms of what you write. It also has most of the advantages of version 3 in terms of not doing work unnecessarily.

First let’s look at the use of the code:

Main.java

01: package varargs.v4;
02: 
03: public final class Main {
04:     private Main() {
05:         // all static methods, do not instantiate me
06:     }
07: 
08:     public static final String FILE_NAME = "SomeFileName";
09:     public static final String ERROR_CONTACT = "someemail@palcetocontact.com";
10: 
11:     public static void main(final String[] args) {
12:         final ILogger myLogger = LoggingConfiguration.getLoggerFor(Main.class);
13: 
14:         myLogger.debug("Problem with file: %s. Please contact: %s", FILE_NAME, ERROR_CONTACT);
15:     }
16: }

Interesting Lines

Line Description
14 What? This is just back to version 2, right?
12 This was Logger, now it is ILogger.

To make this work, we first introduce an interface that uses Java 5 Variable Arguments:

ILogger.java

01: package varargs.v4;
02: 
03: public interface ILogger {
04:     ILogger debug(String formatString, Object... objects);
05:     ILogger debug(Throwable t, String formatString, Object... objects);
06:     boolean isDebugEnabled();
07: 
08:     ILogger info(String formatString, Object... objects);
09:     ILogger info(Throwable t, String formatString, Object... objects);
10:     boolean isInfoEnabled();
11: 
12:     ILogger warn(String formatString, Object... objects);
13:     ILogger warn(Throwable t, String formatString, Object... objects);
14:     boolean isWarnEnabled();
15: 
16:     ILogger error(String formatString, Object... objects);
17:     ILogger error(Throwable t, String formatString, Object... objects);
18:     boolean isErrorEnabled();
19: 
20:     ILogger fatal(String formatString, Object... objects);
21:     ILogger fatal(Throwable t, String formatString, Object... objects);
22:     boolean isFataEnabled();
23: }

Next, we write a simple implementation for this interface. For now we’ll just look at one of the methods:

14:     public ILogger debug(final String formatString, final Object... objects) {
15:         if (isDebugEnabled()) {
16:             wrappedLogger.debug(String.format(formatString, objects));
17:         }
18:         return this;
19:     }

Interesting Lines

Line Description
14 This method uses variable arguments. You can tell this when you see Object…. It turns out that objects is simply an array of Objects. You can write objects[0] to get the first element (assuming there are some elements in it).
15 Our wrapper performs the isDebugEnabled() check for us rather than having to do it everywhere where we use the logger.
16 This line uses the String.format method we introduced in Version 2. It was a necessary step. Notice that we pass in a variable number of arguments and we simply pass those arguments on to the String.format method.

This version was not possible before variable arguments. We might have some overhead in the passing of parameters but we have much more elegant code that does what it should without burdening the programmer.

Here is the full implementation:

LoggerImpl.java

01: package varargs.v4;
02: 
03: import org.apache.log4j.Level;
04: import org.apache.log4j.Logger;
05: 
06: public class LoggerImpl implements ILogger {
07:     private final Logger wrappedLogger; // NOPMD by brett.schuchert on 8/5/06
08:                                         // 5:58 PM
09: 
10:     public LoggerImpl(final Logger wrappedLogger) {
11:         this.wrappedLogger = wrappedLogger;
12:     }
13: 
14:     public ILogger debug(final String formatString, final Object... objects) {
15:         if (isDebugEnabled()) {
16:             wrappedLogger.debug(String.format(formatString, objects));
17:         }
18:         return this;
19:     }
20: 
21:     public ILogger debug(final Throwable t, final String formatString, final Object... objects) {
22:         if (isDebugEnabled()) {
23:             wrappedLogger.debug(String.format(formatString, objects), t);
24:         }
25:         return this;
26:     }
27: 
28:     public boolean isDebugEnabled() {
29:         return wrappedLogger.isDebugEnabled();
30:     }
31: 
32:     public ILogger warn(final String formatString, final Object... objects) {
33:         if (isWarnEnabled()) {
34:             wrappedLogger.warn(String.format(formatString, objects));
35:         }
36: 
37:         return this;
38:     }
39: 
40:     public ILogger warn(final Throwable t, final String formatString, final Object... objects) {
41:         if (isWarnEnabled()) {
42:             wrappedLogger.warn(String.format(formatString, objects), t);
43:         }
44: 
45:         return this;
46:     }
47: 
48:     public boolean isWarnEnabled() {
49:         return wrappedLogger.isEnabledFor(Level.WARN);
50:     }
51: 
52:     public ILogger info(final Throwable t, final String formatString, final Object... objects) {
53:         if (isInfoEnabled()) {
54:             wrappedLogger.info(String.format(formatString, objects));
55:         }
56: 
57:         return this;
58:     }
59: 
60:     public ILogger info(final String formatString, final Object... objects) {
61:         if (isInfoEnabled()) {
62:             wrappedLogger.info(String.format(formatString, objects));
63:         }
64: 
65:         return this;
66:     }
67: 
68:     public boolean isInfoEnabled() {
69:         return wrappedLogger.isEnabledFor(Level.INFO);
70:     }
71: 
72:     public ILogger error(final String formatString, final Object... objects) {
73:         if (isErrorEnabled()) {
74:             wrappedLogger.error(String.format(formatString, objects));
75:         }
76: 
77:         return this;
78:     }
79: 
80:     public ILogger error(final Throwable t, final String formatString, final Object... objects) {
81:         if (isErrorEnabled()) {
82:             wrappedLogger.error(String.format(formatString, objects), t);
83:         }
84: 
85:         return this;
86:     }
87: 
88:     public boolean isErrorEnabled() {
89:         return wrappedLogger.isEnabledFor(Level.ERROR);
90:     }
91: 
92:     public ILogger fatal(final String formatString, final Object... objects) {
93:         if (isFataEnabled()) {
94:             wrappedLogger.fatal(String.format(formatString, objects));
95:         }
96: 
97:         return this;
98:     }
99: 
100:     public ILogger fatal(final Throwable t, final String formatString, final Object... objects) {
101:         if (isFataEnabled()) {
102:             wrappedLogger.fatal(String.format(formatString, objects), t);
103:         }
104: 
105:         return this;
106:     }
107: 
108:     public boolean isFataEnabled() {
109:         return wrappedLogger.isEnabledFor(Level.FATAL);
110:     }
111: 
112: }

And finally, for completeness, here are the final two files in this final example:

LoggingConfiguration.java

01: package varargs.v4;
02: 
03: import java.net.URL;
04: 
05: import org.apache.log4j.BasicConfigurator;
06: import org.apache.log4j.Logger;
07: import org.apache.log4j.PropertyConfigurator;
08: 
09: public final class LoggingConfiguration {
10:     private static final String LOG4J_PROPS = "varargs/v4/log4j.properties";
11: 
12:     private LoggingConfiguration() {
13:         // utility class
14:     }
15: 
16:     static {
17:         BasicConfigurator.configure();
18:         URL propertiesUrl = LoggingConfiguration.class.getClassLoader().getResource(LOG4J_PROPS);
19:         if (propertiesUrl == null) {
20:             getLoggerFor(LoggingConfiguration.class).fatal("Unable to configure logger using: %s",
21:                     LOG4J_PROPS);
22:         } else {
23:             PropertyConfigurator.configure(propertiesUrl);
24:         }
25:     }
26: 
27:     public static ILogger getLoggerFor(final Class clazz) {
28:         return new LoggerImpl(Logger.getLogger(clazz));
29:     }
30: 
31:     public static void initialize() {
32:         // an empty method, when a class uses this method it causes the class
33:         // to get loaded, which runs its static initializer that reads
34:         // additional configuration.
35:     }
36: 
37: }

log4j.properties

log4j.logger.org.springframework=WARN

Notice that by using variable arguments we’ve:

But it does not come without some costs. If you wrap as demonstrated in the 3rd version:

Summary

Define Variable Arguments

    public void someMethod(Object... objects) {}

Calling a method that takes them

    someObject.someMethod("Brett", 11234, object2, 'a');

Calling a method when you already have an array of objects

    Object[] objects = new Object[]{"Brett", 11234, object2, 'a'};
    someObject.someMethod(objects);

Notes

Here is the source code for all 4 versions in an Eclipse 3.2 archive file. VariableArgumentsExample.zip

Other Resources


Comments

" Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.