8

typealias ,穿了马甲,我就不认识你了?

 3 years ago
source link: https://luyao.tech/archives/kotlin-typealias
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

可能还真的就不认识了。

今天的主角是 type alias,翻译过来叫 类型别名。先来看一下文章目录:

  1. 什么是 typealias ?
  2. typealias 的本质
  3. typealias 存在的意义是什么?
  4. typealias 的使用注意事项

什么是 typealias ?

这是一个很基础的关键字,但可能很多人没有使用过。它的作用十分简单,给已有类型取一个别名,可以像使用原类型一样使用这个 “类型别名”

举个简单的例子:

typealias Name = Stringval name : Name = "java"println(name.length)

String 取个别名 Name ,在使用过程中,NameString 是完全等价的。

既然是等价的,使用别名的意义是什么呢?

别急,typealias 不仅仅支持给类取别名,它的用法丰富的让你想象不到。

// 类和接口typealias Name = Stringtypealias Data = Serializable // 函数类型typealias GetName = () -> Stringtypealias Handler = CoroutineScope.() -> Unit // 泛型typealias P<T> = Comparable<T>typealias Pairs<K, V> = HashMap<K, V>typealias Numbers = Array<Number> // objectobject Single {}typealias X = Single class Util {    companion object {        val count = 1    }}typealias Count = Util.Companion // inner classtypealias NotificationBuilder = NotificationCompat.Builder class Outer { inner class Inner }typealias In = Outer.Inner // 枚举enum class Color { RED, YELLOW, GREEN }typealias color = Color // 注解typealias Jvm = JvmStatic

上面的枚举用法中,需要注意的一点是,只能为枚举类 Color 取别名,无法为具体的枚举值取别名 。诸如 typealias Red = Color.RED 是不允许的。

几乎没有 typealias 不适用的类型。说到现在,你又得疑问了,类型别名存在的意义是什么 ?这样简单的取个别名,为什么不直接使用原类型呢 ?

typealias 的本质

暂且别急,我们先来看一下 typealias 的实现原理,说不定可以有一些发现。

反编译下面这个简单的例子:

typealias Binary = ByteArrayfun getBinary(string: String) : Binary = string.toByteArray()

查看其 Java 代码 :

public final class TypealiasKt {   @NotNull   public static final byte[] getBinary(@NotNull String string) {      Intrinsics.checkParameterIsNotNull(string, "string");      Charset var2 = Charsets.UTF_8;      boolean var3 = false;      byte[] var10000 = string.getBytes(var2);      Intrinsics.checkExpressionValueIsNotNull(var10000, "(this as java.lang.String).getBytes(charset)");      return var10000;   }}

代码中根本找不到类型别名 Binary 的踪影。经过编译之后,类型别名会被原类型直接替换。这仅仅只是 Kotlin 丰富的语法糖之一,编译器在其中做了一些手脚。

typealias 存在的意义是什么 ?

现在,你估计更加困惑了。

开发者手动声明一个类型别名,编译器再自动替换回原类型。意义何在?

唯一能想到的一点大概只有 "代码可读性" ,这里的代码可读性还要打上了一个大大的引号。

复杂的业务逻辑下,你的代码中可能会出现超长命名,多参数,多泛型类型的类名,接口名,函数名。

typealias FileTable<K> = MutableMap<K, MutableList<File>>typealias OnPermissionResult = ActivityCompat.OnRequestPermissionsResultCallbacktypealias SimpleName = LonglonglonglonglonglonglonglonglonglonglonglonglonglonglongName

用类型别名来代替原本可读性不好(名字太长或者声明复杂)的类型名,这可能就是 typealias 的主要作用。

至于到底有没有提升可读性?我觉得这是有代价的。因此而丧失的是直观的类型声明。以上面代码块中的 FileTable 为例,一眼看过去,你能发现它是一个 MutableMap<K, MutableList<File>> 吗?肯定不能。特别在团队开发中,除了这个代码的贡献者,可能每一位都要到类型别名声明处进行查看。

有人可能也会有不一样的声音。统一的全局声明很正常,而且也方便做统一修改,避免到代码使用处一一修改。况且 IDE 都会自动提示类型别名的声明。没有不使用 typealias 的道理。

所以,这是一个仁者见仁,智者见智的问题。你觉得有用就可以使用,任何一门技术肯定都有它的使用场景,并没有必要去过多争论。

我用协程还是用 RxJava?
我用 LiveData 还是用事件总线?
我用 ViewBinding 还是 DataBinding ?
......

这几个问题可能比较常见,但是上面的每一组选择,如果你真正深入了解其技术本质的话,就会发现它们的使用场景并不一样,也就不会存在 如何选择 的疑问了。

typealias 使用注意事项

有点跑偏了,再回到 typealias 。后面再说一些 typealias 的注意事项,内容会比较零散,后续也可能继续增加。

typealias 可以写在哪里?

只能声明在文件顶层位置,其他任何地方都不行。

与 Java 如何交互?

拒绝和 Java 进行交互。

禁止套娃!

首先我们是可以 给别名取别名 的,如下所示:

typealias Names<T> = Array<T>typealias SubNames<T> = Names<T>

虽然没有太大意义,但是语法上是正确的。

下面这样套娃肯定是不行的。

typealias R = R typealias L = List<L>typealias A<T> = List<A<T>> typealias R1 = (Int) -> R2typealias R2 = (R1) -> Int

上面的每一行代码都是无法编译的。

顶层位置的 typealias 可以使用可见性修饰符 public 、 private 、 internal 。同时,typealias 不能修改原有类型的可见性。举个例子:

private class LongName{}typealias ShortName = LongName // 'public' typealias exposes 'private' in expanded type LongName

上面的代码会编译失败, public 的类型别名无法应用在 private 的原类型上。类型别名和原类型的可见性必须保持一致。

导入同名类的处理

对于在同一个类中导入两个同名类,通常的做法是, import 其中一个类,另一个使用全限定名。如下所示:

fun main() {    val user1 = User()    val user2 = com.test2.User()}

这样或多或少不大美观,可以使用 typealias 处理一下。

typealias User2 = com.test2.User fun main() {    val user1 = User()    val user2 = User2()}

另外, import ... as ... 也可以解决这个问题。

import com.test1.Userimport com.test2.User as User2 fun main() {    val user1 = User()    val user2 = User2()}

不妨翻翻你的代码库,看看有没有可以使用 typealias 进行优化的 “烂” 命名。如果有的话,欢迎来到评论区交流分享。

往期目录
object,史上最 “快” 单例 ?


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK