Kotlin入门学习记录
2023/12/02 - 2023/12/03
Class
KT中默认关键字为Java中的public final
,并且添加属性时会自动添加getter
和setter
方法,代码实现如下
class Player{
var name = "LY_C"
}
在Jvm字节码中转为Java,如下
public final class Player {
@NotNull
private String name = "LY_C";
@NotNull
public final String getName() {
return this.name;
}
public final void setName(@NotNull String var1) {
Intrinsics.checkNotNullParameter(var1, "<set-?>");
this.name = var1;
}
}
在修改name
属性值时,默认是使用setter
函数,代码使用如下
fun main() {
var player = Player()
player.name = "CRY"
}
在Jvm字节码中转为Java,如下
public static final void main() {
Player player = new Player();
player.setName("CRY");
}
field关键字
在声明属性时KT会添加field
关键字,该关键字指的是某个属性,KT会封装属性给field
保护属性,只暴漏给getter
和setter
函数,代码实现如下
class Player{
var name = "LY_C"
get() = field.capitalize()
set(value) {
field = value.trim()
}
}
在Jvm字节码中转为Java,如下
public final class Player {
@NotNull
private String name = "LY_C";
@NotNull
public final String getName() {
return StringsKt.capitalize(this.name);
}
public final void setName(@NotNull String value) {
Intrinsics.checkNotNullParameter(value, "value");
this.name = StringsKt.trim((CharSequence)value).toString();
}
}
getter
在获取属性值时使用的是getter
函数,代码实现如下
fun main() {
var player = Player()
player.name = "cry"
println(player.name)//Cry
}
在Jvm字节码中转为Java,如下
public static final void main() {
Player player = new Player();
player.setName("cry");
String var1 = player.getName();
System.out.println(var1);
}
计算属性
计算属性是使用getter
或setter
覆盖定义一个属性,可以不使用field
,代码实现如下
class Player{
val age
get() = (0..100).shuffled().first()
}
fun main() {
var player = Player()
println(player.age)
}
防静态竞争
如果一个属性是var
又是可空类型的,调用属性时需要使用空安全操作符
主构造体
KT中的主构造体直接写道类名之后,类似于Java中的record
类,不加var
和val
可以声明临时变量,临时变量名一般命名为_
开头,代码实现如下
class Player(_name:String,_age:Int){
var name = _name
get() = field.capitalize()
var age = _age
}
fun main() {
var player = Player("cRY",21)
println(player.name)//CRY
}
Javarecord
代码实现如下
public record Player(String name,int age) {
@Override
public String name() {
return _name.toUpperCase();
}
}
class _Main{
public static void main(String[] args) {
Player player = new Player("cRY", 21);
System.out.println(player.name());//CRY
}
}
次构造函数
在类中使用constructor
关键字,次构造函数要么直接调用主构造函数(传入它需要的值参),要么通过调用另一个次构造函数间接调用主构造函数,this
就是在调用当前类的主构造函数,代码实现如下
class Player(_sex: String,_name:String, _age:Int){
constructor(_sex:String):this(_sex, _name = "LY_C", _age = 21 )
var sex = _sex
var name = _name
get() = field.capitalize()
var age = _age
}
fun main() {
var player = Player("female","cRY",21)
println(player.sex + player.age + player.name)//female21CRY
var player1 = Player("male")
println(player1.sex + player1.age + player1.name)//male21LY_C
}
可以定义多个次构造函数,也可以在次构造函数中写初始化代码逻辑,代码实现如下
class Player(_sex: String,_name:String, _age:Int){
constructor(_sex:String):this(_sex, _name = "LY_C", _age = 21 )
constructor(_sex: String,_age: Int):this(_sex, _name = "LY_C",_age){
this.age += 1
}
var sex = _sex
var name = _name
get() = field.capitalize()
var age = _age
}
fun main() {
var player1 = Player("male")
println(player1.sex + player1.age + player1.name)//male21LY_C
var player2 = Player("male",20)
println(player2.sex + player2.age + player2.name)//male21LY_C
}
初始化块
在类中使用init
关键字,完成初始化块的编写,一般结合require
实现对数据的检查,代码实现如下
注意
require
判断条件是反逻辑,如果为false
会抛出IllegalArgumentException
异常
与Java中的静态代码块的区别是Java的静态代码块在类加载时执行,
而KT的init
在构造函数中执行
class Player(_sex: String,_name:String, _age:Int){
init {
require(_age > 0){"年龄必须大于0"}
require(_name.isNotBlank(),{"姓名不能为空串"})
}
...
}
Java中的静态代码块实现如下
public class Player{
static {
System.out.println("静态代码块");
}
...
}
初始化顺序
KT:主构造体声明的属性 -> 类级别属性赋值 -> init
初始化代码块 -> 次构造体和函数调用
//1.age
class KT_Student(_name:String, var age:Int) {
//2.name,scorce,hobby
var name = _name
var scorce = 10
private val hobby = "music"
val subject:String
//3.subject
init {
subject = "Kotlin编程权威指南"
println("initing")
}
//4.name,age,scorce
constructor(_name:String):this(_name,21){
scorce = 90
}
}
fun main() {
KT_Student("LY_C")
}
Java:静态变量和静态代码块 -> 类级变量和初始化块 -> 构造器
注意
静态变量和静态初始化块,在类加载时初始化,只初始化一次
public class Java_Student {
//1.name
static String name = "LY_C";
//1.static{}
static {
}
//2.sex,age,{}
String sex = "male";
int age = 21;
{
}
//3.name,sex,age
public Java_Student(String name, String sex, int age) {
Java_Student.name = name;
this.sex = sex;
this.age = age;
}
}
惰性初始化
使用lateinit
完成延迟初始化,即告诉编译器在赋值时初始化,可以使用反射isLateinit
判断是否初始化完成
注意
lateinit
只能和var
一块使用,并且必须指定类型,代码实现如下
class KT_Student(var name:String, var age:Int) {
lateinit var subject:String
init {
subject = "Kotlin编程权威指南"
}
override fun toString() = "${name} ${age} ${subject}" +
if (::subject.isLateinit) "初始化完成" else "未初始化"
}
fun main() {
var student = KT_Student("LY_C", 21)
println(student)//LY_C 21 Kotlin编程权威指南初始化完成
}
延迟初始化
使用by lazy
关键字完成延迟初始化,当首次使用才进行初始化,代码实现如下
class KT_Student(var name:String, var age:Int) {
val id by lazy { loadId() }
fun loadId():Int{
print("初始化")
return 1
}
}
fun main() {
var student = KT_Student("LY_C", 21)
print(student.id)//初始化1
}
初始化陷阱
本质上是代码顺序的问题
- 在使用初始化代码块时,一定要保证块内的变量已经完成初始化,以下为错误演示
class KT_Init{
init {
val bloodBonus = blood.times(3)
}
var blood = 100
}
- 编译器通过但会出现空指针异常,以下为错误演示
class KT_Init{
val name:String
private fun firstLetter() = name[0]
init {
println(firstLetter())//没有初始化就被调用
name = "LY_C"
}
}
- 在用
InitName
函数初始化initName
时,name
属性应该已完成初始化,但实际并非如此
class KT_Init(_name:String){
val initName = InitName()
val name = _name
private fun InitName() = name;
}
fun main() {
var ktInit = KT_Init("LY_C")
}