1,类的构造器
当我们定义一个Person类时
class Person(var name:String,age:Int)
- 带 var/val 字段的属性 是类内全局可见的
- 而后面age 不带var/val 该属性仅在构造器内可见(init块,属性初始化)
上面的定义相当于
class Person(var name: String, age: Int) {
var age = 0
init {
this.age = age
println(this.age)
}
}
<1>init块
- init块跟Java中的构造块基本类似,唯一的区别是Kotlin中init块中能访问到构造函数中的不带var/val 的变量
例如
class Person(var name: String, age: Int) {
var age = 0
init {
this.age = age
println(this.age)
}
var testa=3
init {
this.age=testa
println(this.age)
}
}
他们会一起编译到函数的构造函数中一起执行
<2>类的属性必须初始化
Kotlin类的属性必须初始化,类的生命周期和属性的相同,这样使用者在调用类的属性时不会有负担
<3>类的继承
子类继承父类必须调用父类的构造方法
abstract class Anim
class Pig(var name: String):Anim(){
//必须调用父类构造函数
}
<4>副构造器
- 副构造器:定义在主构造器后在类内部定义的构造器都被成为福构造器
- 副构造器必须调用到主构造器,确保构造路径唯一性
abstract class Anim
class Pig(var name: String) : Anim() {
//必须调用父类构造函数
var age = 0;
constructor(age: Int) : this("fff") {
this.age = age
}
}
不定义主构造器(不推荐)
abstract class Anim
class Dog : Anim{
var name:String
var age:Int
constructor(name: String,age:Int):super(){
this.name=name
this.age= age
}
}
一般情况下有这种情况的,比较推荐使用:主构造器+默认参数的形式
<5>工厂函数
fun main() {
//Person的工厂方法
Person("1111111")
//String中系统的工厂方法
String(charArrayOf('1'))
//自定义String
String(IntArray(2){ it })
}
val persons=HashMap<String,Person>()
fun Person(name: String):Person{
//缓存person对象
return persons[name]?:Person(name,25).also { persons[name] = it }
}
fun String(ints:IntArray):String{
return ints.contentToString()
}
工厂函数和扩展方法:个人理解扩展方法的场景更大一些,而工厂函数的意义就是为了创建该类的对象,扩展方法可以返回该类对象也可以返回为Unit
2,类与成员的可见性
<1>模块的概念
- Intelij IDEA 模块
- Maven工程
- Gradle SourceSet
- Ant 任务中一次调用<kotlinc>的文件
- 直观的讲模块的可以认为是一个jar包,一个aar包,通常一个jar包或者aar包里面所有的源文件会一次通过<kotlinc>通过编译
<2> internal VS default
- 一般由SDK或者公共组件开发者用于隐藏内部实现细节
- default 可通过外部创建相同的包名进行访问,访问控制比较弱
- default 会导致不同抽象层次的类聚集到相同的包之下
- internal 可方便处理内外隔离,提升模块代码内聚减少接口暴露
- internal 修饰的Kotlin类或者成员在Java中可直接访问
针对Kotlin中被internal修饰的类能被Java访问,可以通过一些特殊手段让其不能访问,例如下例:
internal class CoreApiKotlinA {
@JvmName("%abcd")
internal fun a(){
println("Hello A")
}
}
<3>构造器可见性
class Cat :Anim {
var name: String
var age: Int
private constructor(name: String, age: Int):super() {
this.name = name
this.age = age
}
}
可用于单例
<4>类的属性可见性
class Half (private var name: String,var age: Int){
private var sex:Boolean=true
init {
println("$name---$age")
}
}
//只能访问到age和sex
var half = Half("111", 2)
half.age=11
属性的getter和setter
- 属性的getter必须和属性的可见 一致
下例中 sex是public 所以不能把getter设置为private
class Half (private var name: String,var age: Int){
var sex:Boolean=true
private set
//private get
}
- 属性的setter可见性不得大于属性的可见性
class Half(private var name: String, var age: Int) {
private var firstName: String = ""
// public set
}
<5>顶级声明的可见性(Kotlin独有)
- 顶级声明指文件内直接定义的属性,函数,类等
- 顶级声明不支持protect
- 顶级声明被private修饰表示文件内可见
3,类属性的延迟初始化
为什么要延迟初始化?
- 类属性必须在构造时进行初始化
- 某些成员只有在类初始化后才会别初始化
class Beer(var name: String,var age: Int){
lateinit var firstName:String
}
常见的例子有:在Android中控件的初始化是在Activity的onCreate()方法中才能初始化,可以使用lateinit进行延迟初始化
lateinit的注意事项
- lateinit 会让编译器忽略变量的初始化,不支持Int等基本类型
- 开发者必须能够在完全确定变量的生命周期下使用lateinit
- 不要在复杂的逻辑中使用lateinit,它只会让代码变得更加脆弱
- Kotlin在1.2引入判断lateinit属性是否初始化的api 最好不要用
lazy
4,代理 Delegate
接口代理
对象X 代替当前类A 实现接口B的方法
我 代替 你 处理了 它
属性代理
对象X代替属性a实现getter/setter方法
<1>接口代理
定义接口
interface InterFace {
fun funA()
fun funB()
fun funC()
}
定义增强接口类
class InterfaceWrapper(var inter:InterFace):InterFace{
override fun funA() {
inter.funA()
}
override fun funB() {
inter.funB()
}
override fun funC() {
println("---------------")
inter.funC()
}
代理形式
class InterWrapper (private val inter: InterFace):InterFace by inter{
override fun funC() {
println("---------------")
}
}
接口 inter 代理 InterWrapper 实现 接口 InterFace 的方法
对于对象inter的唯一要求就是实现被代理的接口,因为如果inter没有实现接口方法的话,就不具有代理的能力
小案例 创建一个SupportArray
class SupperArray<E>(
private val list: MutableList<E?> = ArrayList(),
private val map: MutableMap<String, E> = HashMap()
) : MutableList<E?> by list, MutableMap<String, E> by map {
override fun isEmpty(): Boolean {
return list.isEmpty() && map.isEmpty()
}
override fun clear() {
list.clear()
map.clear()
}
override val size: Int
get() = list.size + map.size
override fun set(index: Int, element: E?): E? {
if (list.size <= index) {
repeat(index - list.size + 1) {
list.add(null)
}
}
return list.set(index, element)
}
override fun toString(): String {
return "list:${list};map:${map}"
}
}
SupportArray
- 实现 MutableList 被list代理了接口的实现
- 实现MutableMap 被map代理了接口的实现
使用SupperArray
fun main() {
val supperArray = SupperArray<String>()
supperArray.add("Hello")
supperArray["Hello"] = "wwwwwwwwww"
supperArray["Word"] = "fffffffffffff"
supperArray[4] = "$$$$$$$$$"
println(supperArray)
}
输出
list:[Hello, null, null, null, $$$$$$$$$];
map:{Word=fffffffffffff, Hello=wwwwwwwwww}
<2>属性代理
getter方法代理
class PersonNow(var sex: Boolean) {
val firstName: String by lazy {
if (sex)
"nan"
else
"nv"
}
}
源码分析
--lazy方法
public actual fun <T> lazy(initializer: () -> T): Lazy<T> =
SynchronizedLazyImpl(initializer)
--Lazy.kt
@kotlin.internal.InlineOnly
public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?,
property: KProperty<*>): T = value
可以看到Lazy定义的方法 getValue
getValue(thisRef: Any?, property: KProperty<*>)
要想代理属性的getter需要重写该方法
setter方法代理
对于setter方法,其代理可分为两步:开始设置和设置之后
Delegates.observable代理设置之后
class Manger {
var state:Int by Delegates.observable(0){
property, oldValue, newValue ->
println("$oldValue,$newValue")
}
}
Delegates.vetoable代理设置之前
class Mantou {
var ss: Int by Delegates.vetoable(0) { property, oldValue, newValue ->
println("$oldValue,$newValue")
newValue % 2 == 0
}
}
调用
fun main() {
val mantou = Mantou()
mantou.ss = 1
mantou.ss = 2
mantou.ss = 3
mantou.ss = 4
}
结果
0,1
0,2
2,3
2,4
Delegates.vetoable 给定初始化值后其结果返回值为Boolean,如果返回true表示允许本次对属性的设置,属性的值为newValue,返回false表示拦截此次设置值,属性的值仍然为oldValue
源码得知
Delegate.observable 返回 ReadWriteProperty
public interface ReadWriteProperty<in R, T> {
public operator fun getValue(thisRef: R, property: KProperty<*>): T
public operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}
对于var 来说需要实现下列方法,就可以代理属性
public operator fun getValue(thisRef: R, property: KProperty<*>): T
public operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
对val来说 实现下方法即可代理属性
public operator fun getValue(thisRef: R, property: KProperty<*>): T
具体例子
class Food {
var x: Int by X()
}
class X {
operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
println("getValue")
return 2
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, i: Int) {
println("setValue")
}
}
fun main() {
val food = Food()
food.x=1;
println(food.x)
}
运行结果
setValue
getValue
2
对food 的属性 x 调用其getter或者setter时会调用的属性代理
5,单例object
Kotlin中单例
- 使用object进行定义
- 默认生成无参构造函数且不能自定义构造器,但是可以通过init{}代码块进行一些初始化操作
- 跟普通的Kotlin类一样可以实现接口,可接继承其他类
- 其本质就是Kotlin使用饿汉式内部生成了INSTANCE的实例
public object Sigten {
var x: Int = 0
var y: String = ""
fun mm() {}
}
Kotlin中访问
fun main() {
Sigten.x = 3
Sigten.y = "WWWWWWWW"
println("${Sigten.x}||${Sigten.y}")
}
Java中访问
Sigten.INSTANCE.mm();
Sigten.INSTANCE.setX(1);
Sigten.INSTANCE.getX();
由于Kotlin是一门跨平台语言,不能因为Jvm上有静态变量就定义出相应的变量类型,这对于C语言和JavaScript行不通
@JvmStatic 模拟JVM平台的静态变量
public object Sigten {
@JvmStatic var x: Int = 0
@JvmStatic fun mm() {
}
}
Java调用
Sigten.mm();
Sigten.setX(1);
Sigten.getX();
@JvmField 不让Kotlin属性生成getter和setter,直接访问属性
public object Sigten {
@JvmStatic var x: Int = 0
@JvmField var y:Int=0
@JvmStatic fun mm() {
}
}
Java调用
Sigten.mm();
Sigten.setX(1);
Sigten.getX();
Sigten.y=1;
伴生对象
- 是某个类的另一半
- 其名称和所在类相同
- 伴生对象也是单例的
class FoodX {
companion object {
@JvmStatic var x: Int = 0
@JvmStatic fun mm() {
}
}
}
想当于Java中
public class FoodX {
public static int x = 0;
public static void mm(){}
}
6,内部类
- 内部类分为静态内部类和非静态内部类
- 静态内部类不需要引用外部对象,可直接创建对象
- 非静态内部类需要引用外部对象,容易引起内存泄露
class Outer{
//内部类
inner class Inner(){}
//静态内部类
class StaticInner(){}
}
使用
fun main() {
//创建非静态内部类
var inner = Outer().Inner()
//创建静态内部类
var staticInner = Outer.StaticInner()
}
<1>内部object
- 内部object 不存在非静态,都是静态
object OuterObject {
object InnerStaticObject {
var x: Int = 0
}
}
使用
OuterObject.InnerStaticObject.x=3
<2>匿名内部类
object :Runnable{
override fun run() {
}
}
和Java中不同的是,Kotlin中匿名内部类支持多实现或继承父类
例如
object :Runnable,Cloneable{
override fun run() {
}
}
Java中也可以实现方法中多继承 使用LocalClass
public class TestObjeck {
public static void main(String[] args) {
class LocalClass implements Cloneable,Runnable{
@Override
public void run() {
}
}
}
}
Kotlin中不仅支持LocalClass 还支持localMethod(方法中定义方法)
object :Runnable,Cloneable{
override fun run() {
}
}
class LocalClass:Runnable,Cloneable{
override fun run() {
}
}
fun localMethod(){}
7,数据类 data class
component: 定义在主构造器中的属性又被称为component
- 数据类和一般类最大的区别就在于component
- Kotlin中的data class 不等价与Java bean
- 编译器会基于component 自动生成 equals/hashCode/toString/copy
data class TT(val name: String, val age: Int) {}
fun main() {
var tt = TT("", 4)
}
如何合理的使用data class
- 单纯的把data class 当成数据结构使用既是纯数据,大多数情况下不需要额外的实现
- 主构造函数的变量类型 最好为基本类型\String 或者其他data class 保证数据类为纯数据
- component 不能定义getter和setter 为了防止通过getter或者setter 进行数据篡改
- 定义构造函数的变量 最好定义为 val ,如果可变会导致其hashCode等值的改变 如果该对象在hashset中 则无法移除该元素
为了实现一些特殊的需求,需要data class 有无参构造和可以被继承,官方提供了 noArg 和allOpen 插件进行支持
id 'org.jetbrains.kotlin.plugin.noarg' version '1.3.60'
id 'org.jetbrains.kotlin.plugin.allopen' version '1.3.60'
8, 枚举类 enum class
- 和 Java中枚举相似 区别是通过 enum class 定义
无参构造枚举
enum class CC {
Idle, Busy
}
有参构造枚举
enum class DD(arg: Int) {
Idle(0), Busy(1)
}
枚举实现接口--统一实现方式
enum class FF : Runnable {
Idle, Busy;
override fun run() {
}
}
枚举实现接口--单独实现方式
enum class EE : Runnable {
Idle {
override fun run() {
}
},
Busy {
override fun run() {
}
};
}
<1>因为enum 枚举本身是个类 所以可以为其定义扩展方法
<2>枚举本事是可数的所在when 条件语句中可以不加else
<3> 因为枚举本身有顺序的概念所以可以对其进行比较大小
<4> 枚举有顺序所以也有区间概念
enum class Color {
White, Red, Black, Pink, Ori, Yellow
}
fun main() {
val ran = Color.Black..Color.Yellow
val color = Color.White
color in ran
}
9,密封类 sealed class
- 密封类是一种特殊的抽象类
- 密封类的子类必须定义在与自身相同的文件中
- 因为密封类的子类定义范围有限,所以密封类的子类有限,一旦定义好密封类的子类后,外部不可能出现其他子类
- 密封类首先是个抽象类,其次是个密封类
例子
sealed class PlayState
object Idle : PlayState()
object Playing : PlayState() {
fun start() {}
fun stop() {}
}
object Error : PlayState() {
fun recover() {}
}
使用上面定义的几个类型做个小案例
class Player {
var state: PlayState = Idle
fun play() {
this.state = when (val state = this.state) {
is Idle -> {
Playing.also {
it.start()
}
}
is Playing -> {
state.stop()
state.also {
it.start()
}
}
is Error -> {
state.recover()
Playing.also {
it.start()
}
}
}
}
}
如何区分使用密封类还是枚举呢
- 如果需要进行类型区分则是使用密封类,如果要进行值的区分 则用枚举
- 枚举类不能创建对象,而密封类可以创建对象
枚举不能创建对象这点个人理解起来比较难一点,为什么不能枚举就不能创建对象呢?
因为枚举就是实例对象的存在<O(∩_∩)O哈哈~>
而密封类则不同,他的分支是类型,只要是子类的对象就行,因此可以创建多个对象
10,内联类 inline class
- 内联类是对某一个类型的包装
- 类似于Java中装箱类型的一种类型
- 编译器会尽可能使用被包装的类型进行优化
- 内联类目前在1.3版本中处于公测阶段,谨慎使用
使用注意
- 内联类不能有backingfiled 只能有方法
- 内联类可以实现接口,但不能继承父类也不能被继承
例子
inline class BoxInt(private val value: Int) {
operator fun inc(): BoxInt {
return BoxInt(value + 1)
}
}
内联类的使用场景
- 官方 使用内联类 实现无符号类型
- 内联类模拟枚举,因为枚举内存开销比较大,和dex文件大小
网友评论