kotlin学习--类的延申
一、内部类
在现实开发中,内部类出现的场景可以说非常多,例如,adapter(适配器)中的viewHolder、数据模型中也有可能出现一个或者多个对象,这里的对象就是一个内部类。让我们来了解下内部类哩。
1.1.嵌套类
所谓的嵌套类,通俗来讲就是一个类嵌套在另一个类里面
eg:
/**
* 嵌套类
*/
private var name = "biao"
fun outdoorMethou(){
//nesteMethou()//无法直接调用嵌套类的成员
//只能通过实例嵌套类来调用
val nestingClass = NestingClass()
nestingClass.nesteMethou()
}
class NestingClass{
fun nesteMethou(){
//println("name:$name")//无法使用外部类的成员
println("to do something magic")
}
}
fun main(args:Array<String>){
val nestingClass = OutdoorClass.NestingClass()
nestingClass.dowhat()
}
reslut:
to do something magic
说明:
- 嵌套类相当于Java中的静态内部类,但是在kotlin中完全取消了static关键字,所以在kotlin类中,除了嵌套类,其余都是非静态成员
- 在Java中静态成员是不能访问非静态成员的,参考于此,可以解释在嵌套类中不能访问外部类的成员,但是可以访问其他嵌套类
- 跟内部类一样,外部类不能直接调用嵌套类成员,如需调用,需创建嵌套类对象,通过对象调用嵌套类成员
- 调用嵌套类的属性或方法的格式为:外部类.嵌套类().嵌套类方法/属性。在调用的时候嵌套类是需要实例化的。
1.2.内部类
内部类相当于Java中没有用static修饰的内部类,使用关键字:inner
eg:
class OutClass{
//外部类的私有属性可以被内部类使用
private var name = "xie"
//外部类的私有函数可以被内部类使用
private fun outFun(){
println("i am outFun")
}
//通过此方法实例化内部类,再调用内部类方法
fun getInnerClassTodo(){
//innerFun()//这种外部类直接调用内部类成员的方式是编译不通过的,因为此时根本不存在内部类的对象
val innerClass = InnerClass()
//外部类想调用内部类,需实例化内部类
innerClass.innerFun()
}
inner class InnerClass{
var age = 20
fun innerFun(){
println("name:$name+age:$age")
outFun()
}
}
}
fun main(args:Array<String>){
val innerClass = OutClass().InnerClass()
innerClass.innerFun()
}
reslut:
name:xie+age:20
i am outFun
说明:
- 外部类的成员可以被内部类使用,外部类无法直接使用内部类成员,只能通过实例化内部类成员
- 调用内部类的属性或方法的格式为:外部类().内部类().内部类方法/属性。在调用的时候外部类、内部类都是需要实例化的。
1.3.匿名内部类
在实际开发中,匿名内部类并不陌生,随处可见,像view的点击事件:OnClickListener,多线程的实现。
其实匿名内部类就是一个没有名字的内部类,通常用来简化代码,使用匿名内部类的前提条件是:必须继承一个父类或者实现一个接口
eg:
/**
* 定义一个接口
*/
interface OnClickListener{
fun onItemClick(str : String)
}
/**
* 匿名内部类
*/
class AnonymousClass{
lateinit private var listener : OnClickListener
fun setOnClickListener(listener: OnClickListener){
this.listener = listener
}
fun testListener(){
listener.onItemClick("我是匿名内部类的测试方法")
}
}
fun main(args:Array<String>){
val anonymousClass = AnonymousClass()
anonymousClass.setOnClickListener(object :OnClickListener{
override fun onItemClick(str: String) {
println("to do something")
}
})
}
1.4.局部类
局部类:跟Java一样,就是在方法中的类
eg:
class LocalClass{
var numOut = 1
fun partMouth(){
var name = "partMouth"
class PartClass{
var numPart : Int = 2
fun test(){
name = "PartClass"
numOut = 3
numPart = 4
println("我是局部类中的方法")
}
}
val partClass = PartClass()
println("name = $name \t numPart = " + partClass.numPart + "\t numOut = $numOut")
partClass.test()
println("name = $name \t numPart = " + partClass.numPart + "\t numOut = $numOut")
}
}
fun main(args:Array<String>){
val localClass = LocalClass()
localClass.partMouth()
}
result:
name = partMouth numPart = 2 numOut = 1
我是局部类中的方法
name = PartClass numPart = 4 numOut = 3
说明:
- 局部类只能在定义该局部类的方法中使用。
- 定义在实例方法中的局部类可以访问外部类的所有变量和方法。也能修改
- 局部类中的可以定义属性、方法。并且可以修改局部方法中的变量。
二、抽象类
在我们日常开发中,一般会需要写一个基类,封装一些方法以及处理一些共有的逻辑,只是不同的类会因其不同的功能实现不同的代码。就这样的一个基类,一般都是一个抽象类
2.1.抽象类的定义
抽象类:可以理解为一个模板,所有的子类根据这个模板填充自己的代码
2.1.1 关键字
跟Java一样,kotlin抽象类的关键字也是abstract
在这里我们需要注意的是抽象分为抽象类、抽象函数和抽象属性,而一个抽象类和一个普通类的区别就在于抽象类的除了可以有其自己的属性、构造函数和函数等组成部分,还包含了抽象函数和抽象属性。
eg:
abstract class AbstractClass {
//自身的属性
var tag = this.javaClass.simpleName
//自身的方法
fun simpleFun():Unit{//Unit相当于java中的void,表示没有返回值,可以省略不写
TODO("do something")
}
//抽象属性和方法
abstract var string:String
abstract fun init()
}
class ChildrenClass : AbstractClass() {
//不是抽象类的子类必须实现父类的方法和属性
override var string: String
get() = tag
set(value) {}
override fun init() {
println("Implementing parent class methods,Obtain tag:$string")
}
}
abstract class AbsChildrenClass : AbstractClass(){
//抽象子类可不用实现父类的方法和属性
var name = this.javaClass.simpleName
abstract fun check()
}
class absExtend : AbsChildrenClass(){
var className = this.javaClass.simpleName
override var string: String
get() = name
set(value) {}
override fun init() {
println("name:$name")
}
override fun check() {
if (className == string) println("do something")
else println("Nothing can be done")
}
}
fun main(args : Array<String>){
//抽象类不能直接实例化
//val abstractClass = AbstractClass()//Cannot create an instance of an abstract class
val childrenClass = ChildrenClass()
var abstractClass:AbstractClass = ChildrenClass()//若要实现抽象类的实例化,需要依靠子类采用向上转型的方式处理
val absExtend = absExtend()
childrenClass.init()
absExtend.init()
absExtend.check()
}
result:
Implementing parent class methods,Obtain tag:ChildrenClass
name:absExtend
do something
小结:
- 抽象类本身具有普通类特性,以及组成部分。不过值得注意的是,抽象类不能直接被实例化
- 抽象类是为其子类定义了一个模板。不同是类实现不同的功能
- 若普通类继承了抽象类,需全部重写父类的抽象方法和属性,如子类是抽象则不用
规则:
- 在Kotlin中的抽象类在顶层定义的时候只能使用public可见性修饰符修饰。
- 抽象类中可以定义内部抽象类。
- 只能继承一个抽象类。
- 若要实现抽象类的实例化,需要依靠子类采用向上转型的方式处理。eg:var abstractClass:AbstractClass = ChildrenClass()
- 抽象类可以继承自一个继承类,即抽象类可以作为子类。不过,抽象类建议不用open修饰符修饰,因为可以覆写抽象类的父类的函数。
三、接口类
3.1 声明以及用法
3.1.1 声明
关键字:interface
定义格式:interface 接口名{
}
3.1.2 用法
关键字:“:”,表示实现该接口,Java不一样的是,Java使用implement关键字
综合之前我之前写过的文章,内容使用到:的次数表示,这里讲一下:有哪些代表含义:
a、用于属性的定义
b、用于表示实现接口
c、用户继承
d、用于方法返回类型定义
eg:
interface InterfaceClass{
fun funOne()
}
class Demo:InterfaceClass{
override fun funOne() {
println("funOne are using")
}
}
fun main(args: Array<String>) {
val demo = Demo()
demo.funOne()
}
result:
funOne are using
3.2.接口中的方法使用
eg:
interface InterfaceClass {
fun funOne()
fun funTwo(num: Int)
fun funThree(num: Int): Int
/**
* 定义了一个有参无返回值的方法
* 因为有结构体故可以不用重写
*/
fun funFour(): String {
return "I am funFour"
}
}
class Demo : InterfaceClass {
override fun funOne() {
println("funOne are using")
}
override fun funTwo(num: Int) {
println("funTwo are using and parem is num:$num")
}
override fun funThree(num: Int): Int {
println("funThree are using and parem is num:$num")
return num + 1
}
/**
* 接口中的funFour()方法默认返回”funFour“字符串.
* 可以用super.funFour()返回默认值
* 也可以不用super关键字,自己返回一个字符串
*/
override fun funFour(): String {
println("user I am funFour")
return super.funFour()
}
}
fun main(args: Array<String>) {
val demo = Demo()
demo.funOne()
demo.funTwo(8)
println(demo.funThree(9))
demo.funFour()
}
result:
funOne are using
funTwo are using and parem is num:8
funThree are using and parem is num:9
10
user I am funFour
I am funFour
说明:
- 不带结构体的函数可以省略大括号,且不用强制重写带结构体的函数就可以直接调用。
- 接口方法可以默认实现,Java8中需要一个default关键字,kotlin只需要写方法体就好
3.3.接口中的属性使用
- 在接口中申明属性。接口中的属性要么是抽象的,要么提供访问器的实现。接口属性不可以有后备字段。而且访问器不可以引用它们。
3.3.1 作为抽象的
eg:
interface InterfaceFeild {
var num:Int
var name:String
}
class Demo1(override var num: Int, override var name: String) :InterfaceFeild{
fun test(){
println("num:$num\t+name:$name")
}
}
fun main(args: Array<String>) {
val demo1 = Demo1(21, "biao")
demo1.test()
}
result:
num:21 name:biao
说明:
- 即重写属性的时候是在实现类的类参数中。这也是用代码提示去重写的实现方法
3.3.2 作为访问器
手动方式去重写,并提供get方法
eg:
interface InterfaceFeild {
var num:Int
var name:String
val age:Int
get() = 2
var number:Int
}
class Demo1(override var num: Int, override var name: String) :InterfaceFeild{
fun test(){
println("age:$age\tnumber:$number")
}
override val age: Int
get() = super.age
override var number: Int = 25
}
fun main(args: Array<String>) {
val demo1 = Demo1(21, "biao")
demo1.test()
demo1.number = 3
demo1.test()
}
result:
num:21 name:biao
age:2 number:25
num:21 name:biao
age:2 number:3
3.3.3 接口的冲突问题解决
该问题是指当我们在父类中声明了许多类型,有可能出现一个方法的多种实现
eg:
interface InfOne{
fun funTest(){
println("I am InfOne")
}
}
interface InfTwo{
fun funTest(){
println("I am InfTwo")
}
}
class Demo2 : InfOne,InfTwo{
override fun funTest() {
super<InfOne>.funTest()
super<InfTwo>.funTest()
}
}
fun main(args: Array<String>) {
val demo2 = Demo2()
demo2.funTest()
}
说明:Demo4实现了InfOne和InfTwo两个接口,而两个接口中都存在两个相同方法名的方法。因此编译器不知道应该选哪个,故而我们用super<接口名>.方法名来区分。
四、继承类
4.1 超类 Any
在Kotlin中,说有的类都是继承与Any类,这是这个没有父类型的类。即当我们定义各类时,它默认是继承与Any这个超类的
来看看Any的源码
package kotlin
/**
* The root of the Kotlin class hierarchy. Every Kotlin class has [Any] as a superclass.
* 看这个源码注释:意思是任何一个Kotlin的类都继承与这个[Any]类
*/
public open class Any {
// 比较: 在平时的使用中经常用到的equals()函数的源码就在这里额
public open operator fun equals(other: Any?): Boolean
// hashCode()方法:其作用是返回该对象的哈希值
public open fun hashCode(): Int
// toString()方法
public open fun toString(): String
}
从源码可以我们看出,它直接属于kotlin这个包下。并且只定义了上面所示的三个方法。或许你具有Java的编程经验。在我们熟知的Java中,所有的类默认都是继承与Object类型的。而Object这个类除了比Any多了几个方法与属性外,没有太大的区别。
此外,从上面的源码中我们可以看到类和函数都有open关键字,那么这个修饰符的作用是什么呢?
来,让我们来分析一下,超类(Any)是所有类的父类,而分类中所有元素都有open修饰符,如果我们跟Any的语法去写一个类,是不是就是一个继承类了,说明,open修饰符是我们定义继承类的修饰符
4.2 定义
4.2.1 基础使用
- 定义继承类的关键字为:open。不管是类、还是成员都需要使用open关键字。
- 定义格式为:
open class 类名{
open var/val 属性名 = 属性值
open fun 函数名()
}
eg:
open class InheritClass{
open var num = 3
open fun funOne() = "funOne"
open fun funTwo() = 25
}
class DemoTest : InheritClass(){
// 这里值得注意的是:Kotlin使用继承是使用`:`符号,而Java是使用extends关键字
}
fun main(args:Array<String>){
val demoTest = DemoTest()
println(demoTest.num)
println(demoTest.funOne())
println(demoTest.funTwo())
}
result:
3
funOne
25
说明:
从上面的代码可以看出,DemoTest类只是继承了Demo类,并没有实现任何的代码结构。一样可以使用Demo类中的属性与函数。这就是继承的好处。
4.2.2 构造函数
在上次的分享中有提到过,在kotlin中,可以有一个主构造函数,或者多个辅助函数。或者没有构造函数的情况。
在这里,来说一下实现类无主构造函数,和存在主构造函数的情况。
- 无主构造函数
当实现类无主构造函数时,则每个辅助构造函数必须使用super关键字初始化基类型,或者委托给另一个构造函数。 请注意,在这种情况下,不同的辅助构造函数可以调用基类型的不同构造函数
这里用view的自定义来说明一下,eg:
class MyView : View(){
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int)
: super(context, attrs, defStyleAttr)
}
可以看出,当实现类无主构造函数时,分别使用了super()去实现了基类的三个构造函数。
- 存在主构造函数
当存在主构造函数时,主构造函数一般实现基类型中参数最多的构造函数,参数少的构造函数则用this关键字引用即可了。这点在Kotlin——中级篇(一):类(class)详解这篇文章是讲解到的。
这里同样用view自定义的例子,eg:
class MyView(context: Context?, attrs: AttributeSet?, defStyleAttr: Int)
: View(context, attrs, defStyleAttr) {
constructor(context: Context?) : this(context,null,0)
constructor(context: Context?,attrs: AttributeSet?) : this(context,attrs,0)
}
4.2.3 函数的重载和重写
kotlin的重载和重写的理解和Java一样,重载:是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同;重写是子类对父类的允许访问的方法的实现过程进行重新编写,返回值和形参都不能改变。即外壳不变,核心重写!
4.2.3.1 重写函数中的两点特殊用法
这里介绍两点kotlin相较于Java特殊的地方
- 当基类中的函数,没有用open修饰符修饰的时候,实现类中出现的函数的函数名不能与基类中没有用open修饰符修饰的函数的函数名相同,不管实现类中的该函数有无override修饰符修饰。
eg:
open class InheritClass{
fun test(){}
}
class DemoTest : InheritClass(){
// 这里声明一个和基类型无open修饰符修饰的函数,且函数名一致的函数
// fun test(){} 编辑器直接报红,根本无法运行程序
// override fun test(){} 同样报红
}
- 当一个类不是用open修饰符修饰时,这个类默认是final的
4.2.3.1 方法重载
方法重载也是体现了面向对象中的多态的特性
方法重载在继承类同样有效
eg:
open class InheritClass{
open fun funThree() = "funThree"
}
class DemoTest : InheritClass(){
fun funThree(name:String):String{
return name
}
override fun funThree(): String {
return super.funThree()
}
}
fun main(args:Array<String>){
val demoTest = DemoTest()
println(demoTest.funThree())
demoTest.funThree("I am funThree,Do you known polymorphic?")
}
result:
funThree
I am funThree,Do you known polymorphic?
4.2.4 重写属性
- 重写属性和重写方法其实大致是相同的,但是属性不能被重载。
- 重写属性即指:在基类中声明的属性,然后在其基类的实现类中重写该属性,该属性必须以override关键字修饰,并且其属性具有和基类中属性一样的类型。且可以重写该属性的值(Getter)
eg:
open class InheritClass{
open var num = 3
}
class DemoTest : InheritClass(){
override var num: Int = 10
}
fun main(args:Array<String>){
val demoTest = DemoTest()
println(demoTest.num)
}
result:
10
4.2.4.1 重写属性中,val与var的区别
有一点值得我们注意的是当基类中属性的变量修饰符为val的使用,其实现类可以用重写属性可以用var去修饰。反之则不能。
eg:
open class InheritClass {
open val valStrOne = "I am val string one"
open val valStrTwo = "I am val string two"
open val valStrThree = "I am val string three"
open val valStrFour = "I am val string four"
}
class DemoTest : InheritClass() {
override val valStrOne: String
get() = super.valStrOne
override var valStrTwo: String = ""
get() = super.valStrTwo
override val valStrThree: String = ""
override var valStrFour: String = "abc"
set(value){field = value}
}
fun main(args: Array<String>) {
val demoTest = DemoTest()
println(demoTest.valStrOne)
println(demoTest.valStrTwo)
println(demoTest.valStrThree)
println(demoTest.valStrFour)
demoTest.valStrFour = "I am Icon man"
println(demoTest.valStrFour)
}
result:
I am val string one
I am val string two
I am val string three
I am val string four
I am Icon man
4.2.4.2、Getter()函数慎用super关键字
在这里值得注意的是,在实际的项目中在重写属性的时候不用get() = super.xxx,因为这样的话,不管你是否重新为该属性赋了新值,还是支持setter(),在使用的时候都调用的是基类中的属性值。
eg:
open class InheritClass {
open val valStrFive = "I am val string five"
}
class DemoTest : InheritClass() {
override var valStrFive: String = "cba"
get() = super.valStrFive
set(value){field = value}
}
fun main(args: Array<String>) {
val demoTest = DemoTest()
println(demoTest.valStrFive)
demoTest.valStrFive
println(demoTest.valStrFive)
}
result:
I am val string five
I am val string five