2019独角兽企业重金招聘Python工程师标准>>>
Kotlin分享(一)
Kotlin分享(二)
Kotlin分享(三)
Kotlin分享(四)
Kotlin分享(五)
Kotlin 协程 coroutines
前言
和java无缝衔接。
各类工具配置https://kotlinlang.org/docs/tutorials/
基础语法
包定义
package my.demo
import java.util.*
和java 相同的package 和 import 关键字。但是其中有一点区别:
可以看到包名 != 文件夹路径,文件名!=类名(当你你也可以相同。)对于某些时候的文件组织可能有些作用,但是大多数情况下我们还是默认使用java的路径规则。
import 可以使用一个as关键字对类进行重命名
import bar.Bar as bBar // bBar stands for 'bar.Bar'
变量定义
变量定义关键字:
val & var
区别在于 val相当于final 类型变量,var表示可变的变量。
最简单的定义 val a: Int = 1 val(r) 变量名 : 变量类型 = 初始值
在变量类型可推测的情况下甚至可以省略 比如: val b = 2 //Int 被省略
变量分为三种
class ClassNew{
var a:String = "你好" //成员变量
fun print(){
var b : String = "kotlin" //本地变量
Log.d("sss",a + b +c)
c = "我是程序猿2"
}
}
var c : String = "我是程序猿" //全局变量
成员变量和全局变量需要在定义的时候进行初始化(成员变量可以使用lateinit 延迟初始化,后面会介绍), 而本地变量只需要保证在使用前初始化即可。
方法定义
方法定义的一般形式如下
fun 方法名(参数名:参数类型 ,参数名:参数类型 ... ) : 返回类型{
}
使用关键字fun来定义一个方法类似如下:
fun test(num:Int):Int{ return num+1; }
对于这种方法体只是一个表达式的情况,我们还可以如下简写:
fun test(num:Int)= num+1
对于没有返回值的方法来说可以使用类型Unit或者直接省略返回类型
fun print(){ }
默认值
方法可以有默认值,比如这样
fun reformat(str: String,
normalizeCase: Boolean = true,
upperCaseFirstLetter: Boolean = true,
divideByCamelHumps: Boolean = false,
wordSeparator: Char = ' ') {
...
}
我们可以直接 reformat(str) 这样调用来省略其他默认值。当然不使用默认值也可以reformat(str,normalizeCase) 至于你想要几个参数 那是你的事情了。
那么假设我们只有divideByCameHumps不想要默认值呢?在java中这是无法实现的,那么kotlin中是否能实现呢?可以,kotlin中引入了一个新东西,这样
reformat(str,divideByCameHumps = true)
这样带上参数名 = ,就能指定不适用默认值的参数了。不过我们发现,第一个参数还是使用了位置来匹配。我们当然可以使用参数名来指定。
如果所有参数都指定了名字,我们甚至可以不用按顺序来调用方法,像这样
reformat(divideByCameHumps = true,str = str)
对于即使用位置,又使用参数名指定的方法调用来说,有一个基本规则,使用位置的参数一定要在指定参数名的参数前面。比如 test(1, y =2 )是可以的 但是 test(x = 1,2) 这样就是错误的。
在java中有默认值的参数一定要放在最后,但是在kotlin中这就不是必须的,比如
fun momo(str:String ="haha",age:Int){....}
完全可以,但是在调用的时候,不能再使用位置匹配了,而是必须制定参数名,像这样
momo(age = 12)
但是这条规则对于最后一个参数是lambda表达式的方法有些不太适用。
fun foo(bar: Int = 0, baz: Int = 1, qux: () -> Unit) { /* ... */ }
我们可以这样调用
foo(1) { println("hello") } // 使用位置参数,bar =1 baz默认
foo { println("hello") } // bar和baz都默认
主要原因在于,我们看到lambda表达式并没有写进括号内,而是直接写在方法后面。我们后面会介绍。
vararg 不定长参数
在java中使用 ... 来定义不定长参数,但是必须要放在参数列表最后,不过在kotlin中没有这个限制。
fun foo2(vararg num:Int,str:String){}
比如我们可以将它定义在开头,或者中间。使用时
base.foo2(1,2,3,4,str = "sss")
注意一旦遇到vararg参数,它会吃掉随后的所有参数传入,如果这个时候想要传入str,那么就需要指定参数名了。
另外在vararg参数中,实际上可以传入已经存在的数组,比如上面的定义,可以这样
val a = arrayOf(1,2,3).toIntArray() base.foo2(1,2,*a,3,4,str = "sss")
我已经存在了a的数组,使用*a可以把数组中的元素一道传入到varary参数中。
infix 中缀符
infix fun Int.shl(x: Int): Int {
...
} // call extension function using infix notation
1 shl 2 // 等同于 1.shl(2)
比如上面,我们给Int定义了一个额外方法。然后可以通过 1 shl 2 这种类似二元操作符的方式调用。
这样使用有几个规则
1. 方法一定要是成员函数或者额外函数(extension function)
2.只有一个参数
3.使用infix修饰。
PS:这是一个纯种的语法糖,而且使用效果其实也不太明显,所以算不上关键用法。
本地方法
我们知道在方法中定义的变量叫做本地变量,实际上在kotlin中 我们可以在方法中定义方法,这种方法就称为本地方法。
本地方法能够调用本地变量
fun dfs(graph: Graph) {
val visited = HashSet()
fun dfs(current: Vertex) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v)
}
dfs(graph.vertices[0])
}
数据类型
数字类型
kotlin提供一下数字类型,注意
使用常量初始化时类型规则其实和java是差不多的
var a = 123 //Int var b = 123f //Float var c = 123.0 //Double var d = 123.0f //Float
对象比较
kotlin中没有基本类型,连Int Float都是对象,和java的对象比较使用equal和 == 不太相同,kotlin中 == 就是equal,使用===来表示值得比较(是否是同一个变量)
val a: Int = 10000
Log.d("sss",(a === a).toString()) // Prints 'true'
val boxedA: Int? = a
val anotherBoxedA: Int? = a
Log.d("sss",(boxedA === anotherBoxedA).toString())// !!!Prints 'false'!!!
//PS:当 a的值在 128以内的时候,返回true!!
val a: Int = 10000
Log.d("sss",(a == a).toString()) // Prints 'true'
val boxedA: Int? = a
val anotherBoxedA: Int? = a
Log.d("sss",(boxedA == anotherBoxedA).toString())// !!!Prints 'true'!!!
另外需要注意的是,不同类型的变量是不能相互赋值和比较的比如
var a: Int = 10000
//var b: Long = a 错误,提示int 不能给long 赋值
var b: Long = a.toLong()
//Log.d("sss",(b == a).toString()) 错误,long 和 int 不能比较
字符类型
Char 和 java 类似,但是Char 不能当做int来使用,比如
var ch:Char = '1' if(ch == 49) 这样是错误的!
布尔类型
Boolean 只有 true 或者false两个值
var a: Int = 10000 if(a) //错误,不是Boolean类型,没有这种操作
数组
kotlin中使用类型 Array
var array:Array= Array(5,{i -> i.toString()})
或许你会对后面初始化的内容不解,后面会有介绍,现在你只需要知道 {i -> i.toString()} 整体是一个参数,它代表了一个方法。类似c++的函数指针。
虽然Array中提供了 get 和set方法,但是更多的,我们会使用 [] 下标来访问 : array[i]
我们知道,在java中
val x: IntArray = intArrayOf(1, 2, 3) x[0] = x[1] + x[2]
String
kotlin的String 很多规则都和java类似,当然也有独特的地方。
首先String 可以使用 [] 下标来访问其中任意一个Char。
另外就是两种非常有用的用法,java君表示梦寐以求
val i = 10
val s = "i = $i" // evaluates to "i = 10"
val s = "abc"
val str = "$s.length is ${s.length}" // evaluates to "abc.length is 3"
另外就是关于转义,kotlin并不适用 \ 反斜杠来进行转义,而是使用如下方法
var price = "${'$'}9.99"
讲道理,相比java经常转义不清,虽然写起来复杂,奇怪,但是懂了以后看起来挺方便的。
流程控制
if
几乎有所语言的if控制没有太大区别。
相比于java,kotlin中不再有 boolean ? a:b 这种用法,而是使用if来替代
if(boolean) a else b 或者
val max = if (a > b) {
print("Choose a")
a
} else {
print("Choose b")
b
}
这里,if语句就代表了一个表达式,有返回值!
when
kotlin移除了关键字kotlin,而是使用when来代替:
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> { // Note the block
print("x is neither 1 nor 2")
}
}
when 使用关键字else来捕获其他情况(相当于final)。
多分支合并使用逗号
when (x) {
0, 1 -> print("x == 0 or x == 1")
else -> print("otherwise")
}
另外,分支判断除了使用常数,还可以使用表达式,比如
when (x) {
parseInt(s) -> print("s encodes x")
else -> print("s does not encode x")
}
另外,可以使用 is 关键字来判断是否是某个指定的类
when(x) {
is String -> xxxx
else -> xxxx
}
还可以使用in 关键字来判断是否在某个范围以内
when (x) {
in 1..10 -> print("x is in the range")
!in 10..20 -> print("x is outside the range")
else -> print("none of the above")
}
PS: 注意 1..10 是左右闭合区间!
when 也可以用来替代 if-else if -else这样的策略,这种情况下输入值为空,可以这样用
when {
x.isOdd() -> print("x is odd")
x.isEven() -> print("x is even")
else -> print("x is funny")
}
最后,when和if一样,同样可以作为表达式,如果是表达式,需要每一个分支都有返回值,另外,必须yon拥有else分支!
for
kotlin中的 for语句和java中有了一定的变化,没有了传统的for应用
所有使用拥有迭代器的对象都能够遍历,相当于foreach
val items = lostOf("apple","banana","kiwi")
for (item in items) {
println(item)
}
可以使用index来做
val items = lostOf("apple","banana","kiwi")
for (index in items.indices) {
println(${items[index]})
}
虽然不具备类似于 C 中那样的 for 循环,但是并不意味着 Kotlin 不可以实现那种需求:
for (i in 1..5) { // for (int i = 1;i <= 5;i++) {print(i)}
print(i) // 12345
}
但是1..5这种写法用于步进,想要步退需要使用downTo关键字
for (i in 5 downTo 1) {
print(i) // 54321
}
如果实际的步长不是1,那么需要使用 step
函数来指定步长:
for (i in 1..5 step 2) {
print(i) //1 3 5
}
另外,又有一些时候(大部分的时候)可能并不需要包括结束区间。那么,这时候需要使用到 until
函数来替代 ..
:
for (i in 1 until 5) {
print(i) // 1234
}
While
和java一样了
Return 和 jump
kotlin和java 类似,关于结构跳转的关键字有三个 return break 和continue
和 java 区别在于kotlin的这些关键字带label功能,什么是label功能呢?大概就是这样
loop@ for (i in 1..100) {
for (j in 1..100) {
if (...) break@loop
}
}
原本break是跳出内层循环,但是我们可以使用 xxx@给循环添加label,然后在break的时候指定label来直接跳出多层。
看下面foo函数,其中使用forEach进行便利,传入的参数是一个lambda表达式,这时候,return 直接从foo函数返回。
fun foo() {
ints.forEach {
if (it == 0) return // nonlocal return from inside lambda directly to the caller of foo()
print(it)
}
}
如果我们只想退出forEach怎么办呢?
fun foo() {
ints.forEach lit@ {
if (it == 0) return@lit
print(it)
}
}
我们可以给forEach添加label。甚至forEach其实提供了一个隐藏的label,像这样
fun foo() {
ints.forEach {
if (it == 0) return@forEach
print(it)
}
}
需要注意一点,只有在使用lambda的时候才能够直接返回,如果是一个函数,那么return只会回到上一层调用,比如
fun foo() {
ints.forEach(fun(value: Int) {
if (value == 0) return // local return to the caller of the anonymous fun, i.e. the forEach loop
print(value)
})
}
下面这种情况,虽然实现的功能一样,但是return 会返回到forEach,因为forEach的参数并不是一个lambda表达式,而是一个匿名函数!
类 Class
kotlin同样是面向对象的语言所以势必还是会有类的概念,和java一样使用class关键字来定义一个类
class A { }
另外,如果这个class是一个空的类,我们甚至能省略类体,像这样
class A
构造函数
一个类可以有一个主构造方法,多个辅构造方法,我们先来看主构造方法,定义的方式如下
class NewClass2 constructor(name:String){
}
就是在类名后面加上一个 constructor关键字,然后输入构造方法需要的参数。
如果,这个构造方法没有注解,同样不需要可见性修饰符(public private等),也没有注解(比如@Inject),那么可以省略constructor关键字
class NewClass2 (name:String){
}
和上面一样,表示有一个String参数的构造函数。
PS:和JAVA一样,一旦定义了构造函数(无论是主还是辅),就会隐藏默认构造函数。
但是我们发现,上面这种写法存在一个问题,在哪里初始化?所以class还提供了一个关键的块来进行初始化,就像这样
class NewClass2 (name:String){
init {
//TODO
}
}
通过一个init的块来进行初始化操作。
实际上,无论我们是否手动定义构造函数(无论是主还是辅),init块都会在对象被创建的时候第一时间运行(比辅构造函数的函数体还要早)
另外,我们可以在init块中调用主构造函数的参数,比如上面,我们可以在init块中使用name这个变量。
再来说一下这个主构造函数的参数,它可以在init块中使用,也可以用来初始化类内的字段。但是无法被类内方法访问。
class NewClass2 constructor(name:String){
var mName = name //类内字段初始化使用
init {
//TODO
Log.d("sss","${name}") //init中使用
}
fun print(){
Log.d("sss",name) //错误
}
}
但是,kotlin很厉害,我们可以在name 前加上变量定义的符号,var 或者val,这之后就能够像使用成员变量一样使用它。
class NewClass2 constructor(var name:String){
fun print(){
Log.d("sss",name) //正确
}
}
除了主构造函数,kotlin还可以使用辅构造函数,ps,虽然是辅构造函数,但是实际上单独使用也可以的。并且可以有多个辅构造函数。
class NewClass2{
constructor(name:String,age:Int){
Log.d("sss","new NewClass2!! constructor ")
}
constructor(name:String){
Log.d("sss","new NewClass2!! constructor ")
}
}
辅助构造函数同样使用关键字 constructor,并且放在类的内部,没有函数名。
上面展示了,没有主构造函数,也可以定义多个辅助构造函数。
当存在主构造函数的时候
class NewClass2 constructor(var name:String){
var mName = name //类内字段初始化使用
init {
//TODO
Log.d("sss","${name}") //init中使用
}
constructor(name:String,age:Int):this(name){
Log.d("sss","new NewClass2!! constructor ")
}
fun print(){
Log.d("sss",name) //正确
}
}
注意,辅助构造函数一定要使用this来调用主构造函数!!
构造函数可以有默认值
class NewClass2 constructor(var name:String="")
最后,关于对象的创建,java需要使用关键字new,但是kotlin中没有new关键字,而是直接调用构造方法
val newClass2 = NewClass2("test")
继承
kotlin中所有的类都继承与Any,any中只有equal hashCode 和toString三个方法。
kotlin中使用如下方法来书写继承关系
open class Base(p:Int){
}
class ChildClass(p:Int):Base(p){
}
在类后面使用 : 来表示继承与某个类。
需要注意,kotlin中所有类默认都是final的,不允许继承的,所以如果Base想要被继承,那么类就需要定义成open。
如果基类有主构造函数,那么在继承的时候要如上一样提供构造参数。
使用辅构造函数和super当然也是可以的,如下
open class Base(p:Int){
init {
Log.d("sss","init base ${p}")
}
}
class ChildClass:Base {
init {
Log.d("sss","init ChildClass")
}
constructor(p:Int):super(p){
}
}
override
重写方法虽然和java类似,但是多了更多的限制
open class Base{
open fun print(){
}
}
class ChildClass:Base {
override fun print(){
}
}
比如上面的代码,我们被重写的方法必须是open的,并且重写方法必须要添加override关键字。然后如果一个类中有open的方法,那么该类应该是open的,否则将会没有意义(无法被继承的类不需要open方法)。
PS:这些限制在封装性上面确实有一定帮助,但是对于库文件而言就多了很多自由使用的限制。
使用了override定义的方法默认是open的,如果想要这个方法不能再被重写,我们可以添加关键字final
final override fun print()
重写变量和重写方法基本相同,区别在于一点,我们能够使用var类型的变量来重写val类型的,但是不能使用val类型来重写var类型。
重写方法后想要在类内调用父类方法,这个和java是一样的,都是使用super关键字。但是有一点可以一提的,对于inner class(就是能够使用外层class的变量和函数的类)
open class Base{
open fun print(){
}
}
open class ChildClass:Base {
final override fun print(){
}
inner class inClass{
fun doSomething(){
[email protected]()
}
}
}
inClass是一个inner class 想要调用base的print方法,直接super.print是不正确的,需要使用@来转移到外层类。
另外kotlin中的interface是允许有方法实现的,所以
Abstract Classes
kotlin中也有abstract关键字,用法和java一样
属性
kotlin中类内的属性是有默认的getter和setter方法的,但是我们可以自定义getter和setter方法,如下这样
var stringRepresentation: String
get() = this.toString()
set(value) {
setDataFromString(value) // parses the string and assigns values to other properties
}
看上去并不复杂,其实其中有很多可以探究的地方。
比如,我们知道kotlin中变量一般都需要初始化,但是这里,我们并没有初始化这个变量(当然初始化也是可以的)!什么情况下我们能够不初始化么? 就是在get 和set中都不会用到该变量的时候。
我们可以给set设置可见性,但是不重写set的实现
var setterVisibility: String = "abc"
private set
或者添加注解
var setterWithAnnotation: Any? = null
@Inject set
那么get方法可以吗? 可以添加注解,但是不能够修改可见性,get方法的可见性等于变量自己的可见性!!
field
看下面代码
var counter = 0 // the initializer value is written directly to the backing field
set(value) {
if (value >= 0) counter = value
}
看上去没啥问题,但是实际上这样写是错误的,原因在于,我们使用counter这个方法的时候都是通过set和get方法访问的额,在set中我们给counter赋值,实际上这个赋值操作还是会调用set方法,这就很尴尬了,他会一直这样循环下去。那么如何解决呢 ?在get和set中,有一个特殊的关键字,field,使用它就能解决问题,像这样
var counter = 0 // the initializer value is written directly to the backing field
set(value) {
if (value >= 0) field = value
}
const
在编译时可以确定的变量可以使用const来进行修饰(这个好像和c++差不多)
lateinit
一般来说,变量的定义的时候都需要提供初始化,但是这在类中是不方便,如果这个变量是通过依赖注入或者其他方法初始化的,这种情况下我们可以使用lateinit来进行延迟初始化
接口
kotlin的接口和java8中的接口很像,首先,它可以有方法的默认实现,在java8中使用关键字default,而在kotlin中我们不需要使用关键字,像这样
interface InterfaceBase{
fun foo1()
fun foo2(){
Log.d("sss","InterfaceBase.foo2")
}
}
class A:InterfaceBase{
override fun foo1() {
foo2()
}
}
接口中可以包含属性
interface InterfaceBase{
val prop: Int // abstract
var prop2:Int
val prop3:Int
get() = 1
}
class A:InterfaceBase{
override var prop2: Int =2
override val prop: Int = 1
}
属性默认是abstract类型的,必须被重写。另外,我们可以给属性提供默认值,通过重写get的方法,这种方法的变量一定是val类型的,并且在get中是无法使用关键字field的,这样这个属性实际上就相当于一个const类型的变量了。
由于接口可以有方法的默认实现,所以就会出现冲突
interface InterfaceBase{
fun foo2(){
Log.d("sss","InterfaceBase.foo2")
}
}
interface InterfaceBase2{
fun foo2(){
Log.d("sss","InterfaceBase2.foo2")
}
}
class A:InterfaceBase,InterfaceBase2{
override fun foo2() {
super.foo2()
super.foo2()
}
}
InterfaceBase 和 InterfaceBase2都默认实现了foo2方法,然后A同时实现这两个方法,这个时候就会出现冲突,如果调用A.foo2应该要调用哪一个?
所以kotlin规定,如果出现这种冲突,那么A必须重写foo2这个方法,然后可以通过super
模块
模块式一起编译的kotlin文件的集合(a module is a set of Kotlin files compiled together)
- an IntelliJ IDEA module;
- a Maven project;
- a Gradle source set;
- a set of files compiled with one invocation of the Ant task。
为什么要介绍模块呢?因为kotlin相比java有一个新的可见性修饰符,internal ,表示在同一个模块内可见。
另外一点不同,kotlin种外部类无法访问内部类中的private变量。