Kotlin 笔记(一) 基础知识点--java对比

2017-07-06  本文已影响0人  Boyko

创建对象

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的对象(调用者)
}

常见例子,其中 this 代表 context ,

fun Context.showLongToast(msg: String) {
    Toast.makeText(this, msg, Toast.LENGTH_LONG).show()
}

扩展方法位置

简单说可以分为两种: class 里面 ,class 外面

  1. 在一个类里面写扩展方法,这时,在其他类直接调用是不生效的.
class B {

    fun Student.doF(){
        doFly() // Student 中原本就存在的方法
    }

    fun dow2(s:Student){
        s.doF()
    }

}


// 调用
B().dow2(Student())

在其他类调用 student.doF() 是不生效的 ,只有创建B对象调用dow2(s),扩展方法才能被调用

  1. 单独新建一文件,或者写在类的外面,可全部任意调用,如上面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()

扩展函数名与原类中的函数名相同

相同时,调用时根据扩展函数写的位置不同,会存在两种情况

  1. 类外.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打印
亲生的
  1. 类内,通过下面的例子可以得出结论,当出现重名 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}")
}

为了保证生成代码的一致性和有意义的方法,数据类对象必须满足一下要求

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)

相关文档

Kotlin-扩展;
Kotlin 基础学习+快速实践

上一篇下一篇

猜你喜欢

热点阅读