kotlin中的类
class Bird{
val weight:Double = 500.0
val color:String = "blue"
val age:Int = 1
fun fly(){}
}
上面代码反编译成Java的版本后,会有get方法,除此之外还有一些不同
1)不可变属性成员。这是利用java中的final修饰符来实现的,使用var声明属性则引用可变。
2)属性默认值。Java中的属性有默认值,kotlin中,除非显试的声明延迟初始化,不然就需要指定属性的默认值。
3)不同的可访问修饰符。kotlin中默认是public,而Java中默认是 default
可带有属性和默认方法的接口
Kotlin中的接口
interface Flyer{
val speed:Int
fun kind()
fun fly(){println("I can fly")}
}
kotlin中的接口是不能像java那样直接赋值的 不过可以通过get方法来实现
interface Flyer{
val speed:Int
get() = 100
}
如果 接口中没有初始化,则需要在子类中重写进行初始化
class parot() :Bird {
override val speed: Int
get() =101
}
有了类就需要构造方法
kotlin中可以通过构造方法默认参数 实现构造方法重载
举个
class Bird(val weight:Double = 0.00,val age:Int = 1)
如果用Java来实现同样的效果 需要几个构造方法? 4个。
构造方法中可以用val 和var来声明构造方法的参数,val代表引用不可变 ,那么在构造方法中用val修饰这个变量还能被赋值吗,当然可以。声明后代表在类的内部声明了一个同名的属性,类似一下实现
class Bird(weight:Double = 0.0,age:Int= 0){
val weight:Double
val age: Int
init{
this.weight = weight
this.age = age
}
}
上面的代码很有意思,说明kotlin也可以分开初始化,还有用val 修饰的变量是在初始化之后不可变。
init语句块 是构造方法的一部分,两者在表现形式上是分离的,如果在初始化时进行其他的额外操作,那么我们就可以使用init语句块来执行 。
构造方法的参数可以在init语句中访问,也可以用于初始化类内部的属性成员的情况
class Bird(weight:Double = 0.00){
val weight:Double = weight
}
但是不能在成员函数中访问
class Bird(weight:Double){
fun printWeight(){
print(weight) // 这里不能直接访问 weight
}
}
主从构造方法
我们可能需要从一个特殊的数据来获取构造类的参数值,这时候如果可以定义一个额外的构造方法,接收一个自定义的参数会显的特别方便
class Bird(val age:Int){
constructor(birth:DateTime) :this(getAgeByBirth(birth)){}
}
类外部定义的构造方法被称为主构造方法,每个类可最多存在一个主构造方法,和多个从构造方法,如果主构造方法存在注解或者可见星修饰符,则constructor 关键字不可省略。从构造方法会先执行对其他构造方法的委托,然后执行自身代码块的逻辑; 如果存在主构造方法 那么每个从构造方法都要直接或者间接的委托给它。
不同的访问控制原则
继承虽然灵活,但是如果被滥用会引起一些问题。举个 我认为企鹅也是一种鸟, 于是声明了一个Penguin类来继承Bird
open class Bird{
open fun fly(){ print("I can fly.")}
}
class Penguin :Bird(){
override fun fly(){
print("I can't fly actually")
}
}
kotlin中的继承是用:,kotlin中类和方法默认是不可被继承或重写的,所以必须加上open 修饰符。上面的例子明显不太好,所以kotlin中默认类和方法都是加了final修饰的
sealed
用sealed修饰的类 不能被初始化,因为他背后是基于一个抽象类实现的,若要继承则需要将子类定义在用一个文件中(同一个包也不行,就是同一个文件)
可见性修饰符
- kotlin与Java的默认修饰符不同,kotiin中是public ,而Java中是default
2)kotlin中有个独特的修饰符internal (模块内可见)
3)kotlin可以在一个文件内单独声明方法以及常量,同样支持可见性修饰
4)Java中除了内部类可以用private修饰以外,其他类都不允许private修饰,而kotlin可以
5)protected的访问访问范围不同,Java中是包,类子类,而kotlin中只允许类及子类。
模块内访问(internal)
- 一个Eclipse项目
- 一个Intellij IDEA项目
- 一个Maven项目
- 一个Grandle项目
- 一组由一次Ant任务执行编译的代码
inner
kotlin中的内部类默认是static 的,所以想要定义一一个内部类 要加上inner关键字
(如果不加则没有外部类的引用,无法访问外部类的属性和方法)
通过委托来代替多继承实现需求
interface CanFly{
fun fly()
}
interface CanEat{
fun eat()
}
class Flyer:CanFly {
override fun fly() {
println("i can fly")
}
}
class Animal: CanEat{
override fun eat() {
println(" i can eat")
}
}
class Bird(flyer:Flyer,animal: Animal) :CanFly by flyer ,CanEat by animal
object Test{
@JvmStatic
fun main(args: Array) {
val flyer = Flyer()
val animal = Animal()
val bird = Bird(flyer,animal)
bird.eat()
bird.fly()
}
}
疑问:首先,委托方式怎么跟接口实现多继承如此相似,而且好像也没有简单多少,其次,这种方式好像跟组合也很像,那么它到底有什么优势呢?只要有以下亮点:
1)接口是无状态的,所以即使它提供了默认方法实现也是很简单的,不能实现复杂逻辑(也不推荐) 我们可以利用上面委托的这种方式,虽然他也是接口委托,但是是用一个具体的类去实现方法逻辑,可以拥有更强大的逻辑
2)假设我们需要继承的类是A,委托对象是B,C,我们在具体调用的时候并不是像组合一样A.B.method,而是可以直接调用A.medthod,这样更能表达A拥有该mehod的能力,更加直观
真正的数据类
有这样一种场景,我们并不想要那么强大的类,也许我们只是想要单纯的使用类来封装数据,类似于Java中的DTO(Data transfer object)的概念,但是我们知道在Java中显的繁琐,因为通常情况下我们会声明一个javaBean,然后定义一堆getter和setter。但是你很可能已经厌烦了这些冗长的代码,那来看看kotlin是如何改进这个问题的吧
用data class 创建数据类
data class 顾名思义就是数据类
data class Bird(var weight:Double ,var age:Int,var color:String)
那么 这个data关键字帮我们做了哪些事情
1)提供get set方法
2)重写了toString 方法,重写equals 和hashcode
3)提供了一些方法 如 copy 和componentN
val b1 = Bird(3.0,1)
val b2 = b1.copy(age = 2)
copy 内部实现是通过传过来的参数重新new了一个对象
接下来我们看看componentN方法。简单来说,componentN可以理解为类属性的值,其中N代表属性的顺序,比如component1代表第一个属性的值,那么这样设计到底有什么用呢? 我们来思考一个问题,我们或多或少的知道怎么将属性绑定到类上,到那时如何将类的属性绑定到相应变量上却不是很熟悉 如:
// 普通方式
val b1 = Bird(3.0,1,Color.BLACK)
val weight = b1.weight
val age = b1.age
val color = b1.color
//kotlin 进阶
val (weight,age,color) = b1
只有提供了component函数的时候才可以这么写
看到kotlin的语法信息你一定会感到兴奋,因为你可能写过类似下面的代码
String birdInfo = "20.0,1,bule";
String[] temps = birdInfo.split(".")
double weight = Double.valueOf(temps[0]);
int age = Integer.valueOf(temps[1]);
String color = temps[2];
这样的代码有时真的很繁琐,我们明明知道值的情况,却要分好几步来给变量赋值,很幸运,kotlin提供了更优雅的做法:
val (weight,age,color) = birdInfo.split(".")
这里的原理就是解构,通过编译器的约定实现解构,kotlin对数组的结构有一定限制,在数组中它默认最多允许赋值5个变量
自己实现对应属性的componentN方法,如:
//
data class Bird(val weight:Double,val age:Int,val color: Color){
var sex = 1
operator fun component4() = sex
constructor(weight: Double,age: Int, color: Color,sex:Int):this(weight, age, color){
this.sex = sex
}
}
//使用
val b1 = Bird(3.0,1,Color.BLACK,1)
val (weight,age,color,sex) = b1
除了数组支持解构外,kotlin也提供了其他常用的数据类,分别是Pair和Triple,使用如下
val(weightT,ageT,colorT) = Triple(20.0,1,Color.BLUE)
伴生对象
半生对象的设计目的就是为了把普通变量和静态变量区别开,而不是混在一个类中靠static关键字区别。
关于object关键字
两个作用
1.天生的单例(饿汉式,线程安全)
2.写匿名内部类