创建对象
Java | Kotlin |
---|---|
A a = new A() | var a = A() |
类型声明
Java | Kotlin |
---|---|
String s | var s : String |
final String s | val s : String |
class A extends B | class A : B |
class A implements B | class A : B |
字符串模板
Java | Kotlin |
---|---|
String.format("%s今年%d岁", name, age); | "${name}今年${age}岁" |
方法
Java | Kotlin |
---|---|
String text(int para1,String para2) { ... } | fun test(para1: Int, para2: String): String { ... } |
Kotlin 特性 : 函数参数默认值和可变参数
对Kotlin函数中的某个参数可以用“=”号指定其默认值,调用函数方法时可不不传这个参数,但其他参数需要用“=”号指定。下文例子中没有传递参数para2,其实际值为默认值"para2"
fun test(para1: Int, para2: String = "para2", para3: String): String { ... }
test(22, para3 = "hello")
可变参数值的话,需要用关键字vararg来定义。这里需要注意的是,一个函数仅能有一个可变参数。该可变参数不一定得是最后一个参数,但当这种情况下时,调用该方法,需要给其他未指明具体值的参数传值。
fun test(vararg para1: String, para2: String): String { ... }
test("para1", "para4", "para5", para2 = "hello")
构造函数
Kotlin
类的构造函数分为==primary constructor==和==secondary constructor==,前者只能有一个,而后者可以有多个。如果两者都未指定,则默认为无参数的primary constructor。
primary constructor是属于类头的一部分,用constructor关键字定义,无可见性字段定义时可省略。初始化代码需要单独写在init代码块中,==primary constructor的参数只能在init代码块和变量初始化时使用。==
secondary constructor也是用constructor关键字定义,必须要直接或间接代理primary constructor。
class Student(name: String) { // primary constructor
var mName: String = name
init {
println("Student is called " + name)
}
constructor(age: Int, name: String):this(name) {
println("Student is ${age} years old")
}
}
继承
Java | Kotlin |
---|---|
Java中类默认可被继承,只有被final关键字修饰的类才不能被继承 | 仅有被open修饰的类才可以被继承 |
单例
Java | Kotlin |
---|---|
用代码实现单例 | 用关键词 object 定义单例类 |
object Shop { // 不能有 primary constructor的参数 ,
fun buySomething() {
println("Bought it")
}
}
Shop.buysomething() // 调用
静态标识与调用
Java | Kotlin |
---|---|
static标识一个类里的静态属性或方法 | 用companion修饰单例类object,来实现静态属性或方法功能 |
class Mall(name: String) {
// companion 伴随对象 在一个类中 [唯一]
companion object Shop { // 与 java 不同,Kotlin 所有静态方法和字段都写在内部单例类中
val SHOP_NAME: String = "McDonald" // 等同于Java中写public static String
fun buySomething() { // 等同于Java中写public static void
println("Bought it")
}
}
}
Mall.buySomething() // 可直接调用,
if-else语句
Kotlin
Kotlin中的if-else语句与Java一致,结构上都是if (条件A) { 条件A为真要执行的操作 } else { 条件A为假要执行的操作 }
这里主要要介绍的是Kotlin的一个特点,即if-else语句可以作为一个逻辑表达式使用。不仅如此,逻辑表达式还可以以代码块的形式出现,代码块最后的表达式作为该块的值。
// 逻辑表达式的使用
fun maxNum(x: Int, y: Int): Int {
var max = if (x > y) x else y
return max
}
// 代码块形式的逻辑表达式
fun maxNumPlus(x: Int, y: Int): Int {
var max = if (x > y) {
println("Max number is x")
x
} else {
println("Max number is y")
y
}
return max
}
when语句
Java | Kotlin |
---|---|
switch-case语句 | 有多种形式的条件表达。与if-else一样Kotlin中的when也可以作为逻辑表达式使用 |
// 逻辑表达式的使用
fun judge(obj: Any) { // Any 相当于 java中 Object
when (obj) {
1 -> println("是数字1")
-1, 0 -> println("是数字0或-1")
in 1..10 -> println("是不大于10的正整数")
"abc" -> println("是字符串abc")
is Double -> println("类型是双精度浮点数")
else -> println("其他操作")
}
}
标签
Kotlin中可以对任意表达式进行标签标记,形式为abc@,xyz@。而这些标签,可以搭配return、break、continue等跳转行为来使用。
fun labelTest() {
la@ for (i in 1..10) {
println("outer index " + i)
for (j in 1..10) {
println("inner index " + j )
if ( inner % 2 == 0) {
break@la
}
}
}
}
符号“?”
Kotlin中,当我们定义一个变量时,其默认就是非空类型。如果你直接尝试给他赋值为null,编译器会直接报错。Kotlin中将符号“?”定义为安全调用操作符。变量类型后面跟?号定义,表明这是一个可空类型。同样的,在调用子属性和方法时,也可以用字符?进行安全调用。Kotlin的编译器会在写代码时就检查非空情况,因此下文例子中,当s2有前置条件判断为非空后,即便其本身是可空类型,也可以安全调用子属性或方法。对于ifelse结构的逻辑,Kotlin还提供了“?:”操作符,极大了简化了代码量又不失可读性。Kotlin还提供“!!”双感叹号操作符来强制调用对象的属性和方法,无视其是否非空。这是一个挺危险的操作符,除非有特殊需求,否则为了远离NPE,还是少用为妙。
var s1: String = "abc"
s1 = null // 这里编译器会报错
var s2: String? = "abc"
s2 = null // 编译器不会报错
var l1 = s1.length // 可正常编译
var l2 = s2.length // 没有做非空判断,编译器检查报错
if (s2 != null) s2.length // Java式的判空方案
s2?.length // Kotlin的安全调用操作符?。当s2为null时,s2?.length也为null
if (s2 != null) s2.length else -1 // Java式判空的ifelse逻辑
s2?.length ?: -1 // Kotlin的elvis操作符
s2!!.length // 可能会导致NPE
扩展方法
扩展函数定义形式
在方法体内是可以调用receiverType的对象中原本的方法
fun receiverType.functionName(params){
body // 注: 在方法体中使用 this ,那么 this 代表receiverType的对象(调用者)
}
- receiverType:表示函数的接收者,也就是函数扩展的对象
- functionName:扩展函数的名称
- params:扩展函数的参数,可以为NULL
常见例子,其中 this 代表 context ,
fun Context.showLongToast(msg: String) {
Toast.makeText(this, msg, Toast.LENGTH_LONG).show()
}
扩展方法位置
简单说可以分为两种: class 里面 ,class 外面
- 在一个类里面写扩展方法,这时,在其他类直接调用是不生效的.
class B {
fun Student.doF(){
doFly() // Student 中原本就存在的方法
}
fun dow2(s:Student){
s.doF()
}
}
// 调用
B().dow2(Student())
在其他类调用 student.doF() 是不生效的 ,只有创建B对象调用dow2(s),扩展方法才能被调用
- 单独新建一文件,或者写在类的外面,可全部任意调用,如上面toast的例子,将扩展方法写在单独的文件中,在任意activity或者拥有Context的类中都能调用
/**
* 单独一个类
*/
package ****
import android.content.Context
import android.widget.Toast
import java.util.*
// 扩展方法
fun Context.showLongToast(msg: String) {
Toast.makeText(this, msg, Toast.LENGTH_LONG).show()
}
/**
* 一个类内 ,class 外
*/
fun Context.showLongToast(msg: String) {
Toast.makeText(this, msg, Toast.LENGTH_LONG).show()
}
class Student(name: String) {
...
}
// 调用
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
showLongToast("111")
}
}
伴随对象的扩展
伴随对象通过“类名.”形式调用伴随对象。假如对伴随对象声明了扩展函数该怎么调用呢?其调用方式与伴随对象一样,都是通过用类名限定符来调用。当然,扩展属性也是这样的。(伴随对象companion 的使用,请看上文)
fun Student.Shop.doW(){
println("伴随对象的扩展函数")
}
//调用
Student.doW()
扩展函数名与原类中的函数名相同
相同时,调用时根据扩展函数写的位置不同,会存在两种情况
- 类外.setText()与 TextView 中的方法同名,但是在调用时,会全部提示出来
fun TextView.setText(msg: String) {
this.setText("扩展函数"+msg)
}
但是就傲娇的不按提示调用,生写一个一样的方法时,这种情况下,总是会优先使用成员函数,而不是扩展
open class Person {
fun doFly() {
println("亲生的")
}
}
fun Person.doFly() {
println("后生的")
}
fun main(args: Array<String>) {
val person: Person = Person()
person.doFly()
}
// Log打印
亲生的
- 类内,通过下面的例子可以得出结论,当出现重名 doFly() 时,会默认调用this(也就是 Person)中的方法
open class Person {
fun doFly() {
println("Person do fly")
}
}
class MyInfo {
fun doRun() {
println("MyInfo do run")
}
fun doFly() {
println("MyInfo do fly")
}
fun Person.doSwim() {
doFly() // 默认调用this(也就是 Person)中的方法
doRun()
}
fun doSomething(person: Person) {
person.doSwim()
}
}
// 测试实例
fun main(args: Array<String>) {
val myInfo: MyInfo = MyInfo()
val person: Person = Person()
myInfo.doSomething(person)
}
// Log
Person do fly
MyInfo do run
但是如果也需要调用 MyInfo 中的doFly(),那么在
doFly()前面加上 "this@MyInfo. " 即可.
fun Person.doSwim() {
doFly()
this@MyInfo.doFly()
doRun()
}
// Log
Person do fly
MyInfo do fly
MyInfo do run
接收者可为NULL
调用者对象为空时,也可以调用
fun Person.doFly() {
if (null == this) {
println("null")
}
println("doFly")
}
扩展属性
扩展属性, 即 Extension Property , 即把某些函数添加为数据, 使用”=”, 直接设置或使用。
val List.lastIndex: Int
get() = size - 1
注:
由于扩展属性实际上不会向类添加新的成员, 因此无法让一个扩展属性拥有一个后端域变量. 所以,对于扩展属性不允许存在初始化器. 扩展属性的行为只能通过明确给定的取值方法与设值方法来定义,也就意味着扩展属性只能被声明为val而不能被声明为var.如果强制声明为var,即使进行了初始化,在运行也会报异常错误,提示该属性没有后端域变量。
数据类
Java中数据model,通常都是由多个属性和对应的getter、setter组成。当有大量多属性的数据类时,不仅这些类会因为大量的getter和setter方法而行数爆炸,也使整个工程方法数骤增。Kotlin中也做了这层特性优化,提供了数据类的简单实现。不用再写get和set方法,这些都由编译器背后去做,你得到的是一个清爽干净的数据类。具体使用参考下面的例子。
data class Student (
var name: String,
var age: Int,
var hobby: String?,
var university: String = "NJU"
)
fun printInfo() {
var s: Student = Student("Ricky", 25, "playing Gwent")
println("${s.name} is from ${s.university}, ${s.age} years old, and likes ${s.hobby}")
}
为了保证生成代码的一致性和有意义的方法,数据类对象必须满足一下要求
- 构造函数必须至少有一个参数
- 所有的构造函数参数必须标注val或者var;
- 数据类对象不能是 abstract, open, sealed or inner;
- 数据类对象不能继承其他类,但是可以实现接口
- 在Java虚拟机里,如果生成的类需要有一个无参数的构造函数,所有属性的默认值必须有一个具体的值,
componentN()
类对象里,有几个属性,会有几个这个方法componentN(),比如上面的示例,调用方法 s.name 和 s.component1() 效果是一样的
复制
我们经常有这么一个使用场景:我么需要复制一个类对象,但是改变它的某些属性,保持剩余其他的属性值不变。这就需要用到copy()函数实现。对于上面的User类,可以如下实现复制:
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
// 调用
val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)
网友评论