3

I'm trying to create a lazy loading method using Java Lists that takes in an index, adds elements to the List until the index is valid, and returns the value at that index.

For example, say I have a List like this [0, 1, 2, 3]. If I call my method with it and pass in index 1, it should return 1 without changing the List in any way. If I call my method with it and pass in index 5, it should return 0 (the default Integer value) and my List should now look like this [0, 1, 2, 3, 0, 0].

It seems pretty simple to implement at first, but I run into problems when I try to pass in Lists like a List<List<String>>. I know that you can't instantiate a list, so I try to make an ArrayList, but it doesn't work.

Here's the current incarnation of my method

protected <T> T getOrCreateAt(int index, List<T> list, Class<T> elementClass) {
    while (list.size() < index + 1) {
        try {
            list.add(elementClass.newInstance());
        } catch (InstantiationException e) {
            e.printStackTrace();
            System.exit(1);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    return list.get(index);
}

Here's one place where I call it

List<List<String>> solutionText = new ArrayList<List<String>>();

for (Node node : solution) {
    List<String> row = getOrCreateAt(node.rowNo, solutionText, ArrayList.class);
    getOrCreateAt(node.colNo, row, String.class);
    row.set(node.colNo, String.valueOf(node.cellNo));
}

The second call to getOrCreateAt works, but the first one doesn't compile.

How do I get my lazy loading method to work on interfaces and abstract classes?

Eva
  • 4,397
  • 5
  • 43
  • 65
  • The easy way is to have a way to tell your list of a concrete implementation of the interface or abstract class. The hard way is to use proxy objects or a proxying library such as mockito; but those are most probably not what you want anyway. There is no way to directly instantiate an interface or abstract class. – vanza Jul 22 '12 at 22:02

4 Answers4

3

I understand the answer to this question to mean this cannot be done by simple means. A somewhat unsatisfying solution: Make the parameter elementClass non-parameterised:

static <T> T getOrCreateAt(int index, List<T> list, Class<?> elementClass) {
    while (list.size() < index + 1) {
        try {
            list.add((T) elementClass.newInstance());
            // ...

You will get a compiler warning (unchecked cast) on the cast. And indeed you will unfortunately lose type safety this way.

Community
  • 1
  • 1
2

A pattern I've followed in the past such factory-like behavior is accomplished through anonymous classes.

interface Factory<T> {
    T newInstance() throws InstantiationException, IllegalAccessException;
}

and then the implementations you need

Factory<List<String>> arrayListFactory = new Factory<List<String>>() {
    @Override
    public List<String> newInstance() throws InstantiationException, IllegalAccessException {
        return new ArrayList<String>();
    }
};
Factory<String> stringFactory = new Factory<String>() {
    @Override
    public String newInstance() throws InstantiationException, IllegalAccessException {
        return String.class.newInstance();
    }
};

and of course change your method to accept the factory thing instead of a class.

protected static <T> T getOrCreateAt(int index, List<T> list, Factory<T> factory) {
    while (list.size() < index + 1) {
        try {
            list.add(factory.newInstance());

so the usage would look like

for (Node node : solution) {
    List<String> row = TestThing.getOrCreateAt(node.rowNo, solutionText, arrayListFactory);
    getOrCreateAt(node.colNo, row, stringFactory);
    row.set(node.colNo, String.valueOf(node.cellNo));
}

If I didn't need this outside of one file, I'd probably stick them all in that source file as inner members.

Jason Dunkelberger
  • 1,207
  • 14
  • 18
0

Have you tried

protected <T> T getOrCreateAt(int index, List<T> list, Class<? extends T> elementClass) {
    // ...
}

List<String> row = getOrCreateAt(node.rowNo, solutionText, ( Class< ArrayList< String > > )solutionText.getClass());

? ArrayList< String > is a subclass of List< String >, so Class< ArrayList > is a subclass of Class< ? extends List > (but not Class< List >). The cast and getClass() call is necessary because ArrayList.class is not a Class< ? extends List< String > >.

Judge Mental
  • 5,209
  • 17
  • 22
  • My method takes in a List and Class. If I get the class of the List, I'll have the wrong type. (I can't put a List in a List.) I tried to split my call into two lines, so that I could get the class of the result, and it turns out to have the same unchecked cast warning as Sebastian Koppehel's answer. – Eva Jul 23 '12 at 06:13
  • You can't change the signature of your getOrCreateAt method as indicated? – Judge Mental Jul 23 '12 at 15:29
  • 1
    You tried to get around the fact that one cannot write something like ArrayList.class by using getClass() and then casting it. Won't work for the same reason one cannot write the former. The answer I linked explains why this is so. – Sebastian Koppehel Jul 23 '12 at 16:21
0

Subclass ArrayList (remember generics) and override the set method to get the behaviour you describe. This code shows how if nullis to be the value for unassigned elements.

public T set(int index, T arg1) {
    while (index >= size()) {
        add(null);
    }
    return super.set(index, arg1);
}

You can then use the list as you want with the new behavior just by using a different new when allocation the list.

I would guess you can do this in a single screenfull of code.

Thorbjørn Ravn Andersen
  • 73,784
  • 33
  • 194
  • 347
  • I'm a bit confused at how your answer works. To my understanding, what you're describing is moving the problem to a new class. – Eva Jul 23 '12 at 22:55
  • You create a new class which is almost an ArrayList but with a slightly different behaviour. – Thorbjørn Ravn Andersen Jul 23 '12 at 22:56
  • The problem is not that the lazy loading method doesn't work. It's that it doesn't work on certain input. How would subclassing ArrayList make the method work on interfaces and abstract classes? – Eva Jul 23 '12 at 22:58
  • 1
    Can you make a List work on interfaces and abstract classes? Oh, and by the way, if you let the elements added by expansion be left as null then it gets even simpler. – Thorbjørn Ravn Andersen Jul 23 '12 at 23:04
  • Yes, Lists can indeed have interface or abstract class elements. For example, `List>` is a valid List. Your second idea of entering null into my Lists doesn't require an ArrayList subclass. Tis a good idea though. – Eva Jul 23 '12 at 23:10