Ошибка объявления общего класса с типом члена значения перечисления

1

Я пытаюсь объявить общий класс таким образом, который будет использовать тип, который может быть изменен, без необходимости рефакторировать мой общий класс при каждом изменении типа. Например, если элемент 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 в конце, я получаю синтаксическую ошибку ("Идентификатор ожидается"). Должен ли я получить тот тип, который я хочу из перечисления значения во время компиляции?

Мне бы очень хотелось, чтобы это было как-то так. Есть идеи? Или это невозможно?

  • 0
    Обобщения Java стираются во время компиляции. FOO.colType.dataType - это не тип, а переменная, содержимое которой не устанавливается до времени выполнения, поэтому не доступно для компилятора. Я думаю, что в этом случае система типов Java не сможет вам помочь.
  • 0
    @ggovan Да, так что значения enum создаются во время выполнения ... Я боялся, что это будет так. Поэтому, хотя я и объявляю ColTypes.VARCHAR с типом так явно, я не могу вывести эту String из него? Как насчет передачи в объекте Class ?
Показать ещё 13 комментариев
Теги:
generics
enums

1 ответ

0
Лучший ответ

Я думаю, вы хотите иметь возможность изменить перечисление и все остальное быть компилируемым.

Это довольно невозможно, поскольку типы не являются первоклассными данными на 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
    };
}
  • 0
    Я в порядке с приведением типов, если я могу быть уверен, что у меня не будет ClassCastException . Есть ли способ, которым я могу переписать приведение к ColValsGetter<String> чтобы мне не пришлось писать String явно?
  • 1
    Я думаю, вы имели в виду статически типизированный, а не строго типизированный. И нет, потому что Java статически типизирована. Вся информация о типе всех объектов ДОЛЖНА существовать в типе компиляции. Вы могли бы написать какой-то невероятно черный магический самоанализ, который выясняет, к какому типу он должен быть приведен, и передает его функции, выполняющей приведение, но функция для каждого типа приведений должна существовать, которую вы не можете динамически создать. бросок, и вам понадобятся совершенно отдельные конвейеры для каждого типа броска.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню