使用带参数的方法引用

Gei*_*eir 5 java lambda java-8

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

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

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

它只用一堆字段名称开始简单明了:

enum ThingColumn {
    id, language;
}
Run Code Online (Sandbox Code Playgroud)

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

public Collection<Thing> findAll(ThingColumn... columns);
Run Code Online (Sandbox Code Playgroud)

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

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

@FunctionalInterface
static interface ThingResultMapper {
    void map(Thing to, ResultSet from, String column) ;
}
Run Code Online (Sandbox Code Playgroud)

并将其添加到枚举中:

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

    ThingColumn(ThingResultMapper mapper){..}
}
Run Code Online (Sandbox Code Playgroud)

我创建了一个mapResultSetRow方法,它使用枚举中的lambda来从以下位置提取数据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;
}
Run Code Online (Sandbox Code Playgroud)

以上findAll可以使用mapResultSetRow相关的映射器来应用ResultSet.干净整洁.

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

enum ThingColumn {
    id(ResultSet::getLong, Thing::setId), 
    language(ResultSet::getString, Thing::setLanguage);
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

我想我想要的是要么不可能做,要么通过改变枚举构造函数中labmda的签名.

我在这个问题中发现了一个类似的问题: Java 8静态方法引用的限制,其中Dmitry Ginzburg的答案(向下滚动,不被接受为正确答案)概述了一些问题,但没有解决方案.

谢谢你到目前为止阅读:)

有什么想法吗?

Tag*_*eev 5

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

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

其次,而不是在处理它的枚举中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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

与第二个方法的问题是,你有不同的数据类型(Long,String,等).要解决这个问题,您需要一个功能接口来匹配ResultSet::getLong等方法参考:

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

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

对于Thingsetter,您可以使用标准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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @FedericoPeraltaSchaffner,它们也可用于课程. (2认同)