Kotlin 和 Java EE(Part 1)

栏目: Java · 发布时间: 8年前

内容简介:Kotlin 和 Java EE(Part 1)

Kotlin 和 Java 都是 JVM 语言,所以它们之间相互转换很容易,是这样吗?不完全是,让 Kotlin 的类对 JEE 友好还需要一点工作。

Kotlin 的主要优势之一就是能很好地集成 Java。事实上 Java 很容易转换为 Kotlin,看起来用 Kotlin 写 Java EE 应用似乎不需要动什么脑筋。然而,两者之间存在一些微妙的差别,使得转换并不那么顺畅:

  • 大多数框架要求非 final 的类,而 Kotlin 的类是 final 的。

  • 注入会引入大量不必要的空检查。

  • 上述两点以及强制的无参数构造函数会妨碍编写函数式风格的代码。

Java EE 和 Kotlin 并不是真正的朋友,除非你撮合它们。幸好所有这些问题都可以避免。

我们的转换目标是一个简单的 Java WAR,它可以通过 REST 接口从数据库中存储和检索记录。从 GitHub 拉取 fables-kotlin 仓库开始吧。这个项目在  jee/java 目录下。如果想运行它并进行测试,请查看 GitHub 上的说明。

让 Kotlin 类对 Java EE 友好

Java EE 服务器对类的构造方式非常挑剔。它们大多数必须是非 final,而且拥有一个无参数的构造函数,以及公有方法。无参数构造函数用于实例化,而另外两个需求用于生成代理。代理会拦截对象的调用并丰富它们的附加功能。如果写的是 Java 代码,不需要考虑太多,但写 Kotlin 代码会有点不一样。

在 Build 脚本中加入 Kotlin

在开始转换之前,将 Kotlin 编译器添加到 build 脚本中。也就是从这个:

apply plugin: 'war'
description = 'Java Reference Server'

改为这个:

plugins {
  id "org.jetbrains.kotlin.jvm" version '1.1.1'
}
apply plugin: 'kotlin'
apply plugin: 'war'
description = 'Java Reference Server'
ext.kotlin_version = '1.1.1'
dependencies {
  compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 
}

我们必须注册 Kotlin 编译器插件,将其应用到模块,并添加一些依赖项。Kotlin 标准库并不是强制要求使用的,它虽然小型,却提供了大量有用的功能。

简单的开始:RestApplication 类

IntelliJ 内置支持将 Java 类转换为 Kotlin。这很容易使用:你可以打开一个 Java 类然后按下 [CTRL]+[ALT]+[SHIFT]+K,或者拷贝一段 Java 文件中的代码并在 Kotlin 文件中粘贴。两种方式都会自动转换代码。下面把这个组合键称为 [Kotlin]。

打开 RestApplication.java 类,按下 [Kotlin]。完成。

转换后的代码正常工作,但它仍然是 Java 风格的代码,只是用了不同的语言。通过不可变的 Kotlin set 并把 getClasses 变成一个真正的函数来代替复杂的 Java HashSet 初始化过程 —— 这样做:

@ApplicationPath("api") 
class RestApplication : Application() { 
    private val classes = setOf(KittenRestService::class.java) 
    override fun getClasses() = classes 
}

转换接口

按下 [Kotlin] 然后继续。

转换简单的不可变类

下一步,我们将转换 fables.kotlin.jee.java.rest.KittenRest.java 类。注意这个类只有一个构造函数,其中包含所有属性的赋值,没有 setters。这个类用于在 REST 中传输数据,所以它被写成不可变的。如果这个类拥有一个无参数构造函数并含有 setters,框架就能对它进行实例化并用 setters 填充数据。这种情况下,框架必须使用无参数构造函数。因为 Java 构造函数不提供参数名称,因此自动绑定是不可能实现的。这就是为什么需要 @JsonProperty 注解。参数名元数据在 Java 8 中 可以 通过一个特殊的编译参数提供,但默认情况下没有这个特性。

按下 [Kotlin],IDEA 会帮你把类转换成 Kotlin 代码。很神奇,类的内容不见了。

class KittenRest ( 
    @param:JsonProperty("name") val name: String, 
    @param:JsonProperty("cuteness") val cuteness: Int 
)

Kotlin 类可以拥有默认构造函数,它将作为类声明的一部分。每个被声明为 val  或  var   的参数将成为类的属性,并拥有 getter 函数。 var   还会有 setter。如果我们声明一个类是   data class ,它还会拥有 hashCode()、equals()、toString() 及其它 Kotlin 特有的方法。

休息一下,做点练习:

创建一个类型为 Person 且具有 person 属性的 JavaBean。为其添加 getter, setter, equals, hasCode, 和 toString 方法,以及接收 person 的构造函数。现在统计一下你写“person”的次数,不区分大小写。

希望你对结果不要太震惊。拥有与这个 Java 类同样功能的 Kotlin 类在代码格式化良好的情况下只需要编写 4 行代码。

JPA 实体类

热身过后,我们来尝试更有趣的事情:JPA 实体类。这是很典型的,拥有很长的 setters 和 getters、巨大的 equals hashCode , 和 toString 的类。下面会用一个简短的摘要来提醒你它有多臃肿。

@SequenceGenerator(name = "kittens_id_seq", sequenceName = "kittens_id_seq", allocationSize = 1) 
@Entity 
@Table(name = "kittens") 
public class KittenEntity implements Kitten { 
    @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "kittens_id_seq") 
    private Integer id; 
    private String name; 
    private int cuteness; 
    protected KittenEntity() { } 
    public KittenEntity(String name, int cuteness) { 
        this.name = name; this.cuteness = cuteness; 
    } 
    public Integer getId() { 
        return this.id; 
    } 
    ... 
}

这里我们有第一个进行真正改进的机会。再使用一次 [Kotlin]。现在声明它是一个数据类(data class)。将主构造函数声明为私有的,放入所有字段的声明,删除默认的东西并添加 override。删除 所有的 方法(但要保留构造函数)。让次构造函数调用主构造函数。来看看“瘦身”的效果:

@SequenceGenerator(name = "kittens_id_seq", sequenceName = "kittens_id_seq", allocationSize = 1) 
@Entity 
@Table(name = "kittens") 
data class KittenEntity private constructor( 
    @Id 
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "kittens_id_seq") 
    var id: Int?, 
    override var name: String, 
    override var cuteness: Int // set Int.MAX_VALUE for Nermal 
) : Kitten { 
    protected constructor() : this(null, "", 0) 
    constructor(name: String, cuteness: Int) : this(null, name, cuteness) 
}

不过我们仍然停留在无参数构造函数的阶段。来看看强制的 name 属性。该值将通过公共构造函数或 JPA 提供。然而,JPA 会先构造对象,然后再设置值,这意味着我们必须为构造提供一些默认值。空字符串是个简单但成本小的方案。

业务服务

KittenBusinessService.java 是一个简单的服务,它能插入和读取数据。在将其转换为 Kotlin 之后,我们必须使用一点技巧来让它发挥作用。

首先, entityManager 的声明完全错了:

protected var entityManager: EntityManager? = null 
// ...
entityManager!!.persist(kitten)

作为在类构造之后注入的值,它必须声明为可空,并使用 null 作为初始值,所有调用都需要进行空值检查。幸好 Kotlin 有办法在变量第一次使用之前设置为非空值: lateinit (延迟初始化)。也就是说现在没有值,但晚一点会有,这正是注入所做的事情。这允许我们将其声明为非空的(non-nullable),并抛出所有空值检查(null-checks):

protected lateinit var entityManager: EntityManager 
// ... 
entityManager.persist(kitten)

类和所有非私有方法必须声明为 open —— 否则就无法创建代理。我们还要对返回的 id 类型做一个小小的修复,因为实体在被持久化之后,id 不再是 null。 !! 意味着“值不能是 null;如果是,则抛出异常”。 find 方法只是个函数,所以我们按这种方式声明它。

@Stateless 
open class KittenBusinessService { 
  @PersistenceContext protected lateinit var entityManager: EntityManager 
  open fun add(kitten: KittenEntity) = { 
    entityManager.persist(kitten) 
    return kitten.id!! 
  } 
  open fun find(id: Int): Optional<KittenEntity> = 
    Optional.ofNullable(entityManager.find(KittenEntity::class.java, id)) 
}

这个方法的返回值由编译器推导。不过,我更愿意声明它。如果你弄错了,返回了一个错误的类型, 声明将会捕获到这一点,你也将得到一个编译错误,而不是一些奇怪的运行时行为。

REST 服务

再次从自动转换开始。为类和所有非私有成员声明 open。现在尝试使用服务,不会成功:

Caused by: org.jboss.weld.exceptions.UnproxyableResolutionException: 
  WELD-001480: Bean type class fables.kotlin.jee.java.rest.KittenRestService is not proxyable because it contains a final method 
  protected final void fables.kotlin.jee.java.rest.KittenRestService.setKittenBusinessService 
  (fables.kotlin.jee.java.business.KittenBusinessService) - <unknown javax.enterprise.inject.spi.Bean instance>.

记住每个类属性会自动创建 getter 和 setter,而且 Kotlin 中所有东西都是 final 的。受保护(protected)的变量 kittenBusinessService 创建了一个 final 的受保护的 setter,我们并不喜欢这样,因为它不能被代理。这里我们可以将其声明为 open 或 private,两种方式都可以。因为它是私下里使用的,我们声明它为 private,同时声明它为  lateinit 来处理空值检查:

@Inject 
private lateinit var kittenBusinessService: KittenBusinessService

Kotlin 最酷的地方在于它与 Java 能很好的互动。 我们可以一个个的去转换类,期间不会出现问题。

Kotlin 和 Java 在默认情况下有两点差异;Kotlin 的类和方法是 final 的,但 Java 的是 open 的;Kotlin 的成员是公共的,而 Java 的是受保护的。在写独立程序的时这并没有太大的差别,因为编译器会对所有问题发出警告,但是在 Java EE 框架中这会产生问题。在应用程序部署和使用之前你不会收到任何对问题的警告。通过谨慎的编码可以避免问题,但小心的对待每个类的时候难免出错。只要忘了一个 open 就会造成应用程序出错。

在下一部分中,我会告诉你如何利用 Kotlin 编辑器插件来处理 Kotlin 的细节,让你的 Java EE 应用更健壮。这些插件也会让代码更清晰,更有效,最终比对应的 Java 代码更好。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

从零开始学微信公众号运营推广

从零开始学微信公众号运营推广

叶龙 / 清华大学出版社 / 2017-6-1 / 39.80

本书是丛书的第2本,具体内容如下。 第1章 运营者入门——选择、注册和认证 第2章 变现和赚钱——如何从0到100万 第3章 决定打开率——标题的取名和优化 第4章 决定美观度——图片的选取和优化 第5章 决定停留率——正文的编辑和优化 第6章 决定欣赏率——版式的编辑和优化 第7章 数据的分析——用户内容的精准营销 书中从微信运营入门开始,以商业变......一起来看看 《从零开始学微信公众号运营推广》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具