Kotlin(二)面向对象
面向对象
2.1 kotlin中的类
class Bird {
val weight: Double = 500.0
val color: String = "blue"
val age: Int = 1
fun fly() {}// 全局可见
}
- 不可变属性成员。用val声明不可见成员,底层是java 的final实现
2)属性默认值,除非显示声明延迟初始化,否则必须指定属性默认值。java 不用指定,引用默认null,其他对应有默认值
3)不同的可访问的修饰符。kotlin默认是全局可见。java 默认是包可见,java 必须用public才能达到同样效果
可带有属性和默认方法的接口
Java 8 中引入
//java 8 默认实现
public interface Flyer {
long height = 1_000l;
public String kind();
default public void fly(){
System.out.println("I can fly");
}
}
kotlin 是基于java6 但是,同样的实现,反编译底层使用final static final class DefaultImpl实现
//kotlin 中的接口
interface Flyer {
val speed: Int
val height: Long
get() = 1_000L
fun kind()
fun fly() {
println("I can fly")
}
}
虽然,kotlin接口支持属性声明,但是通过一个get方法来实现,对比不难发现
2.2 更简洁构造类的对象
// kotlin 直接声明一个对象
val bird = Bird()
对于Java来说有多个重载
package top.zcwfeng.java.base;
public class Bird {
public double weight;
public String color;
public int age;
public Bird(double weight) {
this.weight = weight;
}
public Bird(String color, int age) {
this.color = color;
this.age = age;
}
public Bird(double weight, String color, int age) {
this.weight = weight;
this.color = color;
this.age = age;
}
}
缺陷:
- 支持任意多个参数组合创建对象,实现多个构造方法(用设计及模式 Builder模式能解决,但是还是比较麻烦,各种组合对外暴露)
- 每个构造方法餐宿不同冗余严重
Kotlin 构造方法的解决
- 多参数指定默认值,避免不必要的重载
- init 语句块,属于构造的一部分,但是确实分离开的,对初始化进行额外操作用
class Bird(weight: Double = 0.00, age: Int = 0, color: String = "blue") {
val weight: Double
val color: String
val age: Int
fun fly() {}
//构造方法可以在init 块调用
init {
this.weight = weight
this.color = color
this.age = age
}
init {
println("多个init")
}
}
可以有多个init 可以将初始化进行职能分离
3. 延迟初始化: by lazy 和 lateinit
by lazy
class Bird3Bird(val weight: Double, age: Int, color: String ) {
val sex:String by lazy{
if (color=="yellow") "male" else "female"
}
}
- by lazy 必须是引用不可变,而且不能通过var声明
- 在第一次调用是,才被赋值,赋值 后不能更改
lazy 背后是接受一个lambda 并且返回一个Lazy<T>实例的函数
/**
* Creates a new instance of the [Lazy] that uses the specified initialization function [initializer]
* and the default thread-safety mode [LazyThreadSafetyMode.SYNCHRONIZED].
*
* If the initialization of a value throws an exception, it will attempt to reinitialize the value at next access.
*
* Note that the returned instance uses itself to synchronize on. Do not synchronize from external code on
* the returned instance as it may cause accidental deadlock. Also this behavior can be changed in the future.
*/
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
第一此 访问执行lazy的lambda表达式并记录结果,后续访问该属性,只返回记录的结果。
系统会默认给lazy属性加上同步锁,也就是LazyThreadSafetyMode.SYNCHRONIZED,在同一时刻只允许一个线程对lazy属性进行初始化,所以他是线程安全的。
如果 你能确认该属性可以并行执行,没有线程安全问题,那么可以给lazy传递LazyThreadSafetyMode.PUBLICATION参数。
还可以传递LazyThreadSafetyMode.NONE,不会有任何线程开销,这个没有线程安全的保证了。
class Bird3Bird(weight: Double, age: Int, color: String ) {
//并行模式
val sex:String by lazy(LazyThreadSafetyMode.PUBLICATION){
if (color=="yellow") "male" else "female"
}
// 不安全,没有线程开销
val sex2:String by lazy(LazyThreadSafetyMode.NONE){
if (color=="yellow") "male" else "female"
}
}
lazyinit
用于var不能用于基本类型,要用Integer等包装
class Bird(weight: Double = 0.00, age: Int = 0, color: String = "blue") {
val weight: Double
val color: String
val age: Int
fun fly() {}
//构造方法可以在init 块调用
init {
this.weight = weight
this.color = color
this.age = age
}
lateinit var sex:String
fun printSex(){
if (color=="yellow") "male" else "female"
print(sex)
}
init {
println("多个init")
}
}
lateinit var 用语法Delegates.notNull<T>实现,举个栗子:
// 委托 lateinit var 实现原理
var test by Delegates.notNull<Int>()
fun doSomething(){
test = 1
println("test value is ${test}")
test = 2
}
2.3 主从构造方法
class Bird4(age: Int) {
var age: Int
constructor(color: String):this(0) {
//....
println("------$color-----从构造")
}
constructor(color: String,weight: Double):this(0) {
//....
println("------$color-----从构造2")
}
init {
this.age = age
println("init")
}
}
kotlin也支持多个构造方法。这个看Android中自定义view多个重载构造
目的方便扩展第三方库
2.4 限制修饰符,访问控制
- kotlin中的extends和implemens用的是符号”:“
- kotlin默认是不能继承和重写的,必须加上关键字
open
才可以
open class Bird2 {
val weight: Double = 500.0
val color: String = "blue"
val age: Int = 1
open fun fly() {}// 全局可见
}
class Penguin: Bird2() {
override fun fly() {
super.fly()
}
}
根据里氏替换原则
类默认final的,如果fly 不加open也是不允许继承的
除了利用final外,kotlin还可以用sealed密封类来限制继承
要继承sealed类,就必须在同一个文件中,其他文件无法实现。而且sealed类是抽象类实现不能被初始化
,背后实现是一个抽象类
修饰符 | 含义 | 与Java对比 |
---|---|---|
public | Kotlin中默认全局可见 | 与java中的public相同 |
protected | 受保护修饰,类和子类可见 | 含义一致,除了类和子类,包内可见 |
private | 私有修饰,类内修饰只有本类可见,类外修饰只有文件内可见 | 只有类内可见 |
internal | 模块内可见 | 无 |
2.4 多继承问题
C++ 支持多继承的。有一个经典问题---> 钻石问题---骡子的多继承困惑,我们用类图表示
2020-12-19 16.23.53.png也叫做菱形问题,但是kotlin和java一样支持单继承
kotlin可以通过其他方式完成多继承需求
接口实现多继承
一个类实现多个接口这个是java经常干的事。kotlin也是这样。
//多继承
interface Flyer1{
fun fly()
fun kind() ="[Flyer]flying animals"
}
interface Animal{
val name:String
fun eat()
fun kind() = "[Animal] flying animals"
}
class Bird6(override val name:String):Flyer1,Animal{
override fun eat() {
println("i can eat")
}
override fun fly() {
println("I can fly")
}
override fun kind() = super<Flyer1>.kind()
}
Bird6 继承了Flyer1和Animal接口,但他们都用有默认kind的实现,同样会引起钻石问题。kotlin 提供了super关键字解决问题。可以指定继承那个父类接口super<Flyer1>.kind(). 也可以实现kind覆盖父类方法
1)Kotlin 中实现一个接口,需要实现没有默认实现的方法以及未初始化的属性。同时实现多个接口有相同名字的方法默认实现,需要主动指定使用那个接口的方法或者重写方法
2)如果是默认的接口方法,可以在实现类中用super<T> 的方式调用,T为拥有改方法的接口名字
3)在实现接口的方法和属性时候,需要带上Override关键字,不能省略
我们是通过主构造方法实现的Animal接口中的name属性。
还可以通过val 声明构造参数,其实是在内部定义了一个同名的属性
class Bird6(name:String):Flyer1,Animal{
override val name:String
init {
this.name = name
}
override fun eat() {
println("i can eat")
}
override fun fly() {
println("I can fly")
}
override fun kind() = super<Flyer1>.kind()
}
getter 和 setter
koltin没有字段,只有属性。幕后都帮着实现好了setter和getter
当然我们自己可以主动声明。
1)val 声明的属性只有 getter 因为不可以修改;var 声明可同时拥有setter和getter
2)用private声明的属性,编译器会省略getter和setter因为外部已经无法访问了
这个和C# setter getter 相似
内部类解决多继承问题
java 内部类
public class OuterJava {
private String name ="Java inner clas syntax";
class InnerJava{
public void printName(){
System.out.println(name);
}
}
}
Kotlin 必须用inner 修饰内部类否则相当于java中static修饰的嵌套类
// 内部类
class OuterKotlin{
val name = "This is kotlin inner class syntax"
inner class InnerKotlin{
fun printName(){
println(name)
}
}
}
java 加static 嵌套类,不加内部类
kotlin 不加 嵌套类,加 inner内部类
内部类可以包含对外部name属性的引用,嵌套类不行
open class Hourse{
fun runFast(){
println("I can run fast")
}
}
open class Dounkey{
fun runFast(){
println("I can work long time")
}
}
class Mule{
fun runFast(){
HorseC().runFast()
}
fun doLongTimeThing(){
DounkeyC().runFast()
}
private inner class HorseC:Hourse()
private inner class DounkeyC:Dounkey()
}
- 类内部定义多个内部类,每个内部类都有自己独立的状态,与外部对象信息相互独立
2)让HorseC和DonkeyC 分别集成Horse,Donkey.就可以在Mule中定义他们的实现从二获得不同的状态和行为
3)利用private 外部类不能访问内部,具有很好的封装性
因此某种场景,内部类也可以实现多继承
使用委托代替多继承
委托,(C# 里面有delegate思想是一样的)我们之前有提过by lazy,除了延迟外,委托还有观察的行为,比较像观察者模式。
open class Flyer2 : CanFly {
override fun fly() {
println("I can fly")
}
}
open class Animal2 : CanEat {
override fun eat() {
println("I can eat")
}
}
class Bird7(flyer: Flyer2, animal: Animal2) : CanFly by flyer, CanEat by animal {}
// 委托
val mFly = Flyer2()
val mAnimal = Animal2()
val mBird = Bird7(mFly,mAnimal)
mBird.fly()
mBird.eat()
1) 接口是无状态,只能提供默认简单实现。利用委托的方式,虽然是接口委托,但是用类实现,可以有更强大的能力
2)A 委托 B,C 并不是想组合一样A.B.method 而是直接A.metod 更加直观
2.5 数据类
Java 中 定义Bean很麻烦。setter,getter .有时候需要重写hashcode,equals方法
而kotlin只需要一行,data class 声明
data class Bird8(var weight:Double,var age:Int,var color:String)
反编译后,和javaBean 一模一样,多了两个方法copy和componentN
copy 帮助我们实现了简单的浅拷贝相当于,数据类被var修饰,引用是可以修改的
componentN 对应的具体按顺序属性,kotlin中称为解构。
还提供了元组类Pair 两个属性 Triple 三个属性
// 元祖
val pair = Pair("a",11)
val triple = Triple(2,3.0,"aaa")
val name= pair.first
val pV = pair.second
val tripA = triple.first
val tripB = triple.second
val tripC= triple.third
// 解构
val (pName,pValue) = Pair("name",2)
val (ta,tb,tc)= Triple(2,3,"blue")
数据类的使用约定
- 数据类必须有一个构造方法,至少包含一个参数,无参没意义
- 数据类方法强制使用var 或者val,普通类可以不强制
- data class 之前不能用abstract open sealed 或者innder修饰
- data class kotlin 1.1 之后可以实现接口也可以继承类
2.6 object
kotin不会出现static,因为object替代了他
kotlin 伴生对象,cmpaanion object{} 替代了java中的static 声明的类中定义的属性
Java 中的类
public class Prize {
private String name;
private int count;
private int type;
public Prize(String name, int count, int type) {
this.name = name;
this.count = count;
this.type = type;
}
static int TYPE_REDPACK = 0;
static int TYE_COUPON = 1;
static boolean isRedPackl(Prize prize){
return prize.type == TYPE_REDPACK;
}
public static void main(String[] args) {
Prize prize = new Prize("红包",10,Prize.TYPE_REDPACK);
System.out.println(prize.isRedPackl(prize));
}
}
Kotlin 伴生类替换static
class Prize private constructor(val name: String, val count: Int, val type: Int) {
companion object {
val TYPE_COMMON = 0
val TYPE_REDPACK = 1
val TYE_COUPON = 2
val defaultCommonPrize = Prize("普通奖品", 10, Prize.TYPE_COMMON)
fun newRedPackPrize(name: String, count: Int) = Prize(name, count, Prize.TYPE_REDPACK)
fun newCoupo(name: String, count: Int) = Prize(name, count, Prize.TYE_COUPON)
fun defaultCommonPrize() = defaultCommonPrize
}
}
// 伴生
val redPackPrize = Prize.newRedPackPrize("红包",10)
val couplePrize = Prize.newCoupo("十元代金券",10)
val commonPrize = Prize.defaultCommonPrize()
伴生对象是实现工厂方法的一种方式。
object 是天生单例
object DataConfig{
var host:String = "127.0.0.1"
}
object 全局声明对象只有一个,并不用语法上面初始化,甚至不需要构造方法。
object 表达式
Java 中的匿名内部类
public class 匿名内部类 {
public static void main(String[] args) {
List<String> list = Arrays.asList("jffy", "lrri", "tony");
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
if (o1 == null)
return -1;
if (o2 == null)
return 1;
return o1.compareTo(o2);
}
});
}
}
Kotlin object 表达式改善
val comparator = object : Comparator<String>{
override fun compare(o1: String?, o2: String?): Int {
if(o1 == null)
return -1
else if(o2 ==null)
return 1
return o1.compareTo(o2)
}
}
Collections.sort(list,comparator)
object 表达式可以赋值给一个变量,而且每次运行都会生成一个新的对象
使用Lambda表达式改造
Java
Collections.sort(list,(String o1, String o2)->{
if (o1 == null)
return -1;
if (o2 == null)
return 1;
return o1.compareTo(o2);
});
Kotlin
val comparator2 = Comparator<String> { o1, o2 ->
if (o1 == null)
return@Comparator -1
else if (o2 == null)
return@Comparator 1
o1.compareTo(o2)
}
匿名内部类接口需要实现一个方法的时候Lambda更加适合
匿名内部类有多个方法的时候object更加适合