Kotlin基础梳理

1.Kotlin基础

1.1基本语法

kotlin中的;可以省略,但没有python严格的缩进规则

定义变量:
变量前用var或者val修饰,var代表可重新赋值变量,val相当于final修饰的变量,只能在初始化的时候赋值一次。
不需要把变量类型写在前面,而是放在:后

Int n = 30;//Java代码
var n: Int = 30 //Kotlin代码
val j = 3
j = 4//这样写会报错,因为val修饰的变量是final的

定义函数:
同样需要定义函数头,参数,返回值

/**
*Java代码
*/
int add(int m ,int n){
	return m+n;
}
void process(int m){
	System.out.println(m*m);
}
fun add(m:Int,n:Int):Int{
	return m+n
}
fun process(m:Int):Unit{//Unit可省略
	println(m*m)
}

定义Kotlin函数时函数头除了包含函数名之外还必须包含fun关键字,参数的定义和变量的定义方式相同。如果函数有返回值,返回值在函数定义部分末尾指定,与函数定义部分用冒号:分隔,无返回值可以返回Unit或者省略。

1.2基础数据类型

Kotlin提供内置数据类型

数据类型 占用字节数
Double 8
Float 4
Long 8
Int 4
Short 2
Byte 1

Kotlin的数值和Java数值一样,有默认类型。例如,整数的默认类型Int,浮点数的默认类型Double。不过在Java中,如果将占用字节数少的变量赋予给占用字节数多的会进行自动转换。Kotlin中不会自动转换,如果如果把Short值赋值给Int会报错。 如果需要用到类型转换Kotlin提供了一系列方法进行类型转换。
toByte():转换到Byte类型
toShort():转换到Short类型
toInt():Int:转换到Int类型
toLong():Long:转换到Long类型
toFloat():Float:转换到Float类型
toDouble():Double:转换到Double类型
toChar():Char转换到Char类型

var m = 20
val n : Byte = 10
m = n.toInt()	//通过toInt方法将Byte转化为Int
val x:Long = 20
val value:Short = 20
m = value.toInt() //通过toInt方法将SHort转化为Int

Kotlin与Java一样,也提供了一些特殊表示手法,用于表示Long和Float类型的值,以及十六进制和二进制。
表示Long类型的值,在数值后面加L或l,如123L
表示Float类型的值,在数值后面加F或f,如123.1F
表示十六进制,在数值前面加0x,如0x1F
表示二进制,在数值前面加0b,如0b100101

字符类型:

kotlin中字符型用char表示,不过和java不同,字符不能直接看做数字
比如下面这段经典的判断ASCII的java代码,放在kotlin中会报错

void check(char c){
	if(c == 97){
		//可以编译通过
	}
}

虽然字符不能直接作为数值使用,但可以通过toInt方法将字符转换为相应的ASCII,也可以自定义一个函数,讲数值字符串转换为对应的数值。

fun decimalDigitValue(c:Char):Int{
	//字符必须在'0'和'9'之间
	if(c !in '0'..'9')
	throw IllegalArgumentException("Out of range")
	//将当前指定的字符转换为对应的ASCII,然后再与'0'的ASCII相减,就可以将字符转换为对应的数值
	return c.toInt()-'0'.toInt()
}

布尔类型

kotlin中的布尔类型用Boolean表示,有true和false两个值,有三种操作逻辑或(||)逻辑与(&&)和逻辑非(!)

数组

在Kotlin中数组用Array表示,在该类中包含了get和set方法,size属性以及其他很多成员方法。定义数组有很多种方式,是用arrayOf函数定义可以存储任意值的数组,使用arrayOfNulls函数定义指定长度的空数组,使用Array类的构造器指定数组长度和初始化数据的方式定义数组,使用intArrayOf,shortArrayOf等函数定义指定类型的数组,并初始化数组。

val arr1 = arrayIf(1,2,3,'a')
println(arr1[3])
arr1[2] = 'b'
println(arr1[2])

var arr2 = arrayOfNulls<Int>(10)
println("arr2的长度"+arr2.size)

val arr3 = Array(10,{i->(i*i).toString()})
println(arr3[3])

var arr4:IntArray = intArrayOf(20,30,40,50,60)
println("arr4[2] = "+arr4[2]

字符串

Kotlin中使用String表示字符串类型,有如下两种字符串
1.普通字符串:这种字符串类似Java中的String,可以在字符串中加上转义字符,需要放在双引号中
2.保留原始格式的字符串(raw String):这种字符串不能使用转义字符,如果字符串中带有格式,如换行,直接卸载字符串中即可。这种字符串需要3个引号包裹。

val s1 = "hello \n world //kotlin可以自动推导类型
val s2 = """ hello
world """

字符串模板

Kotlin字符串还有一个重要的功能,字符串模板。在字符串中添加若干个占位符,内容在后期指定。
模板使用美元符号 i = 指定,如i= i中 i i就是一个占位符,其实 后面i是变量。

//比如println的内容如果需要变量的值,java中需要这么写
int i  = 10;
System.out.println("i = "+i);
//Kotlin中直接用$就可以了,十分便捷
println("i=$i")

1.3包

Java中Package和目录统一在了一起,也就是说Package就是目录。在Kotlin中也存在包的概念,包在表达方式上和Java完全一样,不过kotlin中的包和目录没有什么关系,仅仅是为了引用文件中的资源而设计的。例如,在下面的代码中顶一个了一个函数和一个类,实际上,完整的函数名和类名分别是foo.bar.process和foo.bar.MyClass

package foo.bar
fun process(){}
class MyClass(){}

1.4控制流

Kotlin的控制流和Java中的控制流基本相同,只是使用when代替了switch,当然,在kotlin中,if和when不仅仅可以作为语句使用,还可以作为表达式使用。

1.4.1条件语句

在Koltin中,if语句本身就是表达式,有返回值,因此,不需要提供Java中的三元操作符。

//传统if语句用法
var a =20
var b =30
var max:Int
if(a<b) max = b
var min:Int
if(a>b){
	min =a
}else{
	min = b
}

//将if语句作为表达式使用
var a = 20
var b = 30
val max = if(a>b)a else b
println(max)
//if else 后面是一个代码块,最后一个表达式将作为返回值
val min = if (a>b){
	print("Choose a")
	a //返回值
}else{
	print("Choose b")
	b //返回值
}

when语句:
Kotlin中,when替换了C语言风格的switch语句。

//when作为语句使用
var x = 1
when(x){
	1 -> {
		println("x==1")
		println("hello world")
	}
	2-> print("x==2")
	else->{
		print("x is neither 1 nor 2")
	}

}

//when作为表达式使用
var x= 1
var m = when(x){
	1->{println("x == 1")
		20
	}
	2->{
		print("x == 2")
		60
	}
	else -> {
		print("x is neither 1 nor 2")
		40
	}
	print(m)//m的值是20
}

//多个分支执行相同的代码
var x = 1
when(x){
	1,2 ->.{println("已经符合条件")}
	3 -> {println("不符合条件")}
	else->{println("条件未知")}
}
//如果要执行的相同代码比较多,或者无法枚举,可以使用in关键字确定一个范围
var n = 25
when(n){
	in 1..10 -> println("满足条件")
	in 11..20 -> {println("不符合条件")}
	!in 30..60 -> println("666")
	else->{println("条件未知")}
}
//when中的分支条件不仅可以是常量,还可以是任意表达式。
fun getValue(x:Int):Int{return x*x}
fun main (args:Array<String>){
	var n = 4;
	when(n){
		getValue(2)->println("满足条件")
		getValue(3)->println("不满足条件")
		else->println("条件未知")
	}
}

for循环
在kotlin中,for循环可以直接枚举集合中的元素,也可以按照集合中的索引来枚举元素。

	//for-iterator
	for(item in collection)print(item)
	//枚举数组中所有元素的值
	var arr = intArrayOf(2,4,6,8,10)
	for(item:Int in arr){
		println(item)
	}
	//使用索引枚举数组中的元素值
	var arr = intArrayOf(2,4,6,8,10)
	for(i in arr.indices){
		println("arr[$i] = $arr[i]")
	}

	//循环时同时对索引和元素值进行循环
	var arr = intArrayOf(2,4,6,8,10)
	//index是索引,value是当前数组元素值
	for((index,value)in arr.withIndex()){
		println("arr[$index] = $value ")
	}

while循环:

kotlin中的while循环和java中的一样,分为do…while和while

var i = 0
while(i++<10){println(i)}
do{if (i == 6)continue
	println(i)
	if(i==5) break
}while(--i>0)

2.类和接口

2.1类的声明

与Java一样,在Koltin中,类的声明也是用class关键字,如果只是声明一个空类,kotlin和java没有任何区别。不过如果有其他成员,差别就比较大了。

   class MyClass{}

2.2构造器

构造器(构造方法)是创建类实例必须的语法元素。在任何面向对象语言的类中,都必须只包含一个构造器,如果未显示定义构造器,编译器就会自动生成一个没有参数的构造器。不过kotlin中的构造器有些不一样。

2.2.1主构造器

在kotlin中,类允许定义一个主构造器和若干个第二构造器。主构造器是类头的一部分,紧跟在类名的后面,构造器参数是可选的,例如,下面的代码定义了一个Person类,并指定了一个主构造器。

class Person constructor(firstName:String){}

如果主构造器没有任何注解或修饰器,constructor关键字可以省略

class Person(firstName:String){}

定义主构造器之后的实现在init块中

class Person(firstName:String){
	init{
		println(firstName)
	}
}

主构造器的参数不仅可以用在init块中,还可以用于对类属性的初始化。

class Person(firstName:String){
	var name = firstName
	init{
		println(firstName)
	}
}

var和val关键字也可以用于主构造器的参数,使用var,参数对于构造器来说是变量,如果使用val变量修饰,参数对构造器来说是常量。

2.2.2第二构造器

Kotlin类除了可以声明一个主构造器外,还可以声明若干个第二构造器。第二构造器需要在类中声明,前面必须加上constructor关键字。

class Person{
	constructor(parent:Person){
		println(parent)
	}
}

如果类中声明了主构造器,那么所有的第二构造器都需要在声明后调用主构造器,或通过另外一个第二构造器间接的调用主构造器。

class QACommunity(var url:String){
	//主构造器实现部分
	init{
		println(url)
	}
	//第二构造器
	constructor(value:Int):this("community"){
		println(value)
	}
	constructor(description:String,url:String):this("["+url+"]"){
		println(description+":"+url)
	}
	constructor():this(20){
		printlin("666")
	}
}

注意:在主构造器中可以使用var和val,但是在第二构造器参数中不能使用var和val,第二构造器的参数都是只读的,不能在构造器中改变参数的值。

2.2.3 kotlin中的单例模式

class Singleton private constructor(){
	public var value:Singleton?=nullx
	private object mHolder{val INSTANCE = Singleton()}
	companion object Factory{
		fun getInstance():Singleton{
			return mHodler.INSTANCE
		}
	}
}

2.3类成员

2.3.1属性的基本用法

Kotlin中属性的语法

var/val <propertyName>[:<propertyType>][=<property_initializer>]
[<getter>]
[<setter>]
class Customer{
	var name:String = "Bill"
	val value:Int = 20
	var glag:Boolean = true
	//输出所有属性值
	fun description(){
		println("name:${name} value:${value} flag:{$flag}")
	}
}

kotlin从语法上支持属性,因此不需要为每一个属性单独定义get和set,不过仍然需要在属性中使用get和set的形式。如果属性是只读的,需要将属性声明为val,并只提供一个get形式。

class Customer{
	//只读属性
	val name:String
		get() = "Bill"
	val v:Int = 20
	//读写属性
	var value:Int
		get() = v
		set(value){
			println("value属性被设置")
			v = value
		}
}

在属性的get和set中可以使用field当做成员变量使用,也就是通过filed值读写属性值

class Customer{
	//只读属性
	val name:String
		get() = "Bill"
	//读写属性
	var value:Int
		get() = field
		set(value){
			println("value属性被设置")
			field = value
		}
	fun main(args:Array<String>){
		var c = Customer()
		c.value = 30
		println(c.value) //输出30
	}
}

2.3.2 函数

kotlin中的函数既可以在类外部定义,也可以在类内部定义。如果是前者,是全局函数,如果是后者,是类成员函数。无论函数在哪里定义,语法规则是一样的。函数也支持默认参数值,但是默认参数值必须是后面几个参数。

class QACommunity{
	//schema参数默认值
	fun printQACommunity(url:String,schema:String = "https"){
		println("${schema}://${url}")
	}
}

如果要传入函数的参数个数不固定,就要使用可变参数。在Kotlin中,可变参数用vararg关键字声明

class Person(name:String){
	private var mName:String = name
	fun getName():String{
		return name
	}
}
class Persons{
	fun addPersons(vararg persons:Person):List<Person>{
		val result = ArrayList<Person>()
		//可变参数在函数内部和处理集合一样
		for(person in persons)
			result.add(person)
		return result
		
	}
}

2.4修饰符

Kotlin中的修饰符有4个:private,protected,internal和public

private:仅仅在类的内部可以访问
protected:类似private,但在子类中也可以访问
internal:任何在模块内部类都可以访问
public:任何类都可以访问

2.5类的继承

与Java不同,Kotlin类的继承需要使用冒号(:),而Java使用的是extents。注意,冒号后需要调用父类的构造器。Kotlin和Java一样都是单继承的,也就是说,一个Kotlin只能有一个父类。Koltin默认类是final的,也就是说默认class不允许继承,需要使用open关键字允许继承class

open class Parent{
	protected var mName:String = "Bill"
	fun getName():String{
		return mName
	}
}

class Child:Parent(){
	fun printName(){
		println(mName)
	}
}

重写方法:在kotlin中,不仅类默认无法继承,方法也是默认不能重写的,需要在子类中重写方法,就要在父类方法中加上open关键字,而且要在子类方法前加上override关键字。
重写属性:和方法一样,需要在父类加上open,子类中加上override。需要注意val属性可以被重写为var属性,反过来不行。

2.6接口

接口是另一个重要的面向对象元素,用于制定规范。Kotlin中的接口和Java中的接口类似,使用interface关键字声明。一个类可以实现多个接口,实现的方法和继承相同,在:后加上接口名称,多个接口用,分隔。接口中的属性和方法都是open的。

2.7抽象类

抽象类和接口类似,Kotlin接口支持默认函数体,因此就更像了。抽象类不能被实体化,需要用abstract关键字声明。抽象类实现接口后,接口中没有函数体的函数可以不重写,接口中的这些方法自动被集成到实现接口的抽象类中,称为抽象方法。

open class Base{
	open fun f()
}
abstract class Derived: Base(){
	override abstract fun f()
}

抽象方法不需要使用open声明,因为抽象类本身就是可继承的。

3.枚举类和扩展

3.1 枚举类

Kotlin中枚举类型时以类的形式存在的,因此称为枚举类。

enum class Direction{
	NORTH,SOUTH,WEST,EAST
}
var direction1:Direction;
var direction2:Direction = Direction.NORTH

//为枚举值指定对应的数值
enum class Direction private constructor (val d:Int){
	NORTH(1),SOUTH(2),WEST(3),EAST(4)
	override fun toString():String{
		return d.toString() //返回当前枚举值对应的数字
	}
}
fun main(args:Array<String>){
	var direction1:Direction = Direction.NORTH
	var direction2 = Direction.WEST
	println(direction1)
	println(direction2)
}

3.2 扩展

扩展是Kotlin中非常重要的功能,通过扩展,可以在没有源代码的情况下向类中添加成员,也可以在团队开发的情况下,通过扩展,将功能模块分散给多个人开发。
Kotlin扩展可以对JDK和Android原生API进行扩展。下面的代码通过对Kotlin原生集合类MutableList的扩展,让该类有交换任意两个集合元素位置的能力。

//为MutableList类添加一个swap方法,用于交互任意两个集合元素的位置
fun MutableList<Int>.swap(index1:Int,index2:Int){
	val tmp = this[index1]
	this[index1] = this[index2]
	this[index2] = tmp
}

这段代码放到哪个Kotlin文件中都可以,一般会放在Kotlin文件顶层。
调用的代码:

val mutableList = mutableListOf(1,2,3)
mutableList.swap(0,2)
println(mutableList) //输出[3,2,1]

4.数据类和封闭类

4.1数据类

数据类是Koltin的一个语法糖。Kotlin编译器会自动为数据类生成一些成员函数,以提高开发效率。
下面的代码创建一个User类,并通过构造器传入两个参数值name和age

class User(name:String,age:Int){
var name:String = name
var age:Int= age
}

对象复制:
Java中对象的复制需要实现cloneable接口,kotlin的数据类提供了一个copy函数用于复制数据类实例。

val john = User(name = "john",age = 30)
val orderJohn = john.copy(age = 60)

4.2封闭类

封闭类也是Kotlin的一个语法糖。可以把它理解为枚举的扩展。一个封闭类,前面用sealed关键字标识。可以有任意多个子类或者对象。封闭类的值只能是这些子类或对象。使用封闭类的好处是when表达式,不需要再使用else形式了。

sealed class Expr
data class Const(val number:Double):Expr()
data class Sum(val e1:Expr,val e2:Expr):Expr()
object NotANumber:Expr()
//在这段代码中,Expr是一个封闭类,该类有两个数据类(Const和Sum)和一个对象(NotANumber)。下面的函数判断Expr类型参数的种类。
fun eval (expr:Expr):Double = when(expr){
	is Const -> exor.number
	is Sum -> eval(expr.e1) + eval(expr.e2)
	NotANumber -> Double.NaM
	//在这里不要求使用else子句匹配其他所有的情况
}

5.泛型

5.1基础

在JDK中,有一类对象,这些对象对应的类都实现了List接口。List中可以保存任何对象,下面创建List对象,并向其中添加数据。

List list = new ArrayList();
list.add("abc")
list.add(30)

List对象保存了String和Int类型。尽管这样做可以保存任意的对象,但每个列表元素就失去了原对象的特性。Java中任何类都是Object类的子类,所以只要将变量类型声明为Object就可以保存任意对象,这样做的弊端是原对象中的成员变量和方法都没有了。如果在定义List的时候就指定数据类型,那么这个List就不通用了。只能存储一种类型的数据。JDK1.5引入了泛型来解决这个问题。下面写一个泛型类

class Box<T>{
	public T value;
	public Box(T t){
		value = t;
	}
}

public class Test{
	public static void main(String[] args){
		Box<Integer> box1 = new Box<>(20);
		Box<String> box2 = new Box<>("Bill")
	}
}

kotlin代码

class Box<T>(t:T){
	var value = t
}
fun main(args:Array<String>){
	var box1 = Box(20)
	var box2 = Box("Bill")
}

5.2 类型变异

Object是所有类的父类,根据里氏替换原则,Object对象当然可以被String类型替代。但是容器类中对象继承关系对于容器类本身是没有影响的。

编译器认为List<Object>类和List<String>类没有任何关系

那么为了解决这个问题,java引入了通配符的概念,用问号(?)表示类型

boolean addAll(Collection<? extends E>c);

关键字extends和super分别代表了上界和下界。

extends关键字表示通配符可以表示E和E的所有子类
super关键字表示通配符可以表示E和E的所有基类

Kotlin没有提供通配符取而代之的是out和in关键字。用out声明的泛型占位符只能在获取泛型类型值的地方,如函数的返回值。用in声明的泛型占位符只能在设置泛型类类型值的地方,如函数的参数。将读取的对象称为生产者,将只能设置的对象称为消费者。如果只用一个生产者对象,如List<?extents Foo>,将无法对这个对象调用add(),set()方法。

abstract class Source<out T>{
	abstract fun nextT():T
}
fun demo(strs:Source<String>){
	val objects:Source<Any> = strs//编译通过,因为T是一个out类型参数
}
abstract class Comparable<in T>{
	abstract fun compareTo(other:T):Int //这里不能调用T,因为T被声明为in
}
fun demo(x:Comparable<Number>){
	x.compareTo(1.0)//1.0是double类型,double是Number的子类型
	val y:Comparable<Double> = x //OK
}

//未完待续

5.3类型投射

5.4星号投射

5.5泛型函数

5.6泛型约束

6.对象和委托

6.1对象

6.2委托

6.3标准委托

7.高阶函数和Lambda表达式

在调用高阶函数时使用Lambda表达式,可以使调用语法更加简洁,不过想要使用这种调用方式,就需要深入理解Lambda表达式的用法。

7.1高阶函数

高阶函数是一种特殊的函数,它接受函数作为参数,或者返回一个函数。在下面的例子中,processProduct是一个高阶函数,该函数的第一个参数是一个对象,第二个参数是一个函数类型。这个函数类型需要传递一个name参数,并返回一个String类型。processProduct函数会通过第2个参数area为产品添加产地。

interface Product{
	car area:String
	fun sell(name:String)
}
class MobilePhone:Product{
	override var area:String = ""
	override fun sell(name:String){
		println("销售${name}")
	}
	override fun toString():String{
		return area
	}
	fun mobilePhoneArea(name:String):String{
		return "${name}美国"
	}
}
fun processProduct(product:Product,area:(name:String)->String:Product{
	product.area = area("IPhone")
	return product
}
fun main(args:Array<String>){
	var product = MobielePhone()
	//将函数作为参数值传入高阶函数,需要在函数名前面加上::作为标记
	processProduct(product,::mobilePhoneArea)
	println(product)
}

现在执行上述代码,会输出以下内容
iPhone美国

Kotlin为我们提供了更好的传递函数参数值的方法,那就是Lambda表达式。如果使用Lambda表达式,可以按照如下形式传值

processProduct(product,{name->"$name{name} 美国"})

Lambda表达式还提供了另外一个表达方式,如果Lambda表达式是函数的最后一个参数,可以将大括号写在外边,如下面代码所示。这种形式就好像是定义一个函数。

processProduct(product){
	name->"${name}美国"
}

7.2 Lambda表达式与匿名函数

8.函数

8.1函数基本用法

8.2使用中缀标记法调用函数

8.3单表达式函数

8.4函数参数和返回值

8.5函数的范围

8.6泛型函数

8.7内联函数

9.其他

9.1数据解构

9.2集合

9.3值范围

9.4类型检查工具与类型转换

9.5this表达式

9.6相等判断

9.7操作符重载

9.8 null值安全性

9.9异常类

9.10注解

9.11反射

10.Activity

11.UI组件和布局

12.数据持久化

12.1SharePreferences存储

12.2文件流操作

12.3APP权限授予与拒绝

12.4读写Json格式数据

12.5SQLite数据库

13.网络

13.1WebView组件

13.2使用HTTP与服务端交互

发布了17 篇原创文章 · 获赞 9 · 访问量 1627

猜你喜欢

转载自blog.csdn.net/weixin_44666188/article/details/103506249