如何使用静态成员泛化 Java 枚举?

发布于 2024-08-24 10:33:17 字数 2920 浏览 3 评论 0原文

我正在重构旧应用程序的一部分,该应用程序处理从 Excel 工作表导出和导入数据库表。我们为每个表都有一个 Formatter 子类,用于提供该表的定义:它有多少列,以及每列的名称、格式和验证器是什么。然后,导出/导入表的模板方法将调用提供此数据的 getter。我已将列数据提取到枚举中,这大大简化了代码。格式化程序现在看起来像这样(为简洁起见,省略了一些细节):

public class DamageChargeFormatter extends BaseFormatter {
    public static final int NUM_COLUMNS = 7;

    public enum Column {
        VEHICLE_GROUP(0, "Vehicle Group", /* more params */),
        NAME_OF_PART(1, "Name of Part", /* more params */),
        //...
        LOSS_OF_USE(6, "Loss of Use", /* more params */);

        private static final Map<Integer, Column> intToColumn = new HashMap<Integer, Column>();

        static {
            for (Column type : values()) {
                intToColumn.put(type.getIndex(), type);
            }
        }

        public static TableColumn valueOf(int index) {
            return intToColumn.get(index);
        }

        private int index;
        private String name;

        Column(int index, String name, /* more params */) {
            this.index = index;
            this.name = name;
            //...
        }

        public int getIndex() { return index; }

        public String getName() { return name; }

        // more members and getters...
    }

    protected String getSheetName() {
        return "Damage Charges";
    }

    public String getColumnName(int columnNumber) {
        TableColumn column = Column.valueOf(columnNumber);

        if (column != null) {
            return column.getName();
        }
        return null;
    }

    // more getters...

    protected int getNumColumns() {
        return NUM_COLUMNS;
    }

    protected boolean isVariableColumnCount() {
        return false;
    }
}

现在,我有大约十几个这样的类,每个类都包含完全相同的代码,除了 NUM_COLUMNS的枚举值列不同。有没有办法以某种方式通用化这个?这样做的主要障碍是静态 Column.valueOf() 方法和静态常量 NUM_COLUMNS。后者的另一个问题是,它确实属于更高一级的抽象,即表,而不是单个列 - 如果能以某种方式将其合并到通用解决方案中,那就太好了。

从技术上讲,我可以使用基本接口(下面的TableColumn)和反射来解决这个问题,但我真的不喜欢那样,因为除了将编译时错误转换为运行时错误之外,它还使代码变得丑陋( me):

public class GenericFormatter<E extends TableColumn> extends BaseFormatter {
    private Method valueOfMethod;

    public GenericFormatter(Class<E> columnClass) {
        try {
            valueOfMethod = columnClass.getDeclaredMethod("valueOf", Integer.class);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

    public String getColumnName(int columnNumber) {
        try {
            @SuppressWarnings("unchecked")
            E elem = (E) valueOfMethod.invoke(columnNumber);

            if (elem != null) {
                return elem.getName();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return null;
    }

    //...
}

请注意,这段代码纯粹是实验性的,尚未经过测试...

是否有更好、更干净、更安全的方法?

I am refactoring a part of our legacy app which handles exporting and importing of DB tables from/to Excel sheets. We have a Formatter subclass for each table, to provide the definition of that table: how many columns it has, and what is the name, format and validator of each column. The getters which supply this data are then called by a Template Method which exports/imports the table. I have extracted the column data into an enum, which greatly simplified the code. A formatter now looks like this (some details omitted for brevity):

public class DamageChargeFormatter extends BaseFormatter {
    public static final int NUM_COLUMNS = 7;

    public enum Column {
        VEHICLE_GROUP(0, "Vehicle Group", /* more params */),
        NAME_OF_PART(1, "Name of Part", /* more params */),
        //...
        LOSS_OF_USE(6, "Loss of Use", /* more params */);

        private static final Map<Integer, Column> intToColumn = new HashMap<Integer, Column>();

        static {
            for (Column type : values()) {
                intToColumn.put(type.getIndex(), type);
            }
        }

        public static TableColumn valueOf(int index) {
            return intToColumn.get(index);
        }

        private int index;
        private String name;

        Column(int index, String name, /* more params */) {
            this.index = index;
            this.name = name;
            //...
        }

        public int getIndex() { return index; }

        public String getName() { return name; }

        // more members and getters...
    }

    protected String getSheetName() {
        return "Damage Charges";
    }

    public String getColumnName(int columnNumber) {
        TableColumn column = Column.valueOf(columnNumber);

        if (column != null) {
            return column.getName();
        }
        return null;
    }

    // more getters...

    protected int getNumColumns() {
        return NUM_COLUMNS;
    }

    protected boolean isVariableColumnCount() {
        return false;
    }
}

Now, I have about a dozen such classes, each of which containing exactly the same code except that NUM_COLUMNS and the enum values of Column are different. Is there any way to genericize this somehow? The main obstacle to this is the static Column.valueOf() method and the static constant NUM_COLUMNS. Another concern with latter is that it really belongs to an abstraction one level higher, i.e. to the table, not to an individual column - it would be nice to somehow incorporate this into the generic solution.

Technically I could solve this with a base interface (TableColumn below) and reflection, but I don't really like that, as apart from trading compile time errors to runtime errors, it makes the code ugly (to me):

public class GenericFormatter<E extends TableColumn> extends BaseFormatter {
    private Method valueOfMethod;

    public GenericFormatter(Class<E> columnClass) {
        try {
            valueOfMethod = columnClass.getDeclaredMethod("valueOf", Integer.class);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

    public String getColumnName(int columnNumber) {
        try {
            @SuppressWarnings("unchecked")
            E elem = (E) valueOfMethod.invoke(columnNumber);

            if (elem != null) {
                return elem.getName();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return null;
    }

    //...
}

Note that this code is purely experimental, as yet untested...

Is there a nicer, cleaner, safer way?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

最丧也最甜 2024-08-31 10:33:17

可能是这样的:

public class TableMetadata<E extends Enum & TableColumn> {
    private Map<Integer, TableColumn> columns = new HashMap<Integer, TableColumn>();

    public TableMetadata(Class<E> c) {
        for (E e: c.getEnumConstants()) {
            columns.put(e.getIndex(), e);
        }
    }

    public String getColumnName(int index) {
        return columns.get(index).getName();
    }
}

public class GenericFormatter<E extends TableColumn> extends BaseFormatter {  
    private TableMetadata<E> m;  

    public GenericFormatter(TableMetadata<E> m) {  
         this.m = m;
    }  

    public String getColumnName(int columnNumber) {  
        return m.getColumnName(index);
    }  

    //...  
}

编辑: Enum 添加到类型参数以提高编译时安全性

May be, something like this:

public class TableMetadata<E extends Enum & TableColumn> {
    private Map<Integer, TableColumn> columns = new HashMap<Integer, TableColumn>();

    public TableMetadata(Class<E> c) {
        for (E e: c.getEnumConstants()) {
            columns.put(e.getIndex(), e);
        }
    }

    public String getColumnName(int index) {
        return columns.get(index).getName();
    }
}

public class GenericFormatter<E extends TableColumn> extends BaseFormatter {  
    private TableMetadata<E> m;  

    public GenericFormatter(TableMetadata<E> m) {  
         this.m = m;
    }  

    public String getColumnName(int columnNumber) {  
        return m.getColumnName(index);
    }  

    //...  
}

EDIT: Enum added to the type parameter for more compile-time safety

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文