Kotlin和Java EE系列之—— 如何让Kotlin类对Java EE友好

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 脚本中。也就是从这个:

改为这个:

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

简单的开始:RestApplication 类

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

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

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

转换接口

按下 [Kotlin] 然后继续。

转换简单的不可变类

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

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

休息一下,做点练习:

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

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

JPA 实体类

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

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

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

业务服务

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

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

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

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

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

REST 服务

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

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

小结

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

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

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

作者:OSC-协作翻译

来源:51CTO

时间: 2024-08-07 01:37:02

Kotlin和Java EE系列之—— 如何让Kotlin类对Java EE友好的相关文章

我的Java开发学习之旅------>工具类:Java获取字符串和文件进行MD5值

ps:这几天本人用百度云盘秒传了几部大片到云盘上,几个G的文件瞬秒竟然显示"上传成功"!这真让我目瞪口呆,要是这样的话,那得多快的网速,这绝对是不可能的,也许这仅是个假象.百度了一下才发现所谓的"秒传"是常见的"忽略式"上传方式,就是您上传了一个文件名为111.exe,MD5为一个数,有一个网友以前也上传一个叫222.exe,MD5和您上传的文件MD5码一模一样,所以这个文件上传到服务器上的时间就很短了,这是因为别人上传过这个文件,您上传这个文件

java动态加载jar中的类 报java.lang.NoClassDefFoundError错误

问题描述 现在两个工程1.工程A是调用工程2.工程B是被调用工程其中如果工程B中没有调用第三方的jar包时通过动态加载(URLClassLoader)的方式可以调用工程B中类的方法.现在工程B中引入了第三方的一个jar包如commons-lang-2.5.jar只用到一个StringUtils.isBlank()方法这时候A动态加载B中的类时就会报java.lang.NoClassDefFoundError求解打包的MANIFEST.MF文件如:Manifest-Version:1.0Class

java io系列17之 System.out.println("hello world")原理

我们初学java的第一个程序是"hello world" 1 public class HelloWorld { 2 public static void main(String[] args) { 3 System.out.println("hello world"); 4 } 5 } 上面程序到底是怎么在屏幕上输出"hello world"的呢?这就是本来要讲解的内容,即System.out.println("hello world

【java开发系列】—— Tomcat编译报错

由于之前Eclipse里面有一个可移植性的web工程,但是在我很久没用后,再次登录这个IDE的时候就发现了问题. 首先,我的电脑里面有两个版本的JDK,1.6和1.7.两个版本的Tomcat6和7以及两个版本的Eclipse IDE 3.5和4.0+. 当我启动开发环境后,想要向server中添加应用,发现报错: There are no resources that can be added or removed from the server. 我之前好用的应用怎么回不好使呢.于是删除ser

【java开发系列】—— struts2简单入门示例

原文:[java开发系列]-- struts2简单入门示例 上篇推荐:JDK安装 前言 最近正好有时间总结一下,过去的知识历程,虽说东西都是入门级的,高手肯定是不屑一顾了,但是对于初次涉猎的小白们,还是可以提供点参考的. struts2其实就是为我们封装了servlet,简化了jsp跳转的复杂操作,并且提供了易于编写的标签,可以快速开发view层的代码. 过去,我们用jsp和servlet搭配,实现展现时,答题的过程是: 1 jsp出发action 2 servlet接受action,交给后台c

Java NIO 系列教程

Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API.本系列教程将有助于你学习和理解Java NIO.感谢并发编程网的翻译和投递.  (关注ITeye官微,随时随地查看最新开发资讯.技术文章.)  [本文转载于 Java NIO 系列教程] Java NIO提供了与标准IO不同的IO工作方式:  Channels and Buffers(通道和缓冲区):标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channe

Java NIO 系列教程(转)

原文中说了最重要的3个概念,Channel 通道Buffer 缓冲区Selector 选择器其中Channel对应以前的流,Buffer不是什么新东西,Selector是因为nio可以使用异步的非堵塞模式才加入的东西.以前的流总是堵塞的,一个线程只要对它进行操作,其它操作就会被堵塞,也就相当于水管没有阀门,你伸手接水的时候,不管水到了没有,你就都只能耗在接水(流)上.nio的Channel的加入,相当于增加了水龙头(有阀门),虽然一个时刻也只能接一个水管的水,但依赖轮换策略,在水量不大的时候,各

JAVA数据结构系列 栈

java数据结构系列之栈 手写栈 1.利用链表做出栈,因为栈的特殊,插入删除操作都是在栈顶进行,链表不用担心栈的长度,所以链表再合适不过了,非常好用,不过它在插入和删除元素的时候,速度比数组栈慢,因为它要维护自己的指针(Next)引用. package com.rsc.stack; import java.util.LinkedList; /** * 利用链表实现的栈 * @author 落雨 * http://ae6623.cn * @param <T> */ public class Li

Java Cache-EHCache系列之AA-Tree实现溢出到磁盘的数据管理(2)

在上一篇<Java Cache-EHCache系列之AA-Tree实现溢出到磁盘的数据管理(1)>已经详细讲解了EHCache中在AATreeSet中对AA Tree算法的实现,并且指出EHCache是采用它作为空闲磁盘管理数据结构,本文主要关注于EHCache是如何采用AATreeSet类来管理空闲磁盘的(这里的磁盘管理是只EHCache data文件中的空闲磁盘). 空闲磁盘管理数据结构和算法在上一篇<Java Cache-EHCache系列之AA-Tree实现溢出到磁盘的数据管理&