【Kotlin】 自学(六)-类型进阶

349 阅读8分钟

类的构造器

基本写法

	class Person constructor(var age: Int, var name: String)

这个constructor可以省略

	class Person(var age: Int, var name: String)

我们这个参数使用var或者val修饰,比如age,它即表示构造器参数,也同时相当于我们定义了一个age成员属性,如果把var或者val去掉,就表示是一个普通的构造参数.用var或者val修饰的属性类内全局可见,如果前面还有public的话,类外也可见,普通的参数只有构造器内可见或者init代码块可见,这个init代码块有点像java中的构造器

	class Person(var age: Int, name: String) {
    var name: String

    init {
        this.name = name
    }

    init {
        this.age = 18
    }

    init {
        this.name = "Hello"
    }
}

init代码块最终会合并执行

我们再看下上面代码,我们定义了一个属性name,如果我们没有在init中初始化,编译器就会报错 就是说在Kotlin中属性必须初始化,在Java中一般属性我们没有初始化的话都会有默认值,但是在Kotlin中需要我们手动初始化值,我们先给它默认个null,我们看下 还是报错,因为我们没有声明name是一个可空类型,如果你一定要给它赋值为null,加个可空就行

	class Person(var age: Int, name: String) {
    var name: String? = null

}

但是你这样做的话你每次在使用这个name的时候都需要注意下,因为它可空.

接下来我们看下类实现继承

	abstract class Animal
	class Person(var age: Int, name: String) : Animal()

继承我们之前说过,使用:,同时调用父类构造器,父类默认构造器这里直接使用()即可 我们还知道我们在类上面声明的构造器是主构造器,我们还可以声明副构造器

	abstract class Animal
	class Person(var age: Int, name: String) : Animal() {
    	//副构造器
    	constructor(age: Int) : this(age, "Hello"){
        
    }
}

这里我们注意下,我们一般建议声明主构造器加默认参数,不声明也不报错,类似Java弄多个构造方法一样

除了使用构造器构造一个类,还可以使用工厂函数

	 val persons = HashMap<String, Person>()
    fun Person(name: String): Person {
        return persons[name] ?: Person(1, name).also { persons[name] = it }
    }

采用了这种写法的就是String

		//调用构造器
	 val str = String()
     //使用工厂函数
    val str1 = String(charArrayOf('1', '2'))

使用的JavaString构造方法

类与成员的可见性

可见性对比

可见性类型JavaKotlin
public公开与Java相同,默认
internalx模块内可见
default包内可见,默认x
protected包内及子类可见类内及子类可见
private类内可见类或文件内可见

这里的模块一般认为是jar包或者aar包

主要说下internaldefault

  • 这俩一般是编写SDK或者公共组件用的比较多,隐藏内部实现细节
  • default可以通过外部创建相同的包名访问,访问控制很弱
  • default会导致不同层次的类合到一个包下
  • internal可方便处理内外隔离,提升模块代码内聚,减少接口暴露
  • internal修饰的类或者成员在Java中可直接访问

举例说明,假设我们有个Api类提供两个方法,这两个方法又是另外两个类提供的,我们应该怎么写呢?我们的前提是外部不允许直接调用那两个类和对应的方法,只能通过我们这个Api获得,我们先看下Java实现

public class ApiJava {
    private CoreApiJavaA coreApiJavaA = new CoreApiJavaA();
    private CoreApiJavaB coreApiJavaB = new CoreApiJavaB();

    public void a(){
        coreApiJavaA.a();
    }

    public void b(){
        coreApiJavaB.b();
    }
}

public class CoreApiJavaA {
    public void a(){

    }
}

public class CoreApiJavaB {
    public void b(){

    }
}

这样肯定不行对吧,使用public修饰的类外部可以直接访问,所以我们需要去掉public修饰 我们发现我们改完之后会报错,因为现在这个CoreApiJavaA和我们的ApiJava没在一个包下

所以我们还需要把这两个类从core包内移出来,和ApiJava一个包下,这样就没问题了

如果说ApiJava里面需要多个实现方法,就需要把那些类全部挪到这个包下,这会让代码看起来不容易看

Kotlin实现就很容易,定义成internal就可以

class ApiKotlin {
    private val coreApiKotlinA = CoreApiKotlinA()
    private val coreApiKotlinB = CoreApiKotlinB()

    fun a(){
        coreApiKotlinA.a()
    }

    fun b(){
        coreApiKotlinB.b()
    }
}

internal class CoreApiKotlinA {
    internal fun a(){
    }
}

internal class CoreApiKotlinB {
    internal fun b(){

    }
}

他们也不需要在一个包下面 但是呢,如果仅定义成这样,在Java代码中还是可以访问到的

直接编译运行也不会运行,因为Java哪认得你这个internal,那这个怎么解决呢?就需要我们另辟蹊径了,我们可以在我们的方法上加上注解

internal class CoreApiKotlinA {
    
    @JvmName("%abc")
    internal fun a(){
    }
}

调用处就会变成

因为%abcjava中不合法,所以不能直接调用

同样我们也可以给我们的属性,构造器,set方法加修饰符

class Foo private constructor(var age: Int)

class Foo1(private var age: Int)

class Foo2 {
    private var age: Int = 0
}

class Foo3 {
    var age: Int = 0
        //set的可见性不能大于属性
        private set
        //不可以,get的可见性必须与属性一致
        //private get
}

类属性的延迟初始化

第一种:可空类型

	private var textView : TextView? = null
    
    override fun onCreate(...){
    	textView = findViewById(R.id.text_view)
        textView?.text="Jack"
    }
    

第二种:使用lateinit

	private lateinit var textView : TextView
    
    override fun onCreate(...){
    	textView = findViewById(R.id.text_view)
        textView.text="Jack"
    }
  • lateinit会让编译器忽略变量初始化,不支持Int等基础类型
  • 一般是确定变量生命周期使用
  • 复杂的逻辑中不要使用

第三种:使用lazy (推荐使用!!!)

	private var textView by lazy {
    	textView = findViewById(R.id.text_view)
    }

代理

  • 接口代理:对象X代替当前类A实现接口B的方法
  • 属性代理:对象X代替属性a实现getter/setter方法

代理的作用主要就是简化代码编写,使用by关键子

接口代理:

首先定义一个接口

	interface Api {
    fun a()
    fun b()
    fun c()
}

然后我们写一个实现类

class ApiImpl : Api {
    override fun a() {}
    override fun b() {}
    override fun c() {}
}

然后我们想在这个基础上增强,包装这个实现类

class ApiWrapper(val api: Api) : Api  {
    override fun a() {
        api.a()
    }

    override fun b() {
        api.b()
    }

    override fun c() {
        println("调用c方法")
        api.c()
    }
}

我们就想改写c方法,但是我们却必须复写a和b,怎么样简化呢?我们就可以使用代理

class ApiWrapper(val api: Api) : Api by api {
    override fun c() {
        println("调用c方法")
        api.c()
    }
}

这个就是对象api代替类ApiWrapper实现接口Api,对象api的要求就是实现Api接口

属性代理

比如上面我们说过的lazy

class Person(val name: String){
    
    val firstName by lazy {
    	name.split(" ")[0]
    }
}

这个lazy相当于代理firstNamegetter

class Foo {
    val x: Int by X()

    var y: Int by X()
}

class X {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
        return 2
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, i: Int) {

    }
}

代理val需要getValue,代理var还需要setValue,这两个方法的参数就这么写就行

使用案例:使用属性代理读写Properties 思路:

1.创建一个类,拿到所有的Properties,同时提供get/set方法
2.创建一个接口
3.创建一个实现类
4.使用

1.第一步

class PropertiesDelegate(private val path: String, private val defaultValue: String = ""){

    private lateinit var url: URL
	
    //获取所有的properties
    private val properties: Properties by lazy {
        val prop = Properties()
        url = try {
            javaClass.getResourceAsStream(path).use {
                prop.load(it)
            }
            javaClass.getResource(path)
        } catch (e: Exception) {
            try {
                ClassLoader.getSystemClassLoader().getResourceAsStream(path).use {
                    prop.load(it)
                }
                ClassLoader.getSystemClassLoader().getResource(path)!!
            } catch (e: Exception) {
                FileInputStream(path).use {
                    prop.load(it)
                }
                URL("file://${File(path).canonicalPath}")
            }
        }

        prop
    }
    
	//get方法
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return properties.getProperty(property.name, defaultValue)
    }
    
	//set方法
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        properties.setProperty(property.name, value)
        File(url.toURI()).outputStream().use {
            properties.store(it, "Hello!!")
        }
    }
}

第二步:

abstract class AbsProperties(path: String){
    protected val prop = PropertiesDelegate(path)
}

第三步:

class Config: AbsProperties("Config.properties"){
    var author by prop
    var version by prop
    var desc by prop
}

第四步:

fun main() {
    val config = Config()
    println(config.author)
    config.author = "Greathfs"
    println(config.author)
}

单例object

Java饿汉式单例写法

public class Singleton {
    public static final Singleton INSTANCE = new Singleton();

}

Kotlin写法

object Singleton{

}

访问object成员

object Singleton {

    var x: Int = 2
    fun y() {}
}

 Singleton.x
 Singleton.y()

静态成员@JvmStatic@JvmField

 @JvmField var x: Int = 2
 @JvmStatic fun y(){ }

普通类的静态成员

class Foo {
    companion object {
        @JvmStatic fun y(){  }
    }
}

等价的Java代码

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

内部类

Java

public class Outer {
    class Inner { }
    //静态内部类需要加static
    static class StaticInner { }
}

Kotlin

class Outer {
    //费静态内部类需要加inner
    inner class Inner
    class StaticInner
}

//用法
val inner = Outer().Inner()
val staticInner = Outer.StaticInner()

匿名内部类 Java

  new Runnable() {
            @Override
            public void run() {

            }
        };

Kotlin

//可以有多个接口
object : Cloneable, Runnable {
        override fun run() {

        }
    }

数据类 data class

就是普通类前面添加data关键字

data class Book(val id: Long,
                val name: String,
                val author: Person) {

}

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

这个和我们java中的JavaBean不太一样,这个类不能被继承,不能有无参构造,同时还提供了component,我们反编译下这个类,看下这个类最终生成什么样

public final class Person {
   private final long id;
   @NotNull
   private final String name;
   private final int age;

   public final long getId() {
      return this.id;
   }

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final int getAge() {
      return this.age;
   }

   public Person(long id, @NotNull String name, int age) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      super();
      this.id = id;
      this.name = name;
      this.age = age;
   }

   public final long component1() {
      return this.id;
   }

   @NotNull
   public final String component2() {
      return this.name;
   }

   public final int component3() {
      return this.age;
   }

   @NotNull
   public final Person copy(long id, @NotNull String name, int age) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      return new Person(id, name, age);
   }

   // $FF: synthetic method
   public static Person copy$default(Person var0, long var1, String var3, int var4, int var5, Object var6) {
      if ((var5 & 1) != 0) {
         var1 = var0.id;
      }

      if ((var5 & 2) != 0) {
         var3 = var0.name;
      }

      if ((var5 & 4) != 0) {
         var4 = var0.age;
      }

      return var0.copy(var1, var3, var4);
   }

   @NotNull
   public String toString() {
      return "Person(id=" + this.id + ", name=" + this.name + ", age=" + this.age + ")";
   }

   public int hashCode() {
      long var10000 = this.id;
      int var1 = (int)(var10000 ^ var10000 >>> 32) * 31;
      String var10001 = this.name;
      return (var1 + (var10001 != null ? var10001.hashCode() : 0)) * 31 + this.age;
   }

   public boolean equals(@Nullable Object var1) {
      if (this != var1) {
         if (var1 instanceof Person) {
            Person var2 = (Person)var1;
            if (this.id == var2.id && Intrinsics.areEqual(this.name, var2.name) && this.age == var2.age) {
               return true;
            }
         }

         return false;
      } else {
         return true;
      }
   }
}

final类型,自动生成了get/set方法,hashCode方法,toString方法,equals方法,copy方法,还有就是几个component,有几个属性就有几个component,我们取属性的值就需要使用

    val book = Book(0, "Kotlin", Person(1, "Jack", 20))
    val id = book.component1()
    val name = book.component2()
    val author = book.component3()

如果想让这个类被继承,或者使用无参构造器,需要使用插件noArgallOpen

枚举类 enum class

枚举定义

Java

 enum State {
        IDLE, BUSY
    }

Kotlin

enum class State {
    IDLE, BUSY
}

枚举属性

名字和序号

Java

  State.IDLE.name();
  State.IDLE.ordinal();

Kotlin

State.IDLE.name
State.IDLE.ordinal

定义构造器

Java

 enum State {
        IDLE(0), BUSY(1);

        int id;

        State(int id) {
            this.id = id;
        }

    }

Kotlin

enum class State(var id: Int) {
    IDLE(0), BUSY(1)
}

实现接口

这里可以统一实现,也可以单个实现 Java

 enum State implements Runnable{
        IDLE(0){
            @Override
            public void run() {
            }
        }
        ,
        BUSY(1){
            @Override
            public void run() {
            }
        }
        ;

        int id;

        State(int id) {
            this.id = id;
        }

        @Override
        public void run() {
            
        }
    }

Koltin

enum class State(var id: Int) :Runnable{
    IDLE(0) {
        override fun run() {
        }
    }, BUSY(1) {
        override fun run() {
        }
    };

    override fun run() {
    }


}

扩展方法

这个Kotlin中才行

fun State.next(): State {
    return State.values().let {
        val nextState = (ordinal + 1) % it.size
        it[nextState]
    }
}

区间

枚举类可以当做区间使用

val state = State.IDLE..State.BUSY
val currentState = State.BUSY
val flag = currentState in state

密封类 sealed class

实际上是一个特殊的抽象类,子类个数固定,构造器私有化

sealed class PlayerState

object Idle : PlayerState()

class Playing(val song: Song) : PlayerState() {
    fun start() {}
    fun stop() {}
}

class Error(val errorInfo: ErrorInfo) : PlayerState() {
    fun recover() {}
}

内联类 inline class

  • 对某一个类型进行包装
  • 类似Java包装类
  • 编译器会尽可能使用被包装的类型进行优化
inline class BoxInt(val value: Int)

使用inline,主构造器必须是一个成员,不能私有化,必须使用val修饰

基本上里面只能定义方法

模拟枚举

inline class State(val ordinal: Int) {
    companion object {
        val Idle = State(0)
        val Busy = State(1)
    }

    fun values() = arrayOf(Idle, Busy)

    val name: String
        get() = when (this) {
            State.Idle -> "Idle"
            State.Busy -> "Busy"
            else -> throw  IllegalArgumentException()
        }
}