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就是一个占位符,其实
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}美国"
}