面对对象:把程序的数据和方法当作为一个整体来看待
在java和kotlin中,都有类,接口,继承,嵌套,枚举的概念,唯一的区别就是这些概念在具体的语法不同而已,在kotlin中也比java多一些东西,比如数据类,密封类,密封接口等。
类kotlin中的类,我们可以将其理解成对某种事物的抽象模型,比如人
class Person(val name : String, var age:Int)
而我们用java来写的话是这样的
public class Person{
private String name;
private int age;
public Person(String name,int age){
this.name = name ;
this.age = age ;
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
public void setAge(int age){
this.age = age;
}
}
在上面的Person类中 分别定义了两个属性,分别是name,age。在kotlin 中,我们name 属性是用val修饰的,这意味着它在初始化后无法被修改,对应java中,就是该变量只有getter没有setter,而age则是用var修饰,这意味它可以被随意修改,对应java中,既有getter也有setter。
再者,kotlin定义的类,默认是public的,编译器会为我们生成构造函数,对于类中的属性,也会为我们自动生成getter和setter。
自定义属性 getter and setter
如果想要实现一个功能,就是根据年龄的大小判断是不是成年人,也就是age>=18.
如果按照我们java思维,我会给Person定义一个新的方法,isAdult().
class Person(val name :String ,var age :Int ){
fun isAdult():Boolean{
return age >= 18
}
}
又或者利用kotlin的简洁语法
lass Person(val name :String ,var age :Int ){
fun isAdult() = age >= 18
}
这样已经算不错的写法了,然而kotlin有一种更符合直觉的写法,那就是把isAdult()定义为属性,也就是kotlin属性的自定义getter
lass Person(val name :String ,var age :Int ){
val isAdult
get() = age >= 18
}
如果get()内部逻辑比较复杂的话,我们可以这样写
lass Person(val name :String ,var age :Int ){
val isAdult :Boolean
get(){
// 扒拉扒拉
retrun age >= 18
}
}
这里要注意一下,这种情况下编译器的类型推导就会失效,就需要加上明确的类型Boolean
这里会想, 如果需要用setter的呢,我们可以这样写
class Person(val name :String){
var age : Int = 0
set(value:Int){
field = value
}
}
kotlin编译器为我们提供字段field,field = value 就实现了我们对age的赋值 *** 作。
抽象类与继承在kotlin中,抽象类的定义跟java的几乎一样,也就是在关键字“class” “fun”的前面加上abstract关键字即可。我们把Person定义为抽象类,然后为它添加一个抽象方法:
abstract class Person(val name :String){
abstract fun walk()
}
这样一来,我们要创建Person类,就必须要使用匿名内部类的方式,或者使用Person的子类来创建变量,而这里,我们就需要用到类的继承了;
继承在java中,我们继续是需要用extends关键字,而kotlin 是直接使用冒号表示继承。
在kotlin中,除了抽象类以外,正常的类其实是可以被继承的,不过我们必须对这个类标识为open
class Person(){
fun walk()
}
class Boy:Person(){}
//报错,如果一个类不是抽象类,并且没有open修饰的话,它是无法继承的
open class Person(){
open fun walk()
}
class Boy:Person(){
override fun walk()
}
//编译通过
对于被open修饰的普通类,它的内部方法,属性默认都是不可以重写的,除非它们也用open修饰
在继承的行为上,kotlin和java完全相反,Java当中,一个类如果没有被final修饰的话,它默认是可以被继承的。也就是说,Java的继承是默认开放的,Kotlin的继承是默认封闭的
接口和实现Kotlin当中的接口和java也是大同小异,它们都是通过interface定义
interface Behavior {
fun walk()
}
class Person(val name :String) : Behavior {
override fun walk(){
}
}
我们定义一个新的接口Behavior,它里面有一个需要被实现的方法walk。我们可以发现,Kotlin的继承跟接口都是使用冒号去实现的
kotlin跟java接口的最大的差异,就是kotlin在接口可以有默认实现的情况下,同时它也可以有属性
interface Behavior {
//接口内可以有属性
val canWalk :Boolean
//接口方法的默认实现
fun walk(){
if(canWalk){
}
}
}
class Person(val name :String) : Behavior {
override val canWalk :Boolean
get() = true
}
我们在Behavior接口当中定义了一个属性canWalk,代表是否可以行走,并且在接口方法中,默认实现了walk。我们可以看到,在person类中我们就可以不必实现walk方法了。
嵌套在kotlin中,嵌套类跟java一样,分为两种:非静态内部类,静态内部类。
class A {
val name : String = ""
fun foo() = 1
class B{
val a = name
val b = foo()
//报错
}
}
看一下上面的代码,B是A的嵌套类,这非常容易理解,不过我们这样写编译器报错,我们无法在B类当中访问A类的属性和成员方法。如果有java的基础,应该可以发现这样的写法对应java当中的静态内部类!所以说,kotlin当中的普通嵌套类,它本质上是静态的,如果你想定义一个普通的内部类的话,我们需要在嵌套类的前面加上关键字 inner 关键字。
class A {
val name : String = ""
fun foo() = 1
//加上关键字inner
inner class B{
val a = name
val b = foo()
//编译通过
}
}
inner关键字,代表B类是A类的内部类,这种情况下B类的内部是可以访问A类的成员属性和方法的
kotlin的设计非常巧妙,如果你熟悉java开发,你会知道,java当中的嵌套类, 如果没有static关键字修饰的话(我们经常会忘记加static关键字),它就是个内部类,这样的内部类会持有外部类的引用的,而这样会非常容易出现内存泄漏。
Kotlin中的特殊类数据类
用于存放数据的类,定义一个数据类,我们只需要在普通类的前面加上一个关键字data就可以
data class Person(val name :String, val age : Int)
在kotlin当中,编译器会为数据类自动生成一些有用的方法
如:
equals()
hashCode()
toString()
componentN();
copy()
val tom = Person("Tom",18)
val(name,age) = tom // 等价于 name =Tom,age = 18
val mike = tom.copy(name = "Mike")
val(name ,age) = Tom这行代码其实使用了数据类的解构声明,这种方式可以让我们快速通过数据类来创建一连串的变量。另外就是copy方法,我们可以拷贝的同时,修改某个属性 。
密封类我们先看看枚举类,我们通过enum就可以定义枚举类,所谓枚举,就是一组有限的数量的值,比如,人分为男人女人,这样的非类是有限的,我们可以枚举出每一种情况,我们在when表达式使用枚举时,编译器甚至能自动帮我们推导出逻辑是否完备。这就是枚举的优势
enum class Human{
MAN,WOMAN
}
fun isMan(data:Human) = when (data){
Human.MAN -> true
Human.WOMAN -> false
}
但是,枚举也有局限性。比如我们想判断枚举的结构相等和引用相等时,结果始终都为ture,而这代表了,每一个枚举的值,它在内存当中始终都是同一个对象引用。
println(Human.MAN == Human.MAN)
println(Human.MAN === Human.MAN)
输出
true
true
那么万一,我们想要枚举的值拥有不一样的对象引用,那么我们就需要使用密封类,定义密封类,我们需要使用sealed关键字,在android开发中,我们会经常使用密封类对数据进行封装,比如
sealed class Result{
data class Success(val data:T,val message:String = "") :Result()
data class Error(val exception:Exception) : Result()
data class Loading(val time:Long = System.currentTimeMillis()):Result
}
首先我们使用sealed关键字定义了Result类并且它需要一个泛型参数R,R前面的out我们可以暂时先忽略。
这个密封类。我们是用来封装网络请求结果的。可以看到在Result类中,分别有三个数据类,分别是Success,Error,Loading,这样当请求返回结果后,我们将其与kotlin协程中的when表达式相结合,我们UI展示逻辑就会变得非常简单,成功,失败,进行中,我们可以相对应的显示
fun display(data:Result) = when(data){
is Result.Success -> displaySuccessUi(data)
is Result.Error -> showErrorMsg(data)
is Result.Loading -> showLoading()
}
由于我们的密封类只有这三种情况,所以我们的when表达式不需要else分支,可以看到,这样的代码风格,即实现了类似枚举类的逻辑完备性,还完美实现了数据结构的封装。
下一篇我会复习一下object关键字的语意以及具体使用场景
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)