bluedevil2k
bluedevil2k

Reputation: 9501

Java - How to Avoid Creating List and Then Copying Into it

I have this general function to populate an ArrayList of objects from a database. The problem is that I'm getting a general ArrayList class back from the DB, and then creating the specific subclass of the ArrayList I need to create, and then copying from the generic ArrayList to my subclass. I want to eliminate that unnecessary step of copying from one array to the other, since the performance won't be great with hundreds of rows. How can I eliminate that step using generics?

So, to use a more specific example, I have a data class like

public class UserData {}

and then a class like

public class UserSet extends ArrayList<UserData>

and I would populate the UserSet object by using a function call like

UserSet s = selectAll("SELECT * FROM users", UserSet.class);

and my general function to query the DB and return a UserSet instance is like this.

public static <T, S extends List<T>> S selectAll(String sql, Class<S> listType, Object...args) throws Exception
{
    // t = UserData.class in my example
    Class<T> t = (Class<T>)((ParameterizedType)listType.getGenericSuperclass()).getActualTypeArguments()[0];

    // From Apache's DBUtils project
    QueryRunner run = new QueryRunner();
    // AnnotatedDataRowProcessor is my class that just converts a DB row into a data object
    ResultSetHandler<List<T>> h = new BeanListHandler<T>(t, new AnnotatedDataRowProcessor());

    Connection conn = DB.getConnection();
    try
    {
        // creates the new instance of my specific subclass of ArrayList
        S result = listType.newInstance();
        // returns the ArrayList which I then copy into result
        result.addAll(run.query(conn, sql, h, args));
        return result;
    } 
    finally 
    {
        DbUtils.close(conn);  
    }
}

Upvotes: 0

Views: 117

Answers (2)

bluedevil2k
bluedevil2k

Reputation: 9501

I actually had to pass in the Class type into the constructor of AnnotatedDataRowProcessor so I wouldn't break the interface methods in this BeanListHandler.

private Class<?> type;

public <T> AnnotatedDataRowProcessor(Class<T> type)
{
    this.type = type;
}

@Override
public <T> List<T> toBeanList(ResultSet rs, Class<T> type) throws SQLException
{
    try
    {
        List<T> list = (List<T>)this.type.newInstance();

        while (rs.next())
        {
            list.add(toBean(rs,type));
        }

        return list;
    }
    catch (IllegalAccessException ex)
    {
        throw new SQLException(ex.getMessage());
    }
    catch (InstantiationException ex)
    {
        throw new SQLException(ex.getMessage());
    }
}

Upvotes: 0

Flavio
Flavio

Reputation: 11977

You can customize your BeanListHandler, something like this:

ResultSetHandler<List<T>> h = new BeanListHandler<T>(t, new AnnotatedDataRowProcessor()) {
    @Override
    public List<T> handle(ResultSet rs) throws SQLException {
        List<T> rows = listType.newInstance();
        while (rs.next()) {
            rows.add(this.handleRow(rs));
        }
        return rows;
    }
};

You will probably need some casts to make this compile, but this is the general idea.

Then calling run.query(conn, sql, h, args) will directly create the type you're looking for.

Upvotes: 1

Related Questions