Kotlin学习 - 类和对象

类定义

使用Java定义一个Person类,代码如下:

public class Person {
    
    
    private final String name;
    private final int age;

    public Person() {
    
           
    }

    ...
}

使用Kotlin来定义这个类

class Person {
    
    
    var name = ""
    var age = 0
    fun printInfo() {
    
    
        println("$name's age is $age")
    }
}

新建对象代码:

fun main() {
    
    
   val person = Person()
   person.name = "Kotlin"
   person.age = 20
   person.printInfo()
}

//输出
Kotlin's age is 20

属性field、setter、getter

针对类中定义的每个属性,Kotlin都会产生一个field,一个getter,和一个setter(只读属性没有setter方法),field用来存储属性性的数据,无法直接定义field。Kotlin会封装field,保护里面的数据,只暴露给gettersetter使用。

Kotlin会提供默认的gettersetter方法,在需要的时候,也可以重新自定义gettersetter方法。代码如下:

class Person {
    
    
    //属性必须赋值除非是可空类型
    var nickName: String? = null
    var age = 0
    
    var name = ""
        set(value) {
    
            	
        	field = value.trim()
        }       
        get() = field.capitalize()
}

对于可空属性的空判断使用

class Person {
    
    
	//属性必须赋值除非是可空类型
	var nickName: String? = null
    var age = 0
    
    var name = ""
        set(value) {
    
    
        	field = value.trim()
        }
        get() = field.capitalize()


    //如果一个函数即可变又可为空,可以在使用前使用also或者let进行判空处理
    fun printHello() {
    
    
   		nickName?.also {
    
    
            println("also - hello $it")
        }
        nickName?.let {
    
    
            print("let - hello $it")
        }
    }
}

关于标准库函数的使用可以看下Kotlin学习 - Kotlin中的标准函数let、with、run、apply、also,看下使用代码:

fun main() {
    
    
	var p = Person()
    p.name = "Lucky"
    println(p.name)
    p.name = " tom"
    println(p.name)
	p.nickName = "kk"
    p.printHello()
}
//运行结果
Lucky
Tom
also - hello kk
let - hello kk

类继承

反编译下上面的Kotlin代码

public final class Person {
    
    
   @NotNull
   private final String name;
   ...
}

Person类前有final修饰符,也就是默不可以被继承,而且有public修饰符,Kotlin的默认访问修饰符就是public

如果我们声明一个新的类想继承Person,就需要借助open关键字。只需在Person类前加上open

open class Person {
    
    
    var name = ""
    var age = 0
    fun printInfo() {
    
    
        println("$name's age is $age")
    }
}

再反编译下:

扫描二维码关注公众号,回复: 15981917 查看本文章
public class Person {
    
    
   @NotNull
   private final String name;
   ...
}

final修饰符没了,新建一个Student类继承Person,代码如下:

class Student : Person(){
    
    
    ...
}

类构造

1、主构造:直接写在类后面,有点像函数的入参,例如上面定义的Person类修改如下。

open class Person constructor(_name: String, val age: Int) {
    
    
    val name: String
	
	init{
    
    
		this.name = _name.capitalize()
	}
	
    fun printInfo() {
    
    
        println("$name 's age is $age")
    }
}

上面的定义中,关键字constructor表示开始构造函数的声明,如果主构造方法没有访问修饰符或注解,可以去掉该关键字。

init引入一个初始化语句块,在类创建的时候执行。

构造函数中可以定义个临时变量,然后在给属性进行赋值,例如:_name为临时变量,内部给成员属性name进行赋值。和Java中的this.name = _name一个意思。也可以直接在构造方法中定义属性,例如:val age: Int。下面是去掉关键字constructor后的代码:

open class Person (_name: String, val age: Int) {
    
    
    val name: String
	
	init{
    
    
		this.name = _name.capitalize()
	}
	
    fun printInfo() {
    
    
        println("$name 's age is $age")
    }
}

由于修改了Person的构造函数,此时Student 报错,因为之前使用的是Person无参构造,上面我们修改了Person的构造,则不存在无参构造了。修改代码如下:

class Student(name: String,  age: Int, val nickName: String, val grade: Int) : Person(name, age){
    
    
   	...
}

2、次构造
任何一个类只能有一个主构造,但是可以有多个次构造。当一个类既有主构造又有次构造函数时,所有的次构造函数都要调用主构造。

定义构造函数的时候还可以给构造函数指定默认值,当用户调用不提供值参的时候,就使用默认值,代码如下:

class Student(name: String,  age: Int, val nickName: String, val grade: Int) : Person(name, age){
    
    
    constructor(name: String, age: Int, nickName: String) : this(name, age, nickName, 8) {
    
    
        
    }
    
    constructor() : this("Default", 12, "Default", 8) {
    
    
        
    }
    ...
}

使用方式:

fun main() {
    
    
    val student1 = Student("lucky", 20, "luck", 9)
    val student2 = Student("Joice", 20, "joo")
    val student3 = Student()
    
    println("${
      
      student1.name} + ${
      
      student1.nickName} + ${
      
      student1.age} + ${
      
      student1.grade}")
    println("${
      
      student2.name} + ${
      
      student2.nickName} + ${
      
      student2.age} + ${
      
      student2.grade}")
    println("${
      
      student3.name} + ${
      
      student3.nickName} + ${
      
      student3.age} + ${
      
      student3.grade}")
}

//运行结果:
Lucky + luck + 20 + 9
Joice + joo + 20 + 8
Default + Default + 12 + 8

3、无主构造

若类不使用主构造,则后续继承类也不需要使用构造即可去掉继承类的(),次构造可以调用父类构造super进行初始化,但是次构造的参数在其他地方无法引用。

open class Person {
    
    
    var name: String = ""
    var age: Int = 0
    
    constructor(_name: String, _age: Int) {
    
    
        this.name = _name
        this.age = _age
    }
}


class Student : Person {
    
    
    constructor(name: String, age: Int, nickName: String) : super(name, age) {
    
    

    }
    
	fun study() {
    
    
        //name,age可使用
        println(name + "is studying")
        //使用nickName则会报错,若nickName是主构造的参数则可引用
        //println(nickName) 报红
    }
}

初始化块

1、init 初始化块

在上面Person 类中,出现了一个init修饰的代码块,这个看起来和Java中的静态代码块很像,其实他们用法差异很大:

  • Java中的static表示的是静态代码块,在类加载的时候就会执行,不是在对象创建的时候执行
  • Kotlin中初始化代码块会在构造类实例的时候执行,即在对象创建的时候执行

初始化代码块作用:可以设置变量或值,及执行有效性检查,例如检查传给某构造函数的值是否有效,看下例子:

open class Person (_name: String, val age: Int) {
    
    
    var name = _name;
    
	init{
    
    
        require(age > 0){
    
    "age must be positive"}
        require(name.isNotBlank()){
    
    "person must be have a name"}
    }
    
	fun printInfo() {
    
    
        println("$name 's age is $age")
    }
}

上面代码就是对构造函数的传入值进行判断,并抛出异常。

2、初始化顺序

属性可以在主构造、次构造、init代码块中执行,那么他们的执行顺序是怎么样的呢:

open class Person (_name: String, val age: Int) {
    
    
    var name = _name;
    
    constructor() : this("Default", 12) {
    
    
        println("执行次构造函数")
    }
    
	init{
    
    
		println("执行init")
        require(age > 0){
    
    "age must be positive"}
        require(name.isNotBlank()){
    
    "person must be have a name"}
    }
    
	fun printInfo() {
    
    
        println("$name 's age is $age")
    }
}

将上面代码转为Java字节码候在反编译,看下:

//主构造函数
public Person(@NotNull String _name, int age) {
    
    
	Intrinsics.checkNotNullParameter(_name, "_name");
	super();
	this.age = age;
	this.name = _name;
	
	String var3 = "执行init";
	boolean var4 = false;
	System.out.println(var3);
	boolean var7 = this.age > 0;
	var4 = false;
    boolean var5 = false;
    boolean var6;
    String var9;
    if (!var7) {
    
    
       var6 = false;
       var9 = "age must be positive";
       throw (Throwable)(new IllegalArgumentException(var9.toString()));
    } else {
    
    
       CharSequence var8 = (CharSequence)this.name;
       var4 = false;
       var7 = !StringsKt.isBlank(var8);
       var4 = false;
       var5 = false;
       if (!var7) {
    
    
          var6 = false;
          var9 = "person must be have a name";
          throw (Throwable)(new IllegalArgumentException(var9.toString()));
       }
   }         
}
//次构造函数
public Person() {
    
    
    this("Default", 12);
    String var1 = "执行次构造函数";
    boolean var2 = false;
    System.out.println(var1);
}

从上面看,有两个构造方法,在次构造中又调用了主构造函数,因此执行顺序是:

1、先赋值的是age,也就是主构造函数中声明的属性

2、然后是类属性name

3、执行init代码块中属性赋值和函数调用

4、次构造函数中的属性赋值和函数调用

参考博客:

Kotlin基础学习-入门篇

猜你喜欢

转载自blog.csdn.net/kongqwesd12/article/details/130973821