尝试Java,从入门到Kotlin(下)
上篇已提(tu)到(cao)Java中的各种坑。习惯了C#的各种特性和语法糖后,再转到Java感觉比较别扭。最后本着反正Java也不是很熟悉,干脆再折腾折腾其他语言的破罐子破摔的心态,逛了一圈JVM语言,最终决定转Kotlin。
为何选择Kotlin
- 项目遭遇人员变动,包括我在内就剩两个人开发,转型成本低,代码质量容易控制。
- JVM语言。号称与Java 100%兼容。实际使用的确能够与Java几乎无缝地相互调用,基本上可以无缝迁移,完美兼容Java生态。
- OOP。目前OOP仍是主流,方便后续交接或者其它新加入的开发成员上手。
- 静态类型。在选择语言的时候也考虑过像Groovy,JRuby等的动态类型语言。然而俗话说得好,动态一时爽,重构火葬场。当项目变大的时候,静态类型支持的较为完善的语义分析能够帮助项目快速整理、重构代码。并且引入很多函数式特性后,静态类型语言的开发效率与爽感,不比动态类型语言低多少。
- 吸收了一些函数式特性。除了常见的lambda,map,filter,reduce之外,还吸收了ruby的一些如对象上下文切换、代码块语法糖等便捷的特性(但是也可能导致代码可读性下降)。
- 对JetBrain的信任。JetBrain在静态分析的成果上有目共睹。相信JetBrain设计的语言应该会比较有品位(然而严格得不近人情的null safety是有点让人纠结)。
- 最后,就是刚好看到Kotlin,确认了眼神……
Kotlin好用的特性
Lambda
牺牲了CE使得Lambda不像Java中那么多的约束。引入类似Ruby代码块的写法(默认it参数),让代码看起来比较好看,虽然我个人不是很喜欢这种默认约定,但是用起来真香。
面向表达式
不同于其他语言,Kotlin里的if else,try catch等都是表达式,我们可以直接这样子写代码:
val y = if (x % 2 == 0) "even" else "odd"
val z = try { readFromFile() } catch (ex: IOException) { "" }
DSL
- Lambda是最后一个参数时,可以写在括号外面(学自ruby)。主要是用来让回调比较好看,和实现DSL。
val ls = listOf(1, 2, 3)
ls.map { 2 * it } // returns [2, 4, 6]
- Receiver。Kotlin不仅有纯函数类型,还可以通过Receiver声明类的方法类型。这个特性可以用来实现类的方法扩展、this切换的功能。
下面代码给Int
扩展了个double
方法:
val double = fun Int.() = 2 * this
val x = 3.double() // x = 6
下面例子通过切换this
实现了一个类似C#初始化对象的方法:
class Obj(init: Obj.() -> Unit) {
var prop1: Int = 0
var prop2: String = ""
init {
init(this)
}
}
val obj = Obj {
prop1 = 1
prop2 = "abc"
}
其他
- 很多好用的方法,像
listOf
,mapOf
。to
操作符等 - ……
Kotlin的坑
Kotlin没有final
,但是有open
。
Kotlin中Class默认都是不能继承的。需要继承的Class要在声明的地方加上open修饰。另外提一下有个插件叫all-open,专门用来让所有Kotlin的类变为可继承的……
注解的继承
Kotlin不支持可继承的注解。
纯的容器类型
List
,Map
不能修改其内部存储的元素。需要修改应该用MutableList
和MutableMap
。
Lombok
号称和Java 100%兼容,但是不能访问Lombok生成的方法!
因为Lombok的方法是编译期通过注解处理器(annotation processing)生成的,Kotlin编译时只调用了Javac,所以无法处理Lombok定义的方法。强制先编译Java代码,后编译Kotlin代码,可以解决这个问题,但是又会有新的问题:你不能在Java代码中调用Kotlin代码。所以如果你要混合使用Java和Kotlin的话,推荐所有数据类型都用Kotlin写。
val
和var
var
就是普通变量。val
相当于const
。平时尽量使用val
,有益身心健康。
重头戏,null safety
Null safety是Kotlin宣传得最多的特性,但是我并没有放在“好用的特性”节中介绍,因为它的坑非常多,以至于我十分怀疑null safety的好处是否能抵消它带来的副作用。
- 所有类型默认都不包括
null
值,除非加个问号定义为Nullable类型。Nullable类型取值时,强制check null。如果调用Java代码,默认Java代码都是Nullable。不过从Java来的变量不做check null倒是不会报error,只报warning。如果运行时值为null
的话,仍然会抛NullPointerException
。Kotlin的null safety的特性其实只是一个编译器的特性,通过将null
与其他类型区分开来,在类型检查的时候顺便检查了可能出现的NullPointerException
,但是在运行时非Nullable的变量实际上也是可以放进去null
值的(比如通过反射)。 - 由于非Nullable类型不被赋值为
null
值(废话),导致这些类型的变量可能会没有默认值!这是个严重的问题。如果是像Int
,String
这种比较像值的类型(其实也是引用类型)还好,可以有0
,空字符串等默认值。而像自定义的类,这种类型的变量其实是个引用,如果不能默认为null
的话,那么它的默认值的取值只能有这么几种方案:- 类似C语言,未初始化的随机值:会产生更大更不确定硬隐蔽的问题。
- 定义一个“未初始化”的值:那么这个值和
null
有什么区别?又绕回来了。 - 类似C++,默认创建一个空对象:但是并非所有类都有默认构造函数,而且在拥有GC的语言中,创建空对象需要分配内存,还会调用构造函数中的逻辑。声明变量时引入这么多过程是非常不合适的。
- 所以,Kotlin最终选了一种简单粗暴的方案:禁止变量未初始化。
禁止变量未初始化的问题在于,当你需要定义大量的数据类的时候,你就知道有多蛋疼了——所有属性都必须有个初始值。这不仅需要多敲不少键盘,影响手指健康,当碰到属性是非Nullable的聚合时,也常常无法确定其初始值。我已经隐隐看到某些开发人员将所有变量都标记为Nullable的画面了……Kotlin自身也发现了这个问题,因此引入了lateinit
特性,然而用起来仍然有点令人胆战心惊。
- 反序列化。即使是业务逻辑上明确了不会为
null
值的属性,你也无法保证网络上/数据库里传输过来的数据中,对应的属性会不会是null
值,或者干脆漏了,所以就算model设计正确的,实际运行时可能还是会出现NullPointerException
。我又隐约看到某些开发人员将所有变量都标记为Nullable的画面了……另外反序列化时,需要先生成一个空对象,也就是属性都没初始化的对象。当然Kotlin不会允许这么做的,所以还需要引入NoArg插件来自动生成无参数的构造函数……
类型擦除式泛型
为了和Java 100%兼容,Kotlin不得不跟着Java用类型擦除式泛型,也拥有了前面说过的类型擦除式泛型的所有坑。不过Kotlin可以使用内联函数来稍微缓解类型擦除的负面影响。比如可以这样定义json反序列化的方法:
inline fun <reified T> parse(json: String): T = objectMapper.readValue(json, T::class.java)
return
Kotlin有两种方法定义一个匿名函数:lambda和anonymous function。当在这两种方法的函数体中使用return时,执行的语义是不同的。根据官方文档return
会跳出最近的显示声明的函数或anonymous function。例如下面的return
会直接跳出foo
函数。
fun foo() {
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return // non-local return directly to the caller of foo()
print(it)
}
println("this point is unreachable")
}
// outputs: 12
而下面这个只是当value == 3
时跳过一次循环,相当于其他语言的continue
fun foo() {
listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) {
if (value == 3) return // local return to the caller of the anonymous fun, i.e. the forEach loop
print(value)
})
print(" done with anonymous function")
}
// outputs: 1245 done with implicit label
或者也可以使用Label来指定执行return
后跳到的位置(感觉像goto似的)。
fun foo() {
listOf(1, 2, 3, 4, 5).forEach lit@{
if (it == 3) return@lit // local return to the caller of the lambda, i.e. the forEach loop
print(it)
}
print(" done with explicit label")
}
另外,break和continue也是有类似的问题。
写在最后
最近家庭工作都比较忙,这短短的一篇转型踩坑记竟然写了个跨年。有些踩坑的记忆随着时间流逝以及用习惯了给慢慢淡化掉了,于是也没写进来。目前Java系这边的开发我尽量使用Kotlin,并没有碰到什么根本上的大问题,与Java的兼容性也挺好的,有精力的同学可以放心品尝。
尝试Java,从入门到Kotlin(下)的更多相关文章
- java/ kotlin下的单例模式
单例模式属于创建型模式, 顾名思义,就是说整个系统中只有一个该对象的实例. 为什么要使用单例模式? 1, 对于一些需要频繁创建,销毁的对象, 使用单例模式可以节省系统资源 2, 对于全局持有的对象,单 ...
- 自学 Java 怎么入门
自学 Java 怎么入门? 595赞同反对,不会显示你的姓名 给你推荐一个写得非常用心的Java基础教程:java-basic | 天码营 这个教程将Java的入门基础知识贯穿在一个实例中,逐 ...
- Ruby入门--Linux/Windows下的安装、代码开发及Rails实战
Ruby入门--Linux/Windows下的安装.代码开发及Rails实战 http://www.linuxidc.com/Linux/2014-04/100242.htm Ubuntu 13.04 ...
- java秀发入门到优雅秃头路线导航【教学视频+博客+书籍整理】
目录 一.Java基础 二.关于JavaWeb基础 三.关于数据库 四.关于ssm框架 五.关于数据结构与算法 六.关于开发工具idea 七.关于项目管理工具Mawen.Git.SVN.Gradle. ...
- 史上最强Java NIO入门:担心从入门到放弃的,请读这篇!
本文原题“<NIO 入门>,作者为“Gregory M. Travis”,他是<JDK 1.4 Tutorial>等书籍的作者. 1.引言 Java NIO是Java 1.4版 ...
- Java Annotation入门
Java Annotation入门作者:cleverpig 版权声明:本文可以自由转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明作者:cleverpig(作者的Blog:http:/ ...
- 《JAVA 从入门到精通》 - 正式走向JAVA项目开发的路
以前很多时候会开玩笑,说什么,三天学会PHP,七天精通Nodejs,xx天学会xx ... 一般来说,这样子说的多半都带有一点讽刺的意味,我也基本上从不相信什么快速入门.我以前在学校的时候自觉过很多门 ...
- Java学习心得之 Linux下搭建JavaWeb环境
作者:枫雪庭 出处:http://www.cnblogs.com/FengXueTing-px/ 欢迎转载 Java学习心得之 Linux下搭建JavaWeb环境 1. 前言2. Java安装3. t ...
- Java NIO入门(二):缓冲区内部细节
Java NIO 入门(二)缓冲区内部细节 概述 本文将介绍 NIO 中两个重要的缓冲区组件:状态变量和访问方法 (accessor). 状态变量是前一文中提到的"内部统计机制"的 ...
随机推荐
- 安装部署jumpserver3.0
1.安装依赖包yum -y install git readline-devel automake autoconf2.下载 jumpservergit clone https://github.co ...
- vue项目安装vux
本文章默认基于“vue init webpack myproject”已经搭好基本的项目, 而且本文是从我有道笔记拷贝稍加修改过来的 本来我私人笔记写给自己看的所以有些地方可能描述不够清晰 需要修改的 ...
- Linux 的文件权限和目录配置
1.Linux文件属性 用root用户登录linux后,执行 ls -al 命令查看文件.显示如下: 文件属性示意图如下: 第一列代表这个文件的类型和权限 第一个字符代表这个文件是:目录.文件或链接文 ...
- Python内置函数(59)——sorted
英文文档: sorted(iterable[, key][, reverse]) Return a new sorted list from the items in iterable. Has tw ...
- 虎牙数万主播同时在线直播的秘密,CDN推流日志上行实时监控
6 月 10 日,又拍云 Open Talk | 2018 音视频技术沙龙·深圳站 顺利落幕,来自虎牙的直播运维研发架构师张波在沙龙上做了<基于CDN推流日志的主播上行实时监控及其自动化解密&g ...
- .NET Core实战项目之CMS 第十一章 开发篇-数据库生成及实体代码生成器开发
上篇给大家从零开始搭建了一个我们的ASP.NET Core CMS系统的开发框架,具体为什么那样设计我也已经在第十篇文章中进行了说明.不过文章发布后很多人都说了这样的分层不是很合理,什么数据库实体应该 ...
- 【转】CGI
CGI是什么 (一): CGI是Common Gateway Interface 的简称.是一个用于定Web服务器与外部程序之间通信方式的标准,使得外部程序能生成HTML.图像或者其他内容,而服务器处 ...
- SpringCloud(6)---熔断降级理解、Hystrix实战
SpringCloud(6)---熔断降级理解.Hystrix实战 一.概念 1.为什么需要熔断降级 (1)需求背景 它是系统负载过高,突发流量或者网络等各种异常情况介绍,常用的解决方案. 在一个分布 ...
- Python操作Redis及连接方式
前沿:随着互联网的高速发展,数据变得越来越重要,Python成为了人工智能的热门语言,而Nosql数据库已成为日常开发用品. 今天要写的是Python操作Redis Redis的安装我就不介绍了,你可 ...
- 带着新人学springboot的应用07(springboot+RabbitMQ 下)
说一两句废话,强烈推荐各位小伙伴空闲时候也可以写写自己的博客!不管水平高低,不管写的怎么样,不要觉得写不好或者水平不够就不写了(咳,我以前就是这样的想法...自我反省!). 但是开始写博客之后,你会发 ...