Java:如何使用遵循(通用)可扩展枚举类型模式的字段创建不可变值对象

标签 java generics interface enums

在 Effective Java,第 2 版中,Joshua Bloch 开发了“可扩展枚举模式”(第 34 项)。由于枚举不能被子类化,他建议通过让每个枚举实现一个通用类型(即接口(interface))来统一相关枚举组。这允许通过统一的类型名称引用枚举。此解决方案的明显问题是类型安全性有所妥协,因为至少在理论上可以通过简单地创建一个实现统一接口(interface)的类来用任何非枚举对象替换枚举。

为了解决这个问题,提出了一个解决方案。这是书中使用的带有枚举的方法声明。示例应用程序有两个枚举( BasicOperationExtendedOperation ),它们都实现了一个名为 Operation 的统一类型接口(interface)。 .该方法旨在接受适当类型的任何枚举:

private static <T extends Enum<T> & Operation> void test(
        Class<T> opset, double x, double y) {
    :
    :
}

之所以可行,是因为泛型方法类型参数确保作为函数的第一个参数提供的类文字既是枚举类型又是操作类型。

这是我正在使用的枚举中的一些代码。这个枚举是我用来描述我在我的应用程序中使用的多个数据库表中的任何一个数据库列的元数据的一组枚举之一。每个表都有自己的枚举来描述这些数据,它们都通过实现 ColumnMetaData<T> 统一起来。接口(interface)(其中 T 对应于数据库表的类)。

class Client extends DB {  // Class for the Clients table

    // MetaData for all the columns in Client
    static enum Column implements ColumnMetaData<Client> {

            CLIENT_ID   (...
                :
                :
            );
    }
}

我想在我的应用程序中使用一个名为 Datum 的值类.它旨在将数据库列的值与其列枚举放在一起。

这是我的问题:

我不能在 Datum 的构造函数中使用通用方法参数.我如何告诉编译器 Datum 中的字段之一?必须同时执行 ColumnMetaData<table>Enum<table.Column> ?目前,我正在使用以下内容:

static class Datum {

    private final Object                        val;  
    private final ColumnMetaData<? extends DB > col;

    private Datum(Object val, ColumnMetaData<? extends DB> col) {
        this.val = val;
        this.col = col;
    }

    // assorted static factories here...
    :
    :
}    

这有效,但该值未被识别为枚举类型,我想将关联的枚举常量与 EnumSet 一起使用和 EnumMap .

是否有我没有看到的优雅解决方案?

最佳答案

这是一种方法 - 我用过它并且效果很好。

使用带有泛型参数的基类:

public class Table<Column extends Enum<Column> & Table.Columns> {
  // Name of the table.
  protected final String tableName;
  // All of the columns in the table. This is actually an EnumSet so very efficient.
  protected final Set<Column> columns;

  /**
   * The base interface for all Column enums.
   */
  public interface Columns {
    // What type does it have in the database?
    public Type getType();
  }

  // Small list of database types.
  public enum Type {
    String, Number, Date;
  }

  public Table(String tableName,
               Set<Column> columns) {
    this.tableName = tableName;
    this.columns = columns;
  }
}

然后将其子类化并提供一个具体的枚举来实现您想要的每个表的接口(interface)。

public class VersionTable extends Table<VersionTable.Column> {
  public enum Column implements Table.Columns {
    Version(Table.Type.String),
    ReleaseDate(Table.Type.Date);
    final Table.Type type;

    Column(Table.Type type) {
      this.type = type;
    }

    @Override
    public Type getType() {
      return type;
    }
  }

  public VersionTable() {
    super("Versions", EnumSet.allOf(Column.class));
  }
}

我在这里使用了极少的代码,删除了大量细节,但我希望你能看到它是如何适合的。

请注意,我使用了 EnumSet 而不是您的 Class,因为 EnumSet 中有更多详细信息。

另请注意,最大类型安全性得到保留。

有趣的是有多少有用的特性都脱离了这个模式。例如,您可以使用 EnumSet 定义列集,这样您就可以立即免费获得 unionintersection 技巧。

关于Java:如何使用遵循(通用)可扩展枚举类型模式的字段创建不可变值对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18704933/

相关文章:

java - 我们可以在服务器端 JAVA 中使用 angular2 通用吗

java - 如何使用 selenium web 执行完整的代码覆盖率

java - 两个 OutputStream 而不是一个?

java - 泛型和非泛型类的类型推断和泛型构造函数

java - 将 JpaRepository 注入(inject) Generic 类时出现 NoUniqueBeanDefinitionException

c# - 如果你有一个只有抽象方法的抽象类怎么办?这与界面有何不同?

php - 何时在 PHP 中使用接口(interface)

java - 错误: Undefined Method for the Type <Interface>

java - 为什么 Java 的 DateTimeFormatter 不能安全地解析/格式化 Instant 上的往返?

java - 使用 Java 8 中的供应商用泛型列表填充数组抛出删除类型的 ClassCastEx b/c