1. 泛型
概念:
在不指定代码中使用到的确切类型的情况下来编写算法。可以创建函数或者类型。
优点:
提高代码可重用性。
1.1 泛型类
定义一个泛型如下
class TypedClass<T>(parameter: T) {
val value: T = parameter
}
使用上面的泛型
val t1 = TypedClass<String>("Hello World!")
val t2 = TypedClass<Int>(25)
更加简洁的用法可以省略<>类型,让程序自行推断类型
val t1 = TypedClass("Hello World!")
val t2 = TypedClass(25)
val t3 = TypedClass<String?>(null)
如第三个对象接收一个null引用,那仍然还是需要指定它的类型,因为它不能去推断出来。
限制泛型参数非空,可以这样定义
class TypedClass<T : Any>(parameter: T) {
val value: T = parameter
}
如果想要 限定泛型参数类型为Context的子类,可以做如下定义
class TypedClass<T : Context>(parameter: T) {
val value: T = parameter
}
1.2 泛型函数
定义泛型参数函数
fun <T> typedFunction(item: T): List<T> {
...
}
1.3 变体
对泛型参数的通配符的限定、泛型擦除等概念
举例1 说明
List<String> strList = ...;
List<Object> objList = ...;
objList.addAll(strList);
这样是可以的,因为定义在Collection接口中的addAll()是这样的:
List<String>
interface Collection<E> ... {
void addAll(Collection<? extends E> items);
}
增加Strings到ObjectList
void copyStrings(Collection<? super String> to, Collection<String> from) {
to.addAll(from);
}
增加Strings到另一个集合中唯一的限制就是那个集合接收Strings或者父类。
但是通配符都有它的限制。通配符定义了使用场景变体(use-site variance),这意味着当我们使用它的时候需要声明它。这表示每次我们声明一个泛型变量时都会增加模版代码。
class TypedClass<T> {
public T doSomething(){
...
}
}
不合法使用,无法通过编译
TypedClass<String> t1 = new TypedClass<>();
TypedClass<Object> t2 = t1;
合法使用
TypedClass<String> t1 = new TypedClass<>();
TypedClass<? extends String> t2 = t1;
这会让代码更加难以理解,而且增加了一些额外的模版代码。
针对通配符的弊端,Kotlin通过协变(covariance)和逆变(contravariance)来解决。
相比冗长的通配符,Kotlin仅仅使用out来针对协变(covariance)和使用in来针对逆变(contravariance)。
当我们类产生的对象可以被保存到弱限制的变量中,我们使用协变。
class TypedClass<out T>() {
fun doSomething(): T {
...
}
}
使用上面定义的泛型类创建对象
val t1 = TypedClass<String>()
val t2: TypedClass<Any> = t1
1.4 变体实例
泛型使用举例
1.4.1 let
它接收一个函数(接收一个对象,返回函数结果)作为参数,作为参数的函数返回的结果作为整个函数的返回值。它在处理可null对象的时候是非常有用的,下面是它的定义:
inline fun <T, R> T.let(f: (T) -> R): R = f(this)
通常的非空判定
if (forecast != null) dataMapper.convertDayToDomain(forecast) else null
使用let简化使用
forecast?.let { dataMapper.convertDayToDomain(it) }
多亏?.操作符,let函数只会在forecast不是null的时候才会执行。否则它会返回null。也就是我们想达到的效果。
1.4.2 with
with接收一个对象和一个函数,这个函数会作为这个对象的扩展函数执行。这表示我们根据推断可以在函数内使用this。
定义
inline fun <T, R> with(receiver: T, f: T.() -> R): R = receiver.f()
T代表接收类型,R代表结果。如你所见,函数通过f: T.() -> R声明被定义成了扩展函数。这就是为什么我们可以调用receiver.f()。
fun convertFromDomain(forecast: ForecastList) = with(forecast) {
val daily = dailyForecast map { convertDayFromDomain(id, it) }
CityForecast(id, city, country, daily)
}
1.4.3 apply
和with非常像,apply可以避免builder的方式来使用,因为对象调用的函数可以根据自己的需要来初始化自己,然后apply函数会返回它同一个对象
定义
inline fun <T> T.apply(f: T.() -> Unit): T { f(); return this }
使用
val textView = TextView(context).apply {
text = "Hello"
hint = "Hint"
textColor = android.R.color.white
}
它创建了一个TextView,修改了一些属性,然后赋值给一个变量。
让我们用在当前的代码中。在ToolbarManager中,我们使用这种方式来创建导航drawable:
private fun createUpDrawable() = with(DrawerArrowDrawable(toolbar.ctx)) {
progress = 1f
this
}
使用with和返回this是非常清晰的,但是使用apply可以更加简单:
private fun createUpDrawable() = DrawerArrowDrawable(toolbar.ctx).apply {
progress = 1f
}
2. 异常(Exceptions)
在Kotlin中,所有的Exception都是实现了Throwable,含有一个message且未经检查。这表示我们不会强迫我们在任何地方使用try/catch。
Kotlin中抛出异常和捕获异常的方式和Java非常相似。
抛出异常
throw MyException("Exception message")
捕获异常
try{
// 一些代码
}
catch (e: SomeException) {
// 处理
}
finally {
// 可选的finally块
}
在Kotlin中,throw和try都是表达式,这意味着它们可以被赋值给一个变量。这个在处理一些边界问题的时候确实非常有用:
val s = when(x){
is Int -> "Int instance"
is String -> "String instance"
else -> throw UnsupportedOperationException("Not valid type")
}
或者
```kotlin
val s = try { x as String } catch(e: ClassCastException) { null }