V2EX 首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  Kotlin

请教两个 Kotlin 问题

  •  
  •   hantsy · 88 天前 · 823 次点击
    这是一个创建于 88 天前的主题,其中的信息可能已经有所发展或是发生改变。
    最近看来很多人往 Kotlin 上靠,看来不少人已经入坑。我也花几天时间试用了下,属于试水阶段,将我的小例子转成 Kotlin,Helloworld 级别。

    [https://github.com/hantsy/spring-reactive-sample/tree/master/kotlin-gradle]( https://github.com/hantsy/spring-reactive-sample/tree/master/kotlin-gradle)

    目前正在移植 [https://github.com/hantsy/spring-reactive-sample/blob/master/security-data-mongo]( https://github.com/hantsy/spring-reactive-sample/blob/master/security-data-mongo) 全部功能( CRUD REST APIs, Spring Security 等)

    遇到一些问题:

    1. data class 如果实现 UserDetails 之类的接口,其中定义了一系列 , getXXX, isXX,这个 override 怎么写,简单的话,如何将这个 [User]( https://github.com/hantsy/spring-reactive-sample/blob/master/security-data-mongo/src/main/java/com/example/demo/User.java) 转成 data class.
    2. Java 变参如何转换,比如 AuthorityUtils.createAuthorityList(String... roles), Kotlin 会其参数识别 String!, 而不是 Java 中的数组。
    3. 复杂的 Java 8 Lambda 表达式转换,很多 Kotlin 不能正确识别。比如,下面的 Mono.zip 方法,如何转换成 Kotlin:

    ```
    return Mono
    .zip(
    (data) -> {
    Post p = (Post) data[0];
    Post p2 = (Post) data[1];
    p.setTitle(p2.getTitle());
    p.setContent(p2.getContent());
    return p;
    },
    this.posts.findById(req.pathVariable("id")),
    req.bodyToMono(Post.class)
    )
    .cast(Post.class)
    .flatMap(post -> this.posts.save(post))
    .flatMap(post -> ServerResponse.noContent().build());
    ```
    此代码位于: https://github.com/hantsy/spring-reactive-sample/blob/master/boot-routes/src/main/java/com/example/demo/DemoApplication.java
    10 回复  |  直到 2017-08-31 11:37:21 +08:00
        1
    hantsy   87 天前
    扫了一圈 StackOverFlow,第一个找到方案:

    ```
    @Document
    data class User(
    @Id var id: String? = null,
    private var username: String? = null,
    private var password: String? = null,
    var active: Boolean = true,
    var roles: List<String> = ArrayList()
    ) : UserDetails {

    override fun getUsername() = username

    override fun getPassword() = password

    override fun isAccountNonExpired() = active

    override fun isAccountNonLocked()= active

    override fun isCredentialsNonExpired()= active

    override fun isEnabled()= active

    override fun getAuthorities(): Collection<out GrantedAuthority> {
    // return roles.map(::SimpleGrantedAuthority).toList()
    return AuthorityUtils.createAuthorityList(* roles.toTypedArray())
    }

    }
    ```

    但是 destruction 有问题, (id, username, password, active, roles) =<user> 其中, username, password, 不能识别。

    第二个在 Kotlin 文档 Java 互操作上有说明, 需要添加额外的 Spread operator。

    ```
    return AuthorityUtils.createAuthorityList(* roles.toTypedArray())
    ```
        2
    sagaxu   87 天前
    Java 自身的 lambda 转换成 Kotlin 是能识别的,但是第三方库,它如何知道某个方法的语义?所以只能自己转换

    用 Kotlin 实现大概是这样的,reactor 我没用过,所以只能猜了,也许猜错了

    this.posts.findById(req.pathVariable("id")).zip(req.bodyToMono(Post::class.java)) {
    (p, p2) ->
    p.apply {
    title = p2.title
    content = p2.content
    }
    }.map { this.posts.save(it)}.forEach { ServerResponse.noContent().build()}


    从你提出的疑问看,Kotlin 的文档大概没有完整的扫过一遍,扫过估计就没这些问题了
        3
    hantsy   87 天前
    @sagaxu 是的,文档还没全部浏览,只看完了基础的几篇。data class 用上面 private 的话,可以 override fun getUsername(), 但 destruction 出问题,username, password 两个 private 报错: **destructing declaration initializer of type User! must have a component2 function.**.

    目前只是感觉与 Java 8 互操作上有点别扭。
        4
    sagaxu   86 天前
    @hantsy 我看了下你代码
    fun findByUsername(username: String) = template.findOne(Query().addCriteria(Criteria.where("username").`is`(username)), User::
    class.java)

    findOne 返回的是 User 的一个实例,


    UserDetailsRepository { username -> it.ref<UserRepository>()
    .findByUsername(username)
    .map { (_, username, password, active, roles) ->

    map 操作只能作用于容器,而 user 不是容器,所以 destructing 必然失败,你需要的是 List<User>,或者把 map 改成 let
        5
    hantsy   86 天前
    @sagaxu 目前这个项目所有的 API 都是尽可以用 Spring 5 的 Reactive 基础, 而那个 Mongo 相关的肯定是 ReactiveMongoTemplate 返回是 Flux,或者 Mono。

    目前 Github 上的版本没有实现 UserDetails(注释掉了), 这段代码是没有问题的,可以跑起来的。关于这个 data calss 实现 UserDetails 看了不少方案, 包括官方论坛提供的一些方法,没有完美兼容的,username, password 加上 private 后,实现就去掉了 data class 自动生成功能。

    这个 .map 是基于 Mono, 与 List/Stream,只是把 User 转换成 UserDetailsRepository 需要的返回结果类型 UserDetails。如果 User 实现了 UserDetails,就可以去掉 map 这一步, 直接返回了。
        6
    hantsy   86 天前
    Mongo template bean 定义:

    bean {
    ReactiveMongoTemplate(
    SimpleReactiveMongoDatabaseFactory(
    //ConnectionString(it.env.getProperty("mongo.uri"))
    ConnectionString("mongodb://localhost:27017/blog")
    )
    )
    }
        7
    hantsy   86 天前
    另外,Spring Data Mongo 也开始加 Kotlin 扩展了,下个版本应该可以擦除最后的 User::class.java 参数了。

    ```
    fun findByUsername(username: String) = template.findOne(Query().addCriteria(Criteria.where("username").`is`(username)), User::
    class.java)
    ```
        8
    sagaxu   85 天前
    @hantsy 抱歉,我没用过 reactive 相关技术。

    加上 private 并不会去除 data class 自动合成的方法,但是方法的隐私性会改变
    public final component1()变成 private final component1()

    既然声明为 private,从外部如何能获取? Kotlin 跟 Java 很不同的一点是,Java 习惯 private 成员通过 getter/setter 访问,而 Kotlin 直接用 public property,自动合成 getter/setter,所有的取值和赋值,会变成调用 getter/setter。如果这个值在外部还能获取,在 kotlin 中就不建议用 private 了。

    你可以试试把 username 和 password 从 constructor 里挪出去,放入到 class body 里定义,body 里定义的属性,是不会自动合成 getComponentX 方法的
        9
    hantsy   84 天前
    @sagaxu 如果实现 UserDetails 接口,无论如何修改这个 data class 看起来也不会那么优雅,代码不如 Java 版本的 Lombok @Data 干净,暂且保持现状不改这个类了。
        10
    sagaxu   83 天前
    @hantsy 自动合成的 getter 不能 override 掉 interface 的方法,目前好像无解,这个类可以不用 kotlin 写,反正 Kotlin 项目里掺几个 Java 文件也在所难免
    DigitalOcean
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   鸣谢   ·   543 人在线   最高记录 3541   ·  
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.0 · 34ms · UTC 20:28 · PVG 04:28 · LAX 12:28 · JFK 15:28
    ♥ Do have faith in what you're doing.
    沪ICP备16043287号-1