这里是 重学 Kotlin 系列第二篇,本文永久更新地址 :https://xiaozhuanlan.com/topic/0846175293 。java
可能还真的就不认识了。web
今天的主角是 type alias,翻译过来叫 类型别名。先来看一下文章目录:编辑器
这是一个很基础的关键字,但可能不少人没有使用过。它的做用十分简单,给已有类型取一个别名,能够像使用原类型同样使用这个 “类型别名” 。函数
举个简单的例子:flex
typealias Name = String
val name : Name = "java" println(name.length) 复制代码
给 String
取个别名 Name
,在使用过程当中,Name
和 String
是彻底等价的。优化
既然是等价的,使用别名的意义是什么呢?ui
别急,typealias
不只仅支持给类取别名,它的用法丰富的让你想象不到。this
// 类和接口
typealias Name = String typealias Data = Serializable // 函数类型 typealias GetName = () -> String typealias Handler = CoroutineScope.() -> Unit // 泛型 typealias P<T> = Comparable<T> typealias Pairs<K, V> = HashMap<K, V> typealias Numbers = Array<Number> // object object Single {} typealias X = Single class Util { companion object { val count = 1 } } typealias Count = Util.Companion // inner class typealias 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
是不容许的。url
几乎没有 typealias 不适用的类型。说到如今,你又得疑问了,类型别名存在的意义是什么 ?这样简单的取个别名,为何不直接使用原类型呢 ?spa
暂且别急,咱们先来看一下 typealias
的实现原理,说不定能够有一些发现。
反编译下面这个简单的例子:
typealias Binary = ByteArray fun 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 FileTable<K> = MutableMap<K, MutableList<File>>
typealias OnPermissionResult = ActivityCompat.OnRequestPermissionsResultCallback typealias SimpleName = LonglonglonglonglonglonglonglonglonglonglonglonglonglonglongName 复制代码
用类型别名来代替本来可读性很差(名字太长或者声明复杂)的类型名,这可能就是 typealias
的主要做用。
至于到底有没有提高可读性?我以为这是有代价的。所以而丧失的是直观的类型声明。以上面代码块中的 FileTable
为例,一眼看过去,你能发现它是一个 MutableMap<K, MutableList<File>>
吗?确定不能。特别在团队开发中,除了这个代码的贡献者,可能每一位都要到类型别名声明处进行查看。
有人可能也会有不同的声音。统一的全局声明很正常,并且也方便作统一修改,避免到代码使用处一一修改。何况 IDE 都会自动提示类型别名的声明。没有不使用 typealias 的道理。
因此,这是一个仁者见仁,智者见智的问题。你以为有用就可使用,任何一门技术确定都有它的使用场景,并无必要去过多争论。
我用协程仍是用 RxJava? 我用 LiveData 仍是用事件总线? 我用 ViewBinding 仍是 DataBinding ? ......
这几个问题可能比较常见,可是上面的每一组选择,若是你真正深刻了解其技术本质的话,就会发现它们的使用场景并不同,也就不会存在 如何选择 的疑问了。
有点跑偏了,再回到 typealias 。后面再说一些 typealias 的注意事项,内容会比较零散,后续也可能继续增长。
只能声明在文件顶层位置,其余任何地方都不行。
拒绝和 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) -> R2 typealias 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.User
import com.test2.User as User2 fun main() { val user1 = User() val user2 = User2() } 复制代码
不妨翻翻你的代码库,看看有没有可使用 typealias 进行优化的 “烂” 命名。若是有的话,欢迎来到评论区交流分享。
往期目录 : object,史上最 “快” 单例 ?
本文使用 mdnice 排版