Optional Parameters Handling Strategy in Java
A detailed comparison of six different ways of handling optional parameters in Java.
Join the DZone community and get the full member experience.
Join For FreeOften we face a situation where we need to design an object that expects a lot of parameters from the client. Some parameters are required and some are optional. In this article, we will focus on various strategies by which we can design such objects, as well as their pros and cons.
Strategy 1. Telescopic Constructors
To design such objects, we can use a chain of overloading constructors. First, a minimal constructor is taking only the required parameters, then delegate calls another constructor, which takes an optional parameter.
Then this constructor calls another one, which takes another optional parameter until all optional parameters are covered. When calling another constructor, it can pass the default value for an optional parameter.
Pros
- For a small number of parameters, this is a good way to design objects.
- Very easy to implement.
Cons
- For a large number of parameters, it creates problems. The developer often confuses passing parameters.
- If two adjacent parameters have the same datatype, and a developer can unintentionally swap values. The compiler won't complain, but it creates a genuine problem at runtime and is very hard to track.
Example
package com.example.builder;
public class EmployeeTelescopic {
private String name;
private Integer empId;
private String company;
private Integer passport;
private String tempAddress ;
public EmployeeTelescopic(String name,Integer empId,String company)
{
this(name,empId,company,0);
}
public EmployeeTelescopic(String name,Integer empId,String company,int passport)
{
this(name,empId,company,passport,"NA");
}
public EmployeeTelescopic(String name,Integer empId,String company,Integer passport,String tempAddress)
{
this.name=name;
this.empId=empId;
this.company=company;
this.passport=passport;
this.tempAddress=tempAddress;
}
@Override
public String toString() {
return "EmployeeTelescopic [name=" + name + ", empId=" + empId
+ ", company=" + company + ", passport=" + passport
+ ", tempAddress=" + tempAddress + "]";
}
public static void main(String[] args) {
EmployeeTelescopic emp = new EmployeeTelescopic("Shamik",100,"IBM");
EmployeeTelescopic emp1 = new EmployeeTelescopic("Akash",101,"IBM",1234,"1,bangalore");
System.out.println(emp);
System.out.println(emp1);
}
}
Strategy 2: By Getters and Setters
Create a class and expose every property through getters and setters. The client will set the property using a setter, and access it via getters.
Pros
- Easy to implement.
- In setters, you can set validation or pass a default value for optional parameters.
Cons
- If an object is shared between multiple threads, it is possible that one thread will just create the object and try to set properties, while another thread accesses the object. As its properties have not been set, an exception can occur.
- Properties are exposed through getters and setters, and extra care is needed to make this object immutable.
Strategy 3: Using Varargs
Create a constructor like Employee(Object… args)
. The client can pass variable length parameters. A constructor then checks the type of each parameter by using an instanceof
operator.
If a type is same as a bean property, set a value. Otherwise, throw an IllegalArgument Exception.
Pros
- None
Cons
For each property, you need to check the type so it loses static type checking.
There is a conditional block for each property, which increases cyclomatic complexity.
Strategy 4: Using Map
Same as varargs, but instead of varargs we use a map.
Pros
- None
Cons
For each property, you need to check type so it loses static type checking.
There is a conditional block for each property, which increases cyclomatic complexity.
Example
package com.example.builder;
import java.util.HashMap;
import java.util.Map;
public class EmployeeMap {
private String name;
private Integer empId;
private String company;
private Integer passport;
private String tempAddress ;
public EmployeeMap(Map<String,Object> map) throws IllegalArgumentException
{
if(!(map.containsKey("name") && map.containsKey("empId") && map.containsKey("company")))
{
thrownew IllegalArgumentException("Required Parameter missing");
}
if((map.get("name")==null || map.get("empId")==null || map.get("company")==null))
{
thrownew IllegalArgumentException("Required Parameter missing");
}
if(map.get("name") instanceof String)
{
this.name =(String) map.get("name") ;
}
else{
thrownew IllegalArgumentException("name Parameter type is wrong");
}
if(map.get("empId") instanceof Integer)
{
this.empId =(Integer) map.get("empId") ;
}
else{
thrownew IllegalArgumentException("enpId Parameter type is wrong");
}
if(map.get("company") instanceof String)
{
this.company =(String) map.get("company") ;
}
else{
thrownew IllegalArgumentException("company Parameter type is wrong");
}
if(map.containsKey("passport") && (map.get("passport") instanceof Integer))
{
this.passport = (Integer)map.get("passport");
}
else
{
this.passport =0;
}
if(map.containsKey("tempAddress") && (map.get("tempAddress") instanceof String))
{
this.tempAddress = (String)map.get("tempAddress");
}
else
{
this.tempAddress="NA";
}
}
@Override
public String toString() {
return "EmployeeMap [name=" + name + ", empId=" + empId + ", company="
+ company + ", passport=" + passport + ", tempAddress="
+ tempAddress + "]";
}
public static void main(String[] args) {
try
{
Map map = new HashMap<String,Object>();
map.put("name", "Shamik");
map.put("empId", 100);
map.put("company", "IBM");
EmployeeMap emp = new EmployeeMap(map);
Map map1 = new HashMap<String,Object>();
map1.put("name", "Akash");
map1.put("empId", 101);
map1.put("company", "IBM");
map1.put("passport", "1234");
map1.put("tempAddress", "1,bangalore");
EmployeeMap emp1 = new EmployeeMap(map1);
System.out.println(emp);
System.out.println(emp1);
}
catch(IllegalArgumentException ex)
{
ex.printStackTrace();
}
}
}
Strategy 5: Null values
Here, the client pass optional value as null, and the constructor checks if value is null, then sets a default value for optional parameters. If a parameter is required and null, the constructor throws an exception.
Pros
- For a small number of parameters this is good way to design objects.
- Very easy to implement.
Cons
- For large number of parameters, it creates problems. Developer has to pass null value for optional parameters.
- If two adjacent parameters have the same datatype and developer can unintentionally swap values. Compilers do not complain, but it creates a genuine problem at runtime, and it is very hard to track.
Example
package com.example.builder;
public class EmployeeNull {
private String name;
private Integer empId;
private String company;
private Integer passport;
private String tempAddress;
public EmployeeNull(String name,Integer empId,String company,Integer passport,String tempAddress) throws IllegalArgumentException
{
if(name ==null || empId==null || company==null)
{
thrownew IllegalArgumentException("Required Parameter missing");
}
this.name=name;
this.empId=empId;
this.company=company;
this.passport= passport != null?passport:0;
this.tempAddress = tempAddress !=null? tempAddress:"NA";
}
@Override
public String toString() {
return "EmployeeNull [name=" + name + ", empId=" + empId + ", company="
+ company + ", passport=" + passport + ", tempAddress="
+ tempAddress + "]";
}
public static void main(String[] args) throws IllegalAccessException {
EmployeeNull emp = new EmployeeNull("Shamik",100,"IBM",null,null);
EmployeeNull emp1 = new EmployeeNull("Akash",101,"IBM",1234,"1,blore");
System.out.println(emp);
System.out.println(emp1);
}
}
Strategy 6: Builder Pattern
Use a nested static class, which acts as a builder of this bean. The Builder takes required parameters as its constructor arguments, and for each optional parameter there will be a helper method which sets the value and returns a Builder instance itself. So we can invoke another helper method for a parameter. The builder pattern maintains a fluent interface pattern (Channeling of methods). At last, build() method returns an Immutable bean object.
Pros
- Immutability is achieved easily, so Objects are thread-safe by design.
- To set optional parameters, the parameter position is not necessary, as each parameter has a helper method client can invoke them. It offers fluidity by a chain of methods pattern.
Cons
- Complex to implement.
Example
package com.example.builder;
public class Employee {
private String name;
private Integer empId;
private String company;
private Integer passport;
private String tempAddress ;
private Employee()
{
}
privatestaticclass EmployeeBuilder
{
private String name;
private Integer empId;
private String company;
private Integer passport;
private String tempAddress ;
public EmployeeBuilder(String name,Integer empId,String company)
{
this.name=name;
this.empId=empId;
this.company=company ;
}
public EmployeeBuilder setPassport(Integer passport)
{
this.passport=passport;
return this;
}
public EmployeeBuilder setTempAddress(String address)
{
this.tempAddress=address;
return this;
}
public Employee build()
{
Employee emp = new Employee();
emp.name=this.name;
emp.empId=this.empId;
emp.company=this.company;
emp.passport=this.passport!=null?this.passport:0;
emp.tempAddress=this.tempAddress!=null?this.tempAddress:"NA";
return emp;
}
}
@Override
public String toString() {
return "Employee [name=" + name + ", empId=" + empId + ", company="
+ company + ", passport=" + passport + ", tempAddress="
+ tempAddress + "]";
}
public static void main(String[] args) {
Employee emp = new Employee.EmployeeBuilder("Shamik", 100, "IBM").build();
Employee emp1 = new Employee.EmployeeBuilder("Akash", 101, "IBM").setPassport(1234).setTempAddress("1,bangalore").build();
System.out.println(emp);
System.out.println(emp1);
}
}
Comparison: Please see the picture below.
Conclusion: For fewer parameters, four or less, use the Telescopic constructor pattern.
For a larger number of parameters use the Builder pattern.
Published at DZone with permission of Shamik Mitra, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments