1.抽象类与接口
abstract class Person(var age:Int){
abstract fun talk()
fun sayAge(){ println("$age") }
}
interface Sport{
fun run(){
println("run like this")
}
fun jump()
}
interface Study{
var j : Int
fun studyEnglish()
}
class Man(override var j:Int,var name:String,age: Int):Person(age),Sport,Study{
override fun studyEnglish() {
println("$name's age is $age,studyEnglish now,level is $j")
}
override fun talk() {
println("$name's age is $age,talking now")
}
override fun run() {
super<Sport>.run()
println("$name's age is $age,runing now")
}
override fun jump() {
println("$name's age is $age,jumping now")
}
}
在kotlin中,抽象类用abstract修饰,可以有成员变量,可以有普通方法,用open修饰后也可以被继承,可以有抽象方法,抽象方法要用abstract修饰,但是不能有方法体。子类继承抽象类后,将继承抽象类的成员变量,普通方法,需要去实现抽象方法。
在kotlin中,接口使用interface关键字,接口中定义的方法都需要被重写,接口中定义的方法可以有默认实现,可以定义变量,但是不能赋值,因为接口是无状态的,要由继承的子类去赋值。
抽象类是描述对象本质,接口描述的是对象行为。跟接口相比,抽象类已经算是半成品。子类中被重写的方法要使用override关键字。
2.继承(实现)
kotlin中关于继承实现跟java中大同小异,下面介绍一个kotlin特有的叫接口代理。
interface MathLesson{
fun teachMath()
}
interface EnglishLesson{
fun techEnglish()
}
class Teacher:MathLesson,EnglishLesson{
override fun teachMath() {
println("teach math")
}
override fun techEnglish() {
println("teach english")
}
}
上面是正常使用接口的方式,简单的说就是一个老师不仅要教数学,还要教英语,感觉好累。然后就有了接口代理
class MathTeacher : MathLesson{
override fun teachMath() {
println("MathTeacher teach math")
}
}
class EnglishTeacher :EnglishLesson{
override fun techEnglish() {
println("EnglishTeacher teach english")
}
}
class TeacherLeader(var mt : MathTeacher,var et :EnglishTeacher):MathLesson by mt,EnglishLesson by et{
}
上面的例子分别有一个数学老师,一个英语老师,一个老师头。老师头一样需要教数学和英语两门课,于是他雇了两个专项的老师去完成教学任务。这就是接口代理。要使用by关键字,之后我们还会介绍属性代理。
3.类及其成员的可见性
kotlin中的可见性修饰符和java大同小异,一样有public,private,protected,这几个修饰符的使用范围跟java一摸一样。kotlin中有一个特有的internal修饰符,它的作用范围是同一个模块内,还有一点不同是kotlin中默认的修饰符是public。
4.object
在java中object大家再熟悉不过了,java中Object是一切类的基类。而在kotlin中object是一个关键字,用来修饰类。被它修饰的类一样可以继承父类,实现接口,拥有成员变量,区别于一般类的是,object类在虚拟机内存中有且只有一份,简单的说,就是我们俗称的“单例”,kotlin在语法层面支持了单例模式。
object Single: A(),B{
var a: Int = 0
override fun aa() {
}
override fun bb() {
}
fun cc(){
}
}
abstract class A{
abstract fun aa()
}
interface B{
fun bb()
}
上面的例子就是普通的被object修饰的类,使用起来跟一般类没什么区别,那它到底是不是单例呢?我们来看下编译后的字节码反编译的java代码
public final class Single extends A implements B {
private static int a;
public static final Single INSTANCE;
public final int getA() {
return a;
}
public final void setA(int var1) {
a = var1;
}
public void aa() {
}
public void bb() {
}
public final void cc() {
}
private Single() {
INSTANCE = (Single)this;
}
static {
new Single();
}
}
果然,这不就是一个单例的“饿汉”写法嘛。
5.伴生对象与静态成员
在java中有静态方法的概念,俗称“类方法”,一般用于工具类的工具方法,可以直接通过类名调用。在kotlin中,虽然没有静态方法的概念,但是可以通过伴生对象来实现,例如下面这段kotlin代码,模仿java标准库中的Math类的伪代码。
class Math private constructor(){
companion object {
@JvmStatic
fun <T> sin(t:T):T{
return t
}
fun <T> cos(t:T):T{
return t
}
@JvmField
val TAG = "tag"
}
}
这样在kotlin中也可以用调“类方法”的写法去使用了。JvmStatic标注的作用是在java代码中调用kotlin代码,可以用java熟悉的静态方法写法。
同样也有伴生对象属性,就是java中的静态成员,JvmField标注作用于属性。
class SingleMode private constructor(){
companion object {
private val instance : SingleMode = SingleMode()
@JvmStatic
fun SingleMode():SingleMode{
return instance
}
}
}
伴生对象也可以使用在单例中,上面就是kotlin中“饿汉单例”。
6.overload
方法重载的概念在java中也有,在kotlin中也没什么区别。这里讲一个概念,叫做“方法签名”,jvm语言都有这个概念,"方法签名"只取决于方法名,参数类型,参数数量,参数顺序,跟返回值无关。
由于kotlin中默认参数和具名参数的用法,所以一般情况下,方法重载都可以被代替。比如
fun a(p1:Int){}
fun a(p1: Int,p2:Int){}
fun a(p1: Int,p2: Int,p3:String){}
例子中a方法有三种方式的重载,然后我们用默认参数的方式改写成一个方法。
fun a(p1: Int,p2: Int=0,p3:String?=null){}
默认参数和具名参数可以让方法的定义变的很灵活。
7.方法扩展和属性扩展
在java的日常开发中,大家自己都会定义一些工具类,将功能抽象出来,这样不同的模块项目就可以复用这些功能代码了。在java中主要使用静态方法来实现。在kotlin中,可以有伴生对象方式,包函数方式来实现,推荐使用包函数。今天再介绍一种更牛逼的方式,叫做“方法扩展”。
比如,现在有这么个需求,判断字符串中是否含有数字,之前我们都会采用的方式是定义一个StringUtils类,把这个需求设计成这个类的类方法,但是我们用方法扩展的方式,给String类加个方法呢?
fun String.hasNumber():Boolean{
return this.filter { it.toInt() !in 48..57}.length != this.length
}
val s = "anc123"
println(s.hasNumber())
val s2 = "adsf"
println(s2.hasNumber())
神奇的发现,我们给系统api类增加了一个方法。当然有方法扩展,就会有属性扩展,不过感觉属性扩展暂时没找到特别好的用处。
val String.version : Int
get() { return 1 }
8.属性代理
之前我们介绍过接口代理,现在我们来看下属性代理。之前我们写的属性懒加载就是一种属性代理。
val array by lazy { intArrayOf(1,2,3,4,5,6) }
类似这样的代理,当这个属性第一次被使用时,就会调用大括弧里面的lambda表达式,并返回结果。那我们自己要写个属性代理该怎么写呢?通过查看lazy源码发现有这个方法
public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
这就是lazy代理属性的关键,既然知道原因了,我们自己试试。
class X{
operator fun getValue(thisRef: Any?, property: KProperty<*>): String{
return "from X"
}
}
我们定义了X类,里面定义了一个getValue方法,然后我们试着用X类去代理下属性看看
val x by X()
没有报错,使用打印就是“from X”,i get it!然后我试着用X去代理var变量时,报错了...查看警告发现缺少setValue方法,既然缺少什么,我们就补什么。
class Y{
private var value :String? = null
operator fun getValue(thisRef: Any?, property: KProperty<*>): String{
println("$thisRef-$property")
return value?:""
}
operator fun setValue(thisRef: Any?, property: KProperty<*>,value : String){
println("$thisRef-$property-$value")
this.value = value
}
}
Y类不仅能代理val常量,也能代理var变量。代理其实就是“让狗叫出猫叫”,经典案例,大家懂的!
9.数据类
数据类又称为data class,它很某种程度上可以替代java中的javabean,但是这替代的过程并不是一帆风顺的,最后我们来讲下有哪些坑,先看用法,用法非常简单,使用data class声明一个类,例如:
data class User(var id:Int,var name:String)
虽然只有一行代码,但是编译器自动为我们生成了toString,copy,hashcode等方法,大大简化了我们书写模式代码的工作量,我们通过反编译kotlin字节码来看看具体情况。
public final class User {
private int id;
@NotNull
private String name;
public final int getId() {
return this.id;
}
public final void setId(int var1) {
this.id = var1;
}
@NotNull
public final String getName() {
return this.name;
}
public final void setName(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.name = var1;
}
public User(int id, @NotNull String name) {
Intrinsics.checkParameterIsNotNull(name, "name");
super();
this.id = id;
this.name = name;
}
public final int component1() {
return this.id;
}
@NotNull
public final String component2() {
return this.name;
}
@NotNull
public final User copy(int id, @NotNull String name) {
Intrinsics.checkParameterIsNotNull(name, "name");
return new User(id, name);
}
// $FF: synthetic method
// $FF: bridge method
@NotNull
public static User copy$default(User var0, int var1, String var2, int var3, Object var4) {
if((var3 & 1) != 0) {
var1 = var0.id;
}
if((var3 & 2) != 0) {
var2 = var0.name;
}
return var0.copy(var1, var2);
}
public String toString() {
return "User(id=" + this.id + ", name=" + this.name + ")";
}
public int hashCode() {
return this.id * 31 + (this.name != null?this.name.hashCode():0);
}
public boolean equals(Object var1) {
if(this != var1) {
if(var1 instanceof User) {
User var2 = (User)var1;
if(this.id == var2.id && Intrinsics.areEqual(this.name, var2.name)) {
return true;
}
}
return false;
} else {
return true;
}
}
}
这就是我们反编译成java代码后的结果,kotlin的编译器真是为我们操碎了心啊。但是有两个方法感觉设计重复了getId和component1,getName和component2,这是为什么?我们先来看下下面这种语法:
var u = User(1,"u1")
var (id,name) = u
其实就是个操作符重载,因为这种语法设计,所以才有了component1和component2方法。那么我们自己设计的类是不是也能支持这种语法呢?答案是肯定的。
class Person(var id:Int,var name: String,var age:Int){
operator fun component1():String{
return this.name
}
operator fun component2():Int{
return this.age
}
}
var p = Person(0,"22",10)
var (name,age) = p
我们自己定义的类也可以使用这个操作符。
最后我们来说说,data class的坑,查看反编译后的java文件,你会发现User类是final的,而且没有无参的构造方法,在使用一些第三方数据层框架时,框架会通过无参的构造方法反射生成实例。而且javabean中的继承关系也能常见。显然这两点让我们用data class取代javabean近在咫尺,却又远在天边。这里介绍两个kotlin插件:
classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version"
至于这两款插件的用法,这里就不做展开了。
10.内部类
kotlin和java一样有内部类,也分为静态内部类,非静态内部类,匿名内部类,含义上也基本是一样的。我这边就简单介绍下几种内部类的写法:
class Outer{
class Inner{
}
}
这是kotlin种静态内部类,kotlin中默认就是静态内部类,为什么这么说?我们来看下他的用法
var inner = Outer.Inner()
这种用法是不是跟java中的静态内部类一样!
class Outer{
inner class Inner{
}
}
这就是kotlin中的非静态内部类,就比上个例子多了一个inner关键字,用法上是这样的
var inner = Outer().Inner()
这种写法跟java中的非静态内部类也是一致的
interface OnClickListener{
fun onClick()
}
var listener = object : OnClickListener{
override fun onClick() {
}
这就是kotlin的匿名内部类,有点区别的是,kotlin中的匿名内部类可以同时实现或者继承多个接口和抽象类。
11.枚举
kotlin中的枚举和java中的用法也大同小异,枚举就是实例个数已知的对象类型。
enum class Level{
LOW,MID,HIGH
}
那么枚举类可以有构造方法吗?Ofcourse yes!
enum class Level(val id :Int){
LOW(0),MID(1),HIGH(2)
}
我们自己也可以实现枚举类的设计,这里就不展开了,归根到底,他还是类嘛
12.密封类
密封类是kotlin中的全新的概念(sealed class),那什么是密封类呢?密封类是子类数量已知的一种设计,他可以防止外部程序继承串改。
sealed class Cmd{
fun invoke(){}
class ACmd(var id: Int):Cmd()
class BCmd(var name: String):Cmd()
object CCmd:Cmd()
}
这就是密封类,这样Cmd有且只有3个子类,外部不可能继承Cmd类了,个人觉得在第三方库的设计中可以使用密封类,保证核心代码的安全设计。
理解密封类,可以和理解枚举结合起来,枚举是实例数量已知,密封类是子类数量已知