Java – Use generics with specifications on constructors

Use generics with specifications on constructors… here is a solution to the problem.

Use generics with specifications on constructors

How to do it correctly in Java? I want a generic routine that can create a list of objects. In this routine, I want the constructors of the classes of these objects to support specific parameters.

To clarify: I want this routine to create a List<T> from a JSON string. This is part of the larger deserialization code. If I can somehow specify that each T support implements a constructor that creates T given JSONObject, then I can write the routine like this:

interface CreatableFromJSONObject<T> {
    T(JSONObject object);      Complains about missing return type.
} 

static <T> List<T extends CreatableFromJSONObject> jsonArrayToList(JSONArray array) {
    List<T> result = new ArrayList<T>();
    for (int i = 0; i < array.length; ++i) {
         JSONObject jsonObject = array.getJSONObject(i);
         result.add(T(jsonObject));     If T has one constructor with 1 one argument JSONObject
    }
    return result;
}

Then use an example class that implements this interface

class SomeClass implements CreatableFromJSONObject {
    SomeClass(JSONObject object) throws JSONException {
         implementation here
    }
}

I

can use the method I want:

List<SomeClass> list = jsonArrayToList<SomeClass>(someJSONArray);

Now, there are a lot of hits on StackOverflow about this, so I understand that what I outlined above is impossible because Java does not support specifying a particular constructor in an interface not for static methods (this would be an alternative route for the same thing, but impossible for the same reason).

So, what is the best way to achieve this?

My best try at the moment is as follows:

public static <T> List<T> jsonArrayToList(final JSONArray jsonArray, Constructor<T> fromJSONObjectConstructor) {
    List<T> result = new ArrayList<T>();
    try {
        for (int i = 0; i < jsonArray.length(); i++) {
            result.add(fromJSONObjectConstructor.newInstance(jsonArray.getJSONObject(i)));
        }
    } catch (InvocationTargetException e) {
        throw new RuntimeException(e);
    } catch (InstantiationException e) {
        throw new RuntimeException(e);
    } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
    } catch (JSONException e) {
        throw new RuntimeException(e);
    }
    return result;
}

Then add to each class that the method should support:

public class SomeClass {
    public static final Constructor<SomeClass> jsonObjectConstructor;
    static {
        try {
            jsonObjectConstructor = CafellowEntity.class.getConstructor(JSONObject.class);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

SomeClass(JSONObject object) throws JSONException {
         Implementation here
    }
}

I use

List<SomeClass> list = jsonArrayToList(myJSONArray, SomeClass.jsonObjectConstructor);

This is the prettiest solution I can come up with, other than not using a generic implementation at all, just putting (in this case) a few lines of code that actually do the work in a routine I need for a particular class.

Any suggestions? How does this perform with alternative solutions? Java not supporting it may be telling me that I shouldn’t do this, but that doesn’t stop me from wondering about it.

Solution

Unless you are doing some unusual deserialization, this design is overly complex and error-prone. Android bundle has an excellent JSON parser that can already do this and does it well. Each type for which you currently define a custom constructor can be deserialized with a single line of code:

final CustomObj obj = new Gson().fromJson(jsonObj.toString(), CustomObj.class);

Incorporate it into your existing method and you’ll end up with:

public static <T> List<T> jsonArrayToList(final JSONArray jsonArray,
        Class<T> clazz) {
    if (jsonArray == null || clazz == null)
        return null;

final Gson gson = new Gson();
    List<T> result = new ArrayList<T>(jsonArray.length());
    for (int i = 0; i < jsonArray.length(); i++) {
        try {
            result.add(gson.fromJson(jsonArray.getJSONObject(i).toString(),
                    clazz));
        } catch (final JSONException je) {
            je.printStackTrace();
            return null;
        }
    }

return result;
}

Related Problems and Solutions