My-Blog
My-Blog copied to clipboard
Kotlin 吐槽大会
因为不满意 JavaScript 以及相关工具链,所以想换个 JVM 系的语言写前端, 选来选去选了 Kotlin,最初期望很大,试用了几个月后挺失望的,憋得难受,不吐不快。
一开始想用 Kotlin 写个能在前端跑的 html parser,因为已经有一个用 Java 开发的 jsoup了,所以就不想从头造了,反正 Java 代码能转成 Kotlin 代码,正好可以从一个现有成熟 Java 项目的角度来审视 Kotlin 。
每个回贴说一个槽点。
将现有 Java 代码用 Kotlin 重写后并不是每个项目的代码量都能减少 30%
以 jsoup 为例,用 IntelliJ IDEA 2018.1.1 (Community Edition) 转成 Kotlin 后,Java 版的核心代码是 680kb,而 Kotlin 版的是 650kb,只少了 30kb,连个零头都不到。
还试了另一个 Java 开源项目: jericho-html,转成 Kotlin 后代码量也没降多少。
单纯拿 Java 跟 Kotlin 同类的语言特性来比较代码量是没意义的,对于一个成熟的项目来说,因为绝大多数代码都是用最基础的语言特性来写的,这些特性在 Java 和 Kotlin 两者之间并没有多大的区别。
不要指望把现有的 Java 代码自动转成 Kotlin 后没有任何错误
把 jsoup、 jericho-html 这两个 Java 项目转成 Kotlin 后就存在大量的错误。
还不支持'\f'转义字符
需要用'\u000C'这样的格式替换'\f'。
null 检测是个糟糕的语言特性
把现有的 Java 代码自动转成 Kotlin 后最多的问题就是 null 检测问题,多到让人抓狂。
随便拿 jsoup 的代码来举两个例子:
private final String[] stringCache = new String[512] 被转成
private val stringCache = arrayOfNulls<String>(512)
把 stringCache 作为参数调用其他方法时,这些方法的参数必须使用 Array<String?> 类型,但是 IDEA 转成 Kotlin 后用的是 Array<String>。从这个例子不但看出 IDEA 足够弱智,而且转换后的代码包括修正后的都啰嗦难看之极。
第二个例子: 像 org.jsoup.parser.TreeBuilder 这种抽象类,有很多实例字段并不是在构造函数中初始化的,而是通过一个类似init的方法来统一初始化,那么这些字段转成 Kotlin 后要不要加问号?IDEA 对于这个类中的代码场景不知所措,我至今都没有用 Kotlin 的思维修正这个类的代码问题。
啰嗦的 open 关键字
在 Java 中,当一个类/方法用 protected/public 修饰并且没有final时就代表可继承或覆盖, 特别的,按惯例当一个方法用 protected 修饰时就已经暗示原作者已经考虑到被子类覆盖的问题了,而此时又要加上 open,对比原有的 Java 代码显得很啰嗦。
用companion object来模拟static成员的行为很糟糕
存在大量使用 Logger 的 Java 代码,仅仅是下面一行代码:
private static final Logger logger = LoggerFactory.getLogger(XXX.class);
使用 companion object 不但啰嗦,还浪费内存。
在while循环条件中不能使用赋值表达式
比如,在 Java 中可以像下面这样使用赋值表达式:
static void crossStreams(final InputStream in, final OutputStream out) throws IOException {
final byte[] buffer = new byte[bufferSize];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
}
IDEA 转成 Kotlin 代码后会报错,得像下面这样修正:
@Throws(IOException::class)
internal fun crossStreams(`in`: InputStream, out: OutputStream) {
val buffer = ByteArray(bufferSize)
var len: Int
len = `in`.read(buffer)
while (len != -1) {
out.write(buffer, 0, len)
len = `in`.read(buffer)
}
}
对象声æä¸ç代ç ä¸è½è®¿é®åµå¥ç±»çç§ææå
å jsoup ä¸ç org.jsoup.helper.DataUtil åªæ¯ä¸ä¸ªå·¥å ·ç±»ï¼æææå齿¯ static çï¼è½¬æ Kotlin ååæäºä¸ä¸ªå¯¹è±¡å£°æï¼åæ¬å®æä¸ä¸ªå¦ä¸çåµå¥ç±»:
private static class BomCharset {
private final String charset;
private final boolean offset;
public BomCharset(String charset, boolean offset) {
this.charset = charset;
this.offset = offset;
}
}
ç¨ IDEA 转æ Kotlin 代ç ååæäºè¿æ ·:
private class BomCharset(private val charset: String, private val offset: Boolean)
DataUtil çå ¶ä»ä»£ç å¼ç¨ bomCharset.offset æ¶å°±ä¼æ¥éï¼éè¦å»æ privateã
让人困惑的范型
jsoup 中有如下扩展了 java.util.ArrayList 的类:
package org.jsoup.helper;
import java.util.ArrayList;
import java.util.Collection;
/**
* Implementation of ArrayList that watches out for changes to the contents.
*/
public abstract class ChangeNotifyingArrayList<E> extends ArrayList<E> {
public ChangeNotifyingArrayList(int initialCapacity) {
super(initialCapacity);
}
public abstract void onContentsChanged();
@Override
public E set(int index, E element) {
onContentsChanged();
return super.set(index, element);
}
@Override
public boolean add(E e) {
onContentsChanged();
return super.add(e);
}
@Override
public void add(int index, E element) {
onContentsChanged();
super.add(index, element);
}
@Override
public E remove(int index) {
onContentsChanged();
return super.remove(index);
}
@Override
public boolean remove(Object o) {
onContentsChanged();
return super.remove(o);
}
//......
@Override
public boolean removeAll(Collection<?> c) {
onContentsChanged();
return super.removeAll(c);
}
@Override
public boolean retainAll(Collection<?> c) {
onContentsChanged();
return super.retainAll(c);
}
}
用 IDEA 转成 Kotlin 代码后一开始是这样(错误当然一堆了):
package org.jsoup.helper
import java.util.ArrayList
/**
* Implementation of ArrayList that watches out for changes to the contents.
*/
abstract class ChangeNotifyingArrayList<E>(initialCapacity: Int) : ArrayList<E>(initialCapacity) {
abstract fun onContentsChanged()
override operator fun set(index: Int, element: E?): E {
onContentsChanged()
return super.set(index, element)
}
override fun add(e: E?): Boolean {
onContentsChanged()
return super.add(e)
}
override fun add(index: Int, element: E?) {
onContentsChanged()
super.add(index, element)
}
override fun remove(index: Int): E {
onContentsChanged()
return super.removeAt(index)
}
override fun remove(o: Any?): Boolean {
onContentsChanged()
return super.remove(o)
}
//......
override fun removeAll(c: Collection<*>): Boolean {
onContentsChanged()
return super.removeAll(c)
}
override fun retainAll(c: Collection<*>): Boolean {
onContentsChanged()
return super.retainAll(c)
}
}
修正了大部分问题后是这样(注释那段还不知问题原因):
package org.jsoup.helper
import java.util.ArrayList
/**
* Implementation of ArrayList that watches out for changes to the contents.
*/
abstract class ChangeNotifyingArrayList<E>(initialCapacity: Int) : ArrayList<E>(initialCapacity) {
abstract fun onContentsChanged()
override operator fun set(index: Int, element: E): E {
onContentsChanged()
return super.set(index, element)
}
override fun add(e: E): Boolean {
onContentsChanged()
return super.add(e)
}
override fun add(index: Int, element: E) {
onContentsChanged()
super.add(index, element)
}
// override fun remove(index: Int): E {
// onContentsChanged()
// return super.removeAt(index)
// }
override fun remove(o: E): Boolean {
onContentsChanged()
return super.remove(o)
}
//......
override fun removeAll(c: Collection<E>): Boolean {
onContentsChanged()
return super.removeAll(c)
}
override fun retainAll(c: Collection<E>): Boolean {
onContentsChanged()
return super.retainAll(c)
}
}
大佬,你终于想起自己的账号了吗...