系列笔记为学习极客时间张涛讲解Kotlin的笔记。
本篇笔记主要学习了从Java过渡到Kotlin的几个注意点
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,抛出相关的异常)
fun关键字指明Kotlin的函数变量,后面跟函数名,参数列表同变量写法一样,先写变量名后写变量类型,返回值最后写。
fun functionName(str:String,num:Int):String{ if(num==0){ println(str) } return str }
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
匿名内部类主要是针对那些不能创建实例的抽象类和接口而来的,在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
//在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修饰。
在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)
如果在Java代码中定义的变量或常量名使用到了Kotlin的关键字,需要借助一对单引号''解决这个冲突,如下:
//关键字冲突 public static String object = "object"; //Kotlin中调用 println(MainTest.`object`);
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。
这一点在笔记开头已经讲过了
//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不会。
网上很多博客说直接用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效果
@JvmField lateinit const
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
object Singleton { lateinit var provider: Provider }
// Java Singleton.provider = new Provider(); // public static non-final field in Singleton class
// 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;
@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