《第一行代码》 第三版 - 第二章(笔记)

探究新语言,快速入门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")
//正是这种方法,所以,主构造函数添加多个参数,不同使用方法,都同样使用主构造函数来创建对象
上一篇:【C++】类与对象 | const


下一篇:局部内部内引用方法中的局部变量探讨