Kotlin语言入门学习

变量

在Kotlin中定义一个变量,只允许变量前声明两种关键字:val和var

  • val(value的简写)用来声明一个不可变的变量,这种变量在初始赋值之后就不能再重新赋值,对应Java中的final变量。
  • var(variable的简写)用来声明一个可变的变量,这种变量在初始赋值之后可以被重新赋值,对应Java中的非final变量。

这是因为Kotlin拥有出色的类型推导机制。但是,如果我们要对一个变量延迟赋值的话,Kotlin就无法自动推导它的类型了。这时候就需要显式地声明变量类型才行,语法如下:
val a: Int = 10
注意,这里的Int首字母是大写的,这表示Kotlin完全抛弃了Java中的基本数据类型,全部使用了对象数据类型。在Kotlin中,Int变成了一个类,它拥有自己的方法和继承结构。下面是Java中每一个基本数据类型在Kotlin中对应的对象数据类型:

Java基本数据类型Kotlin对象数据类型数据类型说明
intInt整型
longLong长整型
shortShort短整型
floatFloat单精度浮点型
doubleDouble双精度浮点型
booleanBoolean布尔型
charChar字符型
byteByte字节型

值得注意的是,Kotlin一旦推导出一个变量的类型,后续是不允许改变的,这一点与Python不同。

函数

Kotlin中定义函数的语法规则如下:

fun methodName(param1: Int, param2: Int): Int{
	return 0
}

参数的声明格式是参数名:参数类型,如果该函数不接收任何参数,那么一对空括号即可。参数括号后面的部分说明了函数的返回类型,若函数不需要返回任何数据,这部分可以不写。
当一个函数只有一行代码时,Kotlin允许我们不必编写函数体,可以直接将唯一的一行代码卸载函数定义的尾部,中间用等号连接即可。例如:

fun largerNumber(num1: Int, num2: Int): Int = max(num1, num2)

由于Kotlin具有出色的类型推导机制,所以此处的函数返回值类型Int实际上可以略去不写,因为max()函数返回的是一个Int值,而largerNumber()的尾部又使用了等号连接了max()函数,因此Kotlin可以推导出largerNumber()的返回值必然是Int类型,这样就不需要再显式地声明返回值类型了,代码可以进一步简化为如下形式:

fun largerNumber(num1: Int, num2: Int) = max(num1, num2)

可见性修饰符

修饰符JavaKotlin
public所有类可见所有类可见 (默认)
private当前类可见当前类可见
protected当前类、子类、同一包路径下的类可见当前类、子类可见
default同一包路径下的类可见 (默认)
internal同一模块中的类可见

条件语句

if条件语句

Kotlin中的if语句和Java中的if语句几乎没有任何区别,下面是一个简单的例子:

fun largerNumber(num1: Int, num2: Int): Int{
	var value = 0
	if (num1 > num2) {
		value = num1
	} else {
		value = num2
	}
	return value
}

Kotlin中的if语句相比于Java有一个额外的功能,它是可以有返回值的,返回值就是if语句每一个条件中最后一行代码的返回值。因此,上述代码可以简化为如下形式:

fun largerNumber(num1: Int, num2: Int): Int{
	val value = if (num1 > num2) {
		num1
	} else {
		num2
	}
	return value
}

在这里,我们可以将value改成由val声明的,因为现在没有重新赋值的需要了。其实,value在这里是一个多于的变量,我们可以直接将if语句返回,如下:

fun largerNumber(num1: Int, num2: Int): Int{
	return if (num1 > num2) {
		num1
	} else {
		num2
	}
}

接着,我们发现,整个函数体中只有“一行代码”,因此可以继续简化:

fun largerNumber(num1: Int, num2: Int) = if (num1 > num2) num1 else num2
	}

when条件语句

Kotlin中的when语句有些类似于Java中的switch语句,但又比switch语句强大得多。

精确匹配

when语句允许传入一个任意类型的参数,然后可以在when的结构体中定义一些列的条件,格式是:匹配值 -> {执行逻辑}
下面是一个查询考试成绩的函数,输入一个学生的姓名,返回该学生的考试成绩:

fun getScore(name: String) = when (name) {
    "Tom" -> 86
    "Jim" -> 77
    "Jack" -> 95
    "Lily" -> 100
    else -> 0
}

类型匹配

除了精确匹配之外,when语句还允许进行类型匹配。举例来说,我们定义一个checkNumber()函数,如下:

fun checkNumber(num: Number) {
    when (num) {
        is Int -> println("number is Int")
        is Double -> println("number is Double")
        else -> println("number not support")
    }
}

其中,is关键字就是类型匹配的核心,它相当于Java中的insanceof关键字。

不带参数的when语句

when还有一种不带参数的用法,能发挥很强的扩展性。以刚才的getScore()为例,还可以这么写:

fun getScore(name: String) = when {
    name == "Tom" -> 86
    name == "Jim" -> 77
    name == "Jack" -> 95
    name == "Lily" -> 100
    else -> 0
}

【注】Kotlin中判断字符串或对象是否相等可以直接使用==关键字,而不需要像Java那样调用equals()方法。
假设所有名字以Tom开头的人,分数都是86分,那么用带参数的when语句就无法实现,而使用不带参数的when语句就可以这样写:

fun getScore(name: String) = when {
    name.startsWith("Tom") -> 86
    name == "Jim" -> 77
    name == "Jack" -> 95
    name == "Lily" -> 100
    else -> 0
}

循环语句

while循环

while循环无论是在语法还是使用技巧上都和Java中的while循环没有任何区别。

for循环

Java中的for-i循环在Kotlin中被舍弃了,而for-each循环则被Kotlin进行了大幅度的加强,变成了for-in循环。例如:

for main() {
	for (i in 0..10) {
		println(i)
	}
}

其中0..10表示一个区间,这是Java中所没有的东西,在Kotlin中,它表示[0,10],是一个闭区间。..是创建两端闭区间的关键字,在..的两边指定区间的左右端点即可创建一个区间。
另外,可以使用until关键字来创建一个左闭右开的区间,例如:val range = 0 until 10,它的数学表达方式是[0,10)。
默认情况下,for-in循环每次执行循环时会在区间范围内递增1,相当于Java for-i循环中的i++,而如果你想跳过其中一些元素,可以使用step关键字:

for main() {
	for (i in 0 until 10 step 2) {
		println(i)
	}
}

如果你想创建一个降序的区间,可以使用downTo关键字,这是一个两端闭区间,也可以与step关键字结合使用。用法如下:

for main() {
	for (i in 10 downTo 1) {
		println(i)
	}
}

面向对象编程

类的定义与实例化

class Person{
	var name = ""
	var age = 0
	fun eat() {
		println(name + " is eating.")
	}
}

Kotlin中实例化一个类不需要new关键字。

val p = Person()

构造函数

Kotlin中将构造函数分成了两种:主构造函数次构造函数

主构造函数

每个类默认都会有一个不带参数的主构造函数,当然你也可以显式地给它指明参数。主构造函数的特点是没有函数体,直接定义在类名的后面即可。比如下面这种写法:

class Person (val name: String, val age: Int){
    fun eat(){
        println(name + " is eating.")
    }
}

这里我们将姓名name和年龄age这两个字段都放到了主构造函数当中,这就表明在对Person类进行实例化的时候,必须传入构造函数中要求的所有参数,比如:

val person = Person("Jack", 10)

由于构造函数中的参数是在创建实例的时候传入的,不像之前的写法那样还需要重新赋值,因此我们可以将参数全部声明为val。

如果要在主构造函数中编写一些逻辑,可以使用Kotlin为我们提供的一个init结构体,所有的主构造函数中的逻辑都可以写在里面:

class Person (val name: String, val age: Int){
    init{
        println("Primary Constructor: Hello, my name is " + name + ", and I'm " + age +" years old.")
    }
    fun eat(){
        println(name + " is eating.")
    }
}
val person = Person("Jack", 10)

上述语句在创建Person实例时,便会输出如下句子:
在这里插入图片描述

次构造函数

一个类只能有一个主构造函数,但是可以有多个次构造函数。次构造函数也可以实例化一个类,但它是有函数体的。 Kotlin规定,当一个类既有主构造函数又有次构造函数时,所有的次构造函数都必须调用主构造函数(包括间接调用)。
次构造函数是通过constructor关键字来定义的。

class Person (val name: String, val age: Int){
    init{
        println("Primary Constructor: Hello, my name is " + name + ", and I'm " + age +" years old.")
    }
    constructor() : this("**", 0){
        println("Secondary Constructor-1: " + "name = " + name + ", age = "+ age)
    }
    constructor(name: String) : this(name, 0){
        println("Secondary Constructor-2: " + "name = " + name + ", age = "+ age)
    }
    fun eat(){
        println(name + " is eating.")
    }
}

这里我们定义了两个次构造函数:第一个次构造函数不接收任何参数,通过this关键字调用了主构造函数,将name和age初始化为 ** 和0。第二个次构造函数接收一个参数name,然后它又通过this关键字调用了主构造函数,为name赋初始值,并将age初始化为0。
至此,我们共有3中方式对Person类进行实例化:

val person1 = Person("Jack", 10) // 调用主构造函数
person1.eat()
val person2 = Person() // 调用第一个次构造函数
person2.eat()
val person3 = Person("Tom") // 调用第二个次构造函数
person3.eat()

输出结果如下:
在这里插入图片描述

继承

Kotlin中任何一个非抽象类默认都是不可以被继承的,相当于Java中给类声明了final关键字。

之所以这样设计,其实和val关键字的原因是差不多的,因为类和变量一样,最好都是不可变的。在Java中,一个好的编程习惯是,除非一个变量明确允许被修改,否则都应该给它加上final关键字;如果一个类不是专门为继承而设计的,那么就应该主动为它加上final声明,禁止它被继承。但不是每个人都能养成这种良好的习惯,所以Kotlin在设计时就采用了和Java完全不同的方式,提供了val和var这两个关键字,必须由开发者主动声明该变量是可变的还是不可变的。总之,请永远优先使用val来声明一个变量,而当val没有办法满足你的需求是再使用var。

因此,
第一件事,给一个类加上open关键字,表示这个类是专门为继承而设计的。

open class Person (val name: String, val age: Int){
    init{
        println("Person--Primary Constructor")
    }
    constructor() : this("**", 0){
        println("Person--Secondary Constructor-1")
    }
    constructor(name: String) : this(name, 0){
        println("Person--Secondary Constructor-2")
    }
}

第二件事,让子类继承它,Java中继承的关键字是extends,而Kotlin中变成了一个冒号

class Student : Person() {
	var sno = ""
	var grade = 0
}

除此之外,我们注意到Person类的后面加上了一对括号,这是Java中继承时所不需要的。这涉及到Java继承中的一个规定,子类中的构造函数必须调用父类的构造函数,这个规定在Kotlin中也要遵守。
子类的主构造函数调用父类中的哪个构造函数,在继承的时候通过括号来指定。 所以,Person类后面的一对空括号表示Student类的主构造函数在初始化的时候回调用Person类的无参构造函数,即使在无参数的情况下,这对括号也不能省略。

val student = Student()

我们创建一个Student对象试试,运行程序,输出结果如下:
在这里插入图片描述
可以看到,在实例化的过程中,调用了父类Person的第一个无参的次构造函数,而这个次构造函数又先调用了自己的主构造函数。

让我们再看一个例子:

class Student(val sno: String, val grade: Int, name: String, age: Int) : Person() {
    init{
        println("Student--Primary Constructor")
    }
    constructor(name: String, age: Int) : this("", 0, name, age) {
        println("Student--Secondary Constructor-1")
    }
    constructor() : this("", 0){
        println("Student--Secondary Constructor-2")
    }
}

注意,我们在Student类的主构造函数中,增加name和age这两个字段时,不能再将它们声明成val,因为在主构造函数中声明成val或var的参数将自动称为该类的字段,这就会导致和父类中同名的name和age字段造成冲突。因此,这里的name和age参数前面不用再加任何关键字,让它的作用域仅限定在主构造函数当中即可。

这里我们定义了两个次构造函数:第一个次构造函数接收name和age参数,然后它又通过this关键字调用了主构造函数,并将sno和grade这两个参数赋值成初始值;第二个次构造函数不接收任何参数,它通过this关键字调用了我们刚才定义的第一个次构造函数,并将name和age参数也赋值成初始值,由于第二个次构造函数间接调用了主构造函数,因此这仍然是合法的。
此时,我们共有3种方式来对Student类进行实例化:

val student1 = Student() //第二个无参的次构造函数
val student2 = Student("Jack", 19) // 第一个带两个参数的次构造函数
val student3 = Student("a123", 5, "Jack", 19) // 主构造函数

输出结果如下:
在这里插入图片描述
student1的实例化过程:先调用自己的无参构造函数Sencondary Constructor-2,由2调用1,由1调用主构造函数,再由主构造函数调用父类Person的无参构造函数Sencondary Constructor-1,再由1调用主构造函数。

有一种很特殊的情况:类中只有次构造函数,没有主构造函数。这种情况十分少见,但在Kotlin中是允许的。当一个类没有显式地定义主构造函数且定义了次构造函数时,它就是没有主构造函数的。例如:

class Student : Person {
	constructor(name: String, age: Int) : super(name, age){
	}
}

注意这里的代码变化,首先Student类的后面没有显式地定义主构造函数,同时又因为定义了次构造函数,所以现在Student类是没有主构造函数的。那么既然没有主构造函数,继承Person类的时候也就不需要再加上括号了(前面说过,子类的主构造函数调用父类中的哪个构造函数,在继承的时候通过括号来指定)。
另外,由于没有主构造函数,次构造函数只能直接调用父类的构造函数,上述代码也是将this关键字换成了super关键字,这部分和Java比较像。

接口

接口是用于实现多态编程的重要组成部分。我们都知道,Java是单继承结构的语言,任何一个类最多只能继承一个父类,但是却可以实现任意多个接口,Kotlin也是如此。
我们可以在接口中定义一系列的抽象行为,然后由具体的类去实现。下面还是通过具体的代码来学习一下,首先创建一个Study接口,并在其中定义几个学习行为。右击com.example.helloworld包→New→Kotlin File/Class,在弹出的对话框中输入“Study”,创建类型选择“Interface”。

interface Study {
    fun readBooks()
    fun doHomework()
}

接下来让Student类去实现Study接口:

class Student(name: String, age: Int) : Person(name, age), Study {
    override fun readBooks() {
        println(name + " is reading.")
    }
    override fun doHomework() {
        println(name + " is doing homework.")
    }
}

Java中继承使用的关键字是extends,实现接口使用的关键字是implements,而Kotlin中统一使用冒号:,中间用逗号进行分隔。上述代码就表示Student类继承了Person类,同时还实现了Study接口。另外接口的后面不用加上括号,因为它没有构造函数可以去调用。
Study接口中定义了readBooks()和doHomework()这两个待实现函数,因此Student类必须实现这两个函数,如果Student类中没有对着两个函数进行实现,那编译器会报错。Kotlin中使用override关键字来重写父类或者实现接口中的函数。

fun main() {
    val student = Student("Jack", 19)
    doStudy(student)
}
fun doStudy(mystudy: Study){
    mystudy.readBooks()
    mystudy.doHomework()
}

首先在main()里创建了一个Student类的实例,这里是可以直接调用该实例的readBooks()和doHomework()函数的。但我们没有这么做,而是将它传入到了doStudy()函数中。doStudy()函数接收一个Study类型的参数,由于Student类实现了Study接口,因此Student类的实例是可以传递给doStudy()函数的,接下来我们调用了Study接口的readBooks()和doHomework()函数,这种就叫作面向接口编程,也可以称为多态
了让接口的功能更加灵活,Kotlin还增加了一个额外的功能:**允许对接口中定义的函数进行默认实现。**其实Java在JDK 1.8之后也开始支持这个功能了,因此总体来说,Kotlin和Java在接口方面的功能仍然是一模一样的。

interface Study {
    fun readBooks()
    fun doHomework() {
        println("do homework default implementation.")
    }
}

上述代码给Study接口中的函数doHomework()加上了函数体。**如果接口中的一个函数拥有了函数体,这个函数体中的内容就是它的默认实现。**现在当一个类去实现Study接口时,只会强制要求实现readBooks()函数,而doHomework()函数则可以自由选择实现或者不实现,不实现时就会自动使用默认的实现逻辑。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值