探究新语言,快速入门Kotlin编程
1.Kotlin
1.1Kotlin在Android的地位
在Android诞生以来,一直都是只提供Java这一种语言来开发应用程序的
在2017年的I/O大会上,Google宣布了Kotlin作为Android的一级开发语言,和Java平起平坐
在2019年的I/O大会上,Google宣布,Kotlin成为第一开发语言,当然Java开发依然有用
至今,在国外的安卓市场上,已经有绝大多数的App已经在使用Kotlin开发了。
而Google的一些官方视频和开源项目都是使用Kotlin,Kotlin也愈发的重要。
1.2Kotlin的历史进程
2011年 JetBrains 公布了第一个版本的Kotlin
2012年Kotlin开源
2016年 Kotlin 发布了1.0正式版,同时IDEA也支持Kotlin的使用
2017年 Google宣布Kotlin成为Android的一级语言
2019年 Google宣布Kotlin成为Android的第一语言
1.3编程语言
编程语言 | 作用 | 语言 |
---|---|---|
编程型语言 | 编译器将我们编写的源代码一次性编译成计算机可识别的二进制,直接运行 | C、C++ |
解释型语言 | 解释器会一行行读取源代码,实时将这些源代码解释成二进制数据,再去执行,效率会差点 | Python、JavaScript |
2变量与函数
2.1变量
Kotlin与Java的变量有很大的区别
Java:整型变量(int a)、字符串变量(String b)
Kotlin定义变量只有两种关键字声明方式
val(value简写) 用于声明不可变变量,即初始赋值之后,再也无法修改变量,对应Java的final变量
var(variable简写) 用于声明可变变量,即初始赋值之后,还可以改变变量的内容。对应Java的非final变量
在Kotlin写完一行代码后,是不需要分号 ;的,而Java是需要的,所以Java改Kotlin时,需要注意一下
var a = 10 //自动声明为int类型数据
var b = "你好啊" //自动声明为字符串类型数据
在Kotlin中什么类型的数据都是使用var或val来定义变量的,因为它会自动识别这是什么类型的数据,这是Kotlin的类型推导机制,但是如果我们的变量是延迟赋值的,他就无法自动推导,这时就需要我们显式的声明变量的类型是什么
//当显式声明了变量,Kotlin推导机制就不会推导该变量,当你尝试赋值一个String字符串给a,那么会抛出异常
var a:Int = 10
Java基本数据类型 | Kotlin对象数据类型 | 数据类型 |
---|---|---|
int | Int | 整型 |
long | Long | 长整型 |
short | Short | 短整型 |
float | Float | 单精度浮点型 |
double | Double | 双精度浮点型 |
boolean | Boolean | 布尔型 |
char | Char | 字符型 |
byte | Byte | 字节型 |
在Java中很少人会主动去使用final,这就导致,后期并不知道这个变量在哪里被修改了,所以导致提高排插成本。而在Kotlin中,一开始定义变量的时候就必须定义是var还是val,主动声明,变量是否可以改变。
郭婶的说法就是,优先使用val(不可变变量),在你后面需要改变变量的时候,将val改成var
2.2函数
方法与函数
方法和函数其实没有什么区别,只是叫法不一样,是从英文翻译过来的function、method。在Java中叫的多的是方法、在Kotlin中叫的多的是函数
//使用fun关键字定义函数
//methodName就是定义函数名
//(括号内的就是接受的参数,任意数量(可为空),此例子就是两个Int类型的参数)
//格式:参数名(随便取): 参数类型
//()后的 :Int 就是声明该函数要返回的是Int类型的数据
//{}内是函数体
fun methodName(param1: Int, param2: Int): Int{ //标准函数
return 0
}
语法糖
Kotlin允许我们不必编写函数体,可以直接将唯一一行的代码写在函数定义的尾部,中间使用等号连接
//原函数
fun largerNumber(num1: Int, num2: Int): Int{
return max(num1, num2)
}
//返回的是一个整型数据,而这个返回的整型数据是传入max函数后,max函数返回的一个整型数据,因为只有一行,所以允许不写函数体
fun largerNumber(num1: Int, num2: Int): Int = max(num1, num2)
3.程序的逻辑控制
程序的执行语句分3种:顺序语句、条件语句和循环语句
顺序语句:顾名思义就是,按着顺序一行一行的执行代码
条件语句只有两种,一种是if语句,另一种是when语句
循环语句:顾名思义就是,循环执行某一段代码,有两种循环语句,一个是while循环另一个是for循环
3.1if条件语句
fun largerNumber(num1: Int, num2: Int): Int{
var value = 0;
if(num1 > num2){ //如果num1 大于 num2就获取num1
value = num1
} else { //否则就获取num2
value = num2
}
return value //返回获取到的数,其实就是返回最大的数
}
//简化写法
fun largerNumber(nuum1: Int, num2: Int): Int{
val value = if(num1 > num2){
num1
} else {
num2
}
return value
}
//简简化写法
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
}
//最简洁的写法
fun largerNumber(num1: Int, num2: Int) = if(num1 > num2) num1 else num2
3.2when条件语句
与Java中的switch类似,但他不需要每个case后需要加break,只需写需要的逻辑。
whe语句允许传入任意类型的参数,可以在when的结构体中定义一系列的条件
匹配值 -> { 执行逻辑 }
下面是if语句的写法
fun getScore(name: String) = if(name == "Tom"){
86
} else if(name == "Jim") {
77
} else if(name == "Jack") {
95
} else if(name == "Lily"){
100
} else {
0
}
//简洁的写法,使用when条件语句
fun getScore(name:String) = when (name){
"Tom" -> 86
"Jim" -> 77
"Jack" -> 95
"Lily" -> 100
else -> 0
}
when语句可以进行类型匹配
//Number类型,是Int、Long、Float、Double等与数字相关的类都是它的子类
fun checkNumber(num: Number) {
when (num) {
//is关键字相当于Java的instanceof
is Int -> println("is Int")
is Double -> println("is Double")
else -> println("not support")
}
}
不传参数的使用方法
//有时候可能需要单独将表达式放入判断,适用于单独判断
fun getScore1(name: String) = when {
name.startsWith("Tom") -> 86 //名字以Tom开头的
name == "Jim" -> 77
name == "Jack" -> 95
name == "Lily" -> 100
else -> 0
}
3.3循环语句
while方法和Java一样,略
//当while(布尔表达式)的布尔表达式为false则退出循环
//而while与do... while最大的区别是,while先判断表达式再决定是否循环,而do....while则是先执行一次之后才判断表达式再决定是否循环,所以最少也是要循环一次
fun getWhile() {
var i = 10
while (i > 0) {
println("i is $i")
i -= 1
}
var y = 10
do {
println("y is $y")
y -= 1
} while (y > 0)
}
for循环语句
Kotlin的循环语句就只有 for - in循环
比for - i 简单,但没for-i那样灵活
//区间:并且是包括0和10的,数学表达式 : [0,10]
val range = 0..10
//区间,左闭右开,包含0,不包含10.数学表达式: [0,10)
val range = 0 until 10
fun forIn(){
//输出0 - 10
for (i in 0..10){
println(i)
}
//输出0-9
for (i in 0 until 10){
println(i)
}
//循环0-9的数,但每次增加2
//step关键字,相当于Java中的i = i+2
for (i in 0 until 10 step 2){
println(i)
}
//倒叙,10开始降到1,downTo关键字,可以配合step使用
for (i in 10 downTo 1){
print(i)
}
}
4面向对象编程
4.1面向对象、类与对象
Kotlin和Java一样,也是面向对象语言
面向对象是可以创建类的,类就是对一种事或物的封装,将它们封装成一个类,类名通常都是名词,而类里面也有自己的字段和函数,字段是所拥有的属性,而函数是所拥有的行为。
类与对象
对象其实就是类的一个实例对象,简单来说就是,我定义了一个"小柴"类(这是一种动物)
"小柴"这种动物有年龄 age 和 名字name, 也有行为,他会吃饭eat()函数,还会睡觉sleep()函数
然后我定义一个"小柴"实例对象,也就是我有了一只小柴这种动物的宠物,他的名字叫Memory,他刚出生没多久,只有1岁,他会吃饭eat(),也会睡觉sleep()
4.2 继承与构造函数
在Kotlin中,类是默认不可继承的,若是需要被某个类继承某,这个类必须前面加上open。这样子类才可以使用父类的方法和变量
//被继承的类
open class Person{
......
}
//继承的类
class Student : Person(){
//这里的被继承的类是要加上()的,因为这里涉及了主构造函数,主构造和次构造函数与Java的有点不一样
}
Java中的构造函数
public class Student{
public Student(){
}
public Student(int age, String name){
}
}
而Kotlin的比较特殊,分主构造函数和次构造函数
主构造函数将会是你最常用的构造函数,每个类都会默认一个不带参数的主构造函数,你可以显示的指明他的参数。而主构造函数的特点是没有函数体,直接定义在类名的后面既可
//这里的Person()其实就是在调用Person的构造方法Person()
class Student(val sno: String, val grade: Int) : Person(){
//init构造体,主构造函数的逻辑都可以写在这里面(但这不是最好的办法,一般都不会这样去使用)
init{
println("sno is " + sno)
println("grade is "+ grade)
}
}
次构造函数
你几乎用不到的次构造函数,Kotlin提供了一个函数设定参数默认值的功能,基本上可以替代次构造函数的作用。一个类只能有一个主构造函数和多个次构造函数,次构造函数一样可以实例化一个类,但他是有函数体的。(也就是说,你想少某一个变量,在主构造函数设置默认值既可,设置之后,你使用主构造函数创建的时候,可以不用添加该变量)
class Student(var){
}
当一个类既有主构造函数,又有次构造函数时,所有的次构造函数必须调用主构造函数(包括间接调用)
//这里的age和name,不能设置为val,是因为会和父类的age和name发生冲突,不加他们作用域仅在主构造函数内
class Student(val sno:String, val grade: Int, name: String, age: Int)
:Person(name, age) {
//通过constructor关键字来定义次构造函数
constructor(name: String, age: Int):this("", 0, name, age){
}
constructor() : this("", 0){
}
}
//这是在没有主构造函数情况下的次构造函数的创建,没有主构造函数,所以继承Person时不需要加括号
class Student : Person{
constructor(name:String, age: Int) : super(name, age){
}
}
4.3 接口
接口与Java的几乎差不多
一个类可以实现多个接口
//接口
interface Study{
fun readBooks()
fun doHomework()
}
//类实现接口,使用的是用逗号分开
class Student(name: String, age: Int) : Person(name, age), Study{
//override关键字重写或实现接口函数
override fun readBooks(){
println(name + " is reading.")
}
override fun doHomework(){
println(name + " is doing homework.")
}
}
//接口2
interface Study{
fun readBooks()
fun doHomework() { //这种情况下,实现Study接口时,可以不实现doHomework()方法,不实现,就是该方法有效,重写就以重写的方法为准
println(name + " is doing homework.")
}
}
4.4修饰符
修饰符 | Java | Kotlin |
---|---|---|
public | 所有类可见 | 所有类可见(默认) |
private | 当前类可见 | 当前类可见 |
protected | 当前类、子类、同一包路径下的类可见 | 当前类、子类可见 |
default | 同一包路径下的类可见(默认) | 无 |
protected | 无 | 同一模块中的类可见 |
4.5 数据类和单例类
在Java中,数据类通常需要重写equals()、hashCode()、toString()方法
而Kotlin实现很简单
//使用了data关键字时,就会根据主构造函数的参数,帮你自动生成equals()、hashCode()、toString()方法
data class Person(val country: String, val sex: String)
在Java中创建单例的方法
//单例的创建
public class Singleton{
private static Singleton instance;
private Singleton(){}
public synchronized static Singleton getInstance(){
if(instance == null{
instance = new Singleton();
}
return instance;
}
public void singletonTest(){
System.out.println("singletonTest is called.")
}
}
//单例的使用
Singleton singleton = Singleton.getInstance();
singleton.singletonTest();
在Kotlin中创建单例的方法
//就这么简单,这就是一个单例模式
//不需要提供任何方法去创建,就将class关键字改成object关键字既可,因为Kotlin自动会帮你创建一个Singleton实例,保证全局只有一个实例
object Singleton{
//在单例模式中添加一个函数
fun singletonTest(){
println("singletonTest is called.")
}
}
//单例的使用方法,类似于Java中的静态方法的调用方法
Singleton.singletonTest()
4.6Lambda编程
集合主要分3种: List、Set和Map
List 主要实现类是ArrayList和LinkedList
Set主要实现类是HashSet
Map主要实现类是HashMap
//创建一个ArrayList实例
val list = ArrayList<String>()
list.add("Apple")
list.add("Banana")
list.add("Pear")
//使用listOf()函数来简化初始化写法
//但是listOf创建的集合是不可变的集合,也就是创建之后,就不能再改变,只能读取,不能改变
val list = listOf("Apple", "Banana", "Pear")
//循环
for(fruit in list){
println(fruit)
}
//这是可以修改的list初始化方法
val list = mutableListOf("Apple", "Banana", "Pear")
list.add("Orange")
//Set的用法和List几乎一样,就只是改成setOf()和mutableSetOf()
val set = setOf("Apple", "Banana", "Pear")
val set = mutableSetOf("Apple", "Banana", "Pear")
//Map的用法和List与Set的用法有比较大的区别
//Map的第一种用法
val map = HashMap<String, Int>()
map.put("Apple", 1)
map.put("Banana", 2)
map.put("Pear", 3)
//Map的第二种用法
val map = HashMap<String, Int>()
map["Apple"] = 1
map["Banana"] = 2
map["Pear"] = 3
//Map的最简单用法
val map = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3, "Pear" to 4)
//循环
for((fruit, number) in map){
prinltln("fruit is " + fruit + ", number is " + number)
}
集合的函数式API
val list = listOf("Apple", "Banana", "Pear")
val maxLengthFruit = list.maxBy{it.length} //获取最长的水果
Lambda表达式:
{参数名1,: 参数类型,参数名2: 参数类型 -> 函数体}
Lambda表达式,可以编辑任意行的代码,但是不建议太长,,最后一行会自动作为Lambda表达式的返回值
上边的就是一个代码段,只是因为Kotlin的特性,所以就缩减到了一句话
val list = listOf("Apple", "Banana", "Pear")
val lambda = {fruit: String -> fruit.length}
val maxLengthFruit = list.maxBy{lambda} //获取最长的水果
//简化版
val maxLengthFruit = list.maxBy({fruit: String -> fruit.length})
//Kotlin规定,当Lambda参数是函数的最后一个参数时,可以将Lambda表达式移到括号的外面
val maxLengthFruit = list.maxBy() {fruit: String -> fruit.length}
//Lambda参数是函数的唯一参数,可以省略括号
val maxLengthFruit = list.maxBy{fruit: String -> fruit.length}
//Kotlin推导机制,可以省略String类型声明
val maxLengthFruit = list.maxBy{fruit-> fruit.length}
//当Lambda表达式只有一个参数时,可以用it关键字代替
val maxLengthFruit = list.maxBy{it.length}
map函数,将集合中的元素映射成另外的值,而规则是在Lambda中规定
val newList = list.map{"代码块"}
filter函数,时用于过滤集合中的元素,条件就是Lambda中的代码
//保留5个字符以内的水果
val newList = list.filter{ it.length <= 5}
any函数,只要集合中有一个元素满足,就会返回true,如果都不满足才返回false
all函数,只有集合中的所有元素都满足,才会返回true,否则只要有一个不满足就会返回false
//只要有一个水果的长度小于5就返回true
val newList = list.any{ it.length <= 5}
//只要有一个水果不小于5就返回false
val newList = list.all{ it.length <= 5}
4.7 在Kotlin中使用Java的函数式API
在Kotlin中调用Java方法,并且该方法接受一个Java单抽象方法接口参数,就可以使用函数式API
单抽象方法是指只有该接口中只有一个待实现的方法
例子:Runnable接口
//Runnable接口
public interface Runnable{
void run();
}
//使用Runnable接口的用法
new Thread(new Runnable(){
@Override
public void run(){
System.out.println("Thread is running");
}
}).start();
//Kotlin使用Runnable接口的用法
//使用object关键字创建匿名类实力
Thread(object : Runnable{
override fun run(){
println("Tread is running")
}
}).start()
//函数式API的简写方法
//当只有一个待实现办法,Kotlin能自动明白是里面的Lambda表达式就是run()方法的实现内容
Thread(Runnable{
println("Thread is running")
}).start()
//当Java方法的参数列表不存在一个以上的Java单抽象方法接口参数,我们还可以将接口名进行省略
Thread({
println("Thread is running")
}).start()
//Lambda表达式是方法的最后一个参数时,可以将Lambda表达式移到括号外,同时表达式是唯一一个参数是可以省略括号
Thread{
println("Thread is running")
}.start();
虽然现在我们都是使用Kotlin写代码,但我们需要经常和Android SDK打交道,而它还是使用Java编写的。例如Button的监听事件
button.setOnClickListener{
}
4.8 空指针
相信写Android,或多或少都有解除过空指针异常,在Java中,在出现有可能是空数据的时候,我们都要进行判空。而Kotlin则有一个判空机制,就是利用编译的时候判空检查,这样就几乎阻止空指针异常。
但这样可能会使代码写的比较麻烦,不过Kotlin提供了一套工具,可以让我们轻松判空
///这种情况下,你传一个空,编译的时候就会报错,因为出现了空指针
doStudy(null) //传空
fun doStudy(study:Study){
study.readBooks()
study.doHomework()
}
这样的话,我们只要传空就会报错,但有时候,我们又需要传空,那这是我们就需要问号了,在数据类型后面加上?就代表该数据是可以传空的
Int? 代表可空的整型数据 String? 表示可空的数据,
//这个时候,传空就不会报错
doStudy(null) //传空
fun doStudy(study:Study?){
study.readBooks()
study.doHomework()
}
虽然可以传空了,但里面的两个方法肯定会报空指针异常,因为他们是必须要有数据才可以执行,这时候我们就可以想Java 那样写了
fun doStudy(study:Study){
if(study != null){
study.readBooks()
study.doHomework()
}
}
但是这样写的话,就没什么优势了,而且还很麻烦
这里就可以用到?. 这个符号的意思代表着,当对象不空的时候调用,对象为空的时候,就不执行
相当于Java的这一段代码
if(a != null){
a.doSomething()
}
在Kotlin中就可以这样使用?.符号来简化代码
//a是否有对象? 有,就执行doSomething()方法,没有就跳过不执行
a?.doSomething()
?:操作符,该操作符两边都是表达式,则如果表达式的结果不为空就返回左边的表达式的结果,否则就返回右边表达式的结果。
//如果a不为空,则c = a; 否则 c = b
val c = a ?: b
//原写法
fun getTextLength(text: String?): Int{
if(text != null){
return text.length
}
return 0
}
//使用?. 与?:的写法
//当text为空的时候,不执行length方法,返回一个null,再使用?:判断右边是不是null,是就返回一个0,
fun getTextLength(text: String?) = text?.length ?: 0
虽然有空指针检查机制,但是有时候他也有失败的时候
fun main(){
if(content != null){
printUpperCase()
}
}
//虽然在外面的时候已经判断空了,但在方法里面的时候会重新判断该变量有没有可能是空,因为方法是不知道外边主函数里已经进行了判空,只能自己判断是否为空
fun printUpperCase(){
val upperCase = content.toUpperCase()
}
//可使用强行编译 !!. 符号,但这种写法是有风险的,因为要我们自己保证这里是不是不会空,否则会报错闪退
val upperCase = content!!.toUpperCase()
let函数,使用方法,这里是调用了obj对象的let函数,然后Lambda表达式中的代码会理科执行,并且将obj对象传进表达式中,这里的obj 与obj2其实是同一个对象。该函数可以与判空机制检查配合使用
obj.let{ obj2 ->
//编写具体代码
}
//原 代码
//这其实等同于,两次if(study != null){}来判断,每一个?.都判断一次
fun doStudy(study: Study?){
study?.readBooks()
study?.doHomework()
}
//配合let使用,这代码的意思就是,只要study不为空就执行let函数
//而let函数就将study对象传递进Lambda表达式
fun doStudy(study: Study?){
study?.let{ stu ->
stu.readBooks()
stu.doHomework()
}
}
//简化
fun doStudy(study: Study?){
study?.let{
it.readBooks()
it.doHomework()
}
}
当study对象是全局变量时,if语句还是会报错,比如
var study: Study? = null
fun doStudy(){
if(study != null){
study.readBooks()
study.doHomework()
}
}
但let函数可以处理全局判空的问题
4.9 字符串内嵌表达式
Kotlin字符串内嵌表达式
// ${} 表达式,可以踢到一部分内容
"hello, ${obj.name}. nice to meet you!"
//当只有一个变量的时候,还可以省略{}
"hello, $name . nice to meet you!"
//println("my name is " + name + ", my age is " + age)
//println("my name is $name, my age is $age")
4.10函数的参数默认值
Kotlin提供了函数设定参数默认值的功能
正因为这个功能,所以次构造函数使用的情况很少
fun printParams(num: Int, str: String = "hello"){
println("num is $num, str is $str")
}
//上面的函数第二个参数是默认值,所以我们使用的时候,可以只传一个Int参数
fun main(){
printParams(12345)
}
//当给第一个设定默认值的时候
fun printParams(num: Int = 100, str: String){
println("num is $num, str is $str")
}
//错误的使用,这会报错的
printParams("hello")
//那是不是不能这样用了,不是的,Kotlin机制还有一个通过键值对的方式来传参
printParams(str = "world", num = 123)
printParams(str = "world")
//正是这种方法,所以,主构造函数添加多个参数,不同使用方法,都同样使用主构造函数来创建对象