Question

I'm trying to declare a generic class in a way that will use a type, which is subject to change, without having to refactor my generic class every time the type changes. Like, if member a of an enum value A is a String, then declaring Example<a> is as good as declaring Example<String>.

However, I'm having trouble declaring it, since it seems that I can't resolve what I'm trying to do to a type. I'll try to provide a boiled-down example of my particulars, then I'll be able to describe the problem in more detail.

Code example

This enum declares the type of columns in a table. (It's basically a mapping from SQL data types to Java data types.)

public enum ColTypes {
    VARCHAR( String.class ),
    INTEGER( Integer.class );

    public final Class<?> dataType;
    public ColTypes (Class<?> dataType) {
        this.dataType = dataType;
    }
}

This interface represents a column itself.

public interface Column<T> {
    public Object getColVal(T t);
}

Below is an example of a class that represents rows in some table. I have many different kinds of these classes, however, so I actually have some scripts set up to generate this code based on the database schema. They are essentially Dumb 'ol Data Objects, maybe a little more complex than that...

In order to let clients of such a class specify a column in the table for objects of that class, I include an enum of Columns inside of it. This way, you can use a Column of the class' inner enum as an argument so you can getColVal() generically instead of needing to specify particular getters. Look:

public class DBRow {
    private String foo;
    private Integer bar;
    public DBRow(String foo, Integer bar) {
        this.foo = foo;
        this.bar = bar;
    }
    public String getFoo() {
        return this.foo;
    }
    public Integer getBar() {
        return this.bar;
    }

    public enum DBCols implements Column<DBRow> {
        FOO( ColTypes.VARCHAR ) {
            public Object getColVal(DBRow r) {
                return r.getFoo();
            }
        },
        BAR( ColTypes.INTEGER ) {
            public Object getColVal(DBRow r) {
                return r.getBar();
            }
        };

        public final ColType colType;
        public DBCols(ColType colType) {
            this.colType = colType;
        }
    }
}

So you could hypothetically, for some DBRow row, call DBRow.FOO.getValue(row) instead of doing row.getFoo(). Useful for passing either DBRow.FOO or DBRow.BAR as an argument!

This class is a very simplified version of a class I have which works on collections of database objects by using that very strategy.

public abstract class ColValsGetter<T> {
    protected List<T> objs;
    protected Column<T> col;
    protected ColValsGetter(List<T> objs, Column<T> col) {
        this.objs = objs;
        this.col = col;
    }
    protected Object getColValForObj(int index) {
        return col.getColVal(objs.get(index));
    }
}

And here is a simple example of how it gets used in a concrete class. I would have one for the BAR column too. The reason that I do not make these classes generic is the fact that the logic to work with each column is very particular to the column, so it's difficult to abstract most of that behavior so that I can make these ColValsGetters take the Column type that they are working with as a type parameter.

public class DBRowFooColValsGetter extends ColValsGetter<DBRow> {
    private String fooVal;
    public DBRowFooColValsGetter(List<DBRow> rows) {
        super(rows, DBRow.DBCols.FOO);
    }
    public void complexLogicForFooOnly(int i) {
        fooVal = (String) getColValForObj(i);
        // do a lot of stuff that only makes sense for the FOO column
    };
}

The problem

Now, my issue is with potential changes to the types in our schema. (I know, sounds fishy, but just ignore that.) The cast to String that I do will cause a ClassCastException if the FOO column changes to an INTEGER type. I would much rather declare ColValsGetter<T, E> and let its getColValForObj(int) method return an E. However, declaring it as something like ColValsGetter<DBRow, String> is no good, since it suffers from the same problem. What I would like to declare is something like:

public class DBRowFooColValsGetter extends
    ColValsGetter<DBRow, DBRow.DBCols.FOO.colType.dataType>

This way, if the schema changes, the class will automatically adjust. (Please believe me when I say that even though I can't abstract the behavior of these ColValsGetters to make them generic for the columns, I can abstract the data type.)

My attempt to do this fails, however. Eclipse says that DBRow.DBCols.FOO cannot be resolved to a type. I think I understand why, since dataType is a Class object, but when I add .class at the end I get a syntax error ("Identifier expected.") Shouldn't I be able to get the type I want out of the enum values at compile time?

I'd really like to be able to accomplish this behavior somehow. Any ideas? Or is this not possible?

Était-ce utile?

La solution

I think you want to be able to change the enum and have everything else remain compilable.

That's pretty impossible as types are not really first-class data in Java. Your client code needs to use a strongly typed object and you can't get that back from a heterogeneous enum or any other heterogeneous data structure at runtime.

You could include these getters like functional objects in the enum and use delegation to embed business logic, but you're still going to end up casting.

public class ColValsGetter<T> {
    protected List<T> objs;
    protected Column<T> col;
    public ColValsGetter(List<T> objs, Column<T> col) {
        this.objs = objs;
        this.col = col;
    }
    public T getColValForObj(int index) {
        return col.getColVal(objs.get(index));
    }
}
public class ColValsGetterFactory< T > {
    public ColValsGetterFactory() {}
    public ColValsGetter< T > make( List< T > lt, Column< T > col ) {
        return new ColValsGetter< T >( lt, col );
    }
}
public enum ColTypes {
    VARCHAR( String.class, new ColValsGetterFactory< String >() ),
    INTEGER( Integer.class, new ColValsGetterFactory< Integer >() );

    public final Class<?> dataType;
    public final ColValsGetterFactory< ? > gf;
    public < T > ColTypes ( Class< T > dataType, ColValsGetterFactory< T > gf ) {
        this.dataType = dataType;
        this.gf = gf;
    }
}

public class DBRowFooColValsGetter {
    private String fooVal;
    private ColValsGetter< String > getter;
    public DBRowFooColValsGetter( List< DBRow > rows ) {
        this.getter = ( ColValsGetter< String > )DBRow.DBCols.FOO.colType.gf.make( rows, DBRow.DBCols.FOO );
    }
    public void complexLogicForFooOnly( int i ) {
        fooVal = getter.getColValForObj( i );
        // do a lot of stuff that only makes sense for the FOO column
    };
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top