泛型 - 动态使用类型

时间:2015-10-29 15:10:23

标签: java generics

我有一个类Column,它描述了类似SQL的表的列:

public interface Column<S extends Schema<S>, T> {
    default String encode(final T value) {
        return value.toString();
    }
}

一个列包含某种类型(整数,字符串,...),并且有一个实用程序函数可以将该类型的实例转换为字符串(这对于日志记录非常有用)。

接下来,我有一个类Schema,它描述了表的架构:

public interface Schema<S extends Schema<S>> {
    List<Column<S, ?>> getColumns();
}

架构包含列列表。

让我们创建一个只有一列的具体架构:

public static class MySchema implements Schema<MySchema> {
    public static final Column<MySchema, Integer> ID = new Column<MySchema, Integer>(){};

    @Override
    public List<Column<MySchema, ?>> getColumns() {
        return Collections.singletonList(ID);
    }
}

接下来,我有一个类MyData,其中包含与架构相对应的数据:

public static class MyData<S extends Schema<S>> {
    public <T> T get(final Column<S, T> column) {
        return (T) new Integer(164); // actual implementation left out
    }
}

手动编码列的值非常简单:

final MySchema s = new MySchema();
final MyData<MySchema> d = new MyData<>();

System.out.println("encoded identifier: " + MySchema.ID.encode(d.get(MySchema.ID)));

现在,让我们动态尝试:

for (final Column<MySchema, ?> column : s.getColumns()) {
    System.out.println("encoded identifier: " + column.encode(d.get(column)));
}

这不起作用,因为d.get(column)被推断为capture<?>,这不是Column.encode()所接受的。

我该如何解决这个问题?我理解这里出了什么问题(Column.encode(T value)只接受我们现在没有的T),但我找不到一个不会丢失类型的解决方案-garanty我们只接受T强制执行。

作为一个小提琴,这里是完整的代码:

public class Test {
    public interface Column<S extends Schema<S>, T> {
        default String encode(final T value) {
            return value.toString();
        }
    }

    public interface Schema<S extends Schema<S>> {
        List<Column<S, ?>> getColumns();
    }

    public static class MyData<S extends Schema<S>> {
        public <T> T get(final Column<S, T> column) {
            return (T) new Integer(164); // actual implementation left out
        }
    }

    public static class MySchema implements Schema<MySchema> {
        public static final Column<MySchema, Integer> ID = new Column<MySchema, Integer>(){};

        @Override
        public List<Column<MySchema, ?>> getColumns() {
            return Collections.singletonList(ID);
        }
    }

    public static void main(final String a[]) {
        final MySchema s = new MySchema();
        final MyData<MySchema> d = new MyData<>();

        System.out.println("encoded identifier: " + MySchema.ID.encode(d.get(MySchema.ID)));

        for (final Column<MySchema, ?> column : s.getColumns()) {
            System.out.println("encoded identifier: " + column.encode(d.get(column)));
        }
    }
}

1 个答案:

答案 0 :(得分:1)

You can create a helper function with a generic parameter T which allows you to have a Column<T> instead of a Column<?>:

public static void main(final String a[]) {
    final MySchema s = new MySchema();
    final MyData<MySchema> d = new MyData<>();

    for (final Column<MySchema, ?> column : s.getColumns())
        encode(column, d);
}

private static <T> void encode(Column<T> column, MyData<MySchema> d) {
    System.out.println("encoded identifier: " + column.encode(d.get(column)));
}

Another possibility is to use the same trick and provide a convenience method MyData.getEncoded:

public static class MyData<S extends Schema<S>> {
    public <T> T get(final Column<S, T> column) {...}

    public <T> String getEncoded(final Column<S, T> column) {
         return column.encode(get(column));
    }
}