Kotlin 和 Java EE(Part 1)

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

内容简介: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 代码更好。


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

查看所有标签

猜你喜欢:

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

O2O

O2O

张波 / 机械工业出版社华章公司 / 2013-2-5 / 49.00元

2012年是O2O元年,无论是成熟的传统企业、如火如荼的电子商务企业,还是以电信、银行、娱乐等为代表的与民生相关的企业,都在探索和践行O2O模式,因为O2O中孕育着极富创新性的商业模式。本书是国内首部O2O方面的著作,不仅宏观上叙述了O2O的概念、在各行业的应用情况,以及未来的发展趋势,而且还系统阐述和解读了各行业如何借助O2O来顺利实现商业模式的转型和升级;不仅极富洞察力地分析了O2O在营销、支......一起来看看 《O2O》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试