Kotlin第一讲-输入参数

本专栏的第一篇,分享下Kotlin里输入参数的特性

命名参数

我们先来看一个需求:
把集合里每个元素用分号分隔并打印到括号里,例如(Java,Kotlin,Python,JavaScript,Ruby,Go)

我还想改变输出格式,前缀,分隔符,后缀都有可能发生改变,于是提取出参数,Java实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class SeparatorUtils {

/**
*
* @param collections 集合
* @param prefix 前缀
* @param separator 分隔符
* @param postfix 后缀
* @param <T> 集合泛型
* @return 分隔后的结果
*/
public static <T> String separator(Collection<T> collections, String prefix, String separator, String postfix) {
Objects.requireNonNull(collections);

StringBuilder sb = new StringBuilder();
sb.append(prefix);
int size = collections.size();
int index = 0;
for (T t : collections) {
sb.append(t.toString());
if(index < size - 1){
sb.append(separator);
}
index ++;
}
sb.append(postfix);
return sb.toString();
}
}
private static void testSeparator(){
    List<String> list = new ArrayList<>();
    list.add("Java");
    list.add("Kotlin");
    list.add("Python");
    list.add("JavaScript");
    list.add("Ruby");
    list.add("Go");

    String separator = SeparatorUtils.separator(list, "(", ",", ")");
    System.out.println("separator result = " + separator);
}

输出结果:

separator result = (Java,Kotlin,Python,JavaScript,Ruby,Go)

这是一个完整的编码过程,来了一个需求,通过新增类和方法,在需要的地方,调用实现。需求被解决了,代码默默地在角落发挥着作用。

转眼三个月过去,团队里来了新人,他阅读到testSeparator方法;当读到SeparatorUtils.separator()方法,对于传的四个参数代表什么含义,乍看之下他并不清楚,需要点进去读实现代码或者看注释说明才确切明白。那么还有没有更易于阅读的方式呢?Kotlin的命名参数能做到。

SeparatorUtils.separator方法用Kotlin重写如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fun <T> joinToString(
collection: Collection<T>,
separator: String,
prefix: String,
postfix: String
): String {
val sb = StringBuffer(prefix)

for ((index, element) in collection.withIndex()) {
if (index > 0) {
sb.append(separator)
}
sb.append(element)
}
sb.append(postfix)

return sb.toString()
}

调用joinToString方法可以这样

val list = arrayListOf("a", "b", "c")
val s = joinToString(list, postfix = ")", separator = ",", prefix = "(")
println("s = $s")

上述即是命名参数,在调用处使用,形式为:参数名=参数值;
这样的入参带来了两个便利:

  1. 便于阅读,按顺序阅读代码就能知晓方法参数的含义
  2. 调用时入参的位置可以任意(调用的入参顺序和定义的入参的顺序允许不一致)

这样看起来真不错的

默认参数

定义函数时,给入参提供默认值,在调用处,如果不传入实参,则该参数使用默认值,可用于方法重载。例如对上述Kotlin代码的joinToString方法改变入参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@JvmOverloads
fun <T> joinToString(
collection: Collection<T>,
separator: String = ",",
prefix: String = "(",
postfix: String = ")"
): String {
val sb = StringBuffer(prefix)

for ((index, element) in collection.withIndex()) {
if (index > 0) {
sb.append(separator)
}
sb.append(element)
}
sb.append(postfix)

return sb.toString()
}

在Kotlin调用joinToString()支持如下,最后一个我们同时使用了Kotlin的命名参数和默认参数的特性。

val list = arrayListOf("Java", "Kotlin", "Python", "JavaScript", "Ruby", "Go")
val s = joinToString(list)
val s2 = joinToString(list,",")
val s3 = joinToString(list,",","[")
val s4 = joinToString(list,",","[","]")
val s5 = joinToString(list, prefix = "[", postfix = "]")

上述调用体现了方法重载,默认参数可提供方法重载的效果

上面出现的@JvmOverloads注解是用来做什么的呢?

默认参数特性,使用是有前提的:用Kotlin定义函数,并在Kotlin代码里调用该函数。因此,如果在Java文件里调用Kotlin定义的joinToString方法,默认不支持默认参数特性的,也即方法重载失效。

@JvmOverloads提供了让默认参数特性在Java环境也得到支持。原理是:kotlin代码编译成java代码时,会增加增加下面的方法,这些正是Java方法重载。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
public final class StringUtils {
public static final int count = 11;

@JvmOverloads
@NotNull
public static final String joinToString(@NotNull Collection collection, @NotNull String separator, @NotNull String prefix, @NotNull String postfix) {
Intrinsics.checkParameterIsNotNull(collection, "collection");
Intrinsics.checkParameterIsNotNull(separator, "separator");
Intrinsics.checkParameterIsNotNull(prefix, "prefix");
Intrinsics.checkParameterIsNotNull(postfix, "postfix");
StringBuffer sb = new StringBuffer(prefix);

Object element;
for(Iterator var6 = CollectionsKt.withIndex((Iterable)collection).iterator(); var6.hasNext(); sb.append(element)) {
IndexedValue var5 = (IndexedValue)var6.next();
int index = var5.component1();
element = var5.component2();
if (index > 0) {
sb.append(separator);
}
}

sb.append(postfix);
String var10000 = sb.toString();
Intrinsics.checkExpressionValueIsNotNull(var10000, "sb.toString()");
return var10000;
}

// $FF: synthetic method
// $FF: bridge method
@JvmOverloads
@NotNull
public static String joinToString$default(Collection var0, String var1, String var2, String var3, int var4, Object var5) {
if ((var4 & 2) != 0) {
var1 = ",";
}

if ((var4 & 4) != 0) {
var2 = "(";
}

if ((var4 & 8) != 0) {
var3 = ")";
}

return joinToString(var0, var1, var2, var3);
}

@JvmOverloads
@NotNull
public static final String joinToString(@NotNull Collection collection, @NotNull String separator, @NotNull String prefix) {
return joinToString$default(collection, separator, prefix, (String)null, 8, (Object)null);
}

@JvmOverloads
@NotNull
public static final String joinToString(@NotNull Collection collection, @NotNull String separator) {
return joinToString$default(collection, separator, (String)null, (String)null, 12, (Object)null);
}

@JvmOverloads
@NotNull
public static final String joinToString(@NotNull Collection collection) {
return joinToString$default(collection, (String)null, (String)null, (String)null, 14, (Object)null);
}

public static final void testExtend(@NotNull Container $receiver) {
Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
String var1 = "call the container testExtend method";
System.out.println(var1);
}
}

可变参数

可变参数关键词:vararg(分别取variate和arguments前三个字母)

来看一个Kotlin的Collections类里的一个方法

1
2
3
4
5
6
/**
* Returns a new [ArrayList] with the given elements.
* @sample samples.collections.Collections.Lists.arrayList
*/
public fun <T> arrayListOf(vararg elements: T): ArrayList<T>
= if (elements.size == 0) ArrayList() else ArrayList(ArrayAsCollection(elements, isVarargs = true))

调用

val list = arrayListOf("a", "b", "c", "d")

arrayListOf入参数量可以任意多个

Java实现可变参数,在数据类型后面加三个点:… ,看下Java里的Arrays里的一个方法

1
2
3
4
5
@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}

展开运算符 *

把数组展开成一个一个元素。展开运算符常与可变运算符联合使用。比如这样:

1
2
val array = arrayOf("a", "b", "c")
val list = arrayListOf(*array)

我得到了一个ArrayList集合,集合里的元素是”a”, “b”, “c”