Suppose you want to create a list object in Java and populate it with some default elements, how would you go about coding it? One will pursue one of the following approaches: (If you have any other approach feel free to share them via the comments and we will update the post accordingly).
import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class CollectionsWithoutFactoryDemo { public static void main(String[] args) { //Approach 1: Creating a list object and then adding the required elements to it List<String> myItems = new ArrayList<>(); myItems.add("item1"); myItems.add("item2"); myItems.add("item3"); myItems.add("item4"); printList(myItems); //Approach 2: Creating a list object and adding elements by using Arrays.asList method List<String> myItems2 = new ArrayList<>(Arrays.asList("item1","item2","item3","item4")); printList(myItems2); /** * The issues with Approach 1 and Approach 2 are * 1. Verbosity. * 2. Lack of reuse. */ } private static <T> void printList(List<T> myList){ System.out.println("Printing List..."); for(T item : myList){ System.out.println(item); } } }
In the Approach 1 above we create a list object and then use the add()
method to populate the elements for it. In the Approach 2 we make use of Arrays#asList()
API to create a temporary list of elements we want to be added and then pass them in the constructor of the class we are instantiating.
The issues with both the approaches are that:
- Verbosity: These approaches are more verbose i.e we have to write a lot of code to get them working. Imagine a scenario where in we had to pursue Approach 1 repeated number of times thereby leading to lot of redundant code.
- Lack of re-usability: Another major drawback of the above approaches is that they are not reusable. It would have been very easy if the code for creating and adding elements could have been reused for different list object with different elements.
To over come these issues a new idiom was created which makes use of Varargs feature added in Java 5. This idiom was originally documented here.
Collection Factory Using Varargs
I will show you 2 ways to make use of Varargs to create a factory for creating and populating collection objects. In the first approach the factory will always return an instance of ArrayList(Read: Difference between ArrayList, Vextor and LinkedList) with the data populated and in the second approach the factory will accept any Collection<T>
object and the data and then populates the data to the object passed and then returns the same object. Before we have a look at the different implementations, let me just explain what varargs is.
- Read: Annotations, Generics and Enum in Switch
What is Varargs method?
A method can take any number of parameter which are defined in the method declaration. So if the method declares that it accepts 3 parameters then one has to pass exactly 3 parameters while invoking the method. With Varargs a method can accept any number of parameters of the same type which means suppose a method declares that it accepts a vararg parameter of type String
then while invoking that method one can pass Zero or 1 or 2 or any number of String variables. There are some restrictions to while using Varargs:
- A method can declare to accept only 1 parameter of Vararg type.
//Valid declaration public void someMethod(int a, String b, double... many){} //Invalid declaration public void someMethod(int a, String b, double... many, int... someMore){}
- If a method declares to accept a vararg parameter then it has to be the last parameter in the method delcaration
//Valid declaration public void someMethod(int a, String b, double... many){} //Invalid declaration public void someMethod(int a, String b, double... many, int c){}
Now lets look at the code which creates a collection factory using Vararg methods:
import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; public class CollectionFactoryDemo { public static void main(String[] args) { /** * Using a List Factory which takes in the items and * returns a List populated with the items. */ //Initializing a list with some pre-defined elements List<String> names = listOf("name1", "name2", "name3","name4"); //printing the collection constructed using factory printCollection(listOf("item1", "item2", "item3", "item4")); //Add some other element to the names list names.add("name5"); printCollection(names); System.out.println("Using a More Generic Factory"); /** * Using a More generic Factory. * This factory accepts an instance of any implementation Collection interface * and the items to be populated to it. */ Collection<String> myItems = collectionOf(new ArrayList<String>(), "item12", "item21", "item23","item34"); printCollection(myItems); Collection<String> mySetItems = collectionOf(new HashSet<String>(), "item12", "item21", "item23","item23"); printCollection(mySetItems); //This approach is more useful when we want to quickly initialize the collection //with some data and use it in some method printCollection(collectionOf(new HashSet<String>(),"item12", "item21", "item23","item23" )); } public static <T> List<T> listOf(T... items){ return new ArrayList<>(Arrays.asList(items)); } public static <T> Collection<T> collectionOf(Collection<T> collection, T... items){ collection.addAll(Arrays.asList(items)); return collection; } private static <T> void printCollection(Collection<T> toPrintCollection){ System.out.println("Printing Collection..."); for ( T item : toPrintCollection){ System.out.print(item+", "); } System.out.println(""); } }
The output for the above code is:
Let me explain the above code in parts. The 2 approaches which I mentioned above are:
- In the first approach we create a factory which accepts the data elements to be populated and always creates an object of
ArrayList
,
populates the data elements and then returns that object.public static <T> List<T> listOf(T... items){ return new ArrayList<>(Arrays.asList(items)); }
- In the other approach we create a extensible factory method which accepts, in addition to the data elements, the instance of one of the implementations of the
Collection
interface i.e it is more generic than the first approach and can be used to populate all the collections which implement theCollection
interface.public static <T> Collection<T> collectionOf(Collection<T> collection, T... items){ collection.addAll(Arrays.asList(items)); return collection; }
And the below code illustrates how the first approach is used i.e the factory returning an object of ArrayList
/** * Using a List Factory which takes in the items and * returns a List populated with the items. */ //Initializing a list with some pre-defined elements List<String> names = listOf("name1", "name2", "name3","name4"); //printing the collection constructed using factory printCollection(listOf("item1", "item2", "item3", "item4")); //Add some other element to the names list names.add("name5"); printCollection(names);
The below code illustrates how to use the generic collections factory:
System.out.println("Using a More Generic Factory"); /** * Using a More generic Factory. * This factory accepts an instance of any implementation Collection interface * and the items to be populated to it. */ Collection<String> myItems = collectionOf(new ArrayList<String>(), "item12", "item21", "item23","item34"); printCollection(myItems); Collection<String> mySetItems = collectionOf(new HashSet<String>(), "item12", "item21", "item23","item23"); printCollection(mySetItems); //This approach is more useful when we want to quickly initialize the collection //with some data and use it in some method printCollection(collectionOf(new HashSet<String>(),"item12", "item21", "item23","item23" ));