Kotlin核心编程 第三章面向对象
3.1类和构造方法
class Bird{
val weight:Double = 500.0
val color:String = "blue"
val age:Int = 1
fun fly(){}//全局可见
}
///反编译后的java代码
public final class Bird{
private final double weight = 500.0
@NotNull
private final String color = "blue"
private final Int age = 1
public final double getWeight(){
return this.weight
}
@NotNull
public final String getColor(){
return this.color
}
public final Int getAge(){
return this.age
}
public final void fly(){}
}
不同点:
1不可变的属性成员。利用java的final修饰符实现不可变的属性成员。
2属性默认值。因为java都有默认值(int为0,引用类型为null),所以在声明属性的时候我们不需要指定默认值。而在Kotlin中,除非显示地声明延迟初始化,否则就要指定属性的默认值。
3不同的可访问修饰符。Kotlin类中的成员默认是全局可见的,而java默认是包作用域,因此在java版本中要用public修饰符才能达到相同的效果。
接口:
接口方法的默认实现使得我们在向接口中新增方法的时候,之前继承过该接口的类则可以不需要实现这个新方法。
public interface Fly{
public String kind()
default public void fly(){//接口方法的默认实现,java8引入
System.out.println("i can fly")
}
}
kotlin的写法
interface Fly{
val speed:Int
fun kind()
fun fly(){
println("i can fly")
}
}
支持抽象属性(如上面的speed),然而因为kotlin是支持java6的,那么他是如何支持这种行为的呢?转为java代码如下:
public interface Fly{
int getSpeed();
void kind();
void fly();
public static final class DefaultImpls{
public static void fly(Fly $this){
String var1 = "i can fly"
System.out.println(var1)
}
}
}
通过定义一个DefaultImpls静态内部类,来提供fly方法的默认实现,同时虽然kotlin接口支持属性声明,然而他在java源码中是通过一个get方法来实现的。在接口中属性并不能像java那样直接被赋值一个常量。
如下代码是错误的:
interface Fly{
val height = 1000
//Property initializers are not allowed in interfaces
}
kotlin提供了另一种方式实现这种效果
interface Fly{
val height
get() = 1000
}
接口中的普通属性,同方法一样,若没有指定默认行为,则在接口的实现类中必须多该属性进行初始化。
java通过类的构造方法重载实现不同参数组合new对象。不同组合实现其的构造方法非常多;每个构造方法中存在相同赋值操作存在冗余。
kotlin采用新的构造方法
class Bird(val weight:Double = 0.00,val age:Int = 0,val color:String = "blue")
//可以省略{}
val b1 = Bird(color = "black")
val b2 = Bird(100.0,1,"black")
//val b3 = Bird(100.0,"black")//!!必须按照顺序赋值
构造方法中可以没有var和val,带上他们之后接等价于在类的内部声明了一个同名的属性。
class Bird(var name:String,var age:Int ){
val weight = 1000;
}
等价于
class Bird(name:String,age:Int){
var name:String? = null
var age :Int = 0
val weight = 1000;
init{
this.name = name
this.age = age
}
}
kotlin引入了一种叫做init语句块的语法,属于构造方法的一部分。
kotlin规定所有类中所有非抽象属性成员都必须在对象创建时被初始化值。
延迟初始化:by lazy 和lateinit
class Bird(){
val sex:String by lazy{
"male"
}
fun print(){
println(sex)
}
}
by lazy特点:
变量必须是应用不可变的,而不能通过var声明
被首次调用时,才会执行赋值操作,一旦被赋值之后将不能被更改。(后续返回记录的结果,还会默认加上同步锁).
class Bird(){
val sex:String by lazy(LazyThreadSafetyMode.PUBLICATION){
"male"
}
...
}
lateinit主要用于var声明的变量,然而他不能用于基本数据类型,如Int,Long等我们需要用Integer这种包装类作为替代。
class Bird(){
lateinit val sex:String
fun print(){
this.sex =" male"
println(sex)
}
}
主从构造方法:
通过constructor方法定义了一个新的构造方法,被称为从构造方法。相应的我们熟悉的在类外部定义的构造方法叫做主构造方法。每个类最多存在一个主构造方法和多个从构造方法,如果柱构造方法存在注解或可见性修饰符,也必须像从构造方法一样加上constructor关键字。
每个从构造方法由两部分组成,对其他构造方法的委托,花括号包裹的代码块。
执行顺序上,主构造器>init代码块>次构造器
/**
* kotlin 企鹅类
*/
class Penguin {
var weight:Int=100 //体重
var age:Int=0
var name:String?=null
//空参次构造方法,如果没有逻辑,可以省略大括弧
constructor()
//有参次构造方法,如果没有逻辑,可以省略括弧,不过有参的空构造方法是没有意义的
//有参次构造器
constructor(name:String,age:Int){
this.name=name
this.age=age
}
}
当主构造器和次构造器同时存在时,次构造器必须直接或者间接调用主构造器
class Penguin constructor(name: String) {
var weight:Int=100 //体重
var age:Int=0
var name:String?=null
//空参次构造器,调用了下面的次要构造方法
constructor():this("奔波儿霸",10)
//有参次构造器,调用了主构造方法
constructor(name:String,age:Int):this(name){
this.name=name
this.age=age
}
}
与java相似,当我们不声明任何构造方法时,Kotlin编译器会默认给我们增加一个空参构造方法
class Penguin constructor() {
var weight:Int=100 //体重
var age:Int=0
var name:String?=null
}
3.2不同的访问控制原则
kotlin中没有个采用java的extends 和implements关键词,而是用:来代替类的继承和接口的实现
由于kotlin中的类和方法默认是不可被继承或重写的,所以必须加上open修饰符
open class Bird(){
open fun fly(){
println("i can fly")
}
}
class Penguin:Bird(){
override fun fly(){
println("i can not fly")
}
}
*子类应该避免重写父类的非抽象方法--一旦父类方法变化,子类会出错,李氏替换原则
kotlin中类、方法和属性默认加了final修饰符
QQ截图20220121221918.png
- 如果你不指定任何可见性修饰符,默认为
public
,这意味着你的声明将随处可见; - 如果你声明为
private
,它只会在声明它的文件内可见; - 如果你声明为
internal
,它会在相同模块内随处可见; -
protected
不适用于顶层声明。
QQ截图20220121221931.png
private 意味着只在这个类内部(包含其所有成员)可见;
protected—— 和 private一样 + 在子类中可见。
internal —— 能见到类声明的 本模块内 的任何客户端都可见其 internal 成员;
public —— 能见到类声明的任何客户端都可见其 public 成员。
3.3解决多继承的问题
接口实现多继承
用super关键字指定继承哪个父类接口的方法:
fun main() {
val b = Bird()
println(b.kind())//[Flyer] flying animals
}
interface Flyer{
fun kind() = "[Flyer] flying animals"
}
interface Animals{
fun kind() = "[Animals] flying animals"
}
class Bird():Flyer,Animals{
override fun kind () = super<Flyer>.kind()
//或者主动覆盖父类接口
//override fun kind () = "a flying Bird"
}
*kotlin编译器自动帮我们生成了getter和setter方法。
1用val声明的属性将只有getter方法,因为它不可修改;用var修饰的属性将同时拥有getter和setter方法。
2用private修饰的属性编译器会省略getter和setter方法,因为类外部已经无法访问他了,这两个方法也就没意义了。
内部类解决多继承问题的方案
java通过在内部类语法上增加一个static关键词,把它变成嵌套类;kotlin默认是嵌套类,必须加上inner关键字才是一个内部类,也就是说可以把静态内部类看成嵌套类。
内部类包含着对其外部类实例的引用,在内部类中可以使用外部类中的属性。
fun main() {
val b = Bird()
b.kindFly()
}
open class Flyer{
fun kind() = println("[Flyer] flying animals")
}
open class Animals{
fun kind() = println("[Animals] flying animals")
}
class Bird(){
fun kindFly(){
FlyerC().kind()
}
fun kindAnimals(){
AnimalsC().kind()
}
private inner class FlyerC:Flyer()
private inner class AnimalsC:Animals()
}
使用委托代替多继承:by 关键字
fun main() {
val flyer = Flyer()
val animal = Animal()
val b = Bird(flyer,animal)
b.fly()
b.eat()
}
class Bird(flyer:Flyer,animal:Animal):CanFly by flyer,CanEat by animal{}
interface CanFly{
fun fly()
}
interface CanEat{
fun eat()
}
open class Flyer:CanFly{
override fun fly(){
println("i can cly")
}
}
open class Eater:CanEat{
override fun eat(){
println("i can eat")
}
}
open class Animal :CanEat{
override fun eat(){
println("Animal can eat")
}
}
//
i can cly
Animal can eat
委托使使用时更加直观,比如需要继承A,委托对象是BC,具体调用时不是A.B.method()而是A.method()。
真正的数据类 data class
data class Bird(var weight:Double,var age:Int)
一行代码就拥有了getter/setter,equals,hashcode,构造函数,还有两个特别的方法copy和componentN
*data class 之前不能用abstract、open、inner、sealed修饰
3.5从static到object
伴生对象companion object
伴生是相对一个类而言,意为伴随某个类的对象,因此伴生对象跟java中static修饰效果性质一样,全局只有一个单例。他需要声明在类的内部,在类被装载时会被初始化。
fun main() {
println("Hello, world!!!")
val prize = Prize("红包",10,Prize.TYPE_REDPACK)
println(Prize.isRedPack(prize))
}
class Prize(val name:String,val count:Int,val type:Int){
companion object{
val TYPE_REDPACK = 0
val TYPE_COUPON = 1
fun isRedPack(prize :Prize):Boolean{
return prize.type == TYPE_REDPACK
}
}
}
伴生对象也是实现工厂方法模式的一种思路
fun main() {
val redpackPrize = Prize.newRedPackPrize("红包",10)
val couponPrize = Prize.newCouponPrize("代金券",10)
val commonPrize = Prize.defaultCommonPrize()
}
class Prize(val name:String,val count:Int,val type:Int){
companion object{
val TYPE_COMMON = 1
val TYPE_REDPACK = 2
val TYPE_COUPON = 3
val defaultCommonPrize = Prize("普通奖品",10,Prize.TYPE_COMMON)
fun newRedPackPrize(name:String,count:Int) = Prize(name,count,Prize.TYPE_REDPACK)
fun newCouponPrize(name:String,count:Int) = Prize(name,count,Prize.TYPE_COUPON)
fun defaultCommonPrize() = defaultCommonPrize
}
}
天生的单例object
可以直接用它来实现单例
object DatabaseConfig{
var host :String = "127.0.0.1"
var post :int= 3306
}
由于object全局声明的对象只有一个,所以他并不用语法上的初始化,甚至都不需要构造方法。
他还有一个作用就是替代java中的匿名内部类
List <String> list = ArrayList.asList("red","ss","cc")
Collections.sort(list,new Comparator<String>(){
@Override
public int compare(String s1,String s2){
if(s1==null)
return -1;
if(s2==null)
return 1;
return s1.cpmpareTo(s2)
}
})
//利用object表达式对它进行改善
val comparator = object:Compararor<String>{
override fun compare(s1:String?,s2:String?):Int{
if(s1==null)
return -1
else if(s2 == null)
return 1
return s1.compareTo(s2)
}
}
Collections.sort(list,comparator)
优点:
object表达式可以赋值给一个变量,重复使用时减少很多代码;
object可以继承和实现接口,匿名内部类只能继承和实现一个接口,而object却没有这个限制。
用于代替匿名内部类的object表达式在运行中不像我们在单例中那样全集只有一个对象,而是每次运行时都会生成一个对象
当匿名内部类使用的类的接口只需要实现一个方法时,使用lambda表达式更适合,当匿名内部类有多个方法实现的时候,使用object表达式更加合适。
val comparator = Compararor<String>{s1,s2->
if(s1==null)
return @Comparator-1
else if(s2 == null)
return @Comparator1
return s1.compareTo(s2)
}
Collections.sort(list,comparator)