Java varargs performance issues

by Mikhail Vorontsov

One of Java 5 new features is variable length arguments lists. They could be useful if you have to provide several arguments of the same type to your method. For example, if prior to Java 5 you had to write a method which prints all its arguments to a console, it would have been:

1
2
3
4
5
public static void printAll( final Object[] args )
{
    for ( int i = 0; i < args.length; ++i )
        System.out.println( args[ i ] );
}
public static void printAll( final Object[] args )
{
    for ( int i = 0; i < args.length; ++i )
        System.out.println( args[ i ] );
}

And its call would look like:

1
printAll( new Object[] { new Integer( 75 ), new Date(), "String" + 50 } );
printAll( new Object[] { new Integer( 75 ), new Date(), "String" + 50 } );

In Java 5 varargs support was added to the language. The same method now looks much simpler:

1
2
3
4
5
public static void printAllNew( final Object... args )
{
  for ( final Object arg : args )
      System.out.println( arg );
}
public static void printAllNew( final Object... args )
{
  for ( final Object arg : args )
      System.out.println( arg );
}

Its call also became shorter (due to the added variable length argument list support):

1
printAllNew( 75, new Date(), "String" + 50 );
printAllNew( 75, new Date(), "String" + 50 );

The most important thing to remember about varargs is that nothing has really gone. All plumbing was just hidden under the hood - all these arrays are still created. So, if you have a varargs call in the inner loop of your code, check if all arguments in the variable list are constants. This may be often the case if you are checking input data against a predefined set of constants.

For example, you are getting date strings in 'yyyy/mm/dd' format and separately time strings in 'hh:mm:ss' format. It would be handy to have a method which checks that a given string contains only digits at specified positions. That's the straightforward solution:

1
2
3
4
5
6
7
public static boolean checkDigits1( final String s, final Integer... positions )
{
    for ( final Integer pos : positions )
        if ( !Character.isDigit( s.charAt( pos ) ) )
            return false;
    return true;
}
public static boolean checkDigits1( final String s, final Integer... positions )
{
    for ( final Integer pos : positions )
        if ( !Character.isDigit( s.charAt( pos ) ) )
            return false;
    return true;
}

It could be called so:

1
checkDigits1( "2011/10/12", 0, 1, 2, 3, 5, 6, 8, 9 )
checkDigits1( "2011/10/12", 0, 1, 2, 3, 5, 6, 8, 9 )

Unfortunately, on each invocation it creates array of Integer (which is, actually, a double overhead - first of all it creates an Integer[] and then converts every int argument into an Integer and adds it to the array). It would be faster to declare this array explicitly and use it for the same method calls:

1
2
3
private static final Integer[] DATE_POS = { 0, 1, 2, 3, 5, 6, 8, 9 };
...
checkDigits1( "2011/10/12", DATE_POS )
private static final Integer[] DATE_POS = { 0, 1, 2, 3, 5, 6, 8, 9 };
...
checkDigits1( "2011/10/12", DATE_POS )

The last question here is if we should use int instead of Integer, because String.charAt method requires int argument, so all Integer arguments should be unboxed. For unknown reason, using int would be slower in this case:

1
2
3
4
5
6
7
public static boolean checkDigits2( final String s, final int[] positions )
{
    for ( int i = 0; i < positions.length; ++i  )
        if ( !Character.isDigit( s.charAt( positions[ i ] ) ) )
            return false;
    return true;
}
public static boolean checkDigits2( final String s, final int[] positions )
{
    for ( int i = 0; i < positions.length; ++i  )
        if ( !Character.isDigit( s.charAt( positions[ i ] ) ) )
            return false;
    return true;
}

Both old style for loop and new style for-each loop have the same performance here. 10 million calls to check date "2011/10/12" were made:

checkDigits1, varargs checkDigits1, Integer[] checkDigits2, int[]
0.304 sec 0.219 sec 0.264 sec

See also

java.lang.Byte, Short, Integer, Long, Character (boxing and unboxing)

Summary

Varargs are great for most application code because they shorten program code, but they should be replaced with precompiled arrays when all members of varargs are known constants.