1、扩展函数
我们对String定义一个扩展函数
//对String 增加扩展函数lastElement
//直接定义在kotlin文件里,称之为顶层扩展、
fun String.lastElement(): Char? {
if (this.isEmpty()) {
return null
}
return this[length - 1]
}
fun main() {
val a = "Hello World"
println(a.lastElement())
}
扩展函数我们定义在kotlin文件中,称之为顶层扩展,任何地方都可以使用,转成Java代码看实现
public final class ExtentionKt {
@Nullable
public static final Character lastElement(@NotNull String $this$lastElement) {
Intrinsics.checkNotNullParameter($this$lastElement, "$this$lastElement");
CharSequence var1 = (CharSequence)$this$lastElement;
boolean var2 = false;
return var1.length() == 0 ? null : $this$lastElement.charAt($this$lastElement.length() - 1);
}
public static final void main() {
String a = "Hello World";
Character var1 = lastElement(a);
boolean var2 = false;
System.out.println(var1);
}
}
其实实现很简单,定义了一个在ExtentionKt 类中定义了一个lastElement静态方法并传入String对象。在调用的时候,其实就是传入了我们的字符串a即可。然后再算出最后一个字符。
所以 Kotlin 编译器会将扩展函数转换成对应的静态方法,而扩展函数调用处的代码也会被转换成静态方法的调用。
2、扩展属性
//对String 增加扩展函数lastElement
//直接定义在kotlin文件里,称之为顶层扩展、
fun String.lastElement(): Char? {
if (this.isEmpty()) {
return null
}
return this[length - 1]
}
//定义扩展属性
val String.firstElement: Char?
get() = if (isEmpty()) null else this[0]
fun main() {
val a = "Hello World"
println(a.lastElement())
println(a.firstElement)
}
转成Java代码看实现
public final class ExtentionKt {
@Nullable
public static final Character lastElement(@NotNull String $this$lastElement) {
Intrinsics.checkNotNullParameter($this$lastElement, "$this$lastElement");
CharSequence var1 = (CharSequence)$this$lastElement;
boolean var2 = false;
return var1.length() == 0 ? null : $this$lastElement.charAt($this$lastElement.length() - 1);
}
@Nullable
public static final Character getFirstElement(@NotNull String $this$firstElement) {
Intrinsics.checkNotNullParameter($this$firstElement, "$this$firstElement");
CharSequence var1 = (CharSequence)$this$firstElement;
boolean var2 = false;
return var1.length() == 0 ? null : $this$firstElement.charAt(0);
}
public static final void main() {
String a = "Hello World";
Character var1 = lastElement(a);
boolean var2 = false;
System.out.println(var1);
var1 = getFirstElement(a);
var2 = false;
System.out.println(var1);
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
}
从Java代码中我们可以看到,最终也是转成一个静态的getFirstElement方法,其实我们很容易理解,当我们的属性firstElement 设置成val 非私有的时候,对应的Java代码就会默认持有getter方法,也就是我们的getFirstElement。当我们定义了扩展属性,那么对应的Java代码就会转成相应的静态的getter方法。
3、扩展的能力边界?
从以上我们转成Java便可以知道,kotlin中不管的是扩展函数还是扩展属性对应都是Java中的静态方法,也就是主要用于替代Java 中的各种工具类。例如我们的工具类主要是操作字符串,那么我们就对String类进行扩展
扩展能做什么?
在Kotlin中几乎所有的类都可以扩展、主要用途是取代Java中的各种工具类,StringUtils等等。当我们对某个类进行扩展成员的时候,扩展的成员实际上不是真正的成员,但是我们在编译器中有智能的提示,这样一来就方便开发。
扩展不能做什么?
- 扩展不是真正的成员,无法被子类重写
- 扩展属性无法存储状态
如:
//定义扩展属性
val String.firstElement: Char?
get() = if (isEmpty()) null else this[0]
其实很容易理解,扩展属性转成Java代码其实就对应了静态的getter方法而已,具体的值由getter方法返回值决定
- 扩展的访问作用域
(1)定义处的成员
(2)接受者类型的公开成员
如:
private val name = "dog";
//定义扩展属性
val String.firstElement: Char?
get() = if (isEmpty()) {
null
} else {
println(name)
println(this.length)
this[0]
}
name成员虽然是私有的,但是定义在和扩展属性firstElement同一个文件,因此是可以访问到name 的,否则不能
其次就是访问被扩展类型的公开成员。如以上的例子访问String的length 通过this.length,如果length是私有的则不能访问。
以上我们定义的是顶层扩展,如果我们在某个类中进行扩展呢?
class Pig {
//对String 增加扩展函数lastElement
//直接定义在kotlin文件里,称之为顶层扩展、
fun String.lastElement(): Char? {
if (this.isEmpty()) {
return null
}
return this[length - 1]
}
private val name = "dog";
//定义扩展属性
val String.firstElement: Char?
get() = if (isEmpty()) {
null
} else {
println(name)
println(this.length)
this[0]
}
fun testDemo(){
val a = "Hello World"
println(a.lastElement())
println(a.firstElement)
}
}
转成Java代码
public final class Pig {
private final String name = "dog";
@Nullable
public final Character lastElement(@NotNull String $this$lastElement) {
Intrinsics.checkParameterIsNotNull($this$lastElement, "$this$lastElement");
CharSequence var2 = (CharSequence)$this$lastElement;
boolean var3 = false;
return var2.length() == 0 ? null : $this$lastElement.charAt($this$lastElement.length() - 1);
}
@Nullable
public final Character getFirstElement(@NotNull String $this$firstElement) {
Intrinsics.checkParameterIsNotNull($this$firstElement, "$this$firstElement");
CharSequence var2 = (CharSequence)$this$firstElement;
boolean var3 = false;
Character var10000;
if (var2.length() == 0) {
var10000 = null;
} else {
String var4 = this.name;
var3 = false;
System.out.println(var4);
int var5 = $this$firstElement.length();
var3 = false;
System.out.println(var5);
var10000 = $this$firstElement.charAt(0);
}
return var10000;
}
public final void testDemo() {
String a = "Hello World";
Character var2 = this.lastElement(a);
boolean var3 = false;
System.out.println(var2);
var2 = this.getFirstElement(a);
var3 = false;
System.out.println(var2);
}
}
其实非常容易了理解,当扩展成员定义在类中,那么只能在类中访问
因此可知:
如果是顶层扩展,是可以被全局使用的,扩展成员的访问作用域限于所在文件的所有成员,以及被扩展类型的公开成员
如果是在类中扩展,那么只能在类中使用,扩展成员的访问作用域为该类的成员,以及被扩展类型的公开成员
4、实战与思考
以上我们分析了扩展函数常用于替代Java中的各种工具类,仅此而已吗?
我们来看下Kotlin中的String
public class String : Comparable, CharSequence {
companion object {}
public override fun get(index: Int): Char
public override fun subSequence(startIndex: Int, endIndex: Int): CharSequence
public override fun compareTo(other: String): Int
}
我们看到String中只有几个核心方法,而平时我们使用到的这么多API,都在Strings这个顶层扩展文件中,
由此可知,在优化我们的项目架构的时候,我们可以将核心方法写在类中,非核心方法我们可以通过扩展的方式实现分离。
第二就是对于SDK中的代码,我们在使用的时候,总是要写很多公共的模板代码,如果我们使用Java可以想到把他封装成一个静态工具类,在Kotlin我们便可以通过扩展成员的方式进行封装。虽然说扩展的方式转换成Java代码也是通过静态方法进行封装,但是对于我们开发中却非常方便,开发中编译器就类似把扩展成员当做真正的成员,智能提示。
如下代码:
当我们要设置View的margin的时候,总要写很多模板代码
val param = textView?.layoutParams as ViewGroup.MarginLayoutParams
param.bottomMargin = 10
param.topMargin = 10
param.marginStart = 10
param.marginEnd = 10
textView.layoutParams = param
那么我们便可以通过扩展的方式,使得代码更加方便
fun View.setMargin(left: Int, top: Int, right: Int, bottom: Int) {
( layoutParams as ViewGroup.MarginLayoutParams).let {
it.bottomMargin = bottom
it.topMargin = top
it.marginStart = left
it.marginEnd = right
}
}
fun main() {
val textView: TextView? = null
textView?.setMargin(10, 20, 30, 50)
val button: Button? = null
button?.setMargin(10, 20, 30, 50)
}
这样针对所有的View我们都可以使用。
5、小结
- 扩展我们可以用于替代Java中的一些工具类
- 我们可以将核心方法写在类中,非核心成员写在扩展成员中
- 当我们调用一些外部SDK中的函数总是要写很多模板方法,我们便可以对SDK类进行扩展
- 扩展成员不是真正的成员,因此不能继承,不能保存数据,其底层最终也是封装成了静态的方式实现
- 顶层扩展在任何地方都可以使用,类中扩展只能在类中使用,因此我们一般使用底层扩展