[SalesForce] How to write a generic list method that will cast at runtime

I recently tripped across the need to take a list of objects (instances of a class–not sobjects) and split them into smaller lists of 200 members each.

The unit test below shows the code inside a unit test. It passes, but it's not really what I want.

I would like the signature for the method to be:

list<list<object>> listSplit(list<object> aList, integer size)

but the problem with that is a runtime conversion error:

System.TypeException: Invalid conversion from runtime type List<List<ANY>> to List<List<String>>

@isTest
public class TomTest
{
    // this unit test creates a list of 26 members
    // then calls listSplit to create chunks of 
    // 7 members each, except for the last which
    // has the left-overs.

    static testMethod void splitTest() {
        list<string> alphabet = new list<string> {
            'a', 'b', 'c', 'd', 'e', 'f', 'g',
            'h', 'i', 'j', 'k', 'l', 'm', 'n',
            'o', 'p', 'q', 'r', 's', 't', 'u', 
            'v', 'w', 'x', 'y', 'z'
        };

        list<list<string>> octaves = listSplit(alphabet, 7);

        // I should have my list of lists and the
        // first three should have 7 letters each, 
        // and the last one only 5.
        system.assertEquals(4, octaves.size());
        system.assertEquals(7, octaves[0].size());
        system.assertEquals(7, octaves[1].size());
        system.assertEquals(7, octaves[2].size());
        system.assertEquals(5, octaves[3].size());
    }

    // here's the utility for splitting a list into
    // smaller parts, but rather than hard-coding the list type,
    // I would prefer something generic like list<list<object>>, 
    // and though the method works fine that way, I can't
    // cast the result back to list<list<string>> without throwing
    // a runtime conversion exception.
    static list<list<string>> listSplit(list<string> aList, integer size)
    {
        list<list<string>> collector = new list<list<string>>();

        integer i = 1;
        list<string> petiteList = new list<string>();

        for (string each : aList) {
            petiteList.add(each);

            if (Math.Mod(i, size) == 0) {
                collector.add(petiteList);
                petiteList = new list<string>();
            }
            i++;
        }

        if (petiteList.isEmpty() == false)
            collector.add(petiteList);

        return collector;
    }
}

Best Answer

You can't cast ANY to a more specific type, so you have to actually obtain the type that you want to return. Without proper reflection, you have to tell the function what type you'd like to return. There's several ways you can do that, but here's one way:

public class Parser {
    public static List<List<Object>> splitList(List<Object> items, Integer splitSize, Type destType) {
        // Take our destination type and cast to generic
        List<List<Object>> result = (List<List<Object>>)destType.newInstance();
        // Get a copy of source list to obtain list type.
        List<Object> protoList = items.clone();
        protoList.clear();
        // This is the list that will actually be added to result
        List<Object> tempList = protoList.clone();
        // A for loop with two counters.
        Integer index = 0, count = 0, size = items.size();
        while(index < size) {
            tempList.add(items[index++]);
            count++;
            // Split size reached, add to result and make new list
            if(count == splitSize) {
                result.add(tempList);
                tempList = protoList.clone();
                count = 0;
            }
        }
        // Add left-over partial
        if(!tempList.isEmpty()) {
            result.add(protoList);
        }
        return result;
    }
}

You use the code like this:

List<List<String>> v = 
    (List<List<String>>)Parser.splitList(
        myStringList,
        5, List<List<String>>.class
    );

It's a little messy, as you have to give it a primitive data type and then cast it back, but it works.

As an alternative, you might try the "out-param" approach:

public class Parser {
    public static void splitList(List<Object> items, Integer splitSize, List<List<Object>> result) {
        List<Object> protoList = items.clone();
        protoList.clear();
        List<Object> tempList = protoList.clone();
        Integer index = 0, count = 0, size = items.size();
        while(index < size) {
            tempList.add(items[index++]);
            count++;
            if(count == splitSize) {
                result.add(tempList);
                tempList = protoList.clone();
                count = 0;
            }
        }
        if(!tempList.isEmpty()) {
            result.add(tempList);
        }
    }
}

This one can't incur the run-time exception if destType isn't compatible with List<List<Object>> and saves you a bit of casting. You can use it like this:

List<List<String>> v = new List<List<String>>();
Parser.splitList(myStringList, 5, v);

The callee now becomes responsible for initializing the correct object type, but avoids the extra casting.