使用带参数

时间:2015-08-19 23:30:39

标签: java lambda java-8

我正在玩lambdas并进入我的脑海,我想尝试创建一个简单的数据库/对象映射器作为学习的一部分。

是的,有很多框架已经做到这一点,但这更多是关于学习和我遇到的问题是技术。

首先,我想在枚举中定义所有映射逻辑。

只有一堆字段名称才开始简单明了:

enum ThingColumn {
    id, language;
}

这让我创建了以下方法(实现不相关),它为api用户提供了对列的编译检查:

public Collection<Thing> findAll(ThingColumn... columns);

之后我想在枚举中定义更多规则,特别是如何将结果从java.sql.ResultSet映射到我的Thing类。

开始简单我创建了一个功能界面:

@FunctionalInterface
static interface ThingResultMapper {
    void map(Thing to, ResultSet from, String column) ;
}

并将其添加到枚举:

enum ThingColumn {
    id((t, rs, col) -> t.setId(rs.getLong(col))), 
    language((t, rs, col) ->t.setLanguage(rs.getString(col)));

    ThingColumn(ThingResultMapper mapper){..}
}

我创建了一个mapResultSetRow方法,该方法使用枚举中的lambdas从ResultSet中提取数据:

public Thing mapResultSetRow(ResultSet rs, ThingColumn... fields) {
    Thing t = new Thing();
    Stream.of(fields)
        .forEach(f -> f.getMapper().map(t, rs, f.name()));
    return t;
}

然后,上述findAll可以使用mapResultSetRow将相关地图集应用于ResultSet。干净整洁。

几乎无论如何。我认为枚举非常难看,并且包含很多锅炉板,你必须为每个映射添加lambda。理想情况下,我想这样做:

enum ThingColumn {
    id(ResultSet::getLong, Thing::setId), 
    language(ResultSet::getString, Thing::setLanguage);
}

然而,这当然不能编译,现在我卡住了,非静态/静态的问题..我将首先通过消除一些噪声将其分解:

enum ThingColumn {
    id(ResultSet::getLong); // <<- compile error
    ThingColumn(Function<String,?> resultSetExtractor) {..}
}

编译错误:Cannot make a static reference to the non-static method getLong(String) from the type ResultSet

我想我想要的是要么无法做到,要么可以通过改变枚举构造函数中labmda的签名来实现。

我在这个问题中发现了类似的问题: Limits of static method references in Java 8 Dmitry Ginzburg的回答(向下滚动,不被接受为正确答案)概述了一些问题,但没有解决方案。

感谢您阅读目前为止:)

有什么想法吗?

1 个答案:

答案 0 :(得分:5)

第一个示例不起作用,因为您需要处理已检查的SQLException。这可以很容易地修复。首先,在您的功能界面上声明此异常:

@FunctionalInterface
static interface ThingResultMapper {
    void map(Thing to, ResultSet from, String column) throws SQLException;
}

其次,而不是getMapper在枚举中创建一个处理它的map方法:

enum ThingColumn {
    id((t, rs, col) -> t.setId(rs.getLong(col))), 
    language((t, rs, col) ->t.setLanguage(rs.getString(col)));

    private ThingResultMapper mapper;

    ThingColumn(ThingResultMapper mapper){
        this.mapper = mapper;
    }

    public void map(Thing to, ResultSet from) {
        try {
            mapper.map(to, from, name());
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

现在您可以毫无问题地使用它:

public Thing mapResultSetRow(ResultSet rs, ThingColumn... fields) {
    Thing t = new Thing();
    Stream.of(fields).forEach(f -> f.map(t, rs));
    return t;
}

第二种方法的问题是您有不同的数据类型(LongString等)。要解决此问题,您需要一个功能界面来匹配ResultSet::getLong等方法参考:

@FunctionalInterface
static interface ResultGetter<T> {
    T get(ResultSet from, String column) throws SQLException;
}

参数是ResultSet本身(this对象,ResultSet.getLong - 类似方法是非静态的)和列。结果类型可能不同,因此它是通用的。

对于Thing设置者,您可以使用标准BiConsumer<Thing, T>类型。您还需要一个通用的参数化构造函数(是的,它们存在!)。此构造函数将创建另一个BiConsumer<Thing, ResultSet>类型的函数,该函数可以在map方法中使用。

以下是完整代码(mapResultSetRow方法与上述相同):

@FunctionalInterface
static interface ResultGetter<T> {
    T get(ResultSet from, String column) throws SQLException;
}

enum ThingColumn {
    id(ResultSet::getLong, Thing::setId), 
    language(ResultSet::getString, Thing::setLanguage);

    private final BiConsumer<Thing, ResultSet> mapper;

    <T> ThingColumn(ResultGetter<T> getter, BiConsumer<Thing, T> setter) {
        this.mapper = (t, rs) -> {
            try {
                setter.accept(t, getter.get(rs, name()));
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        };
    }

    public void map(Thing to, ResultSet from) {
        this.mapper.accept(to, from);
    }
}