一、类的结构器

类结构器的根本写法

//var name:String 这种办法在界说参数的一起把它的类特点界说了,这个参数是类全局可见
//age:Int 只能是结构器内可见(包括init块,特点初始化)
class Person(var name:String,age:Int)
//init块 相似于主结构器的办法体
class Person(var name:String,age:Int){
    var age:Int
    init{
        this.age = age
    }
}

类的副结构器,调查下面代码:

abstract class Animal
//Person后面界说的便是主结构器
class Person(var name:String,var age:Int):Animal(){
    constructor(age:Int):this("Unknown",age){ //这儿调用主结构器确保了结构途径是唯一的
        //界说了主结构器后在类的内部再界说的结构器就被称为副结构器
    }
}

主结构器默许参数:

abstract class Animal
class Person
    @JvmOverloads //此注解能够使主结构器默许参数在java代码中能够以重载的方式调用
    constructor(var name:String = "Unknown",var age:Int)
    :Animal(){
}
//这种写法是Kotlin中比较推荐的写法

不界说主结构器写法:

class Person:Animal{
    var name:String
    var age:Int
    constructor(name:String,age:Int){
        this.name = name
        this.age = age
    }
}

这种写法和Java很像,但Kotlin不推荐这样写,由于会或许有多个结构器形成多条结构途径而添加杂乱度,但为了兼容Java特性,Kotlin仍是允许这样的写法。

二、类与成员的可见性类型介绍

public:和Java相同,是公开可见,可润饰类、成员以及尖端声明。

internal:模块内可见,可润饰类、成员以及尖端声明。

protected:类内及子类可见,润饰成员。

private:类或文件内可见,可润饰类、成员以及尖端声明。

三、类特点的推迟初始化

推迟初始化的原因

  • 类特点有必要要在结构时初始化
  • 某些成员只要在类结构之后才会被初始化运用,如果咱们不推迟初始化,那咱们只能把特点声明为空类型而声明为null,明显这样做会使代码结构十分不好,至少许多状况咱们还需求不断判空。

lateinit关键字

1、lateinit会让编译器疏忽变量的初始化,不支持Int等根本类型;

2、开发者有必要能够在完全确定变量值的生命周期下运用lateinit;

3、不要在杂乱的逻辑中运用lateinit,它会让代码变得更加软弱。

运用事例:在Android开发咱们往往先声明一个控件变量,然后在onCreate中初始化取得

//这儿若不运用lateinit则需求把textView声明为可控类型,后调用有必要要加?符号判空
private lateinit var textView:TextView
override fun onCreate(savedInstanceState: Bundle?){
    super.onCreate(savedInstanceState)
    setContentView(R.layout.act_main)
    textView = findViewById(R.id.textView)
}

lazy办法推迟初始化

//只要在textView初次被访问时才会履行
private val textView by lazy {
    findViewById<TextView>(R.id.textView)
}
override fun onCreate(savedInstanceState: Bundle?){
    super.onCreate(savedInstanceState)
    setContentView(R.layout.act_main)
    textView.text = "Hello Kotlin"
}

这种办法初始化和声明内聚,无需声明可空类型,是推迟初始化最被推崇的办法。

四、署理Delegate

署理是什么?

署理便是A替代B去处理C事情,口语化一点便是我替代你处理它,主体是我、你、它。

咱们先来看看接口署理:接口署理便是目标X替代当时类A完成接口B的办法。调查下面代码:

//声明一个接口Api
interface Api {
    fun a()
    fun b()
    fun c()
}
class ApiWrapper(val api:Api) 
    :Api by api{ //Api by api 便是目标api替代类ApiWrapper完成Api
    override fun c() { //这样就不需求完成a()和b()
        println("Hello Kotlin")
        api.c() //关于目标api的唯一要求便是完成被署理的接口
    }
}

特点署理

特点署理只需求完成getValue和setValue办法即可。

下面咱们看看经过lazy完成的特点署理:

fun main() {
    val person = Person("zhang san")
    println(person.firstName)
}
class Person(val name:String){
    val firstName by lazy { name.split(" ")[0] }
}
打印成果:
zhang

经过lazy能够署理特点是由于lazy原本便是一个函数,返回的是一个lazy目标:

public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)

lazy目标本身便是一个署理,它还完成了getValue办法,所以接口Lazy的实例署理了目标Person的实例的特点firstName的getter:

public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value

下面咱们再举个一般状况的例子方便理解:

//完成一个署理类,完成getter和setter办法
class Delegate{
    //thisRef 是特点所处在的目标 property便是特点
    operator fun getValue(thisRef: Any?, property: KProperty<*>):String{ //String是特点的类型
        return "getValue ${property.name}"
    }
    //当确定了署理特点类 thisRef: Any?能够直接换成 person:Perosn
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("他是${value}公司的")
    }
}
class Person{
    var company:String by Delegate()
}
fun main() {
    val person = Person()
    person.company = "Google"
    println(person.company)
}
打印成果:
他是Google公司的
getValue company

这儿先调用person.company = “Google”赋值了,所以先调用了setValue办法。

observable特点署理

调查下面代码:

class StateManager{
    var state:Int by Delegates.observable(0){ kProperty: KProperty<*>, oldValue: Int, newValue: Int ->
        println("State change from $oldValue to $newValue")
    }
}
fun main() {
    val stateManager = StateManager()
    stateManager.state = 9
}
打印成果:
State change from 0 to 9

Delegates.observable(0)实践上市创立了别的一个目标,这个目标的类型是ObservableProperty<V>,并经过完成ReadWriteProperty<Any?, T> 接口取得getValue和setValue办法,每次设置特点的时候就会去履行lamba表达式的内容,获取到特点是哪一个以及前后改动的值。

五、单例

Kotlin单例的完成运用object关键字

object Singleton{
}

界说单例时,类加载就一起实例化目标了,所以Singleton既是类名也是目标名,object成员的访问和Java的根本无异:

object Singleton{
    var a:Int = 2
    //静态成员润饰
    @JvmStatic var x:Int = 2
    //不生成setter和getter办法
    @JvmField var x:Int = 2
    fun b(){}
}
fun main() { 
    Singleton.a
    Singleton.b()
}

关于Kotlin中生成静态成员直接运用@JvmStatic注解,这个关于Kotlin没有任何影响,主要是Java运用时object的成员直接按照Java静态成员生成字节码,对Kotlin内部运用无任何影响,Java调用object的成员可直接视同调用静态成员一般。这儿要留意是@JvmStatic和 @JvmField这两个注解只能在Java渠道去运用。

一般类的静态成员 companion object

companion object是在类内部的伴生目标,与该一般类同名的object

class Foo{
    companion object{
        //生成的静态特点变量
        @JvmField var a = 1
        @JvmStatic fun xFun(){}
    }
}

该类和办法的写法和下面Java的完成等价

public class Foo {
    public static void xFun(){}
}

object不能界说结构器,可是能够完成init块、类承继和接口。

六、内部类

Kotlin中静态内部类和非静态内部类和Java中的声明不太相同,Kotlin中默许创立的内部类是静态的,运用inner关键字润饰的对错静态内部类

class Outer{
    //默许不运用任何关键字是静态内部类
    class StaticInner{}
    //添加inner润饰后变为非静态内部类
    inner class Inner{}
}

内部类的实例化

fun main(){
    //非静态 内部类实例化 需求先获取外部类的的实例
    val inner = Outer().Inner();
    //静态内部类实例化
    val staticInner = Outer.StaticInner()
}

内部object

class OuterObject{
    //内部object 不存在非静态的状况,故不可用inner润饰,由于object一旦给界说好就实例化了
    object StaticInnerObject{}
}
fun main(){
    //内部object
    val staticInnerObject = OuterObject.StaticInnerObject
}

匿名内部类:
匿名内部类望文生义便是没有名字的内部类,一般的格式写法是:object:类或许接口,实践Android开发中,咱们在监听控件点击国际是的点击回调运用的便是匿名内部类,这种状况咱们一般不会声明一个类创立一个目标。调查下面代码:

textView.setOnClickListener(object: View.OnClickListener{
    override fun onClick(p0: View?) {
        TODO("Not yet implemented")
    }
})

匿名内部类的一般方式如下:

object:Runnable,Cloneable{ //匿名内部类能够承继父类或许完成多个接口
    override fun run() {
    }
}

这儿要留意的是如果匿名内部类引用了外部目标,隐式持有外部类目标,要考虑会不会形成内存走漏的问题,如果匿名内部类界说在静态办法、静态内部类或许尖端函数里边,不必考虑内存走漏的问题。

七、data class数据类

数据类,望文生义便是设计用来存储数据用的,界说数据类只需求在class前面加上data关键字,这儿需求留意的是data class的主结构函数有必要至少有一个参数且一切参数要标记为var或许val,别的数据类不能是笼统的,敞开的、或许是内部的。下面看怎么结构一个data class

data class Book(
    val id: Long,
    val bookName: String,
    val author: Person
)
data class Person(val name: String, val age: Int)

这儿看到Book的结构和咱们界说JavaBean结构十分相似:

public class Book {
    public long id;
    public String name;
    public Person person;
    public static class Person{
        public String name;
        public int age;
    }
}

但两者并不是等价的,便是说Kotlin的data class不能直接当成JavaBean来用。这是为什么呢?

原因是界说在Book主结构器中的特点又被称为component,一切的特点都是根据component来完成,每一个特点都会有一个对应的componentN()办法,(N便是特点顺次对应的1、2、3、4…这儿能够经过Android Studio菜单栏Tools中Kotlin选项中的show Kotlin Bytecode检查字节码能够看到有component1()、component2()和component3(),这些办法都是编译器帮咱们生成的,实践上它还主动生成了 equals/hashCode/toString/copy 等办法),而component是不能够自界说Getter或许Setter的,所以也就不能直接当JavaBean运用了。那么这个componentN()办法的效果是什么的?

componentN()的效果是用于解构的,解构的意思便是把当时这个数据类目标解构成几个变量,解构语法调查下面代码:

fun main(){
    val book = Book(1234,"Kotlin入门与通晓", Person("JetBrains",25))
    //假定我只想要1、3两个方位的变量只需求改成 val(bookId,_,person) = book
    val(bookId,bookName) = book
    println("编号${bookId}的bookName是$bookName")
}
打印成果:
编号1234的bookName是Kotlin入门与通晓

val(bookId,bookName) = book便是解构的语法,它解构的一起声明晰两个变量,顺次把component1()赋给了bookId,component2()赋给了bookName。

数据类的承继关系是只能承继别人,不能被承继,如果要完成被承继以及作为JavaBean这种方式运用,需求借助编译器的插件来完成数据类的无参结构办法以及去掉编译后的final关键字。

NoArg和AllOpen插件

NoArg插件处理的是Kotlin转换成java没有无参结构办法的问题,这个无参结构办法时编译生成的,编译前无法在代码里访问到的。

AllOpen插件处理的是把Kotlin代码转换成java后final去掉的问题,这样能够让数据类被承继完成它的getter/setter办法,从而完成一些所需的事务逻辑。

NoArg和AllOpen插件的运用:

添加依靠,Android项目下的build.gradle:

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'org.jetbrains.kotlin.plugin.noarg' version '1.3.60'
    id 'org.jetbrains.kotlin.plugin.allopen' version '1.3.60'
}
noArg {
    //true 会履行init块代码 默许为false
    invokeInitializers = true
    annotations "com.qisan.kotlinstu.Poko"
}
allOpen {
    annotations "com.qisan.kotlinstu.Poko"
}

界说一个注解:

package com.qisan.kotlinstu
/**
 * Created by QiSan
 * package com.qisan.kotlinstu
 */
annotation class Poko

对数据类添加注解:

@Poko
data class Book(
    val id: Long,
    val bookName: String,
    val author: Person
) {
    init {
        println("我是数据类Book中的init")
    }
}
@Poko
class Person(val name: String, val age: Int)
fun main() {
    //经过反射获取都Book的实例
    val book = Book::class.java.newInstance()
}
打印成果:
我是数据类Book中的init

经过检查运转后生成的字节码能够看到部分final润饰去掉了,compontentN()和copy办法依旧是final润饰,可是这个时候的数据类现已能够被承继:

public class Book {
   private final long id;
   @NotNull
   private final String bookName;
   @NotNull
   private final Person author;
   public long getId() {
      return this.id;
   }
   @NotNull
   public String getBookName() {
      return this.bookName;
   }
   @NotNull
   public Person getAuthor() {
      return this.author;
   }
   public Book(long id, @NotNull String bookName, @NotNull Person author) {
      Intrinsics.checkNotNullParameter(bookName, "bookName");
      Intrinsics.checkNotNullParameter(author, "author");
      super();
      this.id = id;
      this.bookName = bookName;
      this.author = author;
      String var5 = "我是数据类Book中的init";
      System.out.println(var5);
   }
}

八、枚举类

Kotlin中枚举类的声明:

enum class State{
    idle,Busy
}
fun main(){
    //和Java不相同的是直接经过特点访问,Java是办法,其他无异
    println("枚举名字${State.idle.name}")
    println("枚举序号${State.Busy.ordinal}")
}
打印成果:
枚举名字idle
枚举序号1

枚举类界说结构器:

//界说结构器后,每个枚举的实例都需求调用结构器传一个参数进去
enum class State(val id:Int){
    idle(0),Busy(1)
}

枚举类完成接口:

enum class State : Runnable {
    idle, Busy;
    override fun run() {
        TODO("Not yet implemented")
    }
}

也能够每个枚举完成接口办法履行不同操作:

enum class State : Runnable {
    idle{
        override fun run() {
            TODO("Not yet implemented")
        }
    },Busy{
        override fun run() {
            TODO("Not yet implemented")
        }
    }
}
fun main() {
    //调用完成的办法
    State.idle.run()
}

需求留意的是枚举的父类是Enum,所以不能承继其他类。

枚举类的条件分支:

val state:State = State.idle
val value = when(state){
    State.idle -> {0}
    State.Busy -> {1}
}

枚举的区间:

enum class Color{
    White,Red,Green,Blue,Yellow,Black
}
fun main() {
    val colorRange = Color.White ..Color.Yellow
    val colorValue = Color.Green
    println("Green in colorRange:${colorValue in colorRange}")
}
打印成果:
Green in colorRange:true

九、密封类

密封类的概念:

密封类是一种特殊的笼统类,它首先是一个笼统类,其次才是密封类,且它的子类需界说在与自身相同的文件中,密封类的子类个数是有限的,在某种意义上,它们是枚举类的扩展。要声明一个密封类,需求在类名运用sealed润饰符润饰。

需求留意的是:sealed不能润饰 interface ,abstract class(会报 warning,可是不会呈现编译过错)

调查以下代码:

//声明一个密封类
sealed class Expr
//界说了三种状况 Const Sum NotANumber
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
//这儿便是相似Java中的switch...case句子
fun eval(expr: Expr): Double = when (expr) {
    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(expr.e2)
    NotANumber -> Double.NaN
}
fun main() {
    val c1 = Const(2.0)
    val c2 = Const(5.0)
    println("c1 + c2 = ${eval(Sum(c1,c2))}")
}
打印成果:
c1 + c2 = 7.0

十、内联类

inline关键字所润饰的类为内联类。

内联类的概念:

内联类是对某一个类的包装,相似于Java装箱类型的一种,编译器会尽或许运用被包装的类型进行优化。怎么优化呢?有时候,咱们界说一个类,里边只要一个特点和操作该特点的办法,那咱们就能够把它界说成内联类,编译后就能够生成静态办法,经过类名点办法就能够调用,不必创立类实例,这样就不会添加目标存储到JVM堆上,而存储和运用目标实例都会有性能损失,虽然单个来算很少,可是项目叠加起来仍是或许会对代码运转质量带来不少的影响,所以内联类的效果便是节省类创立目标的开支。

内联类有必要含有唯⼀的⼀个特点在主结构函数中初始化,特点需求运用val润饰。在运⾏时,将使⽤这个唯⼀特点来表⽰内联类的实例。内联类能够完成接口,但不能承继父类也不能被承继。

内联类的编译优化举例:

//界说一个内联类
inline class PlayerState(val state: String) {
    fun setPlayState() = when (state) {
        "stop" -> {
            println("play stop")
            0
        }
        "prepare" -> {
            println("play prepare")
            1
        }
        "play" -> {
            println("play start")
            2
        }
        "onPuase" -> {
            println("play onPuase")
            3
        }
        else -> {
            println("play error")
            -1
        }
    }
}
fun main() {
    val state = PlayerState("play")
    println(state.setPlayState())
}
打印成果:
play start
2

咱们检查编译成Java后运转的字节码能够看到:

public final class InlineClassKt {
   public static final void main() {
      String state = PlayerState.constructor-impl("play");
      int var1 = PlayerState.setPlayState-impl(state);
      System.out.println(var1);
   }
}

编译器生成了相似Java中的静态办法,所以整个调用过程没有堆目标的发生,由于都是直接经过PlayerState类直接调用办法,故节省了目标创立的内存消耗。