Kotlin新手使用笔记

const关键字

只读属性使用const关键字之后将没有get方法,举个例子
在kotlin文件中,写两个包级属性,一个是const,一个不是const

1
2
const val i = 1
val j = "A"

使用java代码访问,访问方式是不同的

1
2
3
4
5
6
public class TestConst {
public static void main(String[] args) {
int i = ConstKt.i;
ConstKt.getJ();
}
}

一种是使用get方法访问,一种是直接使用类访问。说明const关键字实际上相当于java的static final。
需要注意的是,Const只能是kotlin的string和基本类型。
其实在object中const相当于@JvmField注解,比如下面的代码

1
2
@JvmField
val c = 3

就和

1
const val c = 3

效果一样

1
2
3
4
5
6
7
8
9
@JvmField
val TAGS = WeixinHook.TAG + ".Test"
const val TAGSG = WeixinHook.TAG + ".Test"

@NotNull
@JvmField
public static final String TAGS = "hotB.wechat.Test";
@NotNull
public static final String TAGSG = "hotB.wechat.Test";

在class中

1
2
3
4
5
6
7
8
9
private const val TAG = XLog.hotPanel + "DeviceUtil"
class DeviceUtil{}

生成了另一个文件存储const
public final class DeviceUtilKt {
/* renamed from: MB */
private static final long f14MB = 1048576;
private static final String TAG = "hotPanel.DeviceUtil";
}

这就是kotlin中const关键字的本质。
原文点击

静态内部类,常量,三元运算符

静态内部变量和常量定义方法

1
2
3
4
companion object {
private const val MSG_UPDATE_PROGRESS = 1
private const val MSG_UPDATE_TIME= 1000
}

静态内部类

1
2
3
4
5
6
7
class Parent{
class Child //默认就是 java的 public static
}
fun main(args: Array<String>) {
val inner = Parent.Child()

}

非静态内部类

1
2
3
4
5
6
7
class Parent{
//非静态内部类声明
inner class Child
}
fun main(args: Array<String>) {
val inner = Parent().Child()
}

内部类访问外部持有类的this

1
2
3
4
5
6
7
8
9
10
11
12
13
class Parent{
val a:Int = 0
inner class Child{
val a:Int = 5
fun hello(){
println(this@Parent.a)
}
}
}
fun main(args: Array<String>) {
val inner = Parent().Child()
inner.hello()
}

三元运算符

1
2
3
4
 //Java写法
int size=list!=null?list.size:0
//kotlin写法
val size=if(list!=null) list.size() else 0

原文点击

readLine赋值为空

Java中有一个判断是

1
if((str=bufferedReader.readLine())!= null)

这句话到了kotlin转不过来,java经过强制转换成kotlin,可能最初我们是这么写的

1
2
3
4
var str=bufferedReader.readLine() 
while(str != null){
执行代码
}

可是出oom了,问题在哪呢?不说了。最后

1
2
3
4
5
var line: String
while (true) {
line = bufferedReader.readLine() ?: break
buffer.append(line)
}

属性

val:val属性只提供getter

1
2
3
4
5
6
7
val usbState: UsbState
get() = sUsbState.copy()
编译后的:
@NotNull
public final UsbState getUsbState() {
return UsbMonitor.sUsbState.copy();
}

var:默认生成getter,setter方法

1
2
3
4
5
6
7
8
var usbConnected: Boolean = false
public final boolean getUsbConnected() {
return this.usbConnected;
}

public final void setUsbConnected(boolean <set-?>) {
this.usbConnected = <set-?>;
}

对象表达式 && 对象声明 && 伴生对象

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
var btn: Button = findViewById(R.id.btn)
// 对象表达式实现匿名内部类
btn.setOnClickListener(object: View.OnClickListener {
override fun onClick(v: View?) {
text.setText("${text.text} ${++clickTime}")
}
})
// OnClickListener是函数式接口,可使用Lambda表达式
btn.setOnClickListener { view ->
text.setText("${text.text} ${++clickTime}")
}
// 对象声明---单例
object FoodManager {
var foods: MutableList<String>
init{
foods = mutableListOf<String>()
// 初始化食物池
for (i in 1..9) {
foods.add("food${i}")
}
}
}
var foods = FoodManager.foods // 使用对象声明
// 伴生对象---静态成员
interface Outputable{
fun output(msg: String)
}
class MyClass{
// 定义的MyClass的伴生对象
companion object: Outputable{
val name = ”name属性值”

//重写父接口中的抽象方法
override fun output(msg: String) {
for(i in 1..6){
println (”<h$(i}>${ msg}</h${i}>”)
}
}
}
}
fun main(args: Array<String>) {
// 调用伴生对象里的方法与属性,与调用静态成员一样
MyClass.output("fkit.org")
println(MyClass.name)
}

对象声明定义常量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
object NetUtils {
/**
* 移动
*/
const val PROVIDER_TYPE_MOVE = 1
/**
* 联通
*/
const val PROVIDER_TYPE_UNICOM = 2
/**
* 电信
*/
const val PROVIDER_TYPE_TELECOM = 3

val NET_TYPE_MOVE = "10086"
val NET_TYPE_UNICOM = "10010"
val NET_TYPE_TELECOM = "10001"
}

由于对象声明定义的数据单例,所以一般调用(.java)都会用Class.INSTANCE.getXXXX,(kotlin)会用Class.XXXX,但是如果对象中声明的是const常量,则只能用Class.XXXX调用。
举例说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
switch (NetUtils.INSTANCE.providerType(WxVersions.CURRENT_APPLICATION)) {
case NetUtils.PROVIDER_TYPE_MOVE:
destinationAddress = NetUtils.INSTANCE.getNET_TYPE_MOVE();
break;
case NetUtils.PROVIDER_TYPE_UNICOM:
destinationAddress = NetUtils.INSTANCE.getNET_TYPE_UNICOM();
break;
case NetUtils.PROVIDER_TYPE_TELECOM:
destinationAddress = NetUtils.INSTANCE.getNET_TYPE_TELECOM();
break;
default:
destinationAddress = null;
break;
}

反编译后的NetUtils

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public final class NetUtils {
public static final NetUtils INSTANCE = new NetUtils();
@NotNull
private static final String NET_TYPE_MOVE = NET_TYPE_MOVE;
@NotNull
private static final String NET_TYPE_TELECOM = NET_TYPE_TELECOM;
@NotNull
private static final String NET_TYPE_UNICOM = NET_TYPE_UNICOM;
public static final int PROVIDER_TYPE_MOVE = 1;
public static final int PROVIDER_TYPE_TELECOM = 3;
public static final int PROVIDER_TYPE_UNICOM = 2;

private NetUtils() {
}

如果定义的常量没有加const,则在调用的时候要用Class.INSTANCE.getXXXX,但是,这个时候switch,会报constant expression requierd异常。

注意:伴生对象常量定义方式:

1
2
3
4
5
6
7
8
9
10
11
12
class MicroMsg {
companion object {
val DB_NAME = "EnMicroMsg.db"
}
}
而下面这样写法拿不到DB_NAME
class MicroMsg {
val DB_NAME = "EnMicroMsg.db"
companion object {

}
}

伴生对象如何生成public static final

1
2
3
4
5
6
7
8
9
10
11
class MicroMsg {
companion object {
val TYPE_IMG = 1
val TYPE_FILE = 2
const val TYPE_VOICE = 3
}
}
反编译后
private static final int TYPE_FILE = 2;
private static final int TYPE_IMG = 1;
public static final int TYPE_VOICE = 3;

constructor构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
1.主构造器
class MultiTask<TaskData, Result> constructor(private val mTaskItem: TaskItem, private val mRequest: TaskData, private val mTaskCount: Int, private val mDelay: Long = 500)

2.主构造器可以省略constructor
class MultiTask<TaskData, Result> (private val mTaskItem: TaskItem, private val mRequest: TaskData, private val mTaskCount: Int, private val mDelay: Long = 500)

3.主构造器如果有@JvmOverloads则不可以省略constructor
class MultiTask<TaskData, Result> @JvmOverloads constructor(private val mTaskItem: TaskItem, private val mRequest: TaskData, private val mTaskCount: Int, private val mDelay: Long = 500)

4.使用@JvmOverloads注解的主构造器,不可以再有次构造器
constructor(taskItem: TaskItem,mRequest: TaskData,mTaskCount: Int): this(taskItem, mRequest,mTaskCount,300) {
}
异常,hava the same JVM signature.

构造器几种使用:

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
空构造器
class OuterConstructor{}

私有构造器
class OuterConstructor private constructor(str:String, ins:Int){}

私有构造器+次构造器(可调)
class OuterConstructor private constructor(str:String, ins:Int){
var s1 = "123"
init{
this.s1 = str
}
constructor(str:String):this(str,8)
}
主构造器
class OuterConstructor constructor(str:String, ins:Int){
var s1 = "123"
init{
this.s1 = str
}
constructor(str:String):this(str,8)
}

主构造器省略初始化init
class OuterConstructor {
var s1 = "123"
init{
this.s1 = "qdf"
}
}
主构造器初始化init
class OuterConstructor constructor(str:String, ins:Int){
var s1 = "123"
init{
this.s1 = str
}
}

@JvmOverloads作用

在有默认参数值的方法中使用@JvmOverloads注解,则Kotlin就会暴露多个重载方法。

1
2
3
4
5
6
@JvmOverloads fun f(a: String, b: Int=0, c:String="abc"){
}
反编译后:
void f(String a)
void f(String a, int b)
void f(String a, int b, String c)

所以@JvmOverloads不仅仅用于构造方法。

constructor中参数指定val,var和没有制定的区别

不指定

1
2
3
class OuterConstructor constructor(mHookConfig: HookConfig) 
反编译后
public AvatarServiceImplKT(@NotNull HookConfig mHookConfig) {}

val

1
2
3
4
class OuterConstructor constructor(val mHookConfig: HookConfig) 
反编译后
private final HookConfig mHookConfig;
public AvatarServiceImplKT(@NotNull HookConfig mHookConfig) {}

var

1
2
3
4
class OuterConstructor constructor(var mHookConfig: HookConfig) 
反编译后
private HookConfig mHookConfig;
public AvatarServiceImplKT(@NotNull HookConfig mHookConfig) {}

vararg可变参数

1
2
3
4
5
6
7
8
9
fun print(vararg msgs: String) {
for (msg in msgs) {
println(msg)
}
}

fun LogPrinters(){
print("11","22","33","44");
}

但是如果是下面这样调用是不可以的,会报参数匹配异常:

1
2
val array = arrayOf("a", "b", "c", "d")
print(array)

如果想调用,需要使用*操作符:

1
print(*array)

所以我们调用带有可变参数的方法时,只能传递离散的参数值,直接传递数组会报语法错误。但是当我们手头上就是一个数组,要想传递,就需要借助运算符*(spread operator):相当于将数组分散成一个个的个体再传入进去。

var get() set(xxx)方法

1.

1
2
3
4
5
override var name: String="ninhao"
get() = field
set(value) {
field=value
}

调用

1
2
3
XLog.e("------name1:",name)
name = "henhao"
XLog.e("------name2:",name)

输出

1
2
ninhao
henhao

注意:var name: String=”ninhao”,必须初始化。
2.可以通过fun get() 和fun set(XXX)实现。

3.
错误写法:死循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var name: String
get() = name
set(value) {
name="afff"
}
反编译后

@NotNull
public String getName() {
return getName();
}

public void setName(@NotNull String value) {
Intrinsics.checkParameterIsNotNull(value, UserInfo.COLUMN_value);
setName("afff");
}

详细注解链接

split

1
2
3
4
5
6
7
var sb = StringBuilder()
sb.append("111,").append("222,").append("333,").append(",").append("aaa,").append("bbb,").append("ccc,").append("")
val strs = sb.toString()
val splits = strs.split(",".toRegex()).dropWhile { !it.isEmpty() }.toTypedArray()
for(str in splits){
XLog.e("-------split:",str)
}

1.dropWhile 返回从第一项起,去掉满足条件的元素,直到不满足条件的一项为止;
it.isEmpty():

1
2
3
4
5
[-------split:][main]
[-------split:][main]aaa
[-------split:][main]bbb
[-------split:][main]ccc
[-------split:][main]

!it.isEmpty():

1
2
3
4
5
6
7
8
[-------split:][main]111
[-------split:][main]222
[-------split:][main]333
[-------split:][main]
[-------split:][main]aaa
[-------split:][main]bbb
[-------split:][main]ccc
[-------split:][main]

2.dropLastWhile 返回从最后一项起,去掉满足条件的元素,直到不满足条件的一项为止;
it.isEmpty():

1
2
3
4
5
6
7
[-------split:][main]111
[-------split:][main]222
[-------split:][main]333
[-------split:][main]
[-------split:][main]aaa
[-------split:][main]bbb
[-------split:][main]ccc

!it.isEmpty():

1
2
3
4
5
6
7
8
[-------split:][main]111
[-------split:][main]222
[-------split:][main]333
[-------split:][main]
[-------split:][main]aaa
[-------split:][main]bbb
[-------split:][main]ccc
[-------split:][main]

3.drop 返回去掉前n个元素后的列表;
drop(4)

1
2
3
4
[-------split:][main]aaa
[-------split:][main]bbb
[-------split:][main]ccc
[-------split:][main]

4.filter & slice & take

filter–过滤掉所有不满足条件的元素;
filterNot–过滤掉所有满足条件的元素;
filterNotNull–过滤掉所有值为null的元素;

slice–过滤掉非指定下标的元素,即保留下标对应的元素过滤List中指定下标的元素(比如这里只保留下标为1,3,4的元素),当过滤list中有元素值大于目标List大小时会出现异常;

take–返回从第一个开始的n个元素;
takeLast–返回从最后一个开始的n个元素;
takeWhile–返回不满足条件的下标前面的所有元素的集合;

1
2
3
4
5
6
7
8
filter { !it.isEmpty() }

[-------split:][main]111
[-------split:][main]222
[-------split:][main]333
[-------split:][main]aaa
[-------split:][main]bbb
[-------split:][main]ccc

参考链接点击

显式转换&强制转换

每个数字类型支持如下的转换:

1
2
3
4
5
6
7
> toByte(): Byte
> toShort(): Short
> toInt(): Int
> toLong(): Long
> toFloat(): Float
> toDouble(): Double
> toChar(): Char

强制转换
xxx as 类:强制转换,可能崩溃

xxx as? 类:安全的强制转换,转换失败返回null

注意:数字类型的转换也可以使用as ,编译时不报错,但运行时会报错。

Lambda argument should be moved out of parentheses

解决方式:option+enter;

return@标签

跳转到指定的位置,标签的写法很简单,只需要名字之后加@符号,要用的时候@名字就好,一般在lambda表达式中可能会用到。

1
2
3
4
5
6
7
8
9
10
11
fun print(vararg msgs: String) {
Async.io {
for (msg in msgs) {
XLog.e("-------print:",msg)
}
return@io
XLog.e("-------print:","8")
}

XLog.e("-------print:","9")
}

输出

1
2
3
4
5
[-------print:][main]9
[-------print:][IOExecutor#1]a
[-------print:][IOExecutor#1]b
[-------print:][IOExecutor#1]c
[-------print:][IOExecutor#1]d

for循环使用方法

在Kotlin中想遍历1-100的数值可以这样写:

1
2
3
for (index in 1..10){
print(index)//会输出1..10
}

这样写是正序遍历,如果想倒序遍历就该使用标准库中定义的downTo()函数:

1
2
3
for (index in 10 downTo 1){
print(index)//会输出10..1
}

想不使用1作为遍历的步长,可以使用step()函数:

1
2
3
for (index in 1..100 step 2){
print(index)//会输出1..3..5......
}

要创建一个不包含末尾元素的区间:

1
2
3
for (index in 1 until 10){
println(index)//输出1..9
}

遍历一个数组/列表,想同时取出下标和元素:

1
2
3
4
5
6
7
val array = arrayOf("a", "b", "c")
for ((index,e) in array.withIndex()){
println("下标=$index----元素=$e")
}
[-----index:][main]下标=0----元素=a
[-----index:][main]下标=1----元素=b
[-----index:][main]下标=2----元素=c

遍历一个数组/列表,只取出下标:

1
2
3
4
val array = arrayOf("a", "b", "c")
for (index in array.indices){
println("index=$index")//输出0,1,2
}

遍历取元素:

1
2
3
4
val array = arrayOf("a", "b", "c")
for (element in array){
println("element=$element")//输出a,b,c
}

.class

Service::class.java的语法展现了如何获取java.lang.Class对应的Kotlin类,这和Java中的Service.Class是完全等同的.

java通配符&kotlin声明处型变

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
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
// 校验略
ListIterator<? super T> di = dest.listIterator();
ListIterator<? extends T> si = src.listIterator();
while (si.hasNext()) {
/**
* 编译时异常
* Incompatible types.不兼容类型;
* Required:T;
* Found:Capture<? super T>
*/
T elementD =di.next();

T element = si.next(); //src extent 生产者 作为方法返回值类型是安全的
di.set(element); // dest super 消费者 作为方法参数类型是安全的

/**
* 编译时异常
* set(<? extends T>) in ListIterator cannot be applied.
* to (T)
*/
si.set(elementD);
}
}

kotlin:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
abstract class Supplier<out T> {
abstract fun get(): T
/**
* 编译期错误:
* Type parameter T is declared as 'out' but occurs in 'in' position in type T
*/
abstract fun set(t: T)
}

abstract class Customer<in T> {
abstract fun set(t: T)
/**
* 编译期错误:
* Type parameter T is declared as 'in' but occurs in 'out' position in type T
*/
abstract fun get(): T
}

1.List<? extends T>协变:== out

在si.set(elementD)中, elementD的类型是T,所以我们可以set(T t),以及一切参数中含有 T 的方法(称为消费者方法),T类型或者从T继承的类型都可以被set,但是因为可以set不同的类型,所以这些方法可能会破坏类型安全,所以编译器限制这些方法的调用。

举kotlin例说明:
有一方法

1
2
3
fun generate(args : Array<Any>){
//todo
}

我们调用它:

1
2
val args : Array<String> = arrayOf()
generate(args)

这个时候会提示异常(类型不匹配):type mismitch,为了解决这个问题,我们只需要修改一下调用方法,使用型变out即可。

1
2
3
fun generate(args : Array<out Any>){
//todo
}

List<? super T>逆变:== in

同协变正好相反,在T elementD =di.next();中,因为di的类型是<? super T>,所以我们通过di.next()获取的类型可能T,也可能是从T继承的类型,从而我们不能确定next的类型。注意,这次拒绝的理由跟协变中是不一样的。get方法并不会破坏泛型类的类型安全,主要原因在于我们不能确定get的返回类型。
参考链接1
参考链接2

?: ?. !!操作符

?. == If not null

?.就是当前面的变量!= nuil 时正常调用,如果为null就为null;并且?.可以连续使用。例如user?.userName?.length

!!! == npe

!!,加在变量名后,就是当变量为null时,抛出空指针异常;

?: == if null

?: 仅仅在左边的表达式结果为null时才会计算 ?: 后面的表达式

1
2
3
4
5
6
7
val map = XXXXX ?: return
等同于
val map = XXXXX
if(map == null){
return
}
但是if会有://Replace 'if' with elvis operator

toString()

1
2
3
4
5
/**
* Returns a string representation of the object. Can be called with a null receiver, in which case
* it returns the string "null".
*/
public fun Any?.toString(): String

疑问点:
null.toString()
反编译
String.valueOf(null)

Serializable

1
class XXXXKT:Serializable{}

Cannot access ‘Serializable’:it is internal in ‘kotlin.io’

解决:import java.io.Serializable

@Suppress(“UNCHECKED_CAST”)

当使用 as Array强转时可能会出现Unchecked cast: Any! to Array 警告。解决方法就是在强转代码前加@Suppress(“UNCHECKED_CAST”)。
备注:@SuppressWarnings(“unchecked”)使用时机呢?

接口

详细使用点击

参考资料

Runoob

kotlincn

Kotlin学习笔记