主要是kotlin上函数的特色:命名参数、默认参数值、顶层函数和属性、扩展方法和扩展属性(本质上是静态函数高级语法糖),和能消除重复代码(DRY)的局部函数。数据库
让咱们从一个经常使用例子出发,java集合都有默认的toString()
方法,但它的输出是固定格式化的,有时并非你所须要的([1,2,3]
),如要自定义字符串的前缀、后缀,和分隔符时,通常定义个方法,并传入参数:编程
fun <T> joinToString(collection: Collection<T>,separator:String,prefix:String,postfix:String):String{
val result = StringBuilder(prefix)
for ((index,element) in collection.withIndex()){
if (index>0) result.append(separator)
result.append(element)
}
result.append(postfix)
return result.toString()
}
复制代码
使用:json
val list = listOf("kotlin","java")
println(joinToString(list,",","{","}")) // {kotlin,java}
复制代码
关注的第一个问题就是函数的可读性,如上面的使用joinToString(list,",","{","}")
,难以看出这些string对应的是什么参数(虽然能够借助ide)。kotlin能够优雅的调用,便可以显式的标明一些参数的名称,如joinToString(list,",",prefix = "{",postfix = "}")
,直接在调用时指明了prefix
和postfix
,能清晰明了的分辨出来。bash
注: 为避免混淆,当指明了一个参数名称后,那它以后的全部参数都要显式指明名称app
java函数的另外一个广泛存在的问题是,一些类的重载函数实在太多了(如java.lang.Thread便有8个构造方法),致使参数名和类型被不断重复。ide
kotlin则使用了默认参数值,能够在声明函数的时候,指定参数的默认值,就能够避免重复建立函数。如上面的joinToString
函数,大多数状况下,能够不加前缀或者后缀并用逗号分隔,因此把它们设为默认值:函数
//只改变了参数声明
fun <T> joinToString(collection: Collection<T>,separator:String=",",prefix:String="",postfix:String=""):String{
val result = StringBuilder(prefix)
for ((index,element) in collection.withIndex()){
if (index>0) result.append(separator)
result.append(element)
}
result.append(postfix)
return result.toString()
}
复制代码
能够像java多重重载函数同样地调用:工具
println(joinToString(list)) // 打印:kotlin,java
println(joinToString(list,";")) // 打印:kotlin;java
println(joinToString(list,prefix = "{",postfix = "}")) // 打印:kotlin;java
复制代码
java中,全部的代码都必须写成类的函数。有时存在一个基本的对象,但你不想经过实例函数来添加操做,让它的API继续膨胀,结果就是,会把这些函数写成静态,并交由不包含任何状态和实例函数的类保管,如JDK中的Collections
,或者本身代码中,一些以Util
做为后缀的工具类。post
kotlin能够直接把函数或属性放在代码文件顶层,而不用从属于任何的类。(依然是包内成员,若是须要从包外访问它,则须要import(可使用as更改导入的名字),但再也不须要额外包一层。
kotlin属性也能够放在文件顶层,相似java静态字段; 若想像java的public final static
同样声明一个常量,kotlin可使用const val
修饰
使用方式在下面给出↓
kotlin的一大特点是,能够平滑地与现有代码集成。扩展函数能够在类的外面定义一个类的成员函数,如咱们添加一个方法扩展String
类型,计算一个字符串的最后一个字符并返回:(像成员函数同样地调用)
import...
fun String.lastChar():Char = this.get(length-1) //this能够省略
...
fun main(){
//像成员函数同样地调用
println("kotlin".lastChar()) // 打印: n
}
复制代码
扩展函数的声明,与普通函数区别就是,把你要扩展的类或者接口名称,反到即将添加的函数名签名,这个类被称为接收者类型,如例子中的String
;用来调用这个扩展函数的那个对象,被叫作 接收者对象,如例子中的“kotlin”
在扩展函数中,能够直接访问被扩展类或接口的其余方法和属性(如例子中的String.get方法),就好像是在这个类中定义同样。
对于开始的字符串例子,如今能够这么定义使用:
fun <T> Collection<T>.joinToString(separator:String=",",prefix:String="",postfix:String=""):String{
val result = StringBuilder(prefix)
for ((index,element) in withIndex()){
if (index>0) result.append(separator)
result.append(element)
}
result.append(postfix)
return result.toString()
}
...
val list = listOf("kotlin","java")
println(list.joinToString(prefix = "{",postfix = "}")) //kotlin;java
复制代码
扩展函数本质上,是java的静态函数的高效语法糖,最终是被转为一个接收该对象类型的静态函数,所以是不能被继承重写的。
注: 当类的成员函数和扩展函数有相同签名时,成员函数会优先使用
和扩展函数同样,kotlin也支持扩展属性,使用相似,如:
var StringBuilder.lastChar:Char
get() = get(length -1)
set(value) {
this.setCharAt(length-1,value)
}
...
val sb = StringBuilder("kotlin?")
println(sb.lastChar) // 打印: ?
sb.lastChar = '!'
println(sb) // 打印: kotlin!
复制代码
注: 扩展属性是没有支持字段存储的
提升代码质量标准之一:不要重复你本身的代码(DRY)。kotlin提供了一个整洁的方案,局部函数:能够在函数中嵌套函数。
让咱们来看怎么用局部函数解决常见的代码重复问题,例子中,saveUser函数用于将user信息存到数据库中,并确保user对象含有有效数据
class User(val id:Int,val name:String,val address:String)
复制代码
fun saveUser(user:User){
if (user.name.isEmpty()){
throw IllegalArgumentException("Can't save user ${user.id} :empty Name")
}
if (user.address.isEmpty()){
throw IllegalArgumentException("Can't save user ${user.id} :empty Address")
}
//保存到数据库中
...
}
复制代码
函数saveUser中,存在重复的字段检查,当检查字段增多,代码会显得特别臃肿,可利用局部函数提取重复代码:
fun saveUser(user:User){
//可省略局部函数的user参数
fun validate(user: User,value:String,fieldName:String){
if (value.isEmpty()){
throw IllegalArgumentException("Can't save user ${user.id} :empty $fieldName")
}
}
validate(user,value = user.name,fieldName = "Name")
validate(user,value = user.address,fieldName = "Address")
//保存到数据库中
}
复制代码
声明了一个局部函数validate提取重复的检查逻辑,因局部函数能够访问到所在函数的全部参数和变量,因此,能够去掉冗余的User参数:
fun saveUser(user:User){
//去掉冗余的User参数
fun validate(value:String,fieldName:String){
if (value.isEmpty()){
throw IllegalArgumentException("Can't save user ${user.id} :empty $fieldName")
}
}
validate(value = user.name,fieldName = "Name")
validate(value = user.address,fieldName = "Address")
//保存到数据库中
}
复制代码
继续改进,能够将user的验证扩展成扩展函数
fun User.validateBeforeSave(){
fun validate(value:String,fieldName:String){
if (value.isEmpty()){
throw IllegalArgumentException("Can't save user $id :empty $fieldName")
}
}
validate(value = name,fieldName = "Name")
validate(value = address,fieldName = "Address")
}
fun saveUser(user:User){
user.validateBeforeSave()
//保存到数据库中
}
复制代码
再次代表,扩展函数能够很大程度优化代码。
注:扩展函数也能够被声明为局部函数,但通常不建议多层嵌套,因深度嵌套的局部函数每每会让人太费解
kotlin接口和类的实现与java仍是有一点区别的,如接口能够包含属性声明;kotlin的声明默认是public final 的;此外,嵌套类默认不是内部类,即静态的,没有包含怼外部类的隐式引用
kotlin中的接口与java8中的类似,能够包含抽象方法,和非抽象方法的实现(与java8中默认方法相似);同时能够有属性声明,但没有包含任何状态,即没有支持字段来保存(后面有讲)
接口声明:
interface Clickable{
fun click() //抽象方法
fun showOff() = println("I'm clickable") //带默认实现的方法
}
复制代码
kotlin在类名后面用冒号来代替java的extends和implements关键字。和java同样,一个类能够实现任意多个接口,但只能继承一个类
kotlin用override修饰符标注被重写的方法和属性,同时,override修饰符是强制要求的
class Button:Clickable{
override fun click() {
println("button clicked")
}
}
复制代码
若同时实现两个接口,且两个接口含有相同的函数签名,且都有默认实现,则kotlin会强制要求提供你本身的实现
interface Clickable{
fun click()
fun showOff() = println("I'm clickable")
}
interface Focusable{
fun focus()
fun showOff() = println("I'm focusable")
}
class Button:Clickable,Focusable{
override fun click() {
println("button clicked")
}
override fun focus() {
println("button clicked")
}
override fun showOff() {
//使用尖括号加父类名字的“super”代表了你想要调用哪一个父类方法
super<Clickable>.showOff()
super<Focusable>.showOff()
}
}
复制代码
调用父类实现,kotlin也使用了关键字super
,使用尖括号加父类名字的“super”代表了你想要调用哪一个父类方法
kotlin中,类,方法和属性默认是final的,即不可重写。若是要容许建立子类,须要使用open修饰符来标示这个类,属性或方法也要添加。
open class RichButton:Clickable{ //这个类是open的,便可继承的
fun disable(){} //这个函数是final的,子类不可重写
open fun animate(){} //这个函数是open的,子类可重写
override fun click() {} //重写的成员默认是open的,除非显式标注 final
}
复制代码
注: 重写的成员默认是open的,除非显式标注为final
同java同样,能够将一个类声明为abstract,这种类能够有一些没被实现而且须要在子类实现的抽象成员(用abstract修饰),抽象成员不能有实现,与java基本一致。
注: abstract始终是open的,同时,接口也始终是open的,都不能声明为final。
修饰符 | 相关成员 | 批注 |
---|---|---|
final | 不能被重写 | 类中成员默认使用 |
open | 能够被重写 | 须要明确标示 |
abstract | 必须被重写 | 只能在抽象类中使用;抽象成员不能有实现 |
override | 重写父类或接口中的成员 | 若是没有使用final明确代表,重写的成员默认是open的 |
与java可见性区别:
internal
,表示“只在模块内部可见”修饰符 | 类成员 | 顶层声明 |
---|---|---|
public(默认) | 全部地方可见 | 全部地方可见 |
internal | 模块中可见 | 模块中可见 |
protected | 类和子类中可见 | ---- |
private | 类中可见 | 文件中可见 |
kotlin中,默认嵌套类不能访问外部类实例,即至关于静态内部类,没有隐式拥有外部类的实例。若是要把变成一个内部类来持有一个外部类的引用的话,须要使用inner
修饰符。在kotlin中引用外部类实例语法也与java不一样,需使用this@Outer
(java是Outer.this)
class Outer{
inner class Inner{ //声明为inner
fun getOuterReference():Outer = this@Outer //引用外部类实例
}
}
复制代码
密封类:包含有限数量的类的继承结构。
在使用when
表达式的时候,老是提供一个else
分支很不方便,若是处理的是sealed类的子类,则能够再也不须要提供默认分支,且当sealed添加一个子类时,有返回值的when表达式会编译失败,并告诉你哪里必须修改
sealed class Expr{
class Num:Expr() //括号构造方法下面讲解
class Sum:Expr()
class Plus:Expr()
}
data class Out(val s:String) :Expr() //后续添加的子类
fun eval(e:Expr):String{
when(e){
is Expr.Num -> return "num"
is Expr.Sum -> return "Sum"
is Expr.Plus -> return "plus"
is Out-> return "plus" //必须把后续添加的子类放到分支里,不然报错
}
}
复制代码
注: sealed修饰的这个类始终是open的。且不能用于声明sealed接口
类的构造方法区分了主构造方法和从构造方法。同时也容许在初始化语句块中添加额外的初始化逻辑
一个简单类的声明:
class User(val nickname:String)
复制代码
这段被括号围起来的语句块就叫作主构造方法,主要两个目的,代表构造方法参数和使用这些参数初始化的属性。完成一样事情最明确代码以下:
class User constructor(_nickname:String){ //带一个参数的主构造方法
val nickname:String //属性
init { //初始化代码块
nickname = _nickname
}
}
复制代码
class User(_nickname:String){
val nickname = _nickname
}
复制代码
class User(val nickname:String = "John")
复制代码
对于子类,须要初始化父类,可在类声明中使用父类的构造方法:
open class Button
class RadioButton:Button()
复制代码
这就是为何在父类名称后面还须要一个空的括号;接口没有构造方法,因此不用加括号
open class View{
constructor(context:Context?){ //从构造方法
println("this is view1")
}
constructor(context: Context?,attr:AttributeSet?){
println("this is view2")
}
}
复制代码
class MyView:View{
constructor(context: Context?):this(context,null){ //this委托
println("this is MyView1")
}
constructor(context: Context?,attr: AttributeSet?):super(context,attr){ //初始化基类
println("this is MyView2")
}
}
复制代码
interface User{
val nickName:String
}
复制代码
重写属性有三种方式
class User1(override val nickName: String) :User //有支持字段存储
class User2(val email:String):User{
override val nickName: String //没有支持字段存储
get() = email.substringBefore("@")
}
class User3(val id:Int):User{
override val nickName = "$id" //有支持字段存储
}
复制代码
interface User{
val email:String
val nickName:String //并无持有支持字段和状态
get() = email.substringBefore("@")
}
复制代码
属性能够自定义访问器getter/setter以提供被访问或修改时额外逻辑。假设须要在修改时输出日志:
class User(val name:String){
var address = "unSpecified"
set(value) {
println("address was changed")
field = value // filed表示支持的字段值
}
}
复制代码
field标识符在getter/setter表示支持字段的值,在getter中,只能读取值,在setter中,既能读取也能修改。
注: 能够只修改一个访问器,另外一个会自动用默认的实现
4 有无支持字段的判断:访问属性的方式不依赖因而否含有支持字段。若是显式引用或者使用默认访问器实现,编译器会为属性生产支持字段。若是提供了一个自定义的访问器而且没有使用field(若是是val,就是getter,若是是var,就是getter和setter),支持字段就不会被呈现出来。
5 访问器的可见性默认与属性的可见性相同,但能够修改,如:
class User(val name:String){
var address = "unSpecified"
private set //修改setter的可见性为private
}
复制代码
data class Person(val name:String){
object NameComparator:Comparator<Person>{ //单一实例
override fun compare(p0: Person?, p1: Person?): Int {
if (p0 ==null || p1 == null)
return 0
return p0.name.compareTo(p1.name)
}
}
}
...
//使用
Person.NameComparator.compare(Person("John"),Person("Cap"))
复制代码
kotlin中的类不能拥有静态成员,java的static关键字并非kotlin语言的一部分。做为替代,通常使用顶层函数和对象声明。大多数状况下推荐顶层函数,但顶层函数没法访问private成员,像工厂方法这种例子就得使用对象声明。
class A{
companion object{
fun bar() = println("called")
}
}
...
A.bar() // 输出 called,像静态方法同样调用
复制代码
注:本质上也是被编程成了经过静态字段持有的的单一实例类
class User private constructor(val name: String){
companion object{
fun newFaceBookUser(email:String) = User(email.substringBefore('@'))
fun newMarvelUser() = User("Cap")
}
}
复制代码
能够经过类名来调用companion object的方法
val facebook = User.newFaceBookUser("xxx@163.com")
val marvel = User.newMarvelUser()
复制代码
class Person(val name:String){
companion object Loader{
fun fromJSON(jsonText:String):Person = ...
}
}
复制代码
则能够经过两种方式使用
val person = person.Loader.fromJSON(...)
val person2 = person.fromJSON(...)
复制代码
interface JSONFactory<T>{
fun fromJSON(json:String):T
}
class Hero private constructor(val heroName:String){
companion object Loader:JSONFactory<Hero>{
override fun fromJSON(json: String): Hero {
return Hero("IronMan")
}
}
}
fun <T> loadFromJson(jsonFactory: JSONFactory<T>) = jsonFactory.fromJSON("John")
//使用,直接将包含它的类的名字做为实现了改接口的对象实例来使用
loadFromJson(Hero)
复制代码
class Person(val name:String){
companion object{
}
}
//扩展函数显式指明默认名字companion
fun Person.companion.fromJSON(json:String):Person{
...
}
复制代码
使用时,仍能够直接用包含的类名来调用
val p = Person.fromJSON(json)
复制代码
object关键字不只能用来声明单例对象,还能用来声明匿名对象。
与java匿名对象不一样的是:
class MainActivity : AppCompatActivity() {
val c:String = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var clickCount = 0
val view :View= findViewById(R.id.action)
//这里的匿名对象表达式能够被lambda代替
view.setOnClickListener(object :View.OnClickListener{
override fun onClick(p0: View?) {
clickCount ++ //不须要final
println(c)
}
})
}
}
复制代码
匿名对象也能够直接存储到一个变量中
val listener = object :View.OnClickListener{
override fun onClick(p0: View?) {
...
}
}
复制代码