1、回顾下属性和字段的区别
java 中的属性(property),通常可以理解为get和set方法;
而字段(field)通常叫做“类成员”或者“类成员变量”,有时也叫“域”,可以理解为“数据成员”,用来承载数据的。
这两个概念是完全不同的。
1.1 字段(field)
类成员(字段),通常在类中定义的类成员变量,例如:
public class A{
private String s = "12";
}
我们可以说 A类中有一个成员变量叫做 s 或者 A 类中有一个字段 s。
字段一般用来承载数据,所以为了安全性,一般定义为私有的。
字段和常量描述了类的数据(域),当这些数据的某些部分不允许外界访问时,根据“对象封装”的原则,应尽量避免将一个类型的字段以公有方式提供给外部。除了 final 修饰的常量。
一般将其设置为 private 类型。既然为私有,那外界怎么访问呢? 当然是通过Java的属性方法!
1.2 属性(property)
属性只局限于类中方法的声明,并不与类中其他成员相关,属于JavaBean的范畴。例如:
void setA(String s){
a = s;
}
String getA(){
return a
}
当一个类中拥有这样一对方法时,我们可以说,这个类中拥有一个可读写的 a 属性。如果去掉了set 方法,则是可读属性,反之亦然。
其规则是: 去掉 get 或 set 后剩余的字符串,如果第二个字母是小写的,则把第一个字母也变成小写
getAge —-》 age
getCPU —》 CPU
更形象的说就是:属性是对字段的封装,供外部访问。
通常属性将相应的私有字段通过封装成公共属性,以便于外界访问和修改。当然你在封装成属性时,也可以设置该属性为只读,可读写等等权限。
在Kotlin中,其含义和 在Java 中是一样的。
2、Kotlin 属性和字段
2.1 声明属性
通过 var 声明可变属性,val 声明只读属性。
q1: 为什么叫属性而不是字段?
a1: Kotlin 类中声明的变量,都会提供默认的 get、set(val 没有该方法)方法,所以声明的都是属性。
Kotlin中没有字段,只有幕后字段(backing field).
声明的时候基本都采用以下形式:
class Person {
// 需要进行初始化,否则提示错误
var name: String? = null
var address: String = "上海市"
var age: Int? = null
val sex: String = "male"
}
2.2 属性的使用
使用属性,只需要使用名称引用即可:
val person = Person()
person.name = "李四" // 默认调用 set
println(person.name) // 默认调用 get,输出 “李四”
注: 对属性的访问,并不是像Java直接访问属性的本身,而是默认调用了 get 和 set 方法。
3、Getters 和 Setters
完整的属性声明如下:
[属性修饰符 (var 、 val)][属性名称][: ][属性类型] = [初始化]
[getter]
[setter]
对于【初始化】和【getter】和【setter】都是可选的,如果通过【初始化】的值或者【getter】可以推断出类型,则【类型】也可以省略。
只读属性和可变属性的区别:
- 只读属性使用
val
声明,可变属性使用var
声明 - 只读属性不允许 setter
q2: 如何自定义属性的访问方法,是否会和默认的 setter 或者 getter 冲突?
a2: 见 第 4 点。
4、自定义Getters 和 Setters
Getters 和 Setters 的 编写不同一般的方法,在属性声明内部编写。例如:
Getters:
var name: String? = null
get(){
// 重写get方法
return "lisi"
}
// 调用
val person = Person()
person.name = "李四" // 默认调用 set
println(person.name) // 默认调用 get,输出“lisi”
此处的 name 值一直都是 “lisi”, 无论 name 设置成什么值。
Setters:
set(value){
// 这是错误的写法,会导致循环调用
name = value
}
错误原因:当对属性赋值时就会调用 set 方法,当获取属性的值时 就会 调用 get 方法。所以会一直循环调用 set 方法。
正确的写法是:
set(value){
field = value
}
说明:
- value 是 setter 的参数,其类型同于属性的类型,也可以换成别的名字。
- field 就是之前提到的 幕后字段 ,用于将真正的值赋给属性,而不会导致循环调用。它只能在属性的访问器中使用。这个也是可选项,有的时候必须(比如 setter),有时不必须(比如 getter).
如果该 幕后字段 还不能满足要求,也可以使用 幕后属性。如:
private var _table: HashMap<String,String>? = null // 私有属性
var table: HashMap<String,String>? = null
get(){
if(_table == null){
_table = HashMap<String,String>()
}
return _table // 返回私有属性
}
上面的代码需要特殊说明一下,当属性被定义为private后 其Getter和Setter,都是私有的,外部都不可以访问也就是说 person._table 是不允许的。也就进行了隐藏。又因为默认的Setter和Getter调用私有属性会被进行优化,所以不会引入函数调用开销。
5、延迟初始化属性
对于非空属性,在声明时必须对其进行初始化,如果想进行延迟初始化,可以使用 lateinit 关键字,代码举例:
lateinit var name: String
使用 lateinit
关键字修饰的前提条件:
- 针对非空属性。反例:lateinit var name: String?
- 在类体中声明而不是在方法中声明的属性
- 没有自定义 Setter 或者 Getter
- 不能是原生类型(String 可以,它不是原生类型)
如果在初始前访问lateinit
定义的属性会抛出特定异常,指明该属性没有被初始化。
6、 编译期常量(const 修饰符)
在kotlin中已知值的属性可以使用 const 标记 为 编译期常量。
6.1 const 用法
在kotlin中的顶级属性,会以getter(val 和 var)/setter(var才有)的形式暴露给Java,如果你想让其以public static final的字段呈现给调用者,可以在var 或者val前面加上const修饰符。
使用 const
关键字修饰的前提条件:
- const只能修饰val,不能修饰var
正确:const val testName = "ZhangSan"
报错:const var testName = "ZhangSan"
- 只能用在顶级属性,以及object对象的属性中(伴随对象也是object)
class Person {
// 错误
const val name: String = ""
// companion object
companion object {
// 正确
const val PERSON_TAG: String = "person"
}
}
//正确
const val name: String = ""
//object 正确
object myObject {
const val constNameObject: String = "constNameObject"
val nameObject: String = "nameObject"
}
- 没有自定义Getter
// 错误,不能自定义get
const val name: String = ""
get
get(){
}
- 用String或者原生类型值初始化
// 错误
const val name: String? = ""
// 正确
const val name: String = ""
// 错误
const val age: Int? = 0
// 正确
const val age: Int = 0
- 如果经过
const
修饰的字符串属性,去拼接另一个属性,那么这个属性也必须是经过const
修饰的,否则会报错。
const val age: Int = 10
// 普通属性也可拼接 const 属性
val normalName: String = "Jack $age"
// const 属性必须拼接 const 属性
const val appendName: String = "append name $age"
// const 属性不能拼接 非 const 属性,这个会报错
const val appendName: String = "append name $normalName"
6.2 在java中 和 Kotlin中引用比较
新建一个 Kotlin 文件 Person.kt
const val age: Int = 10
val normalName: String = "Jack is $age years old"
const val constName: String = "Jack is $age years old"
//object
object myObject {
const val constNameObject: String = "constNameObject"
val nameObject: String = "nameObject"
}
class Person {
companion object {
const val constNameCompanionObject: String = "constNameCompanionObject"
val nameCompanionObject: String = "nameCompanionObject"
}
}
在kotlin中使用
fun main(args: Array<String>) {
println(constName)
println(normalName)
println(myObject.constNameObject)
println(myObject.nameObject)
println(Person.constNameCompanionObject)
println(Person.nameCompanionObject)
}
在java 中调用
public class TestJava {
public static void main(String[] args) {
System.out.println(PersonKt.constName);
System.out.println(PersonKt.getNormalName());
System.out.println(myObject.constNameObject);
System.out.println(myObject.INSTANCE.getNameObject());
System.out.println(Person.constNameCompanionObject);
System.out.println(Person.Companion.getNameCompanionObject());
}
由此可见,针对纯Kotlin开发来说,有没有const
修饰 没有什么影响;
针对和 java 混合式开发来说,有以下说明:
上述在 Kotlin中定义的属性,编译成 java 后 都变成了 static final 的,加 const 的是 public static final,没加const 的 是 private static final ,也就是说,const 的作用 就是把此处默认的 private 变成了public 。