Lambda
表达式Data classes
Function literals & inline functions
Extension functions
Null safety
Smart casts
String templates
Primary constructors
Class delegation
Type inference
Singletons
Declaration-site variance
Range expressions
上面说简洁简洁,到底简洁在哪里?这里先用一个例子开始,在Java
开发过程中经常会写一些Bean
类:
public class Person {
private int age;
private String name;
private float height;
private float weight;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getHeight() {
return height;
}
public void setHeight(float height) {
this.height = height;
}
public float getWeight() {
return weight;
}
public void setWeight(float weight) {
this.weight = weight;
}
@Override
public String toString() {
...
}
}
使用Kotlin
:
data class Person(
var name: String,
var age: Int,
var height: Float,
var weight: Float)
这个数据类,它会自动生成所有属性和它们的访问器,以及一些有用的方法,比如toString()
方法。
我们看一下MainActivity
的代码:
// 定义类,继承AppCompatActivity
class MainActivity : AppCompatActivity() {
// 重写方法用overide,函数名用fun声明 参数是a: 类型的形式 ?是啥?它是指明该对象可能为null,
// 如果有了?那在调用该方法的时候参数可以传递null进入,如果没有?传递null就会报错
override fun onCreate(savedInstanceState: Bundle?) {
// super
super.onCreate(savedInstanceState)
// 调用方法
setContentView(R.layout.activity_main)
}
}
变量可以很简单地定义成可变var
(可读可写)和不可变val
(只读)的变量。如果var代表了variable(变量),那么val可看成value(值)的缩写
也有人把val解释成variable+final,即通过val声明的变量具有Java中的final关键字的
效果(我们通过查看对val语法反编译后转化的java代码,从中可以很清楚的发现它是用final实现的),也就是引用不可变。
因此,val声明的变量是只读变量,它的引用不可更改,但并不代表其引用对象也不可变。事实上,我们依然可以修改引用对象的可变成员。
声明:
var age: Int = 18
val book = Book("Thinking in Java") // 用val声明的book对象的引用不可变
book.name = "Diving into Kotlin"
字面上可以写明具体的类型。这个不是必须的,但是一个通用的Kotlin
实践是省略变量的类型我们可以让编译器自己去推断出具体的类型,
Kotlin拥有比Java更加强大的类型推导功能,这避免了静态类型语言在编码时需要书写大量类型的弊端:
var age = 18 // int
val name = "charon" // string
var height = 180.5f // flat
var weight = 70.5 // double
在Kotlin
中,一切都是对象。没有像Java
中那样的原始基本类型。
当然,像Integer
,Float
或者Boolean
等类型仍然存在,但是它们全部都会作为对象存在的。基本类型的名字和它们工作方式都是与Java
非常相似的,
但是有一些不同之处你可能需要考虑到:
数字类型中不会自动转型。举个例子,你不能给Double
变量分配一个Int
。必须要做一个明确的类型转换,可以使用众多的函数之一:
private var age = 18
private var weight = age.toFloat()
字符(Char
)不能直接作为一个数字来处理。在需要时我们需要把他们转换为一个数字:
val c: Char = 'c'
val i: Int = c.toInt()
位运算也有一点不同。
// Java
int bitwiseOr = FLAG1 | FLAG2;
int bitwiseAnd = FLAG1 & FLAG2;
// Kotlin
val bitwiseOr = FLAG1 or FLAG2
val bitwiseAnd = FLAG1 and FLAG2
当该对象被赋值给变量时,这个对象本身并不会被直接赋值给当前的变量。相反,该对象的引用会被赋值给该变量。
因为当前的变量存储的是对象的引用,因此它可以访问该对象。
如果你使用val来声明一个变量,那么该变量所存储的对象的引用将不可修改。然而如果你使用var声明了一个变量,你可以对该变量重新赋值。
例如,如果我们使用代码: x = 6
,将x的值赋为6,此时会创建一个值为6的新Int对象,并且x会存放该对象的引用。下面新的引用会替代原有的引用值被存放在x中:
注意: 在Java中,数字类型是原生类型,所以变量存储的是实际数值。但是在Kotlin中的数字也是对象,而变量仅仅存储该数字对象的引用,并非对象本身。
在很多Kotlin的学习资料中,都会传递一个原则:优先使用val来声明变量。这相当正确,但更好的理解可以是:尽可能采用val、不可变对象及纯函数来设计程序。
关于纯函数的概念,其实就是没有副作用的函数,具备引用透明性。
简单来说,副作用就是修改了某处的某些东西,比如说:
Kotlin
会默认创建set get
方法,我们也可以自定义get set
方法:
在
kotlin
的getter
和setter
是不允许调用本身的局部变量的,因为属性的调用也是对get
的调用,因此会产生递归,造成内存溢出。
例如:
var count = 1
var size: Int = 2
set(value) {
Log.e("text", "count : ${count++}")
size = if (value > 10) 15 else 0
}
这个例子中就会内存溢出。
kotlin
为此提供了一种我们要说的后端变量,也就是field
。编译器会检查函数体,如果使用到了它,就会生成一个后端变量,否则就不会生成。
我们在使用的时候,用field
代替属性本身进行操作。按照惯例set
参数的名称是value
,但是如果你喜欢你可以选择一个不同的名称。
setter通过field标识更新变量属性值。field指的是属性的支持字段,你可以将其视为对属性的底层值的引用。在getter和setter中使用field代替属性名称
class A {
var count = 1
var size: Int = 2
set(value) {
field = if (value > 10) 15 else 0
}
get() {
return if (field == 15) 1 else 0
}
}
如果我们不手动写getter和setter方法,编译器会在编译代码时添加以下代码段:
var myProperty: String
get() = field
set(value) {
field = value
}
这意味着无论何时当你使用点操作符来获取或设置属性值时,实际上你总是调用了属性的getter或是setter。那么,为什么编译器要这么做呢?
为属性添加getter和setter意味着有访问该属性的标准方法。getter处理获取值的所有请求,而setter处理所有属性值设置的请求。
因此,如果你想要改变处理这些请求的方式,你可以在不破坏任何人代码的前提下进行。通过将其包装在getter和setter中来输出对属性的直接访问称为数据隐藏。
在类内声明的属性必须初始化,如果设置非null
的属性,应该将此属性在构造器内进行初始化。
假如想在类内声明一个null
属性,在需要时再进行初始化(最典型的就是懒汉式单例模式),这就与Kotlin
的规则是相背的,此时我们可以声明一个属性并
延迟其初始化,此属性用lateinit
修饰符修饰。
class MainActivity : AppCompatActivity() {
lateinit var name : String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var test = MainActivity()
// 要先调用方法让其初始化
test.init()
// 初始化之后才能进行调用test的属性
}
fun init() {
name = "sth"
}
}
需要注意的是,我们在使用的时候,一定要确保属性是被初始化过的,通常先调用初始化方法,否则会有异常。
如果只是用lateinit
声明了,但是还没有调用初始化方法就使用,哪怕你判断了该变量是否为null
也是会crash
的:
We’ve added a new reflection API allowing you to check whether a lateinit variable has been initialized:
这里想要判断是否初始化了,需要用isInitialized来判断:
class MyService{
fun performAction(): String = "foo"
}
class Test{
private lateinit var myService: MyService
fun main(){
// 如果 myService 对象还未初始化,则进行初始化
if(!::myService.isInitialized){
println("hha")
myService = MyService()
}
}
}
注意: ::myService.isInitialized可用于判断adapter变量是否已经初始化。虽然语法看上去有点奇怪,但这是固定的写法。::
前缀不能省。::是一个引用运算符,一般用于反射相关的操作中,可以引用属性或者函数。
这里可以写成::myService.isInitialized
或this::myService.isInitialized
。
如果在listener或者内部类中,可以这样写this@OuterClassName::myService.isInitialized
那lateinit有什么用呢? 每次使用还要判断isInitialized。
lateinit的主要用例是当您不能初始化构造函数中的属性,但可以保证它在某种意义上“足够早”初始化时,大多数使用不需要isInitialized检查。例如,因为某个框架调用了一个方法,在构造后立即对其进行初始化。
除了使用lateinit
外还可以使用by lazy {}
效果是一样的:
private val test by lazy { "test" }
private fun switchFragment(position: Int) {
if (test == null) {
Log.e("xoliu", "test is null")
} else {
Log.e("xoliu", "test is not null ${test}")
check(test)
}
}
执行结果:
test is not null test
那lateinit
和by lazy
有什么区别呢?
by lazy{}
只能用在val
类型而lateinit
只能用在var
类型lateinit
不能用在可空的属性上和java
的基本类型上(有默认值),否则会报lateinit
错误另外系统会给lazy属性默认加上同步锁,也就是LazyThreadSafetyMode.SYNCHRONIZED
,它在同一时刻只允许一个线程对lazy属性进行初始化,所以它是线程安全的。
但若你能确认该属性可以并行执行,没有线程安全问题,那么可以给lazy传递LazyThreadSafetyMode.PUBLICATION
参数。
你还可以给lazy传递LazyThreadSafetyMode.NONE
参数,这将不会有任何线程方面的开销,当然也不会有任何线程安全的保证。例如:
val sex: String by lazy(LazyThreadSafetyMode.PUBLICATION) {
// 可以并行执行
if (color == "yellow") "male" else "female"
}
val sex: String by lazy(LazyThreadSafetyMode.NONE) {
// 不做任何线程保证也不会有任何线程开销
if (color == "yellow") "male" else "female"
}
lateinit
来定义不可空类型的变量,可能会在使用时出现null的情况by lazy { }
实现懒加载,可变变量(var修饰)使用改写get方法的形式实现懒加载// 只读变量
private val lazyImmutableValue: String by lazy {
"Hello"
}
// 可变变量
private var lazyValue: Fragment? = null
get() {
if (field == null) {
field = Fragment()
}
return field
}
当您稍后需要在代码中初始化var时,请选择lateinit
,它将被重新分配。当您想要初始化一个val值一次时,特别是当初始化的计算量很大时,请选择by lazy。