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中使用。关于这类问题实际中遇到比较少,因此不仔细说明。

如果觉得我的文字对你有所帮助的话,欢迎关注我的公众号:

我的群欢迎大家进来探讨各种技术与非技术的话题,有兴趣的朋友们加我私人微信huannan88,我拉你进群交(♂)流(♀)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值