Kotlin:类与面向对象编程(四)

一、面向对象编程简史
参考《kotlin从入门到进阶实践》55页。

二、声明类
1、空类
使用class关键字声明类。可以声明一个什么都不干的类(空类)
在这里插入图片描述

2、声明类和构造函数
1)声明类的时候同时声明构造函数,语法格式 是在类的后面用括号包含构造函数的参数列表。(
使用最多)

class Person(var name : String,var age : Int) {
    override fun toString(): String {
        return "Person {name = $name, age = $age}"
    }
}

// 调用
val person = Person("太难了",35)
        println("person = ${person}")

执行结果:
在这里插入图片描述
2)也可以先声明属性,等构造实例对象的时候再去初始化属性值。

class Person {
    lateinit var name : String // lateinit 关键字表示该属性延迟初始化
    var age : Int = 0          // lateinit  关键字不能修饰primitive类型(Int ,Float)
    lateinit var sex : String  //

    override fun toString(): String {
        return "Person {name = $name, age = $age, sex = $sex}"
    }
}

// 调用
val person = Person()
person.age = 27
person.name = "后来"
person.sex = "W"
println("person = ${person}")

执行结果:
在这里插入图片描述
3) 声明一个具有多种构造方式的类,可以使用constructor关键字声明构造函数

class Person {
    lateinit var name : String // lateinit 关键字表示该属性延迟初始化
    var age : Int = 0          // lateinit  关键字不能修饰primitive类型(Int ,Float)
    lateinit var sex : String

    constructor(name : String) { // 次级构造函数
        this.name = name
    }

    constructor(name: String, age : Int) { // 次级构造函数
        this.name = name
        this.age = age
    }

    constructor(name: String,age: Int,sex : String) { // 次级构造函数
        this.name = name
        this.age = age
        this.sex = sex
    }

    override fun toString(): String {
        return "Person {name = $name, age = $age, sex = $sex}"
    }
}

// 调用
val person = Person("原来你也在这里", 32,"W")
println("person = ${person}")

执行结果:
在这里插入图片描述
4)使用AS自动生成二级构造函数
步骤一:在类中声明变量
在这里插入图片描述
步骤二:在当前类中右击,在弹出的快捷菜单中选择Generate命令
在这里插入图片描述
步骤三:弹出生成次级构造函数的对话,在其中选择Secondary Constructor命令
在这里插入图片描述
在这里插入图片描述
生成之后的结果:
在这里插入图片描述
当需要通过比较复杂的逻辑来构建一个对象的时候,可采用构建者(Builder)模式来实现

三、抽象类与接口
抽象类表示“is-a”关系,而接口所代表的是“has-a”。

1、抽象类与抽象成员
1)抽象类用abstract。
2)因为抽象的概念在问题领域中没有对应的具体概念,所以抽象类是不能够实例化的。
3)只能实例化它的继承子类。
4)抽象类的成员(属性和函数)也必须是抽象的,需要使用abstract修饰。

// 定义抽象类 及 抽象方法
abstract class Shape {
    abstract fun area() : Double         // 抽象方法
    abstract fun perimeter() : Double    // 抽象方法
}

// 继承抽象类
class Rectangle(val width : Double, val height : Double) : Shape() {
    override fun area(): Double {
        return height * height
    }

    override fun perimeter(): Double {
        return 2*(width + height)
    }
}

// 调用
 val rec = Rectangle(4.0,8.0);
 println("shape : are = " + rec.area() + ",perimeter = " + rec.perimeter())

// 执行结果
I/System.out: shape : are = 32.0,perimeter = 24.0

5) 抽象类可以有带实现的函数。在所有子类都可以直接调用这个函数

abstract class Shape {
    abstract fun area() : Double         // 抽象方法
    abstract fun perimeter() : Double    // 抽象方法

    // 带实现的函数
    fun onClick() {
        println("shape: I am clicked!")
    }
}

// 调用
val rec = Rectangle(4.0,8.0);
println("shape : are = " + rec.area() + ",perimeter = " + rec.perimeter())
rec.onClick()

// 输出
I/System.out: shape: I am clicked!

6) 父类中的onClick()函数默认是final的,不可被覆盖重写。如果想要开放给子类重新实现这个函数,可以在前面加上open关键字:

abstract class Shape {
    abstract fun area() : Double         // 抽象方法
    abstract fun perimeter() : Double    // 抽象方法

    // 带实现的函数
    open fun onClick() {
        println("shape: I am clicked!")
    }
}
// 继承类重写
class Rectangle(val width : Double, val height : Double) : Shape() {
    override fun area(): Double {
        return (width * height)
    }

    override fun perimeter(): Double {
        return 2*(width + height)
    }

    override fun onClick() {
//        super.onClick()
        println("shape : ${this::class.simpleName} I am clicked!")
    }

}

// 调用
val rec = Rectangle(4.0,8.0);
println("shape : are = " + rec.area() + ",perimeter = " + rec.perimeter())
rec.onClick()

// 输出
I/System.out: shape : Rectangle I am clicked!

当子类继承了某个类之后,便可以使用父类中的成员变量,但并不是完全继承父类所有成员变量。具体原则如下:
1)能够继承父类的public和protected成员变量;
2)不能继承父类的private成员变量;
3)对于父类的包访问权限成员变量,如果子类和父类在同一个包下,则子类能够继承;否则,子类不能继承;
4)对于子类可以继承的父类成员变量,如果在子类中出现了同名称的成员变量,则会发生隐藏现象,既子类的成员变量会屏蔽掉父类的同名成员变量。如果要在子类中访问父类中的同名成员变量,需要使用super关键字进行引用。

2、接口
1) 使用interface作为关键字;
2) 与抽象方法相比,接口都可以包含抽象的方法既方法实现;
3) 接口是没有构造函数的。使用冒号“:”语法来实现一个接口,如果有多个接口,用“,”逗号隔开。
4) 在重写某个方法时,如果实现的多个接口都实现了该方法,使用 super<接口名>.方法 调用。

四、object对象
单例模式可以保证系统中只有一个实例。既一个类只有一个对象实例。kotlin中没有静态属性和方法,但是可以使用关键字object声明一个object单例对象。object修饰的类是单例。
kotlin中还提供了伴生对象,用companion object(相当于java中的static)关键字声明。一个类只能有一个伴生对象。companion objec 属于成员的单例。

open class Foo() {
    object A{
        val a = 2;
    }

    companion object {
        val b = 3;
    }
}

 println("the Foo a = " + Foo.A.a)
 println("the Foo b = " + Foo.b)
// kotlin 转换为java
public class Foo {
   private static final int b = 3;
   
   public static final class A {
      private static final int a;
      @NotNull
      public static final Foo.A INSTANCE;

      public final int getA() {
         return a;
      }

      private A() {
      }

      static {
         Foo.A var0 = new Foo.A();
         INSTANCE = var0;
         a = 2;
      }
   }
}

五、数据类
数据类是只存储数据,不包含操作行为的类。Kotlin 中的数据类型可以不需要写get set 样本代码。

1、创建数据类
使用关键字data class创建一个只包含数据的类:

data class LoginUser(val name : String,val password : String)

在AS 上 可以使用工具,自动生成对应的java代码,通过java代码可以看出,数据类中自动实现的get set方法。
在这里插入图片描述
2、数据类自动创建的函数
编译器会根据主构造函数中声明的属性,自动创建以下3个函数:
1)equals()/hashCode()函数。
2)toString() 格式为:

@NotNull
   public String toString() {
      return "LoginUser(name=" + this.name + ", password=" + this.password + ")";
   }

3)component1()和component2()函数返回对应下表的属性值,按声明顺序排列;
4)copy()函数:根据旧对象属性重新new LoginUser(name, password) 一个对象出来。

3、数据类的语法限制
1)主构造函数至少包含一个参数;
2)参数必须标识为val 或者var;
3)不能为abstract open sealed 或者 inner;
4) 不能继承其他类(但能实现接口)
数据类可以在解构声明中使用:

// 数据类
data class LoginUser(val name : String,val password : String)

 // 调用
 val user = LoginUser("一辈子孤单","123456")
 val (myName, myPassword) = user  // 解构声明(myName, myPassword)
 println("data class : name = $myName, password = $myPassword")

// 输出
I/System.out: data class : name = 一辈子孤单, password = 123456

4、Pair 和Triple
Kotlin标准库提供了Pair和Triple数据类。分别表示二元组和三元组对象。
使用Pair对象初始化一个Map,代码如下:

val map = mapOf( 1 to "A", 2 to "B",3 to "C")
println("data class : map = $map")

// 输出
I/System.out: data class : map = {0=A, 2=B, 3=C}

六、注解
没看懂。参考《Kotlin从入门到进阶实战》70页

七、枚举
使用enum class关键字声明一个枚举类。

enum class Direction {
    NORTH,SOUTH,WEST,EAST
}

相比字符串常量,使用枚举能够实现类型安全。枚举类有两个内置的属性name和ordinal 分别表示的是枚举的对象值和下标位置。

 // 枚举
 val west = Direction.WEST
 println("enum : name = " + west.name + ",ordinal = " + west.ordinal)

// 输出
I/System.out: enum : name = WEST,ordinal = 2

每一个枚举都是枚举类的实例,他们可以被初始化:

enum class Direction(val c : String) {
    NORTH("北"),
    SOUTH("南"),
    WEST("西"),
    EAST("东")
}

// 调用
val west = Direction.WEST
println("enum : name = " + west.name + ",ordinal = " + west.ordinal + ",west = $west" + ",c = " + west.c)

// 输出
I/System.out: enum : name = WEST,ordinal = 2,west = WEST,c = 西

八、内部类
1、普通嵌套类

class NestedClassesDemo {
    class Outer {
        private val zero : Int = 0
        val one : Int = 1
        
        class Nested {
            fun getTwo() = 2
            
            class Nested1 {
                val three : Int = 3
                fun getFour() = 4
                fun acccessOut() {
                //    println("Nested : one = $one")
                }
            }
        }
    }
}

普通嵌套类没有持有外部类的引用,所以无法访问外部类的变量。

2、嵌套内部类
如果一个内部类想要访问外部类的一个成员,可以在这个类前面添加修饰符inner.

class NestedClassesDemo {

    class Outer {
        private val zero : Int = 0
        val one : Int = 1

        inner class Nested {
            fun getTwo() = 2

            inner class Nested1 {
                val three : Int = 3
                fun getFour() = 4
                fun acccessOut() {
                   println("Nested : one = $one")
                    println("Nested : two = ${getTwo()}")
                }
            }
        }
    }
}

// 调用
NestedClassesDemo.Outer().Nested().Nested1().acccessOut()

// 输出
I/System.out: Nested : one = 1
I/System.out: Nested : two = 2

3、匿名内部类

class NestedClassesDemo {
    class AnonymousInnerClassDemo {
        var isRunning = false
        fun doRun() {
            Thread(object : Runnable {  // 匿名内部类
                override fun run() {
                    isRunning = true
                    println("doRun : i am running ,isRunning = ${isRunning}")
                }
            }).start()
        }
    }
}

匿名内部类就是没有名字的内部类。匿名内部类可以访问外部类的变量。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值