Kotlin-基础笔记整理二
1、接口:
interface 关键字定义接口,允许方法有默认实现
- 接口中的属性
只能是抽象的,不允许初始化值,接口不会保存属性值,实现接口时,必须重写属性
interface MyInterface {
var name:String //name 属性, 抽象的
fun bar() // 未实现
fun foo() { //已实现
// 可选的方法体
println("foo")
}
}
class Child : MyInterface {
override var name: String = "runoob" //重写属性
override fun bar() {
// 方法体
println("bar")
}
}
fun main(args: Array<String>) {
val c = Child()
c.foo()
c.bar()
println(c.name)
}
- 输出
foo
bar
runoob
2、扩展函数:
在已有类中添加新的方法,不会对原类做修改,扩展函数定义形式
fun receiverType.functionName(params){
body
}
receiverType:表示函数的接收者,也就是函数扩展的对象
functionName:扩展函数的名称
params:扩展函数的参数,可以为NULL
- 示例1:
class User(var name:String)
/**扩展函数**/
fun User.Print(){
print("用户名 $name")
}
fun main(arg:Array<String>){
var user = User("Runoob")
user.Print()
}
- 输出
用户名 Runoob
- 示例2:
//MutableList可变列表
// 扩展函数 swap,调换不同位置的值
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // this 对应该列表
this[index1] = this[index2]
this[index2] = tmp
}
fun main(args: Array<String>) {
val l = mutableListOf(1, 2, 3)//向可变列表存入值
// 位置 0 和 2 的值做了互换
l.swap(0, 2) // 'swap()' 函数内的 'this' 将指向 'l' 的值
println(l.toString())
}
- 输出
[3, 2, 1]
扩展函数是静态解析的,并不是接收者类型的虚拟成员,在调用扩展函数时,具体被调用的的是哪一个函数,由调用函数的的对象表达式来决定的,而不是动态的类型决定的
open class C
class D: C()
fun C.foo() = "c" // 扩展函数 foo
fun D.foo() = "d" // 扩展函数 foo
fun printFoo(c: C) {
println(c.foo()) // 类型是 C 类
}
fun main(arg:Array<String>){
printFoo(D())
}
- 输出
c
- 若扩展函数和成员函数一致,则使用该函数时,会优先使用成员函数。
- 扩展一个空对象
fun Any?.toString(): String {
if (this == null) return "This is null"
// 空检测之后,“this”会自动转换为非空类型
// 解析为 Any 类的成员函数
return toString()
}
fun main(arg:Array<String>){
var t1 = 123
println(t1.toString())
var t2 = null
println(t2.toString())
}
- 输出
123
This is null
- 扩展属性
val <T> List<T>.lastIndex: Int
get() = size - 1
展属性允许定义在类或者kotlin文件中,不允许定义在函数中。初始化属性因为属性没有后端字段(backing field),所以不允许被初始化,只能由显式提供的 getter/setter 定义。
val Foo.bar = 1 // 错误:扩展属性不能有初始化器
扩展属性只能被声明为 val
3、伴生对象:
- Kotlin 中,在类中定义的对象(object)声明,可使用 companion 修饰,此对象(object)就是伴生对象
- 示例:
class NumberTest {
companion object Obj {
var flag = false
fun plus(num1: Int, num2: Int): Int {
return num1 + num2
}
}
}
一个类(class)中最多只能定义一个伴生对象。
- 调用
使用 companion 关键字修改的对象之后,伴生对象就相当于是外部类的对象,可以使用类直接调用。 - 示例:
fun main(args: Array<String>) {
println(NumberTest.plus(1, 2)) // 3
println(NumberTest.flag) // false
}
- 伴生对象的作用
类似于 Java 中使用类访问静态成员的语法。因为 Kotlin 取消了 static 关键字,所以 Kotlin 引入伴生对象来弥补没有静态成员的不足。可见,伴生对象的主要作用就是为其所在的外部类模拟静态成员。
- 如果声明伴生对象有名称,则使用:
类名.伴生对象名.方法名()
类名.半生对象名.属性的setter,getter方法
例:NumberTest.Obj.plus(2, 3)
- 如果声明伴生对象无名称,则采用 Companion 关键字调用:
类名.Companion.方法名()
类名.Companion.属性的setter,getter方法
例:NumberTest.Companion.getFlag()
更多:伴生对象
- 扩展的作用域
package foo.bar
fun Baz.goo() { …… }
使用所定义包之外的一个扩展, 通过import导入扩展的函数名进行使用:
package com.example.usage
import foo.bar.goo // 导入所有名为 goo 的扩展
// 或者
import foo.bar.* // 从 foo.bar 导入一切
fun usage(baz: Baz) {
baz.goo()
}
- 扩展声明为成员
class D {
fun bar() {
println("D bar")
}
fun beer() {
println("我是D类的beer方法")
}
}
class C {
fun baz() {
println("C baz")
}
fun beer() {
println("我是C类的beer方法")
}
fun D.foo() {//声明为D的扩展函数
bar()//调用D.bar
baz()//调用C.baz
//-----------------------------------------
beer() //调用D.bar(),扩展接受者优先
this@C.beer()//调用C.bar()
}
fun caller(d: D) {
d.foo()//调用扩展函数
}
}
fun main(args: Array<String>) {
val c: C = C()
val d: D = D()
c.caller(d)
}
输出:
D bar
C baz
我是D类的beer方法
我是C类的beer方法
4、数据类:
- 数据类
创建一个只包含数据的类,关键字为 data
data class User(val name: String, val age: Int)
数据类需要满足以下条件:
主构造函数至少包含一个参数。
所有的主构造函数的参数必须标识为val 或者 var ;
数据类不可以声明为 abstract, open, sealed 或者 inner;
数据类不能继承其他类 (但是可以实现接口)。
- 复制
复制使用 copy() 函数,我们可以使用该函数复制对象并修改部分属性,
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
- 示例
data class User(val name: String, val age: Int)
fun main(args: Array<String>) {
val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)
println(jack)
println(olderJack)
}
输出
User(name=Jack, age=1)
User(name=Jack, age=2)
5、泛型:
- 示例
class Box<T>(t : T) {//声明一个泛型类
var value = t
}
fun main(args: Array<String>) {
var boxInt = Box<Int>(10)//定义类型为整形
var boxString = Box<String>("Runoob")//定义类型为字符串
println(boxInt.value)
println(boxString.value)
}
- 输出
10
Runoob
在调用泛型函数时,如果可以推断出类型参数,可以省略泛型参数。
- 泛型约束
常见的约束是上界(upper bound):
fun <T : Comparable<T>> sort(list: List<T>) {
// ……
}
- 示例
sort(listOf(1, 2, 3)) // OK。Int 是 Comparable<Int> 的子类型
sort(listOf(HashMap<Int, String>())) // 错误:HashMap<Int, String> 不是 Comparable<HashMap<Int, String>> 的子类型
Kotlin 中没有通配符类型,它有两个其他的东西:声明处型变(declaration-site variance)与类型投影(type projections)。
- 类型协变
在类型声明时,使用协变注解修饰符(in或者out)。于这个注解出现在类型参数的声明处, 因此我们称之为声明处的类型变异。
internal interface Source<in T, out R> {
fun mapT(t: T): Unit
fun nextR(): R
}
in T: 来确保Source的成员函数只能消费T类型,而不能返回T类型
out R:来确保Source的成员函数只能返回R类型,而不能消费R类型
- 星号投射
class A<T>(val t: T, val t2 : T, val t3 : T)
class Apple(var name : String)
fun main(args: Array<String>) {
//使用类
val a1: A<*> = A(12, "String", Apple("苹果"))
val a2: A<Any?> = A(12, "String", Apple("苹果")) //和a1是一样的
val apple = a1.t3 //参数类型为Any
println(apple)
val apple2 = apple as Apple //强转成Apple类
println(apple2.name)
//使用数组
val l:ArrayList<*> = arrayListOf("String",1,1.2f,Apple("苹果"))
for (item in l){
println(item)
}
}
6、Kotlin 枚举类:
枚举常量用逗号分隔,每个枚举常量都是一个对象
enum class Color{
RED,BLACK,BLUE,GREEN,WHITE
}
- 枚举初始化
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
- 使用枚举常量
enum class Color {
RED, BLACK, BLUE, GREEN, WHITE
}
fun main(args: Array<String>) {
var color: Color = Color.BLUE
println(Color.values())//以数组的形式返回枚举值
println(Color.valueOf("RED"))//输出:RED
println(color.name)//输出:BLUE
println(color.ordinal)//输出:2
}
自 Kotlin 1.1 起,可以使用 enumValues<T>() 和 enumValueOf<T>() 函数以泛型的方式访问枚举类中的常量 :
enum class RGB { RED, GREEN, BLUE }
inline fun <reified T : Enum<T>> printAllValues() {
print(enumValues<T>().joinToString { it.name })
}
fun main(args: Array<String>) {
printAllValues<RGB>() // 输出 RED, GREEN, BLUE
}
7、对象表达式:
- 示例1
open class A1(x: Int) {
open val y: Int = x
}
interface B {
}
fun main(args: Array<String>) {
val ab: A1 = object : A1(1), B {
override val y = 15
}
println("当前y的值" + ab.y)
}
- 输出
当前y的值15
- 示例2
open class A1(x: Int) {
open val y: Int = x
}
interface B {
fun printB() {
println("我是B的方法")
}
}
fun main(args: Array<String>) {
val ab: B = object : A1(1), B { //使用B接口来接收
override val y = 15
}
println("调用B的方法" + ab.printB())
}
- 示例3
fun main(args: Array<String>) {
val site = object {
var name: String = "大吉大利,今晚吃鸡"
var url: String = "www.baidu.com"
}
println(site.name)
println(site.url)
}
匿名对象可以用作只在本地和私有作用域中声明的类型。如果你使用匿名对象作为公有函数的 返回类型或者用作公有属性的类型,那么该函数或属性的实际类型 会是匿名对象声明的超类型,如果你没有声明任何超类型,就会是 Any。在匿名对象 中添加的成员将无法访问。
class CC {
// 私有函数,所以其返回类型是匿名对象类型
private fun foo() = object {
val x: String = "x"
}
// 公有函数,所以其返回类型是 Any
fun publicFoo() = object {
val x: String = "x"
}
fun bar() {
val x1 = foo().x // 没问题
val x2 = publicFoo().x // 错误:未能解析的引用“x”,访问不到
}
}
8、对象声明:
object Site {
var url:String = ""
}
fun main(args: Array<String>) {
var s1 = Site
var s2 = Site
s1.url = "www.baidu.com"
println(s1.url)
println(s2.url)
}
与对象表达式不同,当对象声明在另一个类的内部时,这个对象并不能通过外部类的实例访问到该对象,而只能通过类名来访问,同样该对象也不能直接访问到外部类的方法和变量。
class Site {
var name = "大吉大利"
object DeskTop{
var url = "www.baidu.com"
fun showName(){
print{"desk legs $name"} // 错误,不能访问到外部类的方法和变量
}
}
}
fun main(args: Array<String>) {
var site = Site()
site.DeskTop.url // 错误,不能通过外部类的实例访问到该对象
Site.DeskTop.url // 正确
}
9、对象表达式和对象声明之间的语义差异:
-
对象表达式是在使用他们的地方立即执行的
-
对象声明是在第一次被访问到时延迟初始化的
-
伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配