欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Kotlin 之旅8 Kotlin与Java共存

程序员文章站 2024-03-14 23:14:29
...

###基本互操作

####属性的读写

#####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:[email protected]
1
add:[email protected]
2
[email protected]
2
[email protected]
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,我拉你进群交(♂)流(♀)