1

Kotlin上手(一)

系列笔记为学习极客时间张涛讲解Kotlin的笔记。

本篇笔记主要学习了从Java过渡到Kotlin的几个注意点

1.最基本语法:

1.1Kotlin的变量:

Kotlin的文件是以.kt结尾,Kotlin的代码不需要;结尾

var表示变量,val表示不可变的变量(不是常量),Kotlin的变量名在前面,变量名写在后面,中间冒号隔开,如果变量类型编译器可以推断出来,那么可以不用写明类型,同时Kotlin是具有空安全的,通过?和!!可以实现这种空安全的转换。

例如:

val name1:String = null   //错误,String类型不为空,不能赋值null

val name1:String? = null  //正确:String?类型可以为空(String与String?是两种不同类型)
val name2:String = name1  //错误:name2是String类型不可以为空,而name1可能为空,不能赋值

val name2:String = name1!! //正确:后面的!!指明开发者已经确保了name1不可能是null,可以赋值(如果运行时出现null,抛出相关的异常)

1.2kotlin的函数:

fun关键字指明Kotlin的函数变量,后面跟函数名,参数列表同变量写法一样,先写变量名后写变量类型,返回值最后写。

fun functionName(str:String,num:Int):String{
    if(num==0){
        println(str)
    }
    return str
}

2.Kotlin与Java的互相调用:

2.1.Java直接调用Kt函数

kotlin的函数可以直接写在文件里面,不用写在类里面,但是编译kt文件之后,实际上最终还是编译成public static修饰

//Util.kt
fun pln(str:String){
    println("$str")
}
//Main.java
public static void main(args[] String){
    UtilKt.echo("$args[0]");
}

其中$是Kotlin语法的转义符号之一,可以直接在字符串中插入变量名或者一段代码(用{}括起来),如果要在字符串中使用$符号,则可以

println("${'$'}name")    //output:$name

2.2.匿名类对象:

匿名内部类主要是针对那些不能创建实例的抽象类和接口而来的,在Kotlin中使用object关键字创建匿名类对象:

首先看下Java和Kotlin的匿名类对象写法上的区别:

//在Java中创建和使用匿名内部类
public interface AInterface {
    void sayMsg(String msg);
    void doMsg(String msg);
}
AInterface aInterface = new AInterface() {
    @Override
    public void sayMsg(String msg) {
        System.out.println("sayMsg"+msg);
    }
    @Override
    public void doMsg(String msg) {
        System.out.println("doMsg"+msg);
    }
};
aInterface.sayMsg("B");
aInterface.doMsg("B");

//在Kotlin中创建和使用匿名内部类
interface KtInterface{
    fun sayBye(msg:String?)
    fun doBye(msg:String?)
}
val bye = object: KtInterface{
    override fun doBye(msg: String?) {
        println("doBye$msg")
    }
    override fun sayBye(msg: String?) {
        println("sayBye$msg")
    }
}
bye.sayBye(" bye")
bye.doBye(" Bye")

如果要在Java中调用Kotlin中的匿名类对象,则如下:类名加.INSTANCE

而Kotlin的单例写法之一,也正是直接用object关键字实现。

//在Test.kt文件中
object Test{
    fun sayMessage(msg:String?){
        println(msg)
    }
}

//在MainTest.java文件中
public static void main(String[] args){
     Test.INSTANCE.sayMessage("Hello");
}

如果不通过INSTANCE来能不能调用到sayMessage方法?直接通过Test调用

Test.sayMessage("Hello")

可以通过@JVMStatic注解实现,该注解最终会把对应方法在编译成public static修饰。

2.3.传递Class对象:

在Java中传递一个Class对象的方法是直接

ClassName.class

而Kotlin如果要传递一个Java的class对象则是

ClassName::class.java

因为Kotlin的Class对象与Java的Class对象并不相同,Kt有着自己的Class类型:KClass,如果使用的是KClass那么直接

ClassName:kotlin

示例代码如下:

fun printClass(clazz:Class<MainTest>){
    println("Class Name:"+clazz.simpleName);
}

fun printClass(clazz:KClass<Test>){
    println(clazz.simpleName)
}

//调用
printClass(MainTest::class.java)
printClass(Test::class)

2.4.关键字上的冲突:

如果在Java代码中定义的变量或常量名使用到了Kotlin的关键字,需要借助一对单引号''解决这个冲突,如下:

//关键字冲突
public static String object = "object";
//Kotlin中调用
println(MainTest.`object`);

3.新手易踩坑

3.1.Kotlin没有封装类

Kotlin中对于基本数据类型是没有封装类这一说的,这一点不同于Java的int->Interger,float->Float......

首先看如下代码:

//用Java定义了一个接口,两个同名方法,参数类型一个是基本数据类型,另一个是它的封装类
public interface NoBoxInterface {
    void printNum(int num);
    void printNum(Integer num);
}
//用Kotlin去实现它:
class NoBoxImpl:NoBoxInterface{
    override fun printNum(num: Int) {}
}

上面的代码中,我们只会要也只能实现一个方法,因为Kotlin中也不存在Integer这个类型。

扩展一下:

碰巧是在搜Kotlin有没有封箱这一说的时候看到的,来看下面这个例子:

简单一点说,装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型。这是在Java1.5中引入的特性,目的是为了节省内存和提高虚拟机对整型数据的处理能力。

eg:

//自动装箱
Integer total = 99;
//自定拆箱
int totalprim = total;
val num1:Int = 127
val num2:Int? = num1
val num3:Int? = num1

fun main(args:Array<String>){
    println(num2== num3)
    println(num2===num3)    //kotlin中===比较的是地址,换言之,比较的是它俩是不是同一个对象
}
//output:true true

当num1>127的时候

//output:true false

可能会有点疑惑为什么举这个例子,跟装箱有什么关系,为什么大于与小于等于127输出的结果不一样?

打开Idea的Tool->Kotlin->Show Kotlin ByteCode可以查看对应的字节码文件,然后decomplie可以反编译成java代码

Int反编译之后对应到int,Int?反编译之后对应到Integer。在用Integer装箱的时候,我们用到了它的valueOf方法

Integer i = Integer.valueOf(8);

去看下valueOf就能知道为和如此了:

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
//low=-128 high=127

正是这里,当整型值在-128到127之间的时候,Integer是不会去创建新对象的,而是从IntegerCache中读取,这个类是Integer的内部静态类,存放了-128到127之间的全部Integer对象。

回到最初的例子,正是因为上面这一点,当数值在这个限定范围里面的时候,num3和num2指向的都是同一个Integer对象,当超过这个范围,就会去创建新的Integer对象,使得===的结果为false。

3.2.Kotlin类型空值敏感

这一点在笔记开头已经讲过了

//java代码
String format(String str) {
    return str.isEmpty() ? null : str;
}

//在kotlin中调用
fun function(str: String) {
    val fmt1 = format(str)
    val fmt2: String = format(str)
    val fmt3: String? = format(str)
}

fmt2这一句会抛出NullPointException,而fmt3不会。

3.3.Kotlin没有静态变量与静态方法

网上很多博客说直接用object关键字修饰的类,它的方法就是static方法;或者companion object{}修饰的就是静态成员,个人认为这种理解是错误的。

来看下面一段代码:

class StaticTest {
    var num0 = 1
    fun method(){
        println("$num0")
    }
    companion object {
        //注意:@JvmField
        var num1 = 1
        //注意:@JvmStatic
        fun staticMethod(){
            println("$num1")
        }
    }
}

在上面的代码中,我先注释了两个个注解,然后又恢复它们,通过查看编译好的字节码文件:

//没加注解
private static I num1
public final staticMethod()V
//加了注解
public static I num1
public final static staticMethod()V

可以看出,对于位于object或者companion object{}修饰的域里面的变量而言 ,kotlin的确把它们看做了static域,但是方法却不一样,只有在加了@JVMStatic注解之后(注意:这个注解只能在有object修饰的域里面才能使用),staticMethod才被编译成了static属性的方法。

那么加与不加有什么区别?毕竟你都可以直接通过ClassName.MethodName来调用它,我个人觉得这更像是一种前面说到过的,Kotlin单例的写法。

如果要给一个变量或者一个方法附上“Static”属性(打引号是指明在Kotlin中没有Static这一说,只是能够达到与Java的Static关键字相同的效果),可以通过以下方式来实现:

下面我援引Kotlin doc来说明如何在Kotlin中达到Static效果
3.3.1.Static字段
  • @JvmField 注解;
  • lateinit 修饰;
  • const 修饰.

@JVMField

class Key(val value: Int) {
    companion object {
        @JvmField
        val COMPARATOR: Comparator<Key> = compareBy<Key> { it.value }
    }
}
// Java
Key.COMPARATOR.compare(key1, key2);
// public static final field in Key class

lateinit

object Singleton {
    lateinit var provider: Provider
}
// Java
Singleton.provider = new Provider();
// public static non-final field in Singleton class

const

// file example.kt
object Obj {
    const val CONST = 1
}
class C {
    companion object {
        const val VERSION = 9
    }
}
const val MAX = 239
int c = Obj.CONST;
int d = ExampleKt.MAX;
int v = C.VERSION;
3.3.2.Static方法
  • @JVMStatic 注解
class C {
    companion object {
        @JvmStatic fun foo() {}
        fun bar() {}
    }
}
//在Java中调用
C.foo(); // works fine
C.bar(); // error: not a static method
C.Companion.foo(); // instance method remains
C.Companion.bar(); // the only way it works

注意Companion,如果是直接object修饰类,则是INSTANCE,如下:

object Obj {
    @JvmStatic fun foo() {}
    fun bar() {}
}
Obj.foo(); // works fine
Obj.bar(); // error
Obj.INSTANCE.bar(); // works, a call through the singleton instance
Obj.INSTANCE.foo(); // works too

一天八升水
4 声望1 粉丝

计算机本科在读