文章目录
1. val、var、const
var、val 声明的变量分为三种类型:顶层属性、类属性和局部变量;
var 属性可以生成 getter 和 setter,是可变的(mutable),val 属性只有 getter,是只读的(read-only,注意不是 immutable);
局部变量只是一个普通变量,不会有 getter 和 setter,它的 val 等价于 Java 的 final,在编译时做检查。
const 只能修饰没有自定义 getter 的 val 属性,而且它的值必须在编译时确定。
val 没有 setter,是只读但非不可变
var 可变
const 只读不可变,只存在顶层或 object 中
Java 中可以使用 static final 来定义常量,这个常量会存放于全局常量区,这样编译器会针对这些变量做一些优化,例如,有三个字符串常量,他们的值是一样的,那么就可以让这个三个变量指向同一块空间。
局部变量无法声明为 static final,因为局部变量会存放在栈区,它会随着调用的结束而销毁。
kotlin引入关键字 const 来定义常量,但是这个常量跟 Java 的 static final 是有所区别的,如果它的值无法在编译时确定,则编译不过,因此 const 所定义的常量叫编译时常量。
2.什么是幕后字段
2.1 kotlin的普通属性
在kotlin中,声明一个属性涉及到2个关键字:var 和 val
- var 声明一个可变属性
- val 声明一个只读属性
在kotlin中 gettter 和 setter 跟Java的getXX 和 setXX 方法一样,叫做访问器。读一个属性的本质是执行了读访问器getter,类似的,写一个属性的实质就是执行了属性的写访问器setter。
2.2 幕后字段
在 getter 和 setter 中用 field 来表示幕后字段,重写 setter 和 getter ,没有使用 field 不会有幕后字段,有幕后字段的属性转换成Java代码一定有一个对应的Java变量
class Bean{
val gender:Int?=null
// field:幕后字段的实际使用场景
var name = ""
set(value) {
field= if (value==gender.toString()) "男" else "女"
}
get() {
field = if (gender==1) "男" else "女"
return field
}
// 重写 setter 和 getter 并且未显示使用 field 是没有幕后字段的
var nameCopy :Int
set(value) {
}
get() {
return 1
}
// 另外一种 val 的写法 ,没有幕后字段
val name:String
get() {
return if (gender==1) "男" else "女"
}
}
3. for 循环
// 1. until:左闭右开
for (i in 0 until 100) {
println(i) // 0 ~ 99
}
// 2. ..:全闭区间(补充:可以通过 val test = 0..100 这种语句声明一个区间
for (i in 0..100) {
println(i) // 0 ~ 100
}
// 3. downTo:降序的全闭区间
for (i in 100 downTo 0){
println(i) // 100 ~ 0
}
4. init{}
class Init{
companion object{
// 相当于 java 中的 static {}
init{
}
}
// 相当于 java 的构造函数,主构造函数,没有声明则是默认的无参构造函数
init{
}
}
5. data class
查看kotlin字节码反编译java文件后
- data class 的类已经重写了hashCode() 和 equals() 方法
- 普通class 是不会的
5.1 gson解析
下面例子用到 @SerializedName
标记后台字段,解析时候就能成功解析。
data class Goods(
val name:String,
@SerializedName("price")
var _price:Int
)
5.2 重写setter/getter
由于data class 是 final 的,并且其 setter/getter也是final的,所以无法重写,要想实现重写,则需要将属性定义到类中。
data class Person(val name:String){
var age:Int=0
set(value){
field =value
}
get()=field
}
//使用的时候
fun call(){
val p = Person("monk")
p.age = 10
}
6. 内联函数
6.1 inline
让变量内联用的是const
,而让函数内联则用 inline
。
可以在某些情况下提升效率:
- 在编译时期,把调用这个函数的地方用这个函数的方法体进行替换(复制和 铺平);
inline
适合在包含lambda
参数的函数上,是因为 lambda 参数多出来的类会增加内存的分配。
// kotlin
inline fun setCallback(listener: () -> Unit) {
listener.invoke()
}
fun testInline(){
setCallback {
print("测试 inline ")
}
}
/**===============================java=====================================*/
public static final void setCallback(@NotNull Function0 listener) {
listener.invoke();
}
// 如果加了inline,方法调用栈就少了一层
public static final void testInline() {
String var2 = "测试 inline ";
System.out.print(var2);
}
// 如果不加inline,反编译后的java函数如下,会产生Function0实例
public static final void testInline(){
setCallback((Function0)null.INSTANCE);
}
6.2 return
inline 函数支持 return,没加 inline 的函数在被调用时,要使用return ,一般会 return@方法名
方式,并且一般方法的后续代码还是会执行的。inline函数则不会让后续代码执行(想想代码平铺就理解了)。
Lambda表达式里不允许使用return,除非—— 这个Lambda是内联函数的参数。
inline fun hello(block:()->Unit){
println("inline start")
block()
println("inline end")
}
fun call(){
hello{
println("block start")
return
}
println("block end ")
}
fun main() {
// 结果:inline start -> block start
call()
}
6.3 noinline
内联函数的「函数参数」 不允许作为参数传递给非内联的函数(看起来跟java 的 static 有点像,静态方法不能调用非静态方法)。
也就是说如果希望只内联一部分传给内联函数的 lambda
表达式参数,那么可以用 noinline
修饰符标记不希望内联的函数参数。
// 编译器提示这里的 inline 对性能预期影响是微不足道的(无需inline,但这里为了说明noinline的用法)
inline fun hello(noinline block:()->Unit){
println("inline start")
// 如果这里没有用 noinline 修饰 block,此处会报错,
call(block)
println("inline end")
}
// 非 inline 函数,
fun call(block:()->Unit){
println("call start")
block()
println("call end")
}
在其他函数调用时候,noinline 也会其作用:
inline fun hello(preAction:()->Unit, noinline postAction:()->Unit){
preAction()//做点前置工作
println("Hello!")
postAction()//做点后置工作
//如果没有oninline修饰,这里编译器会报错
return postAction
}
6.4 crossinline
什么时候需要crossinline?当你需要突破内联函数的「不能间接调用参数」的限制的时候。但其实和noinline一样,你并不需要亲自去判断,只要在看到Android Studio给你报错的时候把它加上就行了。
inline fun hello(crossinline postAction:()->Unit){
println("hello!")
runOnUiThread{
//不加crossinline编译器会报错
postAction()
//return 这里不再允许使用return
}
}
Kotlin增加了一条额外规定:内联函数里被crossinline修饰的函数类型的参数,将不再享有「Lambda表达式可以使用return」的福利。所以这个return并不会面临「要结束谁」的问题,而是直接就不允许这么写。
7. sealed class
sealed class,密封类。具备最重要的一个特点:
其子类可以出现在定义 sealed class 的不同文件中,但不允许出现在与不同的 module 中,且需要保证 package 一致。这样既可以避免 sealed class 文件过于庞大,又可以确保第三方库无法扩展你定义的 sealed class,达到限制类的扩展目的。
是一个有特定数量子类的类,看上去和枚举有点类似。
// TestSealed.kt
sealed class GameAction(times: Int) {
// Inner of Sealed Class
object Start : GameAction(1)
data class AutoTick(val time: Int) : GameAction(2)
class Exit : GameAction(3)
}
fun main(args:Array<String>){
var sc:GameAction = GameAction.Start()
}
8. object
类和对象同时创建,相当于 懒汉式单例,里面的方法是 final 方法
- val 变量相当于 private static final
- var 变量相当于 private static
kotlin中有四种方式表示静态:
- object单例类模式
- companion object
- JvmStatic注解模式
- 顶层函数模式
object:反编译java代码是由一个静态类包含一系列非静态函数
companion object:同上
JvmStatic:只能注解在单例或companion object 中的方法上,反编译java代码是一个静态函数
顶层函数:反编译java代码是一个静态函数
9. open
在java中允许创建任意的子类并重写任意的方法,除非显示的使用了final关键字进行阻止。
而在kotlin的世界里面则不是这样,在kotlin中它所有的类默认都是 final
的,那么就意味着不能被继承,而且在类中所有的方法也是默认是final
的,那么就是kotlin的方法默认也不能被重写,所以open
关键字就是用来解决这个现象的。
10. inner
kotlin 默认内部类是 static
的
-
加
inner
会让内部类成为非静态,这样可以使用外部类方法和函数 -
因为外部类方法或函数有非静态,而 java 是不允许静态使用非静态(kotlin也是一样)
静态属于类,非静态属于对象,也就是静态调用非静态可能会出现对象未初始化的情况,因此编译不通过