类的属性的作用其实就是用于保存该类对象的状态数据的。kotlin中如果不写任何修饰符那么这个属性的访问权限默认为public的。类的属性不需要我们编写setter和getter方法,需要自定义的情况除外。
class Person {
var name: String = ""
var age: Int = 0
lateinit var address:String
}
kotlin规定类的属性必须要有初始化器,简单的说就是要有初始化值,如果你不希望属性一上来就初始化可以使用lateinit这个关键字。lateinit
这个关键字表示延迟初始化。
kotlin中用lateInit来解决属性的延时初始化。使用lateInit的关键修饰的属性,可以不在定义属性时或在构造器中指定初始化值。
inline关键字可以修饰没有幕后字段的属性,至于什么时候没有幕后字段大家可以去看《幕后字段和幕后属性》这篇文章。inline可以直接修饰这个属性(相当于同时修饰getter和setter方法了)或者修饰getter和setter中任意一个。
示例代码如下:
class Name(name:String,desc:String){
var name=name
var desc=desc
}
class Product{
var productor:String?=null
//inline修饰getter方法,表明读取时会内联化
val proName:Name
inline get()= Name("guojingbu","非常棒!!")
//inline修饰了setter方法,表明设置值得时候会内联化
var author:Name
get() = Name("guojingbu","")
inline set(value) {
this.productor=value.name
}
//inline直接修饰属性,表明读取和设置属性值时都是内联化。
inline var pubHouse:Name
get() = Name("中国人民出版社","无")
set(value) {
this.productor=value.name
}
}
在 Kotlin 中的一个类可以有0~1个主构造函数以及一个或多个次构造函数。
主构造函数是类头的一部分:它跟在类名(与类型参数)后
class Person constructor(name:String,age:Int) {}
如果主构造函数没有任何注解或者可见性修饰符,constructor
关键字可以省略。
class Person (name:String,age:Int){}
主构造函数虽然不能定义执行体,但是可以定义一些形参,这些形参可以在属性声明、初始化块中使用。如下代码:
class Person (a:String,b:Int) {
var name: String =a
var age: Int = b
init {
this.name=a
this.age=b
}
}
kotlin中初始化块通过init关键字来标识,初始化块可以有多个,在实例初始化时按照它们的出现顺序执行。代码如下:
class Person (name:String) {
val firstProperty="firstProperty = $name".also(::println)
init {
println("我是初始化器1")
}
val secondProperty="secondProperty= ${name}.length".also(::println)
init {
println("我是初始化器2")
}
}
上面的代码打印的结果如下:
firstProperty = guojingbu
我是初始化器1
secondProperty= 9
我是初始化器2
从打印结果我们可以看出属性的初始化和初始化块是按顺序执行的,我们不能在前面的初始化块中给后面声明的属性进行初始化。
constructor
来实现,代码如下:class Person3{
var name: String=""
constructor(name: String){
this.name = name
}
}
class Person3(){
var name: String=""
var age:Int=0
constructor(name: String):this(){
this.name = name
}
constructor(name: String, age: Int) : this(name) {}
}
**类属性的初始化和类的初始化器都属于主构造函数的一部分,**所以会在次构造之前执行,代码如下:
class Person3(){
var name: String="属性初始化了----".also(::println)
constructor(name: String):this(){
println("次构造执行了----")
this.name = name
}
init {
println("初始化器执行了----")
}
}
打印结果如下:
属性初始化了----
初始化器执行了----
次构造执行了----
在kotlin中所有的类没有指定超类的类默认超类是Any。代码如下:
fun main(args:Array<String>){
println("Example is Any = ${Example() is Any}")
}
class Example
输出:
Example is Any = true
Kotlin中的类默认是final类型的,想要被继承,得用“open”关键字修饰。
open class Animal{}
class Dog: Animal() {
}
子类的所有构造方法必须直接或间接调用一个父类的构造方法
open class Animal {
constructor(name: String) {
}
}
class Dog : Animal {
constructor(name: String) : super(name) {
}
constructor(name: String,age: Int):this(name){
}
}
kotlin中能被子类覆盖的方法必须要用open
关键字修饰,代码如下:
open class Shape {
open fun draw() {
}
}
class Circle() : Shape() {
override fun draw() {
}
}
方法的重写基本和java的规则一致,如果不希望父类的方法被子类重写就不要用open
关键字修饰方法。kotlin默认类的方法和属性都是final
的。
覆盖要遵循两同两小一大规则:
kotlin中属性的覆盖和类成员方法的覆盖是相似的,同样是需要用open
关键字来修饰属性。父类中val
关键字修饰的属性,可以在子类中覆写为var
关键字修饰,反之则不能。代码如下:
open class Shape {
open val sideSize:Int=0
}
class Circle() : Shape() {
override var sideSize:Int=1
}
注意:子类成员的访问权限可以比父类的大不能比父类的小。
子类要调用父类的方法和属性可以通过super
关键字。代码如下:
open class Shape {
open val name: String = "Shape"
open fun draw() {}
}
open class Rectangle : Shape() {
override var name: String = super.name
override fun draw() {
super.draw()
}
}
内部类可以调用外部类父类的方法使用"super@外部类名称"
的方式。代码实例如下:
open class Parent {
open fun method() { println("Parent.method") }
}
class Child:Parent(){
inner class Inner{
fun test(){
super@Child.method()
}
}
}
当继承的父类和实现的接口中出现了相同的方法时,可以使用super<类名>的形式来指定访问哪个方法。示例代码如下:
interface Action {
fun eat() {
println("Action")
}
}
open class Animal {
open fun eat() {
println("Animal")
}
}
class Dog() : Animal(), Action {
override fun eat() {
super<Action>.eat()
super<Animal>.eat()
}
}
包名.函数名()
,而类中的方法通过实例.方法名()
。我们一般把类外面的定义的方法称之为函数而把类中定义的方法称之为方法。他们的定义语法相同,他们之间也是可以转换的比如下面的列子:
//方法与函数的关系
class Dog{
fun eat(food:String){
println("eat---正在吃饭: $food")
}
fun run(){
println("run---开始跑步")
}
}
fun main(args:Array<String>) {
//将Dog类的eat方法赋值给变量et
//系统会自动推断出变量et的类型是(Dog,String)->Unit
var et=Dog::eat
//将Dog类的run方法赋值给变量rn
//变量rn的类型应该是(Dog) -> Unit
var rn: (Dog) -> Unit =Dog::run
}
从上面的实例可以看出一下几点:
类名::方法名
用infix
修饰有一个参数的类方法可以使用中缀表示法调用,类似于双目运算符。
实例如下:
class ApplePack(weight:Int){
var weight = weight
override fun toString(): String {
return "ApplePack:[weight = $weight]"
}
}
class Apple(weight: Int){
var weight=weight
infix fun add(other:Apple):ApplePack{
return ApplePack(this.weight+other.weight)
}
}
//调用
fun main(args:Array<String>) {
val origin = Apple(10)
var applePack:ApplePack = origin add Apple(5)
println(applePack.toString())
}
使用中缀表示法调用的条件:
kotlin中允许把一个对象的N个属性”解构“给多个变量,写法如下:
var(name,age,address)= user
上面这行代码的意思是把user对象里面的属性值赋值给name,age,address变量。
kotlin中通过在类中定义operator关键字修饰componentN()方法来实现对象的解构,实例代码如下:
class User(name:String,age:Int,address:String){
var name = name
var age=age
var address=address
operator fun component1():String{
return this.name
}
operator fun component2():Int{
return this.age
}
operator fun component3():String{
return this.address
}
}
//使用
fun main(args:Array<String>) {
val user = User("guojingbu", 20, "北京")
var(name,age,address)= user
println("name = $name age = $age address= $address")
}
如果我们想忽略对象的一部分属性可以使用”_“占位,代码如下:
val user = User("guojingbu", 20, "北京")
var(_,age2,address2)=user
println(" age2 = $age2 address2= $address2")
注意:解构方法的名字必须是以component+数字命名,否则会报错
kotlin中Map就支持解构语句,比如我们可以如下的代码来遍历map集合:
for((key,value) in map){
}
kotlin中导包的规则基本与java一致,kotlin的import语句支持as关键字为导入类取一个别名。主要的目的是解决导入多个不同包同名的类。
示例代码:
import java.util.Date
import java.sql.Date as SDate
fun main(args:Array<String>) {
var date = Date(System.currentTimeMillis())
var sdate =SDate(System.currentTimeMillis())
}
as:是不安全的强制类型转换符,如果转型失败,程序会报ClassCastException异常
as?:安全的强制类型转换符,如果转型失败,程序不会发生异常,而是直接返回null。