面向对象
接口定义
与java一样,使用interface表示,示例代码:
与java一样,kotlin定义类时要遵循单继承多实现的原则(即只能继承一个父类,可以实现多个接口)
kotlin中定义的类和方法默认都是final的,不可重写,如果要实现重写,需将对应方法类声明为open
示例代码:
package com.zhusp.kotlinuse
open class Person{ //可继承的类必须要用open关键字修饰
open fun work(){ //同样,里面的方法也需要用open修饰后才能在子类中重写,相当于java中的public
}
}
interface Driver {
fun drive()
}
interface Writer {
fun write()
}
class Manager(var driver:Driver,var writer: Writer):Driver by driver,Writer by writer //by 表示代理,此时无需再重写实现接口的方法
class Professor : Person(){
override fun work(){
}
}
class CarDriver : Driver {
override fun drive() {
println("轿车司机正在开车来")
}
}
class PPTWriter : Writer {
override fun write() {
println("秘书正在写PPT")
}
}
fun main() {
val carDriver = CarDriver()
val pptWriter = PPTWriter()
val seniorManager = Manager(carDriver,pptWriter)
seniorManager.drive()
seniorManager.write()
}
运行结果
轿车司机正在开车来
秘书正在写PPT
object
object在kotlin中同class关键字一样,是表示一个类的,与普通class修饰的类不同的是,由object定义的类只有一个实例,并且不嫩自定义构造方法,其实它的本质就是java中对类的单例化。
示例代码:
interface MediaPlay{
fun mount()
fun unMount()
}
abstract class Player{
abstract fun deviceName()
abstract fun deviceNum()
}
object MusicPlayer:Player(),MediaPlay{
override fun mount() {
}
override fun unMount() {
}
override fun deviceName() {
println("I am MusicPlayer")
}
override fun deviceNum() {
}
}
上面定义的MusicPlayer在其它kotlin可以直接通过该类名调用其中的方法:
MusicPlayer.deviceName()
运行:
I am MusicPlayer
object小结
- 只有一个实例的类
- 不能自定义构造方法
- 可以实现接口,继承父类
- 本质上就是单例最基本的实现
伴生对象与静态成员(public static void main)
- kotlin中每个类可以对应一个伴生对象
- 伴生对象的成员全局独一份
- 伴生对象的成员类似Java的静态成员
- 伴生对象用companion object表示
kotlin伴生对象代码示例:
fun main() { //直接调用
println(StaticDemo.TAG)
println(StaticDemo.ofDouble(3.0))
}
class StaticDemo private constructor(val value:Double){
companion object {//表示伴生对象,类似于java中对方法或变量声明static
@JvmStatic
fun ofDouble(double:Double):StaticDemo{
return StaticDemo(double)
}
@JvmField
val TAG:String = "StaticDemo"
}
}
- 静态成员考虑用包级函数、变量替代
- JvmField和JvmStatic的使用(保证在java里调用时看起来跟普通java类的静态方法或变量一致)
扩展成员(二次加工)
类似于我们在Java中定义工具类及工具方法,不一样的是kotlin可以在原本存在的类(jdk或jar中的类,比如String)里直接扩展方法(就是所谓的二次加工)实现我们想要的功能的工具类方法,比如,现在需要一个循环字符串的工具方法(String本身没有这样的方法):
fun String.multiply(int :Int):String{//扩展String里的方法
val strBuilder = StringBuilder()
for (i in 0 until int){
strBuilder.append(this)//这里this代表String
}
return strBuilder.toString()
}
fun main() {
println("hello".multiply(6))//此时 就可以直接通过String调用multiply方法(实现二次加工)
}
运行结果:
hello,hello,hello,hello,hello,hello,
除了扩展方法,kotlin还可以扩展成员变量,需要注意的是,扩展变量时,需要用get()方法来设定默认值,例如:
val String.version: String
get() = "1.0.2"
这是,在使用String对象时就可以直接获取这个扩展变量
试一试:
println("Hello".version)
运行结果:
1.0.2
小结
数据类
- 解放Java中写JavaBean时大量代码的麻烦里
- 默认实现copy、toString等方法
- 生成对应属性个数的componentN方法
- 需添加allOpen和noArg插件(填坑插件)
示例代码:
kotlin实现数据类:
data class Country(val id:Int,val name:String)
编译后自动生成的对应的java代码:
public final class Country {
private final int id;
@NotNull
private final String name;
public final int getId() {
return this.id;
}
@NotNull
public final String getName() {
return this.name;
}
public Country(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 Country copy(int id, @NotNull String name) {
Intrinsics.checkParameterIsNotNull(name, "name");
return new Country(id, name);
}
// $FF: synthetic method
@NotNull
public static Country copy$default(Country 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);
}
@NotNull
public String toString() {
return "Country(id=" + this.id + ", name=" + this.name + ")";
}
public int hashCode() {
int var10000 = this.id * 31;
String var10001 = this.name;
return var10000 + (var10001 != null ? var10001.hashCode() : 0);
}
public boolean equals(@Nullable Object var1) {
if (this != var1) {
if (var1 instanceof Country) {
Country var2 = (Country)var1;
if (this.id == var2.id && Intrinsics.areEqual(this.name, var2.name)) {
return true;
}
}
return false;
} else {
return true;
}
}
}
从上面代码可知,kotlin中我们用一行代码就可以实现传统Java代码中繁杂JavaBean类的编写,这是kotlin给我们的便利之一
componentN是kotlin中除JavaBean中一般方法外特有的方法,使用示例:
fun main() {
val china = Country(0,"China")
println(china)
println(china.component1())
println(china.component2())
println("--------------")
val (id,name) = china
println(id)
println(name)
}
为方便理解上述代码,可看编译后对应生成的java代码:
public static final void main() {
Country china = new Country(0, "China");
System.out.println(china);
int id = china.component1();
System.out.println(id);
String var9 = china.component2();
System.out.println(var9);
var9 = "--------------";
System.out.println(var9);
id = china.component1();
String name = china.component2();
System.out.println(id);
System.out.println(name);
}
运行结果:
Country(id=0, name=China)
0
China
-------------
0
China
需要注意,kotlin数据类在没有添加allOpen和noArg插件时生成的对应的JavaBean类是final类型的且没有无参构造函数,这是kotlin存在的问题,只有在集成了allOpen和noArg插件后才会在对应生成的Java类中实现无参构造函数且为非final类,我们才能像使用普通JavaBean类那样使用我们需要的数据类,不过好像还是有坑,其成员变量还是final型的,并且没有对应的setter方法,只有getter方法,因此可能使用时还会有所限制
内部类
- 定义在类内部的类
- 与类成员有相似的访问控制
- 默认是静态内部类,非静态用inner关键字
- 使用this@Outter,this@Inner关键字来定位是外部类变量或内部类变量
匿名内部类
- 没有定义名字的内部类
- 类名编译时生成,类似Outter$1.class
- 可继承父类、实现多接口,与Java注意区别
示例代码:
class Outter{
val a:Int = 4
class Inner1{ //默认为静态内部类
fun getOutterA():Int{
// return a //错误,编译不通,静态内部类,无法访问外部类变量
return 0
}
}
inner class Inner2{ //设为非静态内部类
val a:Int = 5
fun getOutterA():Int{
return this@Outter.a //可以访问外部变量a,若内部内中有同名变量,访问外部变量需用this@Outter
}
fun getInnerA():Int{
// return this@Inner2.a //获取内部变量a,可用this@Inner2
return a //也可以直接返回内部变量名
}
}
}
静态内部类与非静态内部类方法调用示例代码:
fun main() {
val mOutter = Outter()
println(mOutter.Inner2().getInnerA())//非静态内部类方法调用,需先new外部类实例
println(Outter.Inner1().getOutterA())//静态内部类方法调用,直接使用外部类类名
println(mOutter.Inner2().getOutterA())
}
枚举(实例可数)
- 实例可数的类,注意枚举也是类
- 可以修改构造,添加成员
- 可以提升代码的表现力,也有一定的性能开销
示例代码:
enum class LogLevel(val id:Int){
VERBOSE(0),DEBUG(1),INFO(2),WARN(3),ERROR(4),ASSERT(5);
fun getTag(): String {
return "$id->$name"
}
override fun toString(): String {
return "$id->$name"
}
}
fun main() {
println(LogLevel.DEBUG.getTag())
println(LogLevel.valueOf("ERROR"))
}
可查看其生成对应java代码:
public enum LogLevel {
VERBOSE,
DEBUG,
INFO,
WARN,
ERROR,
ASSERT;
private final int id;
@NotNull
public final String getTag() {
return this.id + "->" + this.name();
}
@NotNull
public String toString() {
return this.id + "->" + this.name();
}
public final int getId() {
return this.id;
}
private LogLevel(int id) {
this.id = id;
}
}
// EnumUseKt.java
package com.zhusp.kotlinuse.dataclass;
public final class EnumUseKt {
public static final void main() {
String var0 = LogLevel.DEBUG.getTag();
System.out.println(var0);
LogLevel var1 = LogLevel.valueOf("ERROR");
System.out.println(var1);
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
}
运行结果:
1->DEBUG
4->ERROR
密封类(子类数量有限的类)
子类可数
- <v1.1,子类必须定义为密封类的内部类
-
=v1.1,子类只需要与密封类在同一个文件中即可
与枚举不同,枚举是其实例有限,而密封类是子类数量有限
示例代码
密封类关键字:sealed
sealed class PlayerCMD{
class PlayCMD(url:String,position: Long = 0):PlayerCMD()
class SeekCMD(position:Long):PlayerCMD()
object PauseCMD:PlayerCMD()
object StopCMD:PlayerCMD()
//不可在当前文件以外定义其它PlayerCMD的子类,这么做是为了限制类的扩展,防止可能存在的安全隐患
}