Java type-safe contructor with variable argument list

by | Jul 8, 2018 | Big Data

Type-safe arguments help to make the code more maintainable. With an increasing number of optional parameters, the number of method signatures often increases. The attempt to cover all important combinations often ends in a multitude of similar methods. This article describes the possibility to implement any number of parameters in a type-safe way with only one signature.For better explanation we are taking a look to a date-time-builder class, but instead of using the builder pattern we use only one constructor or factory method. Some people like this approach, others hate it and the rest is undecided. No matter which group they belong to, this article can help you to better understand foreign interfaces.

public MyFormatter( String pattern, Locale locale, ZoneId zoneId) {...}

All arguments have default values and are optional to formatter. Usage of final interface will look like following:

// no parameters
new MyFormatter();

// locale only
new MyFormatter(locale("en")),

// all parameters
new MyFormatter(locale("en"),format("yyyy/MM/dd"),zoneId("z"));

// same parameters in different order
new MyFormatter(zoneId("z"),locale("en"),format("yyyy/MM/dd"));

Code is self-explaining, but how to create such interface? Behind the scenen a reusable utility class implements simple value holder classes per unique type. So in this example, for the types String, ZoneId, Locale and Boolean wrappers are implemented.

public class Options {

	public static class StringOption {
		private String value;
		public StringOption(String value) { this.value = value; }
		public String getValue() { return value; }
	}

	public static class LocaleOption {
		private Locale locale;
		public LocaleOption(Locale locale) { this.locale = locale; }
		public Locale getLocale() { return locale; }
	}

	public static class BooleanOption {
		private Boolean bool;
		public BooleanOption(Boolean bool) { this.bool = bool; }
		public Boolean getBool() { return bool; }
	}
	
	public static class ZoneIdOption {
		private String zoneId;
		public ZoneIdOption(String zoneId) { this.zoneId = zoneId; }
		public String getZoneId() { return zoneId; }
	}

	@SuppressWarnings("unchecked")
	public static <base, T extends base> T getOption(Class<T> clazz, base[] opts) {
		for (base o : opts) {
			if (o.getClass() == clazz) {
				return (T) o;
			}
		}
		return null;
	}
}

The method getOption() contains a bit of magic. A type named “T” must specialize some “base” class. An array of base class references is supplied and the implementation searches arrays for first occurence of given type in array. If found the instance is returned otherwise null is returned. Will will explain this in the following paragraphs, so don’t confuse.

public class BFDateFormatBuilder implements Serializable {
	
	public static interface Option {}
	
	private static class DateFormatOption extends Options.StringOption implements Option {
		DateFormatOption(String value) { super(value); }
	}
	
	private static class DateLocaleOption extends Options.LocaleOption implements Option {
		public DateLocaleOption(Locale locale) { super(locale); }
	}
	
	private String format;
	private Locale locale;
	
	public static Option format( String format ) { return new DateFormatOption(format); }
	
	public static Option locale( Locale locale) { return new DateLocaleOption(locale); }
	
	public BFDateFormatBuilder(Option ... opts ) {
		
		DateFormatOption dateFormat = Options.getOption(DateFormatOption.class, opts);
		DateLocaleOption localeOption = Options.getOption(DateLocaleOption.class, opts);
		
		if (dateFormat!=null) {
			format = dateFormat.getValue();
		}
		if ( localeOption!=null ) {
			this.locale = localeOption.getLocale();
		} else {
			this.locale = Locale.ENGLISH;
		}
	}
   ...
}
  • Line 3 defines a shared base class for all paramaters. This is “base” class for getOption() method.
  • Line 5-11 implement concrete parameter types by specializing our classes from utils class and implement “base” interface named Option.
  • Line 16+18 define static wrapper methods to be used when calling method. They are public.
  • Line 20+… constructor takes variable list of parameters. Parameters are well known and occurences are searched by getOption(). If option is found its value is used, if it is not found a predefined default value is used instead.

Advantages

  • very readable user code
  • flexible usage of optional parameters

Disadvantages

  • For optimal usage the static helper methods need to be imported. In many IDEs static imports must written manually. Forgetting static imports may confuse users, because static methods are not offered in code completion.
  • Searching parameters by iterating the list several times may not be the most performant code and may not be optimal for high frequently used calls.

Summary

This approachs helps to improve readability of expensive builder classes. Its ideal for users to understand existing code but do not require to edit custom calls. Also it is appropriate for seldom usage on runtime. It is an alternative to the builder pattern.