7
\$\begingroup\$

I have a situation where I need to store fields of differing types of some data structures along with some similar metadata (The application takes data from one of many sources, some data possibly coming from more than one source, and then does stuff with it).

The solution I came up with was to create a class that would hold each piece of data which I could then add to each object. The DataParam object can be loaded with any data type that's determined when it's first constructed. When we get or set it later, those methods are passed a class type similar to what we are getting or setting. Since we always check against the value we originally "typed" our object with, we can never accidentally set it to some other type of value (and mess everything up).

The reason these are all generic methods and the entire class isn't a generic is because I need to store DataParam in a container and that wouldn't be possible if the class itself was a generic (I can't store a DataParam<String>, and a DataParam<int> in one container).

public class DataParam
{
    public String name
    public boolean wasUpdated
    private String type
    private boolean isArray
    private Object data;

    public <T> DataParam(String name, Class<T> typeClass)
    {
        this.name = name;
        type = typeClass.getName();
        isArray = typeClass.isArray();
        data = null;
        wasUpdate = false;
    }
    public <T> T get(Class<T> typeClass)
    {
        if(type.equals(typeClass.getName()) && isArray == typeClass.isArray())
            return typeClass.cast(data);
        else
            return null;
    }
    public <T> T set(T obj, Class<T> typeClass)
    {
        if(type.equals(typeClass.getName()) && isArray == typeClass.isArray())
            data = (Object) obj;
    }
    public String doSomething()
    {
        //An example of doing different things based on type
        switch(type)
        {
        case "java.lang.String":
            if(isArray)
                return TakesAStringArray(get(String[].class));
            else
                return TakesAString(get(String.class));
        break;
        case "java.lang.Integer":
            return TakesAnInteger(get(Integer.class));
        break;
        case "myPackage.DataParam":
            return TakesADataParam(get(myPackage.DataParam.class));
        break;
        //default: Do nothing
    }
}
public DataObj
{
    protected HashMap<String,DataParam> fields;
    //Other functions
}
public SpecificObj extends DataObj
{
    public SpecificObj()
    {
        fields.add(new DataParam("name", String.class));
        fields.add(new DataParam("obj", myPackage.DataParam.class));
        fields.add(new DataParam("count", Integer.class));
    }
}

My questions are, is there anything blatantly obviously wrong with this, like I'm gonna screw myself over later? Is there a better way that I should consider?

This should be type safe, right? (I'm checking the class every time I do anything with that piece of data, but it just seems weird that I have to do it myself versus like, the compiler raising an error that I'm doing it wrong).

\$\endgroup\$
4
\$\begingroup\$

What you are doing appears to be sane. The use of generics is primarily as a compile-time validator (and it makes some code simpler, like not having to cast things).

If you don't know the types of your data at compile time, then you can't use generics.

So, you have run-time specified data, and that's OK. Do you handle that as well as it can be handled? That is the real question.

Items I can see that raise question-marks:

  • Why are you saving away the Class name (as type) when you can save away the actual Class?
  • wasUpdated is not used.
  • type is a bad name for a Java variable. It conflicts with java.lang.Type, and you don't need it anyway if you are going to store the Class instead of its name.
  • if you do keep the class, then you can use the Class.isInstance(...) method to ensure the data types are correct, instead of specifying the class as part of the retrieval in the get(Class<T> clazz) method... or, you can confirm they are the same with a simple .equals() call instead of the name/isArray check.
  • the methods like return TakesAnInteger(...) should have a lower-case t to start with.... this is the Java convention for method names.
  • Similarly, Java convention is to put the opening brace { for a block on the same line as the method declaration.

Edit: with Class saved instead of name and isArray

Also, I note now that you are missing some semi-colons in your data declarations.... and also, you are missing return values from some of your methods.

public class DataParam {
    public String name;
    public Class<?> dataClass;
    public boolean wasUpdated;
    private Object data;

    public DataParam(String name, Class<?> typeClass) {
        this.name = name;
        dataClass = typeClass;
        data = null;
        wasUpdated = false;
    }

    public <T> T get(Class<T> typeClass) {
        if(dataClass.equals(typeClass)) {
            return typeClass.cast(data);
        }
        return null;
    }

    public <T> T set(T obj, Class<T> typeClass) {
        if(dataClass.equals(typeClass)) {
            T toreturn = typeClass.cast(data);
            data = obj;
            return toreturn;
        }
        return null;
    }

    public String doSomething() {
        //An example of doing different things based on type
        //.......
        return null;
    }
}
\$\endgroup\$
  • \$\begingroup\$ I can save the actual class? Can you elaborate? I assumed class was a generic and there would be no way to store Class<T> when T wouldn't be able to be referenced by the DataParam class. wasUpdated will be used later, it isn't shown here. It was more of an example of meta-data. Will change variable name when I figure out how to store the class. And points noted on the convention parts. I'll see what I can do. But more explanation on the storing the class will help. \$\endgroup\$ – Coburn Mar 13 '14 at 2:50
  • \$\begingroup\$ Edited answer with the example \$\endgroup\$ – rolfl Mar 13 '14 at 3:00
  • \$\begingroup\$ Thank you very much! This is also a great example of use of the wildcards. Perfect response. \$\endgroup\$ – Coburn Mar 13 '14 at 3:49
3
\$\begingroup\$

You are breaking encapsulation and the Liskov substitution principle by exposing the individual parameter objects to clients. I expect getting a value looks something like this:

String host = dataObj.get("host").get(String.class);

Blech! :) You could solve the problem and expose a cleaner API by providing type-specific accessors.

String host = dataObj.getString("host");

public String getString(String name) {
    return get(name, String.class);
}

Assuming a set of ten to twenty common types, you can make your API easier to use with these helpers. For the rest, the generic accessor will suffice. Either way, encapsulate all parameter item creation and use behind container methods, which you may already have since you left them out.

public <T> T get(String name, Class<T> type) {
    return getParam(name).get(type);
}
\$\endgroup\$
  • \$\begingroup\$ Actually, I cleaned up my code a few days ago and I came out with roughly the same method you have there at the end. I noticed that Java's SQL ResultSet has a .getString, .getLong, etc. and I wanted to implement something like that as well, I just wasn't sure how. I really like the return get(name, String.class), I didn't think of anything like that at all but I really like that. Thanks for the reply! \$\endgroup\$ – Coburn Mar 19 '14 at 1:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.