文章目录
- 认识Kotlin
- 跨平台特性
- 语言类型
- java的语言类型
- kotlin的运行原理
- hello world
- 基本概念
- 程序入口
- 数据与对象类型
- == 和 ===
- 显式数字转换
- 浮点类型
- 位运算
- Any
- Unit
- Nothing
- 声明变量
- 只读变量 val与可变变量var
- 查看Kotlin字节码
- fun(方法 / 函数)
- 函数参数默认值
- class(类)
- $(字符串模板)
- 控制流程
- if-else(条件表达式)
- 循环
- for
- while
- when表达式
- 异常
- 异常类Throwable
- null值和null值检查
- 安全调用运算符`?.`
- Elvis运算符`?:`
- 按照转换`as?`
- 非空断言`!!`
- let函数
- lateinit 延迟初始化属性
- 可空类型拓展
- 类型参数的可空性
- is:类型检查和自动转换
- 类和对象
- 继承
- 构造
- 主构造
- 次构造
- 权限修饰符
- 数据类和单例类
- 数据类
- 单例类
- 枚举类
- 接口
- 接口的继承
- 解决覆盖冲突
- 函数式接口(SAM)接口
认识Kotlin
kotlin与java语言一样,编译成字节码后,在JVM虚拟机中运行。kotlin语言的特性依赖于kotlin的语法编译器。与kotlin类似的语言还有:
- Scala:大数据领域开发语言
- Groovy:基于 JVM 平台的动态脚本语言,在安卓开发中用来编写依赖关系、插件等
2019年谷歌宣布Kotlin成为安卓第一开发语言,安卓程序员由java转Kotlin已经迫在眉睫。
跨平台特性
语言类型
语言分为解释型和编译型:
- 编译型的语言,编译器会将源代码一次项编译成二进制文件,计算机可以直接执行,例如
C和C++
- 优点:一次编译即可运行,运行期不需要编译,效率高。
- 缺点:不同操作系统需要不同的机器码,且修改代码需要逐个模块重新编译。
- 解释型的语言,程序运行时,解释器会将源码一行一行实时解析成二进制并且放在对应的虚拟机中执行,例如
JS、Python
- 优点:平台兼容性好,安装对应的虚拟机即可运行。
- 缺点:运行时需要解释执行,效率低。
java的语言类型
java准确来说属于半编译半解释的混合型语言,但更偏向于解释型。
Java通过编译器javac先将源程序编译成与平台无关的Java字节码(.class),再由JVM解释执行字节码。
kotlin的运行原理
JVM只负责对字节码文件.class解释执行,而不管字节码文件是怎么产生的,Kotlin就是此原理,运行前会先编译成class,再供java虚拟机运行。
Kotlin不仅能够,还能脱离虚拟机层,直接编译成可以在Windows、Linux和MacOS平台上运行的原生二进制代码,被称为Android领域的Swift
hello world
Kotlin应用入口是主函数main,主函数可以不带任何参数,没有返回值。
fun main() {
var str:String = "Hello World"
println(str)
}
基本概念
程序入口
可以没有参数,也可以带参数列表,但程序入口是没有返回值的。
fun main() {
}
fun main(args: Array<String>) {
}
数据与对象类型
kotlin中取消了Java中原有的基本数据类型,全部使用对象类型。
== 和 ===
kotlin提供了两种方式用于对象的比较
- 比较对象的结构是否相等(
==
或者equals
) - 比较对象的引用是否相等
===
fun main() {
//sampleStart
val a: Int = 100
val boxedA: Int? = a
val anotherBoxedA: Int? = a
val b: Int = 10000
val boxedB: Int? = b
val anotherBoxedB: Int? = b
println(boxedA === anotherBoxedA) // true
println(boxedB === anotherBoxedB) // false
//sampleEnd
}
由于 JVM 对 -128 到 127 的整数(Integer)应用了内存优化,因此,a 的所有可空引用实际上都是同一对象。但是没有对 b 应用内存优化,所以它们是不同对象(===
的结果为false)。
另一方面,它们的内容仍然相等:
fun main() {
//sampleStart
val b: Int = 10000
println(b == b) // 输出“true”
val boxedB: Int? = b
val anotherBoxedB: Int? = b
println(boxedB == anotherBoxedB) // 输出“true”
//sampleEnd
}
显式数字转换
kotlin中较小的类型不能隐式转换为较大的类型,这意味着把Byte
类型赋值给一个Int
变量必须显式转换:
fun main() {
val b : Byte = 1
val i1: Int = b.toInt();
}
所有数字类型都支持转换为其他类型:
- toByte(): Byte
- toShort(): Short
- toInt(): Int
- toLong(): Long
- toFloat(): Float
- toDouble(): Double
很多情况都不需要显式类型转换,因为类型会从上下文推断出来, 而算术运算会有重载做适当转换,例如:
val l = 1L + 3 // Long + Int => Long
浮点类型
Kotlin 提供了浮点类型 Float 与 Double 类型,这两个类型的大小不同,并为两种不同精度的浮点数提供存储:
- 对于以小数初始化的变量,编译器会推断为
Double
类型:
val pi = 3.14 // Double
// val one: Double = 1 // 错误:类型不匹配
val oneDouble = 1.0 //Double
- 如需将一个值显式指定为
Float
类型,请添加f
或者F
后缀,如果这样的值包含多于6~7位十进制数,则会将其舍入:
val e = 2.7182818284 // Double
val eFloat = 2.7182818284f // Float,实际值为 2.7182817
- 与其他语言不同,Kotlin中的数字没有隐式拓宽转换,例如具有Double 参数的函数只能对 Double 值调用,而不能对 Float、 Int 或者其他数字值调用:
fun main() {
fun printDouble(d: Double) { print(d) }
val i = 1
val d = 1.0
val f = 1.0f
printDouble(d)
// printDouble(i) // 错误:类型不匹配
// printDouble(f) // 错误:类型不匹配
}
位运算
Kotlin对于整数类型Int
与Long
提供了一组位运算。
- shl(bits) – 有符号左移(shift left)
- shr(bits) – 有符号右移(shift right)
- ushr(bits) – 无符号右移(unsigned shift right)
- and(bits) – 位与
- or(bits) – 位或
- xor(bits) – 位异或
- inv() – 位非(inverse)
Any
Any是非空类型的根类型,与Object作为Java类层级结构的顶层类似,Any类型是Kotlin中所有非空类型的超类。
在Kotlin函数中使用Any,它会编译成Java字节码中的Object。
Any?类型是Kotlin所有类型的根类
在Kotlin类型层级结构的最底层是Nothing类型。类型结构图如下:
Unit
Unit
的作用类似于Java中的void
,但是void的含义是没有返回值,kotlin面对对象更加彻底,没有返回值的返回值也应该是一种对象,于是就有了Unit,这个概念
如果没有用return
明确指定返回值,那么编译器会自动加上返回Unit
:
fun returnUnit() : Unit {
return Unit
}
Nothing
Unit
的含义这个函数是没有返回值的。
而Nothing
,则是提示开发者这个函数不会返回,例如它可能是抛异常
,不会返回,或者拥有无限循环的函数。
作用1:抛异常的函数的返回值
fun throwOnNameNull() : Nothing {
throw NullPointerException("姓名不能为空!")
}
除了上边抛异常这个用法,==Nothing对象还是所有对象类型的共同子类型。==这样一来,实际上Nothing是多重继承的,即Kotlin中扩展了继承规则,类不允许多重继承,Nothing除外
,因为Nothing的构造器是私有的,它不会有任何实例对象,所以他的多重继承是不会带来任何实际风险的。
基于Nothing是所有类型的子类,可以用它结合泛型,构建通用的空List,如下:
作用2:作为泛型变量的通用的、空白的临时填充
作用3:Nothing使得kotlin在语法层面更加完整。
在kotlin的下层逻辑中,throw是有返回值的,它的返回值类型就是Nothing
因此下边的写法是合法的:
val nothing: Nothing = throw RuntimeException("抛异常!"
因为Nothing是所有类型的子类,所有下面的写法才能够成立:
var _name: String? = null
val name: String = _name ?:throw NullPointerException("_name 运行时不能为空!")
声明变量
kotlin使用关键字var
声明可变变量,变量名与变量类型间用冒号:
隔开,这种脚本语言与Python一样,不需要;
结尾。
var
:可变变量,对应Java的非fina
l变量
var b = 1
val
:不可变变量,对应Java的final
变量
val a = 1
上边的声明过程中并未使用冒号:
指明类型,因为Kotlin存在类推导机制,上述的a
和b
会根据赋值结果默认视为Int
只读变量 val与可变变量var
- var:表示可修改变量
- val:表示只读变量
主要是非final与final的区别。
查看Kotlin字节码
fun(方法 / 函数)
fun 关键字用于声明方法:
下面是一个接收2个Int参数,返回Int的方法。
fun sum(a: Int, b: Int): Int {
return a + b;
}
方法主体可以是一个表达式,它的返回值可以被推断出来:
fun sum (a: Int, b: Int) = a + b
方法可以没有返回值,或者说返回一个无意义的值(Unit)
fun printSum(a: Int, b:Int): Unit {
println("sum of $a and $b is ${a + b}")
}
其中Unit也可以忽略不写;
fun printSum(a: Int, b:Int) {
println("sum of $a and $b is ${a + b}")
}
函数参数默认值
Kotlin支持函数存在默认值,使用如下:
fun main() {
myPrint(1)
myPrint(1, "lalala")
}
fun myPrint(value : Int, str : String = "hello") {
println("num is $value, str is $str")
}
若value想为默认值,则会保存,因为在使用时传入第一个参数必须是Int型。传入字符串类型会不匹配:
fun main() {
myPrint("zjm")//报错
}
fun myPrint(value: Int = 100, str: String) {
println("num is $value, str is $str")
}
Kotlin的函数传参支持与Python一样的键值对传参,从而可以改变参数的顺序:
fun main() {
myPrint(str = "zjm") //正确调用
}
fun myPrint(value: Int = 100, str: String) {
println("num is $value, str is $str")
}
class(类)
定义一个类
类的属性可以放在定义中或者类里边,比如下边:
class Rectangle(var height: Double, var length: Double) {
var perimeter = (height + length) * 2
}
var rectangle = Rectangle(5.0, 2.0)
println("The perimeter is ${rectangle.perimeter}")
对于kotlin 1.4.30版本,类的继承用冒号:
来表示,类默认都是final的,不可继承,为了使得类可以被继承,用open
关键字,放在class前面:
open class Rectangle(var height: Double, var length: Double) {
var perimeter = (height + length) * 2
}
$(字符串模板)
使用$
可以帮助定义字符串模板,转译字符串变量。
直接使用变量用$
,要使用表达式,需要大括号${}
下边是例子:
var a = 1
val S1 = "a is $a"
println(S1)
a = 2;
val S2 = "${S1.replace("is", "was")}, but now is $a"
println(S2)
结果:
a is 1
a was 1, but now is 2
控制流程
if-else(条件表达式)
fun getMax(a: Int, b: Int): Int {
if (a > b) {
return a
} else {
return b
}
}
上边的if也可以写成一个表达式,类似三目运算符:
fun getMax(a: Int, b: Int) = if (a > b) a else b
循环
for
- 使用
in
关键字 - 使用下标
val items = listof("apple", "banana", "kiwifruit")
for (item in items) {
println(item)
}
for (index in item.indices) {
println("item at $index is ${items[index]}")
}
// 闭区间打印[1, 3]
// 输出 1,2,3
for (i in 1..3) {
println(i)
}
// 从7到0,降序,步长为2
// 输出 7,5,3,1
for (i in 7 downTo 0 step 2) {
println(i)
}
for- in
的区间默认是双闭区间,如果想使用左闭右开呢,需要借助关键字until
fun main() {
for (i in 0 until 10) {
}
}
while
val items = listOf("apple", "banana", "kiwifruit")
var index = 0
while (index < items.size) {
println("item at $index is ${items[index++]}")
}
结果:
item at 0 is apple
item at 1 is banana
item at 2 is kiwifruit
when表达式
when表达式看起来有点像java中的swich case。
fun describe(obj: Any): String =
when (obj) {
1 -> "One"
"Hello" -> "Greeting"
is Long -> "Long"
!is String -> "Not a String"
else -> "Unknown"
}
异常
异常类Throwable
Kotlin中所有异常类继承自Throwable
类,使用throw
表达式抛出异常:
fun main() {
//sampleStart
throw Exception("Hi There!")
//sampleEnd
}
使用try … catch 表达式捕获异常:
try {
// 一些代码
} catch (e: SomeException) {
// 处理程序
} finally {
// 可选的 finally 块
}
可以有零到多个 catch 块,finally 块可以省略。 但是 catch 与 finally 块至少需有一个。
null值和null值检查
可以为null的变量,后边需要一个问号?
。
例如下边这个方法,返回Int或者null
fun parseInt(str: String) : Int? {
...
}
安全调用运算符?.
该运算符运行你把一次null检查和一次方法调用合并为一个操作。例如
s?.toUpperCase()
等同于下边的写法:
if (s!=null) {
s.toUpperCase
} else {
null
}
安全调用不光可以调用方法,也能用来访问属性。
如果你的对象中有多个可空类型的属性,通常可以在同一个表达式中方便地使用多个安全调用。使用该运算符,不需要额外的检查,就可以在一行代码中访问到更深层次的属性。如下:
class Address(val streetAddress:String,val code:Int,val city:String)
class Company(val name:String,val address: Address?)
class Person(val name:String,val company: Company?)
fun Person.cityName():String{
val city = this.company?.address?.city
return if (city != null) city else "unKnown"
}
Elvis运算符?:
Elvis 运算符接收两个运算数,如果第一个运算数不为 null,运算结果就是第一个运算数,否则运算结果就是第二个运算数。
例如下边,如果s不为空,则str为s,否则str为空字符串对象
fun foo(s: String) {
val str: String = s?:""
}
Elvis运算符经常和安全调用运算符一起使用,用一个值代替对null对象调用方法时返回的null。下面就对之前的代码进行简化:
fun Person.cityName():String {
val city = this.company?.address?.city
return city ?: "unKnown"
}
在 kotlin 中有一种场景 Elvis 运算符非常适合,像 return 和 throw 这样的操作其实是表达式,因此可以把它们写在 Elvis 运算符的右边。当运算符左边的值为null,函数立即抛出一个异常。
fun printPersonName(person : Person) {
val name = person.name? : throw IllegalArgumetException("no name")
println(name)
}
按照转换as?
as是用来转换类型的Kotlin运算符,和java类型一样,如果被转换的值不是你试图转换的类型。就会抛出ClassCastException异常。
as?
运算符尝试把值转换成指定的类型,如果值不是合适的类型则返回 null。我们可以将该运算符与 Elvis 结合使用,如实现 Person 类的 equals 方法
class Person(val name:String, val company: Company?) {
override fun equals(other: Any?): Boolean {
val otherPerson = other as? Person ?: return false
return otherPerson.name && other.company == company
}
}
非空断言!!
非空断言是kotlin 提供的最简单直接的处理可空类型值的工具。使用双感叹号表示,可以把任意值转换成非空类型。如果对 null 做非空断言,则会抛出异常。
fun ignoreNull(s:String){
val notNull = s!!
println(notNull.length)
}
如果该函数中s为null,则会在运行时抛出异常。抛出异常的位置是非空断言所在的那一行,而不接下来试图使用值的那一行。
因为异常调用栈跟踪的信息只表明异常发生在哪一行,为了让跟踪信息更加明确,最后避免在同一行中使用多个!!
断言。
let函数
let函数可以让处理可空表达式变的更加容易和安全调用运算符一起使用,它允许你对表达式求值,检查求值结果是否为null,并把结果保存为一个变量,所有的这些操作都在同一个简单的表达式中。
let函数所作的事情就是把一个调用它的对象变成lambda表达式的参数,结合安全调用语法,能有效地把调用let函数的可空对象转变为非空类型,换言之,安全调用的let只在表达式不为null时才执行lambda。
s?.let {
print(s.length)
}
如果要实现上边的效果,Java代码如下:
public static final void ignoreNull(@Nullable String s) {
if (s != null) {
System.out.print(s.length());
}
}
lateinit 延迟初始化属性
kotlin通常要求在构造方法中初始化所有属性,如果某个属性是非空属性,就必须提供非空的初始化值,否则就必须使用可空类型,如果这样做,该属性每一次访问都需要null检查
或者!!
断言。
class MyService {
fun performAction():String = "foo"
}
class MyTest{
private var myService:MyService?=null
fun test() {
myService!!.performAction()
}
}
可以使用lateinit
来延迟初始化myService,延迟初始化 的属性都是var,尽管是非空类型,但也不需要在构造方法中去初始化它。
class MyService{
fun performAction():String = "foo"
}
class MyTest{
// 声明一个不需要初始化器的非空类型的属性
private lateinit var myService:MyService
fun test(){
myService.performAction()
}
}
可空类型拓展
Kotlin标准库中定义了String的两个扩展函数isEmpty
、isBlank
isNullOrBlank
,判断是否为空字符串或者是否只包含空白字符,这种扩展函数可以允许接收者为null的调用:
fun verifyInput(input:String?){
if (input.isNullOrBlank()){
print("it is not ok")
}
}
不需要安全访问,因为函数内部会处理可能的null值:
@kotlin.internal.InlineOnly
public inline fun CharSequence?.isNullOrBlank(): Boolean {
contract {
returns(false) implies (this@isNullOrBlank != null)
}
return this == null || this.isBlank()
}
类型参数的可空性
Kotlin中所有泛型类和泛型函数的类型参数默认都是可空的,这种情况下,使用类型参数作为类型的声明都允许为null,尽管类型参数T没有用问号结尾。
fun <T> printSomething(t:T){
// 因为 t 可能为 null,所以必须使用安全调用
print(t?.hashCode())
}
// 传入 null 进行调用
printSomething(null)
在该方法中,类型参数 T 推导出的类型是可空类型 Any? ,因此尽管没有使用问号结尾,实参 t 仍然允许使用 null。
要是类型参数非空,必须要为它指定一个非空的上界,那样泛型会拒绝可空值作为实参。
// 这样的话,T 就不是可空的
fun <T:Any> printSomething1(t:T){
print(t.hashCode())
}
is:类型检查和自动转换
用is
来检查某个对象是不是某个类型,如果确定某个不可变的变量的类型,那后边使用它的时候不用再显式转换:
fun getStringLength(obj: Any): Int? {
if (obj is String) {
// 这里obj已经转为String类型
return obj.length
}
return null
}
或者用!is
反过来写:
fun getStringLength(obj: Any): Int? {
if (obj !is String) return null
return obj.length
}
类和对象
创建一个Person类:
class Person {
var name = ""
var age = 0
fun printInfo() {
println("name is $name, age is $age")
}
}
继承
Kotlin中默认的类是不可被继承的(即被final修饰),如果想让这个类能够被继承,需要在class
前使用open
关键字。
open class Person {
var name = ""
var age = 0
fun printInfo() {
println("name is $name, age is $age")
}
}
声明一个Student类继承Person类,kotlin中继承使用:
后接父类的构造:
class Student : Person() {
var number = ""
var grade = 0
fun study() {
println("$name is studying")
}
}
构造
构造分为主构造和次构造
主构造
主构造直接写在类后边
class Student(val number: String, val grade: Int) : Person() {
fun study() {
println("$name is studying")
}
}
创建一个Student对象:
val student = Student("1234", 90)
因为父类Person还有name和age属性,因此给父类Person也构建一个主构造:
open class Person(val name : String, val age : Int) {
fun printInfo() {
println("name is $name, age is $age")
}
}
此时Student报错,因为继承Person时,后边使用的是Person()无参构造,而上边我们修改了Person的有参构造,则就不存在无参构造了。
class Student(name: String, age: Int, val number: String, val grade: Int) : Person(name, age){
fun study() {
println("$name is studying")
}
}
此时构造一个Student类;
val student = Student("zjm", 20, "1234", 90)
如果构造时需要特殊处理怎么办?kotlin提供了init
结构体,主构造的逻辑可以在init中处理,如:
open class Person(val name : String, val age : Int) {
init {
println("name is $name")
println("age is $age")
}
}
上述修改都为主构造
,如果类需要多个构造怎么办?此时需要借助次构造
次构造
次构造,使用constructor
关键字声明构造器,:
后边接this()
,即次级构造器的本质是调用主构造
实现下边两个次构造:
- 三个参数的构造, name,age,number,grade不传参数,默认为0.
- 无参构造:Int默认为0, String默认为空字符串
class Student(name: String, age: Int, var number: String, var grade: Int) : Person(name, age){
fun study() {
println("$name is studying")
}
constructor(name: String, age: Int, number: String) : this(name, age, number, 0) {
}
constructor() : this("", 0, "", 0) {
}
}
// 调用
val student = Student("lzy", 23, "1234", 90)
val student1 = Student("lzy", 23, "121");
val student2 = Student()
权限修饰符
Java和kotlin的不同如下表:
kotlin引入internal
,摒弃了default
数据类和单例类
数据类
数据类只处理数据相关,与Java Bean
类似,通常需要实现其get
,set
,hashcode
,equal
,toString
等方法。
下面实现UserBean
,包含:id
、name
、pwd
属性。
Java:
public class UserBean {
private String id;
private String name;
private String pwd;
public UserBean() {
}
public UserBean(String id, String name, String pwd) {
this.id = id;
this.name = name;
this.pwd = pwd;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserBean userBean = (UserBean) o;
return Objects.equals(id, userBean.id) && Objects.equals(name, userBean.name) && Objects.equals(pwd, userBean.pwd);
}
@Override
public int hashCode() {
return Objects.hash(id, name, pwd);
}
@Override
public String toString() {
return "UserBean{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", pwd='" + pwd + '\'' +
'}';
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
}
Kotlin编写此类将变的非常简单,新建一个kt
文件,选择如下:
一行代码即可搞定,kotlin会自动实现上述方法。
Shift连按两次调出工具搜索
,搜索show kotlin ByteCode
,查看kotlin字节码:
确实是创建了相应的Java Bean 类
单例类
目前Java使用最广的单例模式(静态内部类)实现如下:
public class SingleInstance {
private SingleInstance() {
}
private static class SingleHolder {
private static final SingleInstance INSTANCE = new SingleInstance();
}
public static SingleInstance getInstance() {
return SingleHolder.INSTANCE;
}
public void test() {
}
}
在Kotlin
中创建单例类需选择Object
生成的代码如下:
object SingleInstance {
fun test () {
}
}
其对应的java
文件如下,和上述使用最多的java
单例实现类似:
public final class Singleton {
@NotNull
public static final Singleton INSTANCE;
public final void test() {
}
private Singleton() {
}
static {
Singleton var0 = new Singleton();
INSTANCE = var0;
}
}
使用如下:
fun main() {
Singleton.test() //对应的java代码为Singleton.INSTANCE.test();
}
枚举类
枚举类最基本的应用场景是实现类型安全的枚举:
enum class Direction {
NORTH, SOUTH, WEST, EAST
}
每个枚举常量都是一个对象。枚举常量以逗号分隔。
因为每一个枚举都是枚举类的实例,所以可以这样初始化:
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
每个枚举常量也都具有这两个属性:name 与 ordinal, 用于在枚举类声明中获取其名称与(自 0 起的)位置:
enum class RGB { RED, GREEN, BLUE }
fun main() {
//sampleStart
println(RGB.RED.name) // prints RED
println(RGB.RED.ordinal) // prints 0
//sampleEnd
}
接口
定义一个接口:
interface Study {
fun study()
fun readBooks()
fun doHomeWork()
fun exercise()
}
接口的继承
继承接口只需在类的后边用,
,并实现Study
声明的全部函数:
class GoodStudent(name: String, age: Int, var ID: String, var grade: Int) : Person(name, age) , Study{
override fun study() {
TODO("Not yet implemented")
}
override fun readBooks() {
TODO("Not yet implemented")
}
override fun doHomeWork() {
TODO("Not yet implemented")
}
override fun exercise() {
TODO("Not yet implemented")
}
}
Kotlin支持接口方法的默认实现,JDK1.8以后也支持此功能,方法有默认实现,则继承类就不是必须实现此方法。
interface Study {
fun study() {
println("study")
}
fun readBooks()
fun doHomework()
}
解决覆盖冲突
实现多个接口时,可能会遇到同一方法继承多个实现的问题:
interface A {
fun foo() { print("A") }
fun bar()
}
interface B {
fun foo() { print("B") }
fun bar() { print("bar") }
}
class C : A {
override fun bar() { print("bar") }
}
class D : A, B {
override fun foo() {
super<A>.foo()
super<B>.foo()
}
override fun bar() {
super<B>.bar()
}
}
上例中,接口 A 和 B 都定义了方法 foo() 和 bar()。 两者都实现了 foo(), 但是只有 B 实现了 bar() (bar() 在 A 中没有标记为抽象, 因为在接口中没有方法体时默认为抽象)。 现在,如果实现 A 的一个具体类 C,那么必须要重写 bar() 并实现这个抽象方法。
然而,如果从 A 和 B 派生 D,需要实现从多个接口继承的所有方法,并指明 D 应该如何实现它们。这一规则既适用于继承单个实现(bar())的方法也适用于继承多个实现(foo())的方法。
函数式接口(SAM)接口
只有一个抽象方法的接口称为函数式接口或 单一抽象方法(single abstract method , SAM)接口。函数式接口可以有多个非抽象成员,但只能有一个抽象成员。
例如,有这样一个 Kotlin 函数式接口:
fun interface IntPredicate {
fun accept(i: Int): Boolean
}
如果不使用 SAM 转换,那么你需要像这样编写代码:
// 创建一个类的实例
val isEven = object : IntPredicate {
override fun accept(i: Int): Boolean {
return i % 2 == 0
}
}
通过利用 Kotlin 的 SAM 转换,可以改为以下等效代码:
fun interface IntPredicate {
fun accept(i: Int): Boolean
}
val isEven = IntPredicate { it % 2 == 0 }
fun main() {
println("Is 7 even? - ${isEven.accept(7)}")
}