Kotlin 类和对象
一.类定义
Kotlin 类可以包含:构造函数和初始化代码块、函数、属性、内部类、对象声明。
1.Kotlin 中使用关键字 class 声明类,后面紧跟类名:
class Runoob { // 类名为 Runoob
// 大括号内是类体构成
}
2.我们也可以定义一个空类:
class Empty
3.可以在类中定义成员函数:
class Runoob() {
fun foo() { print("Foo") } // 成员函数
}
和java不同的是Kotlin中的主类定义可以不用public,
比较奇怪的是,main方法要写在类外
package hello // 可选的包头
class A(){
var name:String="liwenzhi"
}
fun main(args: Array<String>) { // 包级可见的函数,接受一个字符串数组作为参数
println("Hello World!") // 分号可以省略
println("name : "+A().name)
}
运行结果:
Hello World!
name : liwenzhi
二.类的属性
1.属性定义
类的属性可以用关键字 var 声明为可变的,否则使用只读关键字 val 声明为不可变。
class Runoob {
var name: String = ……
var url: String = ……
var city: String = ……
}
我们可以像使用普通函数那样使用构造函数创建类实例:
val site = Runoob() // Kotlin 中没有 new 关键字
要使用一个属性,只要用名称引用它即可
site.name // 使用 . 号来引用
site.url
Koltin 中的类可以有一个 主构造器,以及一个或多个次构造器,主构造器是类头部的一部分,位于类名称之后:
class Person constructor(firstName: String) {}
如果主构造器没有任何注解,也没有任何可见度修饰符,那么constructor关键字可以省略。
class Person(firstName: String) {
}
主构造器和次构造器,我第一次看的时候也是不知道它说的什么意思,这个和java感觉很不同!不过,后面会有示例说明。
三.getter 和 setter
1.属性声明的完整语法:
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
上面看起来有点复杂,其实还是不难的,[]内的内容都是可以根据情况不写的。
2.getter 和 setter 都是可选
如果属性类型可以从初始化语句或者类的成员函数中推断出来,那就可以省去类型,val不允许设置setter函数,因为它是只读的。
var allByDefault: Int? // 错误: 需要一个初始化语句, 默认实现了 getter 和 setter 方法
var initialized = 1 // 类型为 Int, 默认实现了 getter 和 setter
val simple: Int? // 类型为 Int ,默认实现 getter ,但必须在构造函数中初始化
val inferredType = 1 // 类型为 Int 类型,默认实现 getter
3.实例
以下实例定义了一个 Person 类,包含两个可变变量 lastName 和 no,lastName 修改了 getter 方法,no 修改了 setter 方法。
class Person {
var lastName: String = "zhang"
get() = field.toUpperCase() // 将变量赋值后转换为大写
set
var no: Int = 100
get() = field // 后端变量
set(value) {
if (value < 10) { // 如果传入的值小于 10 返回该值
field = value
} else {
field = -1 // 如果传入的值大于等于 10 返回 -1
}
}
var heiht: Float = 145.4f
private set //不让设置属性值,其实相当于用val定义
}
// 测试
fun main(args: Array<String>) {
var person: Person = Person()
person.lastName = "wang" //这就是Kotlin中的set方法使用,其实没有set这个关键字
println("lastName:${person.lastName}") //Kotlin中get方法的使用,也没有get这个关键字
person.no = 9
println("no:${person.no}")
person.no = 20
println("no:${person.no}")
}
输出结果为:
lastName:WANG
no:9
no:-1
4.Kotlin中有个field的关键字
Kotlin 中类不能有字段。提供了 Backing Fields(后端变量) 机制,备用字段使用field关键字声明,
field 关键词只能用于属性的访问器,如实例:
var no: Int = 100
get() = field // 后端变量
set(value) {
if (value < 10) { // 如果传入的值小于 10 返回该值
field = value
} else {
field = -1 // 如果传入的值大于等于 10 返回 -1
}
}
看他举得例子还是能明白field的作用。但是看Kotlin中文档对field的定义,其实我是一头雾水的,不知道他说啥。
我感觉还是按照我自己的理解就可以了:field其实就是类似java中return后面要返回的值。学过java的人理解这句话,肯定是没有问题的。
5.非空属性必须在定义的时候初始化,kotlin提供了一种可以延迟初始化的方案,使用 lateinit 关键字描述属性:
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // dereference directly
}
}
上面看起来有点麻烦。
这里给大家讲解一下:
上面有三个关键字:
(1)laterinit用来延迟初始化的
(2)@SetUp/@Test这是Kotlin的注解
这里的注解并不是代码注释那种注解,而是像java的@override(类的继承)这种注解。
Kotlin的注解是写在方法名前面的,不像java是写在方法上面的。
还有这里MyTest是一个类,TestSubject也是一个类,这个类的相关代码没写出来而已。
四.主构造器
1.主构造器中不能包含任何代码,初始化代码可以放在初始化代码段中,初始化代码段使用 init 关键字作为前缀。
class Person constructor(firstName: String) {
init {
println("FirstName is $firstName")
}
}
注意:主构造器的参数可以在初始化代码段中使用,也可以在类主体n定义的属性初始化代码中使用。
一种简洁语法,可以通过主构造器来定义属性并初始化属性值(可以是var或val):
class People(val firstName: String, val lastName: String) {
//...
}
如果构造器有注解,或者有可见度修饰符,这时constructor关键字是必须的,注解和修饰符要放在它之前。
这句话怎么理解呢。
注解其实是对变量的过滤或限定,比如@NONULL,表示限制非空参数
修饰符就是public 这种啦。
2.实例
创建一个 Runoob类,并通过构造函数传入网站名:
class Runoob constructor(name: String) { // 类名为 Runoob
// 大括号内是类体构成
var url: String = "http://www.runoob.com"
var country: String = "CN"
var siteName = name
init {
println("初始化网站名: ${name}")
}
fun printTest() {
println("我是类的函数")
}
}
fun main(args: Array<String>) {
val runoob = Runoob("菜鸟教程")
println(runoob.siteName)
println(runoob.url)
println(runoob.country)
runoob.printTest()
}
输出结果为:
初始化网站名: 菜鸟教程
菜鸟教程
http://www.runoob.com
CN
我是类的函数
可以看到刚创建的时候就先调用了init的初始化方法(不需要手动调用)
其实init方法就相当于Android中的onCreate方法!
五.次构造函数
1.类也可以有二级构造函数,需要加前缀 constructor:
class Person {
constructor(parent: Person) {
parent.children.add(this)
}
}
2.如果类有主构造函数,每个次构造函数都要,或直接或间接通过另一个次构造函数代理主构造函数。
在同一个类中代理另一个构造函数使用 this 关键字:
class Person(val name: String) {
constructor (name: String, age:Int) : this(name) {
// 初始化...
}
}
3.如果一个非抽象类没有声明构造函数(主构造函数或次构造函数),它会产生一个没有参数的构造函数。构造函数属性是 public 。
如果你不想你的类有公共的构造函数,你就得声明一个空的主构造函数:
class DontCreateMe private constructor () {
}
注意:在 JVM 虚拟机中,如果主构造函数的所有参数都有默认值,编译器会生成一个附加的无参的构造函数,这个构造函数会直接使用默认值。
这使得 Kotlin 可以更简单的使用像 Jackson 或者 JPA 这样使用无参构造函数来创建类实例的库。
class Customer(val customerName: String = "")
4.实例
class Runoob constructor(name: String) { // 类名为 Runoob
// 大括号内是类体构成
var url: String = "http://www.runoob.com"
var country: String = "CN"
var siteName = name
init {
println("初始化网站名: ${name}")
}
// 次构造函数
constructor (name: String, alexa: Int) : this(name) {
println("Alexa 排名 $alexa")
}
fun printTest() {
println("我是类的函数")
}
}
fun main(args: Array<String>) {
val runoob = Runoob("菜鸟教程", 10000)
println(runoob.siteName)
println(runoob.url)
println(runoob.country)
runoob.printTest()
}
输出结果为:
初始化网站名: 菜鸟教程
Alexa 排名 10000
菜鸟教程
http://www.runoob.com
CN
我是类的函数
可以看到先是执行了init代码,然后再执行次构造函数的代码。
看完这个实例代码,可能你才对次构造函数有一些理解,但是又有点陌生!
其实陌生的是关键字!道理和java的都是一样的。下面对主构造函数和次构造函数进行一些理解描述。
六.对主构造器、次构造函数的理解描述
读完上面对主/次构造函数的介绍,好像会感觉有些东西没有理解那种感觉。
其实是对有些关键字陌生的原因,其中一个就是“函数”,其实就是java中的“方法”。
但是没学过C或对C不熟悉的朋友,就会比较疑惑甚至恼火,java代码出现“函数”就是非常不习惯。
这里我也没有直接把“函数”转换成“方法”,因为不想把Kotlin文档中的介绍擅自修改。
其实上面的介绍,阅读的时候把“函数”读成“方法”,上面很多概念你就能理解了。可以再回去阅读一次试试。
还有就是关键字:constructor,其实翻译成中文就是“构造函数”的意思,你可以理解成“构造方法”
这里我也是想吐槽一下Kotlin的编程者,感觉这个人对java不精通,对C静态,Kotlin中很多习惯都是根据C搞过来的。
学习Kotlin感觉就跟学了小半门的语言一样的,这也是很多Android工程师不愿去多看Kotlin的原因!
1.主构造器
上面介绍的主构造器,其实就是类似java中的构造方法
但是又有点不一样,因为有关键字constructor
Kotlin中的主构造器是写在类名后面的!不像java是写在类的里面的。
init方法就相关于Android中的onCreate方法,在类创建的时候就会执行。
2.次构造函数
这个是比较难理解的,并且上面有些语句的介绍很容易让人不明所以然。
class Runoob constructor(name: String) { // 类名为 Runoob
constructor (name: String, alexa: Int) : this(name) {
println("Alexa 排名 $alexa")
}
。。。
}
这里对次构造方法进行一下补充
如果定义了次构造方法,那么创建对象的时候就不能用主构造方法了!
Runoob r1=Runoob("liwen",22)//正确创建对象的方式
Runoob r1=Runoob("liwen")//报错!如果没有次构造方法的话,这里是不会报错的!
并且次构造方法定义的时候必须要再次声明一下主构造方法中的变量!
//上面主构造方法中定义了不是一个变量是两个或更多比如:
class Runoob constructor(name: String,age : Int) { // 类名为 Runoob
constructor (name: String, alexa: Int,age : Int) : this(name,age) {//这里声明的时候要确保有主构造方法的所有变量
println("Alexa 排名 $alexa")
}
。。。
}
还有一点是要注意的,次构造方法内的参数,是要比主构造方法的多,相同都不行!
其实个人感觉次构造方法是比较多余的,没啥意义。有主构造方法一个东西就行了,并且不难理解!
Kotlin中没有像java那样把构造方法可以重载,就是一个方法可以参数不同那种形式!
那样的话还要什么主构造器和次构造函数,再次感觉Kotlin的创建者是一个SB!
不过,Kotlin中其他自定义的方法是可以使用重载的。
到这里对主构造器和次构造函数就介绍完毕了,如果还是不理解,要么多看一遍,要么直接跳过去,后面看习惯了Kotlin代码其实这些都是很简单的内容。
七.抽象类
抽象是面向对象编程的特征之一,类本身,或类中的部分成员,都可以声明为abstract的。
注意:无需对抽象类或抽象成员标注open注解,也就是说open前面不要给它弄个@符号。
open class Base {
open fun f() {}
}
abstract class Derived : Base() {
override abstract fun f()
}
看到上面的代码,我也是一脸闷逼!什么鬼?
其中,主要不理解的就是open了
后面的abstract是抽象的意思,override是继承的意思,这两个在java中都是有使用的。
1.open关键字
open 注解与java 中的 final相反:它允许别的类继承这个类。默认情形下,kotlin 中所有的类都是 final ,用来表示他可以被集成
修饰类: 说明可以被继承
记住我们不用给一个抽象类或函数添加 open 注解,它默认是带着的。
也就是说抽象类或抽象方法是可以被继承和重写的。
实例:
open class Base(p: Int)
class Derived(p: Int) : Base(p)
修饰成员 : 在 kotlin 中坚持做明确的事。表明必须要重写重写它们
2.抽象类的一个完整例子
package hello // 可选的包头
//定义一个可以被继承的类
open class Base {
open fun f() { //open表示可以被重写,并且必须重写!
println("f1")
}
fun f2(){ //默认是final,子类不可重写
println("f2")
}
}
//定义一个抽象类,并且继承了另一个类
abstract class Derived : Base() {
override abstract fun f()
}
//定义一个类继承抽象类
class MyTest:Derived(){
override fun f(){//覆盖方法,继承抽象类,必须实现抽象方法,这个是没得说的
println("ffffff")
}
fun test1(age:Int){
println("my age is $age")
}
}
// 测试
fun main(args: Array<String>) {
var test: MyTest = MyTest()
test.test1(22)
test.f()
test.f2()
}
上面有三个类,充分使用到了open和abstract、override这几个关键字。
上面代码运行的结果:
my age is 22
ffffff
f2
八.嵌套类
我们可以把类嵌套在其他类中,看以下实例:
class Outer { // 外部类
private val bar: Int = 1
class Nested { // 嵌套类
fun foo() = 2
}
}
fun main(args: Array<String>) {
val demo = Outer.Nested().foo() // 调用格式:外部类.嵌套类.嵌套类方法/属性
println(demo) // 打印出: 2
}
嵌套类理解起来也是不难的。
java中只有内部类的说法,没有嵌套类的说法。
九.内部类
内部类使用 inner 关键字来表示。
内部类会带有一个对外部类的对象的引用,所以内部类可以访问外部类成员属性和成员函数。
package hello // 可选的包头
class Outer {
private val bar: Int = 1
var v = "成员属性"
/**嵌套内部类**/
inner class Inner {
fun foo() = bar // 访问外部类成员
fun innerTest() {
var o = this@Outer //获取外部类的成员变量,Outer类对象
println("内部类可以引用外部类的成员,例如:" + o.v)
}
}
}
fun main(args: Array<String>) {
val testInner = Outer().Inner()//创建内部类对象
val demo = testInner.foo()
println(demo) //使用内部类对象获取外部类属性,打印输出:1
testInner.innerTest() //执行内部类的方法
}
为了消除歧义,要访问来自外部作用域的 this,我们使用this@label,其中 @label 是一个 代指 this 来源的标签。
这个内部类使用还是比较简单的,和java的内部类是非常类似的。不过要注意Kotlin的main方法不在类里面,这个是和java不同的
十.匿名内部类
使用对象表达式来创建匿名内部类:
class Test {
var v = "成员属性"
fun setInterFace(test: TestInterFace) {
test.test()
}
}
/**
* 定义接口
*/
interface TestInterFace {
fun test()
}
fun main(args: Array<String>) {
var test = Test()
/**
* 采用对象表达式来创建接口对象,即匿名内部类的实例。
*/
test.setInterFace(object : TestInterFace {
override fun test() {
println("对象表达式创建匿名内部类的实例")
}
})
}
上面看到接口,其实接口就是抽象类差不多一样的东西。
Kotlin嵌套类、内部类、匿名内部类的区别
这里和java做一下对比
1.嵌套类
Kotlin中的嵌套类就像java中的外部类(同一个java文件那种外部类)
比如java代码:
package hello;
public class AA{
String name="liwen";
}
class BB{
int age=22;
String firstName=name;//会报错
AA aa=new AA();
String firstName=aa.name();//这样才能得到AA对象的属性
}
上面的情况就相当于Kotlin中内部类,类BB对象无法直接得到name属性,要先创建AA对象才能得到他的对象和对象内的属性。
2.内部类
Kotlin中嵌套类和内部类就相差了一个inner关键字修饰,但是效果就不同了。
inner修饰的内部类,在内部类里面能直接访问外部类的属性和方法。
Kotlin中的内部类跟java中的内部类是一样的,比如java代码:
package hello;
public class AA{
String name="liwen";
//java的内部类
class BB{
int age=22;
String firstName=name;//可以直接获取到外部类的属性,不会报错
}
}
3.匿名内部类
Kotlin中的匿名内部类和java中是一样样的,就不举例了。
十一.类的修饰符
类的修饰符包括 classModifier 和accessModifier:
1.classModifier: 类属性修饰符,标示类本身特性。
abstract // 抽象类
final // 类不可继承,默认属性
enum // 枚举类
open // 类可继承,类默认是final的
annotation // 注解类
上面除了open关键字,其他关键字和java作用是一样的
还有一点要注意的是Kotlin中类和方法默认是final的,但是abstract修饰的类或方法默认是open修饰的。
2.accessModifier: 访问权限修饰符
private // 仅在同一个文件中可见
protected // 同一个文件中或子类可见
public // 所有调用的地方都可见
internal // 同一个模块中可见
3.实例
// 文件名:example.kt
package foo
private fun foo() {} // 在 example.kt 内可见
public var bar: Int = 5 // 该属性随处可见
internal val baz = 6 // 相同模块内可见
4.Kotlin修饰符
在Kotlin编程中有四种修饰词:private,protected,internal,public,默认的修饰词是public。
这些修饰词可以用在类,对象,接口,构造函数,属性以及属性的set()中。
5.修饰符使用的一个示例:
open class Outer {
private val a = 1
protected val b = 2
internal val c = 3
val d = 4 // 默认为 public
protected class Nested {//嵌套类
public val e: Int = 5
}
}
class Subclass : Outer() {
//继承了Outer,除了private的不能访问,其他Outer内的类和属性都是可以访问的
// a 不可访问
// b, c 和 d 可以访问
// 类Nested 和 e 可以访问
}
class Unrelated(o: Outer) {
//是Outer的外部类,不能访问Outer类中的private和protected修饰的类和属性
// o.a, o.b 不可访问
// o.c 和 o.d 可以访问(属于同一模块)
// Outer.Nested 不可访问, Nested::e 也不可访问
}
上面有讲到同一个模块,我也是对这个Kotlin中”模块”的概念不理解,后面查了一下是这样的:
模块(Model)
一个模块(module)是指一起编译的一组 Kotlin 源代码文件:
一个 IntelliJ IDEA 模块
一个 Maven 工程, 或 Gradle 工程
通过 Ant 任务的一次调用编译的一组文件
其实就是整个项目之内!和public一样的!也有可能public修饰的能在其他项目调用,internal修饰的只能在本项目调用。