Core Java Coding Questions: Creating a custom hierarchical List in Java -- Part 1
Core Java Coding Questions and Answers for beginner to intermediate level
Q1 | Q2 | Q3 | Q4 | Q5 | Q6 |
Q. Can you write a custom list class that supports the following features?
1. Allows you to maintain a parent/child hierarchy of sub lists.
2. Allows you to create sublists from a given list and a predicate (i.e a condition)
3. If you add an item or element to a list, the addition must be propagated to its parent and child lists.
4. If you remove an element from a list, the removal must be propagated to its parent and child lists.
For example:
If you have a list of numbers say 1,2,3,4,5,6,7,8,9,10 in a list
- the customList.subList(oddNumberPredicate) should create a subList of 1,3,5,7,9 and the parent being the numbers 1 to 10.
- the customList.subList(factorOf5) should create a subList of 5,10 and the parent being the numbers 1 to 10.
- the oddNumberSubList.subList(factorOf3) should create a subList of 3,9 and the parent being 1,3,5,7,9
A. To make it easier, I will build it up step by step. This post concentrates on getting the subList(...) function working and expand on this to build the add(...) and remove(...) functions.
Step 1: Define the interface for the predicates. Refer to filtering a list with a predicate to get some background.
package test; public interface Predicate<E> { boolean evaluate(E object); }
Step 2: Define different implementations of the predicate
OddNumberPredicate:
package test; public class OddNumberPredicate<E> implements Predicate<Integer> { @Override public boolean evaluate(Integer number) { return (number != null && number % 2 > 0) ? true : false; } }
FactorOf5Predicate:
package test; public class FactorOf5Predicate<E> implements Predicate<Integer> { @Override public boolean evaluate(Integer number) { return (number != null && number % 5 == 0) ? true : false; } }
FactorOf3Predicate:
package test; public class FactorOf3Predicate<E> implements Predicate<Integer> { @Override public boolean evaluate(Integer number) { return (number != null && number % 3 == 0) ? true : false; } }
Step 3: Create the CustomList class with proper generics (i.e. E to denote Elements). The class implements the interface List from the Java Collection, hence the CustomList class needs to implement the relevant methods. If you don't want to support a particular method, throw an exception named UnsupportedOperationException. The following code only shows the class to get subList method going with the relevant constructors. Refer to the comments in the code for further explanation. Also pay attention to things like
- Declaring the variables as private so that cannot be accessed from outside the class.
- Declaring the constructors as protected to be used within the same package or sub classes of other packages
- Declaring the Generics so that the CustomList can support different data types like Number, String, User, Person, Employee, etc.
- Throwing UnsupportedOperationException from the addAll(int index, Collection c), addAll(Collection c), etc. We will these methods later on as we see fit
- In an entry to a public method that takes parameters, perform pre condition check and fail fast by throwing exceptions early.
- toString( method is added to print the items when the CustomList class is referenced in a System.out.println(...);
package test; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.ListIterator; public class CustomList<E> implements List<E> { private CustomList<E> parent = null; //parent protected List<E> list = null; //initial list private List<CustomList<E>> sublists = new ArrayList<CustomList<E>>(); //children private Predicate<E> predicate; //used to create sublists based on the //predicate /** * DEFINE THE CONSTRUCTORS */ public CustomList(List<E> sourceList) { this.list = sourceList; } // default constructor with an empty list public CustomList() { this(new ArrayList<E>()); // invokes the above constructor by supplying // an empty list } /** * access modifier is protected to only invoke from within the package and * subclasses from the other packages. This protected constructor creates * sub lists from the source or parent lists based on the supplied predicate * * @param sourceList * @param predicate */ protected CustomList(List<E> sourceList, Predicate<E> predicate) { if (predicate == null) { throw new IllegalArgumentException("Expected to receive a non-null predicate on which to base the sublist!"); } this.predicate = predicate; // Each CustomList decorates a concrete List... this.list = new ArrayList<E>(sourceList.size()); // Evaluate each item in the source to create the sublist // not using for each loop as we have not defined the iterator method // currently iterator() is throwing UnSupportedOperationException for (int i = 0; i < sourceList.size(); i++) { E item = sourceList.get(i); if (predicate.evaluate(item)) { this.list.add(item); } } } /** * access modifier is protected to only invoke from within the package and * subclasses from the other packages * * @param sourceList * @param predicate */ protected CustomList(CustomList<E> parent, Predicate<E> predicate) { this((List<E>) parent, predicate); // creates the sublist this.parent = parent; // Add ourselves (i.e. the new sublist created by this predicate) // to the parent subList parent.sublists.add(this); } /** * CREATING SUB LISTS */ public CustomList<E> subList(Predicate<E> subListPredicate) { return new CustomList<E>(this, subListPredicate); } /** * Methods that need to be implemented for the List<E> interface to satisfy * the List interface contract */ @Override public int size() { return this.list.size(); } @Override public boolean isEmpty() { return this.list.isEmpty(); } @Override public boolean contains(Object o) { return this.list.contains(o); } @Override public Iterator<E> iterator() { throw new UnsupportedOperationException(); } @Override public Object[] toArray() { return this.list.toArray(); } @Override public <T> T[] toArray(T[] a) { return this.list.toArray(a); } @Override public boolean add(E e) { throw new UnsupportedOperationException(); } @SuppressWarnings("unchecked") @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); } @Override public boolean containsAll(Collection<?> c) { return this.list.containsAll(c); } @Override public boolean addAll(Collection<? extends E> c) { throw new UnsupportedOperationException(); } @Override public boolean addAll(int index, Collection<? extends E> c) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public void clear() { // TODO Auto-generated method stub } @Override public E get(int index) { return this.list.get(index); } @Override public E set(int index, E element) { throw new UnsupportedOperationException(); } @Override public void add(int index, E element) { throw new UnsupportedOperationException(); } @Override public E remove(int index) { throw new UnsupportedOperationException(); } @Override public int indexOf(Object o) { return this.list.indexOf(o); } @Override public int lastIndexOf(Object o) { return this.list.lastIndexOf(o); } @Override public ListIterator<E> listIterator() { throw new UnsupportedOperationException(); } @Override public ListIterator<E> listIterator(int index) { throw new UnsupportedOperationException(); } @Override public List<E> subList(int fromIndex, int toIndex) { throw new UnsupportedOperationException(); } /** * very basic toString() method */ @Override public String toString() { return list.toString(); } }
Step 4: Finally, the test class with the main method to demonstrate how to use the CustomList and invoke subList method to create derived sub list classes.
package test; import java.util.Arrays; import java.util.List; public class CustomListTest { public static void main(String[] args) { List<Integer> initialList = Arrays.asList(1,2,3,4,5,6,7,8,9,10); CustomList<Integer> customList = new CustomList<Integer>(initialList); System.out.println("customList: " + customList); Predicate<Integer> oddNumberPredicate = new OddNumberPredicate<Integer>(); CustomList<Integer> oddNumbersSubList = customList.subList(oddNumberPredicate); System.out.println("oddNumbersSubList: " + oddNumbersSubList); Predicate<Integer> factorOf5Predicate = new FactorOf5Predicate<Integer>(); CustomList<Integer> factorOf5SubList = customList.subList(factorOf5Predicate); System.out.println("factorOf5SubList: " + factorOf5SubList); Predicate<Integer> factorOf3Predicate = new FactorOf3Predicate<Integer>(); CustomList<Integer> factorOf3SubList = oddNumbersSubList.subList(factorOf3Predicate); System.out.println("factorOf3SubList : " + factorOf3SubList); System.out.println("Demonstrate printing customList again"); System.out.println("customList : " + customList); } }
The output of the above test run
customList: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] oddNumbersSubList: [1, 3, 5, 7, 9] factorOf5SubList: [5, 10] factorOf3SubList : [3, 9] Demonstrate printing customList again customList : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Stay tuned, in the next part I will expand the toString( ) method to print the tree hierarchy iteratively, which can be a bit tricky for some to understand. Practice these Java coding questions by coding.
In part 2, flattening the hierarchy iteratively in the toString( ) method to output the tree details.
0 Comments:
Post a Comment
Subscribe to Post Comments [Atom]
<< Home