准确来说,Java是解释性语言,Kotlin能被编译为class文件,再在虚拟机中运行
Kotlin几乎杜绝了空指针异常
运行Kotlin代码:IDEA创建Kotlin项目;在线运行kotlin代码;Android Studio创建一个main()函数
fun main() {println("Hello, world!!!")}
2.3.1变量
Kotlin中定义一个变量,只允许val或var进行声明。
val(value的简写)声明不可变的变量,对应Java中的final变量,不可二次赋值(地址)
var(variable的简写)用来声明一个可变的变量,对应Java中的非final变量。
Kotlin拥有出色的类型推导机制
var a=10 //整型var b="hello" //字符串
代码结尾不加分号
val a: Int = 10 //显示声明变量a为Int类
Kotlin中完全摒弃基本数据类型,全部使用对象数据类型
全部使用对象数据类型
全部使用对象数据类型
全部使用对象数据类型
Number是Kotlin内置抽象类,是Int、Long、Float、Double的父类
Java与Kotlin数据类型对照表
永远优先使用val来声明一个变量,而当val没有办法满足你的需求时再使用var。这样设计出来的程序会更加健壮,也更加符合高质量的编码规范。
2.3.2 函数
函数==方法
main()函数是程序的入口函数
//入口函数fun main() {println("Hello, world!!!")}//自定义函数fun methodName(param1: Int,param2: Int):Int{return 0}
fun:定义函数的关键字
参数格式:参数名称:类型 paramName:Int
返回值类型:在参数括号后,不写则默认为void
import kotlin.math.maxfun main() {val a = 30val b = 43val value = largerNumber(a,b)println("larger number is "+value)}fun largerNumber(num1: Int,num2: Int):Int{return max(num1,num2)}
语法糖:若函数中只有一行代码,Kotlin允许直接将该行代码写在函数定义尾部,并用等号连接。
= 表达了 return
//= 表达 returnfun largerNumber(num1:Int,num2:Int):Int = max(num1,num2)
继续简化,让Kotlin自动推导返回类型
fun largerNumber(num1:Int,num2:Int) = max(num1,num2)
2.4 程序逻辑控制
顺序语句
条件语句:if 和 when
循环语句:
2.4.1if条件语句
与Java相同的用法;略
if有返回值:
//每个条件的最后一行代码作为返回值fun largerNumber(num1: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
2.4.2when条件语句
when是为解决switch的痛点,并增加许多强大的新特性
when也有返回值
//精准匹配when(param){匹配值 -> { 执行逻辑 }"Tom" -> 10 //当参数值为Tom时返回10}//类型匹配 is相当于Java中instanceOf关键字when(param){is Int -> {}is Double -> {}else -> {}}//简化 前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" -> 100else -> 0}fun checkNumber(num:Number){//类型匹配when(num){is Int -> println("number is Int")is Double -> println("number is Double")else -> println("number not support")}}//无参数的when语句fun getScore(name:String) = when{//将条件表达式写在匹配值的位置,可对参数做一定的操作name.startsWith("Tom") -> 86name == "Jim" -> 77name == "Jack" -> 95name == "Lily" -> 100else -> 0}
2.4.3循环语句
while循环与Java一样
for循环不同
for-in循环
声明一个闭区间
:
val range = 0..10 //创建[0,10]的区间 闭区间
fun main() {//使用 for-in 循环遍历区间for(i in 0..10){println(i)}}
开区间
:
val range = 0 until 10 //创建[0,10)的区间
step
:设置步长,每次加几
fun main(){for(i in 0 until 10 step 2){println(i)}}
..和until创建升序区间
降序区间:downTo
fun main(){for(i in 10 downTo 1){println(i)}}
2.5.1 类与对象
创建类Person:
class Person{var name = ""var age = ""fun eat(){println(name + " is eating. He is " + age +"years old.")}}
实例化类:与Java的区别,去掉了new
val p = Person() //表示调用了该类的构造函数fun main(){val p = Person()p.name = "Jack" // 为Person的实例化对象赋值p.age = 19p.eat()// 调用方法}
2.5.2 继承与构造函数
Effective Java这本书中明确提到,如果一个类不是专门为继承而设计的,那么就应该主动将它加上final声明,禁止它可以被继承。
继承
新建Student类
class Student{var sno = ""var grade = 0}
要让Student类继承Person类,需要做两件事:
1.使Person类可以被继承
在kotlin中任何一个非抽象类默认都不可被继承,相当于Java中给类加final
open关键字:在class之前加
open class Person{}
2.继承:使用:
class Student : Person() {var sno = ""var grade = 0}
()表示调用了构造函数
Kotlin中构造函数
主构造函数 和 次构造函数
主构造函数
类默认都有一个不带参数的主构造函数,可显示指明参数,它没有函数体,直接定义在类名后
//sno与grade字段在被初始化后不可变,故直接设置为val//主构造函数 将字段都放入其中 class Student(val sno: String, val grade:Int) : Person(){}
在实例化以上类时,必须传入构造函数中所有的参数
val student = Student("wu",59)
构造函数中的参数在创建实例时传入的,不需要重新赋值,因此我们可以将参数全部声明为val。
主构造函数无函数体,若想在主构造函数中编写一些逻辑,可写在init结构体中
class Student(val sno: String, val grade: Int) : Person(){init{println("sno is "+sno)println("grade is "+grade)}}
继承规定:子类 中的构造函数必须调用 父类 中的构造函数
如何体现调用了呢?括号()
改造Person为有参构造函数
open class Person(val name: String, val age: Int){...}
改造Student类
// 将父类的name,age属性作为参数,但不需要指定var或valclass Student(val sno: String, val grade: Int,name: String,age: Int) : Person(name,age){init{println("sno is "+sno)println("grade is "+grade)}}
不能再将父类中的字段声明为val,因为在父类已经指定了。
次构造函数
一个类只能有一个主构造函数,但可以有多个次构造函数
次构造函数与主构造函数的区别:有函数体
Kotlin规定:一个类有主又有次,所有次必须调用主(包括间接调用:调用调用主的次构造函数)
次构造函数通过constructor关键词定义
这里定义两个次构造函数:
第一个接收name和age参数,通过this关键字调用主构造函数,并为主构造函数赋值
第二个不接收任何参数,通过this调用另一个次构造函数
class Student( val sno: String,val grade: Int, name: String,age: Int) :Person(name,age){constructor(name:String, age:Int):this("",0,name,age){}constructor(): this("",0){}}
无主构造函数,仅有次构造函数,委派隐式发生,并执行块
class Person(val pets: MutableList<Pet>=mutableListOf())class Pet{constructor(owner: Person){owner.pets.add(this)}}
若类具有主构造函数,则次构造函数,则次构造函数中必须调用主构造,间接或直接
使用this()来调用主构造
即使类没有主构造函数,委派仍然隐式发生,并且仍然执行初始值设定项块:
class Constructors {init {println("Init block")}constructor(i: Int) {println("Constructor $i")}}
且这个生成的 主构造函数的可见性 是公开的
若不希望它是公共的,就需要声明可见性
class DontCreateMe private constructor(){ }
2.5.3接口
单继承,多实现
继承与实现都使用 : 来实现
接口后无()
实现接口就必须实现接口中所有抽象方法
支持多态
支持多个默认方法
函数的可见性修饰符
Kotlin中有4种,定义在fun前
public
所有类可见,在Kotlin中属于默认项
private
与Java一致,只在本类中可以访问
protected
在Kotlin中表示对当前类和子类可见
internal
只对同一模块内的类可见
2.5.4数据类(实体类POJO)与单例类
数据类
在Java中一个实体类中,需要有构造方法,get/set方法,hashCode(),toString等
在Kotlin中只需一行代码 data
data class Cellphone(val brand: String, val price: Double)
只需在类之前加data,Kotlin会根据主构造函数中参数自动生成equals(),hashCode(),toString()等方法,减少开发工作量
在使用==比较两个对象时,会自动调用equals(),data根据参数来比较,若参数相同则true
单例类
在Kotlin中,只需将class关键字改为object关键字即可
object Singleton{}object Singleton{fun singletonTest(){println("singletonTest is called.")}}
//调用方式Singleton.singletonTest()
2.6 Lambda编程
2.6.1 集合的创建与遍历
List,Set
ArrayList,LinkedList
HashSet
HashMap
val list = ArrayList<String>()list.add("Apple")list.add("Banana")list.add("Orange")list.add("Pear")
//简化写,相当于Java中Arrays.asList();val list = listOf("Apple","Banana","Orange","Pear")
List集合
//遍历集合fun main(){val list = listOf("Apple","Banana","Orange","Pear")for(fruit in list){println(fruit)}}
listOf()与Java中一样,也是不可变的集合,不能添加、修改或删除
可变集合
使用mutableListOf()
fun main(){val list = mutableListOf("Apple","Banana","Orange","Pear")list.add("Watermelon")for(fruit in list){println(fruit)}}
Set集合
自动去重
val set = setOf("Apple","Banana","Orange","Pear")for(fruit in set){println(fruit)}
Map集合
HashMap
//类似Java写法val map = HashMap<String,Int>()map.put("Apple" ,1)map.put("Banana", 2)
Kotlin中不建议使用put()和get()方法对Map进行加和读
推荐使用数组下标的语法结构
map["Apple"] = 1
读取
val number = map["Apple"]
简便写法:mapOf(),mutableMapOf()
val map = mapOf("Apple" to 1,"Banana" to 2,"Orange" to 3,"Pear" to 4)
to不是关键字,而是infix函数
遍历Map集合
fun main(){val map = mapOf("Apple" to 1,"Banana" to 2,"Orange" to 3,"Pear" to 4)for((fruit, number) in map){println("fruit is " + fruit +", number is "+ number)}}
2.6.2 集合的函数式API
maxBy
1.找出水果集合中单词最长的水果
val list = listOf("Apple","Banana","Orange","Pear")val maxLengthFruit = list.maxBy { it.length }println("max length fruit is " + maxLengthFruit)
不建议在Lambda表达式中编写太长的代码
Lambda表达式的语法结构:
{ 参数名1: 参数类型, 参数名2: 参数类型 ->函数体 }
参数列表结尾的->,表示函数体的开始
函数体中可以写任意行代码,最后一行代码会自动作为 返回值
由繁入简
maxBy:普通函数,接收一个Lambda类型的参数,
在遍历时将遍历的值作为参数传递给Lambda表达式。
工作原理:以传入的内容为条件并最大化
的要求来遍历集合,
传入length则条件自然就是单词长度
//原始写法:val list = listOf("Apple","Banana","Orange","Pear")//规则 { 参数名1: 参数类型 ->函数体 }val lambda = { fruit: String -> fruit.length }val maxLengthFruit = list.maxBy(lambda)
不用lambda参数
val maxLengthFruit = list.maxBy({fruit: String -> fruit.length})
Kotlin规定:当Lambda参数是函数最后一个参数时,可将Lambda表达式移到函数括号外
val maxLengthFruit = list.maxBy() { fruit: String -> fruit.length }
根据类型推导,进一步简化
val maxLengthFruit = list.maxBy{ fruit -> fruit.length }
当Lambda表达式的参数列表只有一个参数,不必声明参数名,用it关键字代替
val maxLengthFruit = list.maxBy { it.length }
map
//将所有水果名变大写val newList = list.map{ it.toUpperCase() }
filter
过滤集合中的数据,可结合其他函数一起使用
//先保留5个字母以内的水果,留下的单词 变大写
//先过滤再变大写效率高,反过来就低一点
val newList = list.filter{ it.length <= 5 }
.map{ it.toUpperCase() }
any,all
any:是否存在
all:是否所有都
2.6.3Java函数式API的使用
如果在Kotlin中调用Java方法,该方法接收了一个Java单抽象方法接口参数,就能使用函数式API
单抽象方法接口:Runnable接口
new Thread(new Runnable(){@Overridepublic void run(){System.out.println("Thread is running");}}).start();
翻译为kotlin
//创建匿名类用object关键字Thread(object : Runnable{override fun run(){println("Thread is running")}}).start()
使用Lambda表达式规则化简
Thread(Runnable{println("Thread is running")}).start()
若参数列表仅有一个Java单抽象方法接口参数
Thread({println("Thread is running")}).start()
Lambda表达式是方法最后一个参数,移到方法括号外,
唯一一个参数,省略括号Thread()
Thread{println("Thread is running")}.start()
button.setOnClickListener{}
2.7 空指针检查
Kotlin默认参数和变量都不可以为空,若为空,编译出错
若业务逻辑需要个空,则需要可为空的类型系统
2.7.1 可为空的类型系统
在类名后加上问号
Int?表示可为空整型
String?表示可为空字符串
却依然编译不通过,
还需要处理掉空指针异常
即判空
fun doStudy(study: Study?){//这样就是处理了if(study != null){study.xx()study.xx()}}
2.7.2 判空辅助工具
?.操作符
当对象不为空时正常调用,为空什么都不做
if(a!=null){a.doSomething()}等价于||||a?.doSomething()
fun doStudy(study: Study?){//这样就是处理了study?.xx()study?.xx()}
?:操作符
左右都接收一个表达式,左不为空则返回左边,否则返回右边
val c = a ?: b
val c = if(a != null){a} else {b}
操作符混用
// text可能为空 不为空调用length。否则啥也不做 fun getTextLength(text: String?) = text?.length ?: 0//不为空返回length。否则返回0
若已经进行了非空判断,仍编译失败,如在方法外判断非空,而方法内不知道,编译不通过
非空断言
在对象后面加!!
表示我确信这个对象不会为空,不需要你再来做空指针检查了。
var content: String? = "hello"fun main(){if(content != null){printUpperCase()}}fun printUpperCase(){val upperCase = content!!.uppercase() // !! 尽量少用println(upperCase)}
let函数
函数式API编程接口,会将调用对象作为参数传递到表达式中
obj.let{obj2 ->//编写具体业务逻辑}
obj与obj2实际上是同一个对象
实例:
var study: Study?=nullfun doStudy(study: Study?){//有点啰嗦,两次判断同一个对象是否为空study?.xx()study?.xx()}
fun doStudy(study: Study?){study?.let{stu ->stu.XX()stu.XX()}}
//继续简化fun doStudy(study: Study?){study?.let{it.xx()it.xx()}}
2.8 Kotlin中的小魔术
2.8.1 字符串内嵌表达式
将表达式写在字符串中
${}
仅有一个变量时,大括号省略
val brand = "Samsung"val price = 1299.99println("Cellphone(brand="+brand+", price="+price+")")//化简println("Cellphone(brand=$brand, price=$price)")
2.8.2函数的参数默认值
通过给函数的参数设定默认值,一定程度上替代次构造函数的功能
//设置了默认值,实例化时可传可不传fun printParams(num: Int, str: String = "hello"){println("num is $num, str is $str")}fun main(){printParams(123)}
// 存在顺序问题fun printParams(num: Int = 100, str: String){println("num is $num, str is $str")}//通过键值对传参fun main(){printParams(str="hello")printParams(str="hello",num=123)}
2.9 小结
变量、函数、逻辑控制语句、面向对象编程、Lambda编程、空指针检查机制...