Unit 为啥还能当函数参数?面向实用的 Kotlin Unit 详解

news2024/11/17 3:52:20

视频先行

下面是视频内容的脚本文案原稿分享。

ca0f9d978646d62d55e9d40cc0a717de.png

文案原稿

很多从 Java 转到 Kotlin 的人都会有一个疑惑:为什么 Kotlin 没有沿用 Java 的 void 关键字,而要引入这个叫 Unit 的新东西?

// Java
public void sayHello() {
  System.out.println("Hello!");
}
// Kotlin
fun sayHello(): Unit {
  println("Hello!")
}

不过这个问题一般也不会维持很久,因为就算你不明白,好像……也不影响写代码。

直到这两年,大家发现 Compose 的官方示例代码里竟然有把 Unit 填到函数参数里的情况:

LaunchedEffect(Unit) {
  xxxx
  xxxxxx
  xxx
}

我们才觉得:「啊?还能这么写?」

Unit 的本质

大家好,我是扔物线朱凯。

今天来讲一讲 Unit 这个特殊的类型。

我们在刚学 Kotlin 的时候,就知道 Java 的 void 关键字在 Kotlin 里没有了,取而代之的是一个叫做 Unit 的东西:

// Java
public void sayHello() {
  System.out.println("Hello!")
}
// Kotlin
fun sayHello(): Unit {
  println("Hello!")
}

而这个 Unit,和 Java 的 void 其实是不一样的。比如 Unit 的返回值类型,我们是可以省略掉不写的:

// Kotlin
fun sayHello() {
  println("Hello!")
}

不过省略只是语法上的便利,实际上 Kotlin 还是会把它理解成 Unit

Unit 和 Java 的 void 真正的区别在于,void 是真的表示什么都不返回,而 Kotlin 的 Unit 却是一个真实存在的类型:

public object Unit {
    override fun toString() = "kotlin.Unit"
}

它是一个 object,也就是 Kotlin 里的单例类型或者说单例对象。当一个函数的返回值类型是 Unit 的时候,它是需要返回一个 Unit 类型的对象的:

// Kotlin
fun sayHello() {
  println("Hello!")
  return Unit
}

只不过因为它是个 object ,所以唯一能返回的值就是 Unit 本身。

另外,这一行 return 我们也可以省略不写:

// Kotlin
fun sayHello() {
  println("Hello!")
}

因为就像返回值类型一样,这一行 return,Kotlin 也会帮我们自动加上:

// Kotlin
fun sayHello(): Unit {
  println("Hello!")
  return Unit
}

这两个 Unit 是不一样的,上面的是 Unit 这个类型,下面的是 Unit 这个单例对象,它俩长得一样但是是不同的东西。注意了,这个并不是 Kotlin 给 Unit 的特权,而是 object 本来就有的语法特性。你如果有需要,也可以用同样的格式来使用别的单例对象,是不会报错的:

object Rengwuxian

fun getRengwuxian(): Rengwuxian {
  return Rengwuxian
}

包括你也可以这样写:

val unit: Unit = Unit

也是一样的道理,等号左边是类型,等号右边是对象——当然这么写没什么实际作用啊,单例你就直接用就行了。

所以在结构上,Unit 并没有任何的特别之处,它就只是一个 Kotlin 的 object 而已。除了对于函数返回值类型和返回值的自动补充之外,Kotlin 对它没有再施加任何的魔法了。它的特殊之处,更多的是在于语义和用途的角度:它是个由官方规定出来的、用于「什么也不返回」的场景的返回值类型。但这只是它被规定的用法而已,而本质上它真就是个实实在在的类型。也就是在 Kotlin 里,并不存在真正没有返回值的函数,所有「没有返回值」的函数实质上的返回值类型都是 Unit,而返回值也都是 Unit 这个单例对象,这是 Unit 和 Java 的 void 在本质上的不同。

Unit 的价值所在

那么接下来的问题就是:这么做的意义在哪?

意义就在于,Unit 去掉了无返回值的函数的特殊性,消除了有返回值和无返回值的函数的本质区别,这样很多事做起来就会更简单了。

例:有返回值的函数在重写时没有返回值

比如?

比如在 Java 里面,由于 void 并不是一种真正的类型,所以任何有返回值的方法在子类里的重写方法也都必须有返回值,而不能写成 void,不管你用不用泛型都是一样的:

public abstract class Maker {
  public abstract Object make();
}

public class AppleMaker extends Maker {
  // 合法
  @Override
  public Apple make() {
    return new Apple();
  }
}

public class NewWorldMaker extends Maker {
  // 非法
  @Override
  public void make() {
    world.refresh();
  }
}
bd32adc4a868040ce5dcce06f383e45f.jpeg
public abstract class Maker<T> {
  public abstract T make();
}

public class AppleMaker extends Maker<Apple> {
  // 合法
  Override
  public Apple make() {
    return new Apple();
  }
}

public class NewWorldMaker extends Maker<void> {
  // 非法
  Override
  public void make() {
    world.refresh();
  }
}
49dcca858b49ab0e9406494902a44029.jpeg

你只能去写一行 return null 来手动实现接近于「什么都不返回」的效果:

public class NewWorldMaker extends Maker {
  @Override
  public Object make() {
    world.refresh();
    return null;
  }
}
0204ac9bc0dc72b7c0a652f8ea031055.jpeg

而且如果你用的是泛型,可能还需要用一个专门的虚假类型来让效果达到完美:

public class NewWorldMaker extends Maker<Void> {
  @Override
  public Void make() {
    world.refresh();
    return null;
  }
}

fc08cbdf98dac7acd72a514ff7939e50.jpeg而在 Kotlin 里,Unit 是一种真实存在的类型,所以直接写就行了:

abstract class Maker {
  abstract fun make(): Any
}

class AppleMaker : Maker() {
  override fun make(): Apple {
    return Apple()
  }
}

class NewWorldMaker : Maker() {
  override fun make() {
    world.refresh()
  }
}
abstract class Maker<T> {
  abstract fun make(): T
}

class AppleMaker : Maker<Apple>() {
  override fun make(): Apple {
    return Apple()
  }
}

class NewWorldMaker : Maker<Unit>() {
  override fun make() {
    world.refresh()
  }
}

这就是 Unit 的去特殊性——或者说通用性——所给我们带来的便利。

例:函数类型的函数参数

同样的,这种去特殊性对于 Kotlin 的函数式编程也提供了方便。一个函数的函数类型的参数,在函数调用的时候填入的实参,只要符合声明里面的返回值类型,它是可以有返回值,也可以没有返回值的:

fun runTask(task: () -> Any) {
  when (val result = task()) {
    Unit -> println("result is Unit")
    String -> println("result is a String: $result")
    else -> println("result is an unknown type")
  }
}

...

runTask { } // () -> Unit
runTask { println("完成!") } // () -> String
runTask { 1 } // () -> Int

Java 不支持把方法当做对象来传递,所以我们没法跟 Java 做对比;但如果 Kotlin 不是像现在这样用了 Unit,而是照抄了 Java 的 void 关键字,我们就肯定没办法这样写。

小结:去特殊化

这就是我刚才所说的,对于无返回值的函数的「去特殊化」,是 Unit 最核心的价值。它相当于是对 Java 的 void 进行了缺陷的修复,让本来有的问题现在没有了。而对于实际开发,它的作用是属于润物细无声的,你不需要懂我说的这一大堆东西,也不影响你享受 Unit 的这些好处。

…………

那我出这期视频干嘛?

——开个玩笑。了解各种魔法背后的实质,对于我们掌握和正确地使用一门语言是很有必要的。

延伸:当做纯粹的单例对象来使用

比如,知道 Unit 是什么之后,你就能理解为什么它能作为函数的参数去被使用。

Compose 里的协程函数 LaunchedEffect() 要求我们填入至少一个 key 参数,来让协程在界面状态变化时可以自动重启:

LaunchedEffect(key) {
  xxxx
  xxxxxx
  xxx
}

而如果我们没有自动重启的需求,就可以在参数里填上一个 Unit

LaunchedEffect(Unit) {
  xxxx
  xxxxxx
  xxx
}

因为 Unit 是不变的,所以把它填进参数里,这个协程就不会自动重启了。这招用着非常方便,Compose 的官方示例里也有这样的代码。不过这个和 Unit 自身的定位已经无关了,而仅仅是在使用它「单例」的性质。实际上,你在括号里把它换成任何的常量,效果都是完全一样的,比如 true、比如 false、比如 1、比如 0、比如 你好,都是可以的。所以如果你什么时候想「随便拿个对象过来」,或者「随便拿个单例对象过来」,也可以使用 Unit,它和你自己创建一个 object 然后去使用,效果是一样的。

总结

好,这就是 Kotlin 的 Unit,希望这个视频可以帮助你更好地了解和使用它。下期我会讲 Kotlin 里另一个特殊的类型:Nothing。关注我,了解更多 Android 开发的知识和技能。我是扔物线,我不和你比高低,我只助你成长。我们下期见!

f41429e906633310aed9a09af867ab9e.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/504547.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unity大面积草地渲染——2、草地的动态交互

大家好&#xff0c;我是阿赵。 这里继续讲大面积草地渲染的第二个部分&#xff0c;草地动态交互。这里主要有风吹效果和球体碰撞效果2种。 一、风吹效果 Unity使用shader控制草的渲染和动画 风吹动草的效果&#xff0c;主要还是使用顶点程序来控制顶点的偏移 回顾一下之前的基…

全方位揭秘!大数据从0到1的完美落地之Shuffle和调优

MapReduce高级 shuffle阶段 概述 MapReduce会确保每个reducer的输入都是按键排序的。从map方法输出数据开始、到作为输入数据传给reduce方法的过程称为shuffle。在此&#xff0c;我们将学习shuffle是如何工作的&#xff0c;因为它有助于我们理解工作机制&#xff08;如果需要…

MYSQL数据库进阶多表查询,MYSQL数据库主键和外键

目录 友情提醒第一章&#xff1a;MYSQL数据库多表主键和外键1&#xff09;外键介绍&#xff08;FOREIGN KEY&#xff09;2&#xff09;外键约束作用2&#xff09;三种情况下添加外键约束①一对一关系②一对多关系多对多关系 4&#xff09;删除外键约束 第二章&#xff1a;MYSQL…

配置您的 Web 服务器以包含 X-Frame-Options 标头

介绍 X-Frame-Options HTTP 响应头是用来给浏览器指示允许一个页面可否在 <frame>, </iframe> 或者 <object> 中展现的标记。网站可以使用此功能&#xff0c;来确保自己网站的内容没有被嵌套到别人的网站中去&#xff0c;也从而避免了点击劫持 (clickjackin…

mysql数据库之备份和恢复

1.数据备份的重要性 备份的主要目的是灾难恢复。 在生产环境中&#xff0c;数据的安全性至关重要。 任何数据的丢失都可能产生严重的后果。 造成数据丢失的原因&#xff1a; 程序错误人为,操作错误,运算错误,磁盘故障灾难&#xff08;如火灾、地震&#xff09;和盗窃. 2.数据…

Python爬虫入门之爬虫解析提取数据的四种方法

本文主要介绍了Python爬虫入门之爬虫解析提取数据的四种方法&#xff0c;通过具体的内容向大家展现&#xff0c;希望对大家Python爬虫的学习有所帮助。 基础爬虫的固定模式 笔者这里所谈的基础爬虫&#xff0c;指的是不需要处理像异步加载、验证码、代理等高阶爬虫技术的爬虫…

Vscode开发第一个Vue+Element Plus示例

Vscode开发第一个VueElement Plus示例 目前&#xff0c;前端开发工具非常多&#xff0c;如Webstorm、Atom、HBuilder、Visual Studio Code、Sublime Text、Notepad等。对于有经验的开发者来说&#xff0c;使用哪一款工具都可以。笔者习惯使用Visual Studio Code。 Visual St…

什么是镜像?阿里云服务器镜像是什么?镜像怎么选?

阿里云服务器镜像就是云服务器的装机盘&#xff0c;镜像是为云服务器安装操作系统的。阿里云镜像分为类型分为公共镜像、自定义镜像、共享镜像、云市场镜像和社区镜像&#xff0c;如下图&#xff1a; 目录 什么是镜像&#xff1f; 镜像种类说明 公共镜像 自定义镜像 共享…

【HarmonyOS】轻量级智能穿戴应用如何在页面中实现数据传递与全局变量的使用

【关键词】 轻量级智能穿戴、LiteWearable、数据传递、全局变量 【问题描述】 开发轻量级智能穿戴LiteWearable应用&#xff0c;在app.js中定义全局变量&#xff0c;在页面中通过this.$app.$def.xxx获取时&#xff0c;报错TypeError: Cannot read property $def of undefined…

FlinkTableAPI与SQL编程实战

FlinkTableAPI与SQL编程实战 接下来我们一起来进入到FlinkSQL的编码实战当中&#xff0c;通过代码来实现FlinkSQL的编码开发 1、Flink TableAPI实践 1.1、创建Maven工程 并添加以jar包坐标依赖 <properties><maven.compiler.source>8</maven.compiler.source…

Spring Redis 启用TLS配置支持(踩坑解决)

由于线上Redis要启用TLS,搜遍了google百度也没一个标准的解决方案,要不这个方法没有,要不那个类找不到...要不就是配置了还是一直连不上redis.... 本文基于 spring-data-redis-2.1.9.RELEASE 版本来提供一个解决方案: 1.运维那边提供过来三个文件,分别是redis.crt redis.key …

设计模式 -- 解释器模式

前言 月是一轮明镜,晶莹剔透,代表着一张白纸(啥也不懂) 央是一片海洋,海乃百川,代表着一块海绵(吸纳万物) 泽是一柄利剑,千锤百炼,代表着千百锤炼(输入输出) 月央泽,学习的一种过程,从白纸->吸收各种知识->不断输入输出变成自己的内容 希望大家一起坚持这个过程,也同…

易基因:精原干细胞移植后出生小鼠子代中的精子DNA甲基化变化机制|新研究

大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。 精原干细胞移植&#xff08;Spermatogonial stem cell transplantation&#xff0c;SSCT&#xff09;被提议作为儿童癌症幸存者的生育疗法。SSCT首先冷冻保存睾丸活检&#xff0c;然后再…

【2023 · CANN训练营第一季】应用开发深入讲解——第二章 模型推理

学习目标 学习资源 内存管理 在线课程 文档 模型推理 文档 第1节 AscendCL运行资源管理 运行资源管理概述 申请运行管理资源时&#xff0c;需按顺序依次申请&#xff1a; Device 、 Context 、 Stream &#xff0c;然后根据实际需求调用 aclrtGetRunMode 接口获取软件栈的…

黑马Redis实战项目——黑马点评笔记06 | 好友关注

黑马Redis实战项目——黑马点评笔记06 | 好友关注 1、关注和取关2、共同关注2.1 查看他人主页2.2 查询共同关注A 改造关注和取关功能B 求交集 3、关注推送3.1 Feed 流分析3.1.1、拉模式&#xff08;读扩散&#xff09;3.1.2、推模式&#xff08;写扩散&#xff09;3.1.3、推拉结…

C++ JPEG编码

依据上一篇的JPEG编码所得到的RGB信息&#xff0c;我们可以重新对RGB图片进行编码&#xff0c;也可对其他图片如BMP所得到的RGB信息进行编码,来得到*.jpg文件&#xff0c;注意我这里实现的JPEG编码不知道为啥编码出来的文件比原来大了好多。 还有要注意的地方&#xff0c;下面会…

【计算机三级网络技术】 第六篇 交换机及其配置

文章目录 IPS&#xff08;入侵防护系统&#xff09;相关知识点蓝牙服务器技术DNS 服务器WWW 服务器FTP 服务器邮件&#xff08;Winmail 邮件服务器&#xff09;生成树协议IEEEVLAN 标识的描述DHCP 服务器 IPS&#xff08;入侵防护系统&#xff09;相关知识点 1、入侵防护系统&…

迅为i.MX6ULL开发板生成 KEY 文件,并安装

使用“ssh-keygen” 生成个四个 key 文件“ssh_host_rsa_key” “ssh_host_dsa_key” “ssh_host_ecdsa_key” 和“ssh_host_ed25519_key” 。 1 在虚拟机 Ubuntu 控制台&#xff0c; “ /home/ssh/openssh-4.6p1” 目录下&#xff0c; 使用命 令“ssh-keygen -t rsa -f ssh…

帮助客户实现自助服务,企业可以打造产品知识库来解决

随着科技的不断发展&#xff0c;越来越多的企业开始将自助服务作为一种解决客户问题的方式。自助服务不仅可以提高客户满意度&#xff0c;还可以减少企业的工作量和成本。为了帮助客户实现自助服务&#xff0c;企业可以打造产品知识库来解决客户问题。本文将介绍产品知识库的定…

shell脚本----函数

文章目录 一、函数的定义1.1 shell函数:1.2函数如何定义 二、函数的返回值三、函数的传参四、函数变量的作用范围五、函数的递归六、函数库 一、函数的定义 1.1 shell函数: 使用函数可以避免代码重复使用函数可以将大的工程分割为若干小的功能模块&#xff0c;代码的可读性更…