Я пытаюсь объявить общий класс таким образом, который будет использовать тип, который может быть изменен, без необходимости рефакторировать мой общий класс при каждом изменении типа. Например, если элемент a
значения перечисления A
является String
, тогда объявление Example<a>
также хорош, как объявление Example<String>
.
Однако мне сложно объявить об этом, так как кажется, что я не могу решить, что я пытаюсь сделать с типом. Я постараюсь представить пример, который я привел бы в деталях, но я смогу описать проблему более подробно.
Это enum
объявляет тип столбцов в таблице. (Это в основном сопоставление типов данных SQL с типами данных Java).
public enum ColTypes {
VARCHAR( String.class ),
INTEGER( Integer.class );
public final Class<?> dataType;
public ColTypes (Class<?> dataType) {
this.dataType = dataType;
}
}
Этот интерфейс представляет собой столбец.
public interface Column<T> {
public Object getColVal(T t);
}
Ниже приведен пример класса, представляющего строки в некоторой таблице. Однако у меня много разных типов этих классов, поэтому у меня есть некоторые сценарии, созданные для создания этого кода на основе схемы базы данных. Это, по сути, Dumb 'ol Data Objects, возможно, немного сложнее, чем это...
Чтобы клиенты такого класса указывали столбец в таблице для объектов этого класса, я включаю enum
Column
внутри него. Таким образом, вы можете использовать Column
внутреннего enum класса как аргумент, чтобы вы могли getColVal()
общем случае вместо необходимости указывать определенные геттеры. Посмотрите:
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;
}
}
}
Таким образом, вы могли гипотетически, для некоторой DBRow row
вызвать DBRow.FOO.getValue(row)
вместо выполнения row.getFoo()
. Полезно для передачи DBRow.FOO
или DBRow.BAR
в качестве аргумента!
Этот класс является очень упрощенной версией класса I, который работает с коллекциями объектов базы данных, используя эту самую стратегию.
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));
}
}
И вот простой пример того, как он используется в конкретном классе. У меня тоже будет столбец BAR
. Причина, по которой я не делаю эти общие классы, заключается в том, что логика для работы с каждым столбцом очень специфична для столбца, поэтому трудно абстрагировать большую часть этого поведения, чтобы я мог заставить эти ColValsGetter
использовать тип Column
который они работают как параметр типа.
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
};
}
Теперь моя проблема связана с потенциальными изменениями типов в нашей схеме. (Я знаю, звучит подозрительно, но просто игнорируйте это.) Приведение в String
которое я делаю, вызовет ClassCastException
если столбец FOO
изменится на тип INTEGER
. Я бы скорее заявил ColValsGetter<T, E>
и пусть его getColValForObj(int)
возвращает E
Однако, объявляя это как-то вроде ColValsGetter<DBRow, String>
это не хорошо, так как он страдает от одной и той же проблемы. Я хотел бы объявить что-то вроде:
public class DBRowFooColValsGetter extends
ColValsGetter<DBRow, DBRow.DBCols.FOO.colType.dataType>
Таким образом, если схема изменится, класс будет автоматически настраиваться. (Пожалуйста, поверьте мне, когда я скажу, что хотя я не могу ColValsGetter
поведение этих ColValsGetter
чтобы сделать их обобщенными для столбцов, я могу абстрагировать тип данных.)
Однако моя попытка сделать это не удалась. Eclipse говорит, что DBRow.DBCols.FOO
не может быть разрешен для типа. Я думаю, что я понимаю, почему, поскольку dataType
- это объект Class
, но когда я добавляю .class
в конце, я получаю синтаксическую ошибку ("Идентификатор ожидается"). Должен ли я получить тот тип, который я хочу из перечисления значения во время компиляции?
Мне бы очень хотелось, чтобы это было как-то так. Есть идеи? Или это невозможно?
Я думаю, вы хотите иметь возможность изменить перечисление и все остальное быть компилируемым.
Это довольно невозможно, поскольку типы не являются первоклассными данными на Java. Код клиента должен использовать строго типизированный объект, и вы не сможете получить его обратно из гетерогенного перечисления или любой другой разнородной структуры данных во время выполнения.
Вы можете включить эти геттеры как функциональные объекты в перечисление и использовать делегирование для встраивания бизнес-логики, но вы все равно закончите кастинг.
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
};
}
ClassCastException
. Есть ли способ, которым я могу переписать приведение к ColValsGetter<String>
чтобы мне не пришлось писать String
явно?
FOO.colType.dataType
- это не тип, а переменная, содержимое которой не устанавливается до времени выполнения, поэтому не доступно для компилятора. Я думаю, что в этом случае система типов Java не сможет вам помочь.ColTypes.VARCHAR
с типом так явно, я не могу вывести этуString
из него? Как насчет передачи в объектеClass
?