类
构造函数
一个类可以有一个主构造函数和一个或多个次构造函数
class Person @inject constructor(firstName: String) { /*……*/ }
当没有访问修饰符或者注解时可以省略constructor
关键字
class Person (firstName: String) { /*……*/ }
在成员变量和init
代码块中可以直接使用主构造方法中的参数
class Person (firstName: String) {
val firstNameOfUpperCase = firstName.toUpperCase()
init {
Log.d("yaocai", firstName)
}
}
成员变量和init
代码块按定义顺序依次执行。
次级构造函数需要直接或间接委托给柱构造函数
class Person(firstName: String) {
val firstNameOfUpperCase = firstName.toUpperCase()
val children = mutableListOf<Person>()
init {
Log.d("yaocai", firstName)
}
// 次级构造函数,直接委托主构造函数
constructor(firstName: String, son: Person) : this(firstName) {
children.add(son)
}
// 次级构造函数,间接委托主构造函数
constructor(son: Person) : this("", son)
}
继承
kotlin
中类默认是final
的,若要允许类被继承,需要手动声明为open
(同理也适用于函数)
open class Base(val p: Int, val q: String) {
constructor(q: String) : this(1, q)
}
interface Function<T, R> {
fun invoke(t: T): R
}
// 通过父类的次级构造方法间接调用父类的主构造方法
class Derived() : Base("foo"), Function<Int, String> {
override fun invoke(t: Int): String {
return t.toString()
}
}
// 直接调用父类的主构造方法
class Derived() : Base(1, "foo"), Function<Int, String> {
override fun invoke(t: Int): String {
return t.toString()
}
}
子类是用:
表示继承的类或实现的接口,子类必须要直接或间接调用父类的主构造方法
class BarAdapter(data: MutableList<Person>) :
BaseQuickAdapter<Person, BaseViewHolder>(R.layout.item_bar, data) {
override fun convert(holder: BaseViewHolder, item: Person) {
}
}
属性和字段
var
可变
val
不可变,只读,类似java
final
声明一个字段的完整语法
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
val <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
class Foo(var name: String) {
val encryptName: String = name
get() {
return Encrypt.md5(field)
}
}
一般地,属性声明为非空类型必须在构造函数中初始化。 然而,这经常不方便。例如:控件一般在Activity
或者Fragment
的onCreate
方法中实例化、利用Bundle
在Activity
和Fragment
传值。 这种情况下,不能在构造函数内提供一个非空初始器。但是任然想在使用该字段时候避免空检测。
这种场景下我们可以使用延迟初始化lateinit var
:
class MainActivity : AppCompatActivity() {
private lateinit var personLateInit: Person
private var person: Person? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
intent.extras?.let {
personLateInit = it.getSerializable("person") as Person
person = it.getSerializable("person") as Person
}
}
private fun foo() {
if (this::personLateInit.isInitialized) {
personLateInit.children
}
person?.children
}
}
使用lateinit var personLateInit
就可以避免空检测。
同样可以用this::personLateInit.isInitialized
来判断是否该字段已经实例化
数据类
使用data
关键字标识一个类,该类会变成数据类
data class User(val name: String, val age: Int)
编译器自动从主构造函数中声明的所有属性生成以下方法:
equals()
/hashCode()
;toString()
格式是"User(name=yaocai, age=27)"
;componentN()
函数按声明顺序对应于所有属性;copy()
函数。
val user1 = User("yaocai", 27)
val user2 = User("yaocai", 27)
println(user1)
println(user1 == user2)
println(user1.component1())
println(user1.component2())
// 解构声明, 根据声明字段个数,调用相应`componentN
val (name, age) = user1
println("name ${name},age ${age}")
===================================
User(name=sadhu, age=27)
true
sadhu
27
name sadhu,age 27
===================================
密封类
使用sealed
关键字标识一个类,该类会变成数据类
,常用语限定类的继承类型,使用when
就不需要else
分支了,因为所有情况都是已知。
sealed class ItemType
class TextType : ItemType()
class RichTextType : ItemType()
class MediaType : ItemType()
fun foo(itemType: ItemType) {
when (itemType) {
is TextType -> {
}
is RichTextType -> {
}
is MediaType -> {
}
// else ->{} 不需要else分支
}
}
函数
定义
函数使用 fun 关键字声明,括号后面声明返回值类型:
fun foo(x: Int): Int {
return 2 * x
}
如果返回值可以推导出来,可以省略返回值类型
override fun getLayoutResourceId() = R.layout.act_foo
参数
函数的参数是val
隐性修饰的,遵循Effective java
中形参的最佳实践,避免方法内部对实参引用地址的修改
fun foo(user: User){
user = User() // error 编译不通过
}
默认参数
函数参数可以有默认值,当省略相应的参数时使用默认值。与其他语言相比,这可以减少重载数量:
class NetWork {
fun fetchData(page: Int, pageSize: Int = 20) {
}
}
val network = NetWork()
network.fetchData(0)
network.fetchData(0, 10)
如果我们需要生成所有重载方法,可以使用@JvmOverloads
注解,譬如在自定义控件时:
class CustomView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr)
具名参数
当参数太多时候,为了阅读代码更加便捷,可以使用具名参数,是我们的代码更加有可读性:
fun check(content: String,
isWholeString: Boolean,
isUpperCase: Boolean,
needCheckLength: Boolean) {
}
不使用具名参数时,该调用看起来就像:
check(content, true , false ,true)
阅读起来看很困难,使用具名参数后调用看起来就像:
check(content, isUpperCase = true, needCheckLength = false, isWholeString = false)
还允许我们更改参数位置。
高阶函数
高阶函数是将函数用作参数或返回值的函数。
fun filter(predicate: (String) -> Boolean) {}
filter
是高阶函数,函数类型作为参数
fun getFilterFunction(): (String) -> Boolean {
return { input: String ->
input.length > 10
}
}
getFilterFunction
也是高阶函数,函数类型作为返回值。
函数类型
括号中定义形参的类型,箭头后面定义返回值类型:
(String, String) -> Boolean
可以像其他类型子字段一样使用函数类型字段,对函数类型字段赋值和调用:
class Bar {
var function: ((String, String) -> Boolean?)? = null
fun isEqual(str1: String, str2: String): Boolean {
return str1 == str2
}
}
val bar = Bar()
//使用::形式实例化
bar.function = bar::isEqual
//使用invoke方法调用
bar.function?.invoke("str1", "str2")
如果函数类型字段可以空,定义时应该像上述示例代码一样,将括号后面使用?
,返回值后的?
表示返回值可空。
有了函数类型,我们就可以替换项目中使用interface
定义的callback
了:
class BaseAdapter(private val listener: (View?, Int) -> Unit) :
RecyclerView.Adapter<RecyclerView.ViewHolder>(),
View.OnClickListener {
override fun onClick(v: View?) {
// 直接调用
listener(v, position)
}
}
// 使用lambda表达式实例化
val adapter = BaseAdapter { _, position ->
Log.d("sadhu", "click position${position}")
}
两处实力代码函数类型实例化分别使用::functionName
和lambda表达式
两处实力代码函数类型调用分别使用invoke
和()直接调用
lambda表达式
fun filter(predicate: (String) -> Boolean) {
}
filter({ input:String -> input.length > 12 })
函数filter
是高阶函数,它接受一个函数作为参数。该表达式本身是一个函数,相当于有具体名字的函数:
fun filtLength(input: String):Boolean{
return input.length > 12
}
lambda表达式
始终定义在花括号里面,先定义参数类型,函数体在箭头后面。lambda表达式
一般可以根据规则精简得很短。
如果作为方法的最后一个参数,可以放到方法括号外面,如果方法只这一个参数,则可以省略括号:
filter({ input:String -> input.length > 12 })
// 放到括号外面
filter(){ input:String -> input.length > 12 }
// 只有这一个参数,省略括号
filter{ input:String -> input.length > 12 }
接着参数类型也可以省略,如果只有一个参数,这参数也可以省略用,使用时用it
代替:
// 省略参数类型
filter{ input -> input.length > 12 }
// 只有一个参数,参数可以可以省略,使用时用it代替
filter{ it.length > 12 }
在Android
中,监听点击事件使用Lambda表达式
后就类似如下样子了:
view.setOnclickListener{
/**/
}
if、when、for、while
基本用法和java
类似,这里说下不同之处。
if
可以作为表达式,他可以返回一个值,默认分支中的最后一行就代表该分支的返回值,因此可以是用if``esle
代替java
中的三元运算
val max = if (a > b) {
print("Choose a")
a
} else {
print("Choose b")
b
}
when
类似于java
中的switch
,功能更加强大,每个判断分支可以是表达式:
when(x){
0,1->{}
in 2..5->{}
x>10->{}
}
for
循环可以对任何提供迭代器(iterator)
的对象进行遍历,即任何实现了Iterable
接口的对象
for (item in collection) print(item)
对于java
中索引循环,可以使用IntRange
代替
for (int i = 0; i < 100; i++) {
}
for(i in 0..99){
}
for(i in 0 until 100){
}
..
右边是闭区间,until
右边是开区间,还可以使用downTo
降序循环。
return、break、contine
- return。默认从最直接包围它的函数或者匿名函数返回。
- break。终止最直接包围它的循环。
- continue。继续下一次最直接包围它的循环。
return
根据定义,如下代码只会打印1、2,然后返回到foo()
fun foo() {
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return // 非局部直接返回到 foo() 的调用者
print(it)
}
println("this point is unreachable")
}
若我们想返回到forEach
,只是过滤掉3
,且打印this point is unreachable
,则可以使用标签,使用标签名@
定义标签,使用return@标签名
返回到指定标签。
fun foo() {
listOf(1, 2, 3, 4, 5).forEach lit@{
if (it == 3) return@lit
print(it)
}
println("this point is unreachable")
}
还可以使用隐式标签,默认标签与lambda表达式
同名:
fun foo() {
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return@forEach
print(it)
}
println("this point is unreachable")
}
当然,还是使用匿名函数:
fun foo() {
listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) {
if (value == 3) return // 局部返回到匿名函数的调用者,即 forEach 循环
print(value)
})
print(" done with anonymous function")
}
这种场景类似于循环中的contine
,对于break
的情况,若要使用return
,可以在外出嵌套run
方法:
fun foo() {
run {
listOf(1, 2, 3, 4, 5).forEach {
if (it == 3) return@run
println(it)
}
}
print(" done with nested loop")
}
break contine
循环时,break
和contine
同样也可以使用标签
fun foo() {
out@ for (i in 0..10) {
for (j in 0..10) {
if (i == 2 && j == 2) {
break@out
}
println("i:${i},j:${j}")
}
}
}
fun foo() {
out@ for (i in 0..10) {
for (j in 0..10) {
if (i == 2 && j == 2) {
contine@out
}
println("i:${i},j:${j}")
}
}
}
作用域函数(scope function)
作用域函数一共有五种:let
、run
、with
、apply
以及 also
,是在一个对象的上下文中执行代码块,形如:
val user = User()
with(user) {
}
user.apply {
}
user.run {
}
user.let {
}
user.also {
}
他们间的区别在于
- 引用上下文对象的方式
- 返回值
引用上下文对象的方式
run
、with
、apply
通过this
引用上下文对象,因此可以直接使用该对象的成员变量和函数,例如:
user.apply{
name = "sadhu"
age = 27
}
let
、also
通过it
引用上下文对象,例如:
user?.let{
print(it)
}
返回值
also
、apply
返回对象本身
// 返回对象本身 user
val user = user.also{
}
let
、with
、run
返回代码块的返回值
// 返回代码块的返回值,String类型
val string = user.let{
it.name.toUpperCase()
}
主要区别表
函数 | 对象引用 | 返回值 |
---|---|---|
let | it | Lambda 表达式结果 |
run | this | Lambda 表达式结果 |
with | this | Lambda 表达式结果 |
apply | this | 上下文对象 |
also | it | 上下文对象 |
场景
大部分情况5中作用域函数是可以互相替换,对于一些特定场景,可以优选用特定作用域函数
let
通常用于非空值执行代码块
val str: String? = "Hello"
//processNonNullString(str) // 编译错误:str 可能为空
val length = str?.let {
println("let() called on $it")
processNonNullString(it)
it.length
}
with
同样用于对接受对象,进行一下操作
override fun convert(holder: BaseViewHolder, item: Person) {
with(item)
{
holder.setText(R.id.name,name)
holder.setText(R.id.age,age.toString())
}
}
run
用于代码块同时对上下文对象初始化和返回值时
val user = User()
val firstName = user.run{
name = "sadhu.static"
queryFirstNameByName(name)
}
apply
通用用于配置上下文对象
val user = User().apply{
name = "sadhu.static"
age = 27
}
also
执行一些将上下文对象作为参数的操作
val user = User()
user.also{
queryInfoByUser(user)
}
本文由 sadhu 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为:
2020/06/15 01:18