Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)

简介: Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)

本系列学习教程笔记属于详细讲解Kotlin语法的教程,需要快速学习Kotlin语法的小伙伴可以查看“简洁” 系列的教程

快速入门请阅读如下简洁教程:
Kotlin学习教程(一)
Kotlin学习教程(二)
Kotlin学习教程(三)
Kotlin学习教程(四)
Kotlin学习教程(五)
Kotlin学习教程(六)
Kotlin学习教程(七)
Kotlin学习教程(八)
Kotlin学习教程(九)
Kotlin学习教程(十)

Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)

Kotlin 与 Java 共存(二)

上一期我们简单讨论了几个 Java 调用 Kotlin 的场景,这一期我们主要讨论相反的情况。

1. 属性

如果 Java 类存在类似 setXXX 和 getXXX 的方法,Kotlin 会聪明地把他们当做属性来使用,例如:

public class DataClass {
    private int id;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}
AI 代码解读

Kotlin 的访问也非常简单:

fun main(args: Array<String>) {
    val dataClass = DataClass()
    dataClass.id = 0
    println(dataClass.id)
}
AI 代码解读

2. 空安全

空安全这个特性使得 Kotlin 对变量的值要求更严格了,由于 Java 变量通常没有这样的信息,因此 Kotlin 在访问 Java 变量或者 Java 方法时,变量、方法的参数和返回值的空与否由我们自己决定,编译器不再进行约束。例如定义这么一个 Java 类:

public class NullSafetyJava {
    public String getData(){
        return null;
    }
}
AI 代码解读

在 Kotlin 当中访问它:

fun main(args: Array<String>) {
    val nullSafetyJava = NullSafetyJava()
    val data = nullSafetyJava.data
}
AI 代码解读

这个 data 的类型被称作 『平台类型(Platform Type)』,它既可以为可空类型,也可以为不可空类型,这一切取决于我们自己的决定。

val dataCanBeNull: String? = data
val dataCannotBeNull: String = data
AI 代码解读

这样在编译期是允许的,不过如果在运行时由于 data 为 null,那你就会遇到异常:

Exception in thread "main" java.lang.IllegalStateException: data must not be null
    at net.println.kt15.NullSafetyKt.main(NullSafety.kt:10)
AI 代码解读

如果继承 Java 类,父类的方法参数也是平台类型,也需要我们根据实际情况来判断是否可空。例如:

public abstract class NullSafetyAbsClass {
    public abstract String formatDate(Date date);
}
AI 代码解读

在Kotlin中继承这个类时,formatDate 方法的参数和返回值的类型我们根据情况写:

class NullSafetySubClass : NullSafetyAbsClass(){
    override fun formatDate(date: Date?): String? {
        return date?.toString()
    }
}

fun main(args: Array<String>) {
    val nullSafetySubClass = NullSafetySubClass()
    val formattedDate: String? = nullSafetySubClass.formatDate(Date())
    println(formattedDate)
}
AI 代码解读

这样直接写可控类型当然是没有问题的。如果你确信它可以不为空,那么你也可以直接写不可控类型:

class NullSafetySubClass : NullSafetyAbsClass(){
    override fun formatDate(date: Date): String {
        return date.toString()
    }
}

fun main(args: Array<String>) {
    val nullSafetySubClass = NullSafetySubClass()
    val formattedDate: String = nullSafetySubClass.formatDate(Date())
    println(formattedDate)
}
AI 代码解读

当然,对于这种情况,我们在 Java 也已经开始采用一些『曲线救国』的方式来弥补这一不足,比如 采用 JetBrains 的 @Nullable 和 @NotNull 注解以及 Android 当中类似的注解支持,我们可以把我们的 Java 代码改写成这样:

public abstract class NullSafetyAbsClass {
    public abstract @NotNull String formatDate(@NotNull Date date);
}
AI 代码解读

3. 泛型

整体上来讲,Kotlin 的泛型与 Java 的没什么大的差别,Java 的 ? 在 Kotlin 中变成了 *,毕竟 ? 在 Kotlin 当中用处大着呢。另外,Java 泛型的上限下限的表示法在 Kotlin 当中变成了 out 和 in。

不过,由于 Java 1.5 之前并没有泛型的概念,为了兼容旧版本,1.5 之后的 Java 仍然允许下面的写法存在:

List list = new ArrayList();
list.add("Hello");
list.add(1);
for (Object o : list) {
    System.out.println(o);
}
AI 代码解读

而对应的,在 Kotlin 当中要用 List<*> 来表示 Java 中的 List —— 这本身没什么问题。那么我们现在再看一段代码:

public abstract class View<P extends Presenter>{
    protected P presenter;
}

public abstract class Presenter<V extends View>{
    protected V view;
}
AI 代码解读

这个其实是现在比较流行的 MVP 设计模式的一个简单的接口,我希望通过泛型来绑定 V-P,并且我可以通过泛型参数在运行时直接反射实例化一个 presenter 完成 V-P 的实例注入,这在 Java 当中是没有问题的。

那么在 Kotlin 当中呢? 因为我们知道 View 和 Presenter 都有泛型参数的,所以我们在 Kotlin 当中就要这么写:

abstract class View<P : Presenter<out View<out Presenter<out View<...>>{
    protected abstract val presenter: P
}

abstract class Presenter<out V : View<out Presenter<out View<...>>>>{
    protected abstract val view: V
}
AI 代码解读

一直写下去,最终陷入了死循环。编译器给出的解释是:

This type parameter violates the Finite Bound Restriction.

在 @zhangdatou 给我发邮件之前,我曾一直对此耿耿于怀,Kotlin 这么优秀的语言怎么还会有做不到的事情呢。原来不是做不到,而是我没有想到:

abstract class View<out P: Presenter<View<P>>>
abstract class Presenter<out V: View<Presenter<V>>>

class MyView: View<MyPresenter>()
class MyPresenter: Presenter<MyView>()
AI 代码解读

实际上我们需要 View 的泛型参数 P 只要是 Presenter 的子类即可,并且要求这个泛型参数 P 本身对应的泛型参数也需要是 View 的子类,而这个 View 其实就是最初的那个 View,那么这个 View 的泛型参数当然就是 P 了。有点儿绕,但这么写出来明显感觉比 Java 安全靠谱多了。

4. Synchronized 和 volatile

在 Kotlin 当中,这两个关键字被削去了王位,成为平民。也许是设计者们觉得这二位作为关键字出现有点儿太重了,虽然不再身居要职,不过却也是不可或缺。

Synchronized 有两个版本,用于函数的版本是个注解:

var num: Int = 0

@Synchronized fun count(){
    num++
}
AI 代码解读

用于代码块的则是一个函数:

var num: Int = 0

fun count(){
    synchronized(num){
        num++
    }
}
AI 代码解读
@kotlin.internal.InlineOnly
public inline fun <R> synchronized(lock: Any, block: () -> R): R {
    @Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE", "INVISIBLE_MEMBER")
    monitorEnter(lock)
    try {
        return block()
    }
    finally {
        @Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE", "INVISIBLE_MEMBER")
        monitorExit(lock)
    }
}
AI 代码解读

volatile 的命运差不多,也变成了一个注解:

@Volatile var num: Int = 0
AI 代码解读

5. SAM 转换

看名字一头雾水,其实就是对于只有一个方法的 Java 接口,Kotlin 可以用一个 Lambda 表达式来简化它的书写,例如:

fun main(args: Array<String>) {
    val worker = Executors.newCachedThreadPool()
    worker.execute {
        println(System.currentTimeMillis())
    }
}
AI 代码解读

execute 方法传入了一个 Runnable 接口的实例,不过看样子却是一个 Lambda 表达式。不过这里也是有一个需要注意的点的,既然是『转换』,那么 Java 的 execute 方法接受到的实例就一定不是我们看到的这个 Lambda 表达式实例了,换句话说,我们就算每次传入同一个 Lambda 表达式实例,那么 Java 的 execute 方法收到的也并不是同一个对象。举个例子:

public class SAMInJava {
    private ArrayList<Runnable> runnables = new ArrayList<Runnable>();

    public void addTask(Runnable task){
        runnables.add(task);
        System.out.println("after add: " + task + ", we " + runnables.size() + " in all.");
    }

    public void removeTask(Runnable task){
        runnables.remove(task);
        System.out.println("after remove: " + task + ", only " + runnables.size() + " left.");
    }
}
AI 代码解读

然后我们在 Kotlin 当中这么写:

fun main(args: Array<String>) {
    val samInJava = SAMInJava()
    val lambda = {
        println("Hello")
    }

    samInJava.addTask(lambda)
    samInJava.addTask(lambda)
    samInJava.addTask(lambda)
    samInJava.addTask(lambda)

    samInJava.removeTask(lambda)
    samInJava.removeTask(lambda)
    samInJava.removeTask(lambda)
    samInJava.removeTask(lambda)
}
AI 代码解读

运行结果呢?

after add: net.println.kt15.SAMConversionKt$sam$Runnable$9855366b@6ce253f1, we have 1 in all.
after add: net.println.kt15.SAMConversionKt$sam$Runnable$9855366b@53d8d10a, we have 2 in all.
after add: net.println.kt15.SAMConversionKt$sam$Runnable$9855366b@e9e54c2, we have 3 in all.
after add: net.println.kt15.SAMConversionKt$sam$Runnable$9855366b@65ab7765, we have 4 in all.
after remove: net.println.kt15.SAMConversionKt$sam$Runnable$9855366b@1b28cdfa, only 4 left.
after remove: net.println.kt15.SAMConversionKt$sam$Runnable$9855366b@eed1f14, only 4 left.
after remove: net.println.kt15.SAMConversionKt$sam$Runnable$9855366b@7229724f, only 4 left.
after remove: net.println.kt15.SAMConversionKt$sam$Runnable$9855366b@4c873330, only 4 left.
AI 代码解读

每次 Java 的 add 和 remove 方法收到的都是不同的实例,所以 remove 方法根本没有起到作用。

6. 小结

除了这些之外还有一些细节,比如异常的捕获,集合类型的映射等等,大家可以自行参考官方文档即可。在了解了这些之后,你就可以放心大胆的在你的项目中慢慢渗透 Kotlin,让你的代码逐渐走向简洁与精致了。

最后,作为这一系列视频的最后一集,我还想要告诉大家有关 Android 开发的视频可能会在年后开始筹备,公众号在后续的这段时间内会推送我为大家准备的 Kotlin 的一些有意思的文章,请大家继续关注,并与我互动,谢谢大家!

目录
打赏
0
7
7
0
92
分享
相关文章
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
189 96
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
自研Java框架 Sunrays-Framework使用教程「博客之星」
### Sunrays-Framework:助力高效开发的Java微服务框架 **Sunrays-Framework** 是一款基于 Spring Boot 构建的高效微服务开发框架,深度融合了 Spring Cloud 生态中的核心技术组件。它旨在简化数据访问、缓存管理、消息队列、文件存储等常见开发任务,帮助开发者快速构建高质量的企业级应用。 #### 核心功能 - **MyBatis-Plus**:简化数据访问层开发,提供强大的 CRUD 操作和分页功能。 - **Redis**:实现高性能缓存和分布式锁,提升系统响应速度。 - **RabbitMQ**:可靠的消息队列支持,适用于异步
自研Java框架 Sunrays-Framework使用教程「博客之星」
探究Java异常处理【保姆级教程】
Java 异常处理是确保程序稳健运行的关键机制。它通过捕获和处理运行时错误,避免程序崩溃。Java 的异常体系以 `Throwable` 为基础,分为 `Error` 和 `Exception`。前者表示严重错误,后者可细分为受检和非受检异常。常见的异常处理方式包括 `try-catch-finally`、`throws` 和 `throw` 关键字。此外,还可以自定义异常类以满足特定需求。最佳实践包括捕获具体异常、合理使用 `finally` 块和谨慎抛出异常。掌握这些技巧能显著提升程序的健壮性和可靠性。
56 4
【潜意识Java】Java基础教程:从零开始的学习之旅
本文介绍了 Java 编程语言的基础知识,涵盖从简介、程序结构到面向对象编程的核心概念。首先,Java 是一种高级、跨平台的面向对象语言,支持“一次编写,到处运行”。接着,文章详细讲解了 Java 程序的基本结构,包括包声明、导入语句、类声明和 main 方法。随后,深入探讨了基础语法,如数据类型、变量、控制结构、方法和数组。此外,还介绍了面向对象编程的关键概念,例如类与对象、继承和多态。最后,针对常见的编程错误提供了调试技巧,并总结了学习 Java 的重要性和方法。适合初学者逐步掌握 Java 编程。
58 1
Git使用教程-将idea本地Java等文件配置到gitte上【保姆级教程】
本内容详细介绍了使用Git进行版本控制的全过程,涵盖从本地仓库创建到远程仓库配置,以及最终推送代码至远程仓库的步骤。
53 0
Android面试之5个Kotlin深度面试题:协程、密封类和高阶函数
本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点。文章详细解析了Kotlin中的协程、扩展函数、高阶函数、密封类及`inline`和`reified`关键字在Android开发中的应用,帮助读者更好地理解和使用这些特性。
100 1
告别AsyncTask:一招教你用Kotlin协程重构Android应用,流畅度飙升的秘密武器
【9月更文挑战第13天】随着Android应用复杂度的增加,有效管理异步任务成为关键。Kotlin协程提供了一种优雅的并发操作处理方式,使异步编程更简单直观。本文通过具体示例介绍如何使用Kotlin协程优化Android应用性能,包括网络数据加载和UI更新。首先需在`build.gradle`中添加coroutines依赖。接着,通过定义挂起函数执行网络请求,并在`ViewModel`中使用`viewModelScope`启动协程,结合`Dispatchers.Main`更新UI,避免内存泄漏。使用协程不仅简化代码,还提升了程序健壮性。
205 1
Android经典面试题之Kotlin延迟初始化的by lazy和lateinit有什么区别?
**Kotlin中的`by lazy`和`lateinit`都是延迟初始化技术。`by lazy`用于只读属性,线程安全,首次访问时初始化;`lateinit`用于可变属性,需手动初始化,非线程安全。`by lazy`支持线程安全模式选择,而`lateinit`适用于构造函数后初始化。选择依赖于属性特性和使用场景。**
222 5
Android经典面试题之Kotlin延迟初始化的by lazy和lateinit有什么区别?
【颠覆传统!】Kotlin协程魔法:解锁Android应用极速体验,带你领略多线程优化的无限魅力!
【8月更文挑战第12天】多线程对现代Android应用至关重要,能显著提升性能与体验。本文探讨Kotlin中的高效多线程实践。首先,理解主线程(UI线程)的角色,避免阻塞它。Kotlin协程作为轻量级线程,简化异步编程。示例展示了如何使用`kotlinx.coroutines`库创建协程,执行后台任务而不影响UI。此外,通过协程与Retrofit结合,实现了网络数据的异步加载,并安全地更新UI。协程不仅提高代码可读性,还能确保程序高效运行,不阻塞主线程,是构建高性能Android应用的关键。
98 4

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等