Kotlin 之旅8--Kotlin与Java共存
基本互操作
属性的读写
Kotlin能够自动识别Java的Getter与Setter,因此Kotlin中可以使用.的方式去使用Java类的属性:
//Java中的类
public class JavaBean {
private int i;
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
}
//Kotlin中可以直接通过.操作符去访问Java的属性
val bean = JavaBean()
bean.i = 10
println(bean.i)
Java操作Kotlin的属性
//Kotlin中的Bean,注意属性为var的时候,在Java中才会有set方法生成
data class KotlinBean(var i: Int)
//在Java中可以访问Kotlin的Bean,通过getter/setter
KotlinBean bean = new KotlinBean(0);
bean.setI(10);
System.out.println(bean.getI());
空类型
Kotlin在编译的时候,会对值进行空检查,但是在Java里面没有。所以在Kotlin操作Java代码的时候就会遇到平台类型的问题,这时候开发者需要自己确保非空。
当然可以通过下面两种注解来解决这个问题:
@Nullable -- 相当于Kotlin中的可空类型?
@notnull -- 相当于Kotlin中的一般非空类型
函数的调用
Kotlin中的包级函数,Kotlin在编译的时候会为这些包级函数生成一个类。在Java中就是相当于静态方法的调用。
扩展方法:带Receiver的静态方法
运算符重载:带Receiver的对应名称的静态方法
常见注解
@JvmField 将属性编译成Java变量
@JvmStatic 将对象的方法编译成Java静态方法
@JvmOverloads 默认参数生成重载方法
@JvmName 指定Kotlin文件编译后的类名,默认是文件名+Kt
NoArg与Allopen
生成无参数构造
支持Jpa注解,如@Entity
Allopen去掉final
支持Spring注解,例如@Component
支持自定义注解类型,例如@PoKo
泛型
通配符Kotlin的*对应Java的?因为Kotlin中?的用处多
协变与逆变out、in与Java不一样:
ArrayList<out String>
没有Raw类型
Java中的List相当于Kotlin中的List<*>
SAM转换
SAM转换就是,当Kotlin使用Java接口的时候,当接口里面只有一个方法,那么就可以用Lambda表达式代替。
例如,我们有下面的Java代码:
public class SamJava {
private List<Runnable> mRunnables = new ArrayList<>();
public void addTask(Runnable runnable) {
mRunnables.add(runnable);
System.out.println("add:" + runnable);
System.out.println(mRunnables.size());
}
public void removeTask(Runnable runnable) {
mRunnables.remove(runnable);
System.out.println("remove" + runnable);
System.out.println(mRunnables.size());
}
}
那么在Kotlin中可以有两种方式调用,其中,第二种方式就是SAM转换:
fun main(args: Array<String>) {
val sam = SamJava()
sam.addTask(object : Runnable {
override fun run() {
println("run1")
}
})
//sam转换
sam.addTask { println("run2") }
}
SAM转换的注意事项
SAM转换的原理:通过分析Kotlin的字节码的时候发现,编译器编译成字节码的时候,会将Lambda转换为对应的接口对象。
因此在使用SAM转换的时候就需要注意了,SAM转换实际上是会创建不同的接口对象,对象会存在多个,如下面的代码,Task不能够正确移除:
fun main(args: Array<String>) {
val sam = SamJava()
val lambda = {
println("run")
}
sam.addTask(lambda)
sam.addTask(lambda)
sam.removeTask(lambda)
sam.removeTask(lambda)
}
打印的结果是:
add:com.nan.sam.SamKotlinKt$sam$Runnable$13141d62@2f0e140b
1
add:com.nan.sam.SamKotlinKt$sam$Runnable$13141d62@7440e464
2
removecom.nan.sam.SamKotlinKt$sam$Runnable$13141d62@49476842
2
removecom.nan.sam.SamKotlinKt$sam$Runnable$13141d62@78308db1
2
可以发现,在每次进行SAM转换的时候,都创建了不同的Runnable对象,因此remove不成功。
拓展
SAM转换是对Java代码的转换,但是在中使用接口的时候,就必须要使用传统的方式了:
fun main(args: Array<String>) {
val samKtlin = SamKotlin()
samKtlin.addTask(object : Runnable {
override fun run() {
}
})
}
class SamKotlin {
private val mRunnables = ArrayList<Runnable>()
fun addTask(runnable: Runnable) {
mRunnables.add(runnable)
println("add:" + runnable)
println(mRunnables.size)
}
fun removeTask(runnable: Runnable) {
mRunnables.remove(runnable)
println("remove" + runnable)
println(mRunnables.size)
}
}
但是可以通过类型别名的方式来实现类似SAM转换的功能:
//定义类型别名
typealias Runnable = () -> Unit
fun main(args: Array<String>) {
val samKtlin = SamKotlin()
samKtlin.addTask {
println("run")
}
}
但是这样做的话,在Java中使用由会比较麻烦:
SamKotlin samKotlin = new SamKotlin();
samKotlin.addTask(new Function0<Unit>() {
@Override
public Unit invoke() {
return null;
}
});
正则表达式
Java中使用正则表达式
String source = "Hello, This my phone number: 010-12345678. ";
String pattern = ".*(\\d{3}-\\d{8}).*";
Matcher matcher = Pattern.compile(pattern).matcher(source);
while(matcher.find()){
System.out.println(matcher.group());
System.out.println(matcher.group(1));
}
Kotlin中使用正则表达式:
val source = "Hello, This my phone number: 010-12345678. "
val pattern = """.*(\d{3}-\d{8}).*"""
val matcher = Pattern.compile(pattern).matcher(source)
while (matcher.find()) {
println(matcher.group())
println(matcher.group(1))
}
需要注意的是,Kotlin中有raw String,用三引号括起来。
也可以使用Kotlin提供的Regex类,写出更有Kotlin风格的代码:
val source = "Hello, This my phone number: 010-12345678. "
val pattern = """.*(\d{3}-\d{8}).*"""
Regex(pattern).findAll(source).toList().flatMap(MatchResult::groupValues).forEach(::println)
集合框架
以List为例子,Kotlin中可以使用Java的集合框架:
val list = ArrayList<String>()//注意不同导包
list.add("haha")
list.removeAt(0)
Tips:Kotlin中对List进行了优化,比如添加了removeAt,实质上是映射了remove方法。
通过点击源码可以发现,ArrayList实际上是使用了Java的ArrayList:
@SinceKotlin("1.1") public typealias ArrayList<E> = java.util.ArrayList<E>
Kotlin中可以通过xxxOf方法去创建集合,返回的是Kotlin内部定义的接口,是不可变的集合,并没有提供add等方法:
val list = listOf("1", "2", "3")
val map = mapOf("1" to "1",
"2" to "2",
"3" to "3")
但是Java中访问这些集合的时候是当做普通的集合来使用的,因此操作的时候就会抛异常:
//Kotlin代码
object Test {
val list = ArrayList<String>()
}
//Java代码
List<String> list = Test.INSTANCE.getList();
list.add("1");
程序运行不了,控制台输出:
Exception in thread "main" java.lang.UnsupportedOperationException: Operation is not supported for read-only collection
at kotlin.collections.EmptyList.add(Collections.kt)
at com.nan.sam.SamJava.main(SamJava.java:24)
IO操作
以文件的读取为例子,先看看Java版本的:
BufferedReader bufferedReader = null;
try {
bufferedReader = new BufferedReader(new FileReader((new File("build.gradle"))));
String line;
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (bufferedReader!=null) {
bufferedReader.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
然后是Kotlin版本:
val bufferedReader = BufferedReader(FileReader(File("build.gradle")))
var line: String
while (true) {
line = bufferedReader.readLine() ?: break
println(line)
}
bufferedReader.close()
比较突出的不同点是,Kotlin中不能像Java一样“line = bufferedReader.readLine()”,line不会作为返回值。因此只能用传统的写法。
另外也可以通过use关键字进行简化:
val bufferedReader = BufferedReader(FileReader(File("build.gradle"))).use {
var line: String
while (true) {
line = it.readLine() ?: break
println(line)
}
}
其中use是Closeable的一个扩展方法:
@InlineOnly
public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
var closed = false
try {
return block(this)
} catch (e: Exception) {
closed = true
try {
this?.close()
} catch (closeException: Exception) {
}
throw e
} finally {
if (!closed) {
this?.close()
}
}
}
最后,在读取这种小文件的时候,可以直接使用File的扩展方法:
File("build.gradle").readLines().forEach(::println)
readLines的定义如下:
public fun File.readLines(charset: Charset = Charsets.UTF_8): List<String> {
val result = ArrayList<String>()
forEachLine(charset) { result.add(it); }
return result
}
public fun File.forEachLine(charset: Charset = Charsets.UTF_8, action: (line: String) -> Unit): Unit {
// Note: close is called at forEachLine
BufferedReader(InputStreamReader(FileInputStream(this), charset)).forEachLine(action)
}
装箱与拆箱
Java中有装箱与拆箱之分,例如int与Integer,但是Kotlin中统一用Int代替,一切的转换由编译器完成。
但是偶尔会遇到Java代码翻译成Kotlin代码的时候有歧义的情况,解决办法就是用Java代码去实现这个功能,而在Kotlin中使用。关于这类问题实际中遇到比较少,因此不仔细说明。
如果觉得我的文字对你有所帮助的话,欢迎关注我的公众号:
公众号:Android开发进阶我的群欢迎大家进来探讨各种技术与非技术的话题,有兴趣的朋友们加我私人微信huannan88,我拉你进群交(♂)流(♀)。