AndroidKotlin从入门到放弃

Kotlin 之旅8--Kotlin与Java共存

2017-08-21  本文已影响620人  小楠总

基本互操作

属性的读写

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,我拉你进群交(♂)流(♀)

上一篇下一篇

猜你喜欢

热点阅读