接口
- 使用 : 表示继承关系,只能继承一个类,但可以实现多个接口
- override修饰符表示重写
- 可以有默认方法,若父类的默认方法冲突,则需要子类重写,使用super<XXX>.xxx()调用某一父类方法
interface Focusable {
fun focus()
fun show() = println("Focusable")
}
class Button : Clickable, Focusable {
override fun click() {
TODO("Not yet implemented")
}
override fun focus() {
TODO("Not yet implemented")
}
override fun show() {
super<Clickable>.show()
super<Focusable>.show()
}
}
接口中可以声明域,每个子类都要初始化接口中的域
interface User {
val name: String
}
class Person(override val name: String) : User
class Man(val email: String) : User {
override val name: String
get() = email.substringBefore("@")
}
class WonMan() : User {
override val name: String = "A"
}
接口中也可以使用getter和setter,前提是不引用变量
interface User {
val email:String
val name: String
get() = email.substringBefore('@')
}
类
bean类
只有数据没有其他代码的对象通常叫做值对象,如JavaBean
public class Person {
private String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
}
使用过程如下
Person person = new Person("java", 8);
person.setName("kotlin");
System.out.println(person.getName());
System.out.println(person.getAge());
将上述Java复制到代码.kt文件,会触发自动转换(.kt不要求类名和文件名一致,可将多个类放到同一文件,且文件名随意)
转换后的代码如下,name为var变量(默认带有getter和setter),而age为val变量(只有getter)
class Person(
var name: String,
val age: Int
)
使用方法如下
val person = Person("java", 8);
person.name = "kotlin"
println(person.name)
println(person.age)
如果一个属性可以根据其他属性计算,可使用自定义getter
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() {
return height == width
}
}
类的重写
类和方法默认都是final的,否则需要使用open修饰符
open class Button {
fun click() {}
open fun focus() {}
}
open class MyButton : Button() {
override fun focus() {
super.focus()
}
}
抽象类
抽象方法默认为open,其他的可有可无
abstract class Button {
abstract fun click()
open fun focus() {}
fun press() {}
}
open class MyButton : Button() {
override fun click() {
TODO("Not yet implemented")
}
}
可见性
Kotlin没有包的概念,internal为模块可见性
- private类在Java中会被编译成protect
- internal类或域在Java中会被编译成public
嵌套类
在一个类中声明另一个类
- 在Java中,未用static声明的类为内部类(含有外部类的隐式引用),加上static的类为嵌套类(不含有外部类的隐式引用)
- 而Kotlin相反,未用inner声明的类为嵌套类(不含有外部类的隐式引用),加上inner的类为内部类(含有外部类的隐式引用)
- 嵌套类不能访问外部类的实例,外部类不能访问嵌套类中的private域
如果需要使用内部类存储序列化信息,需要声明为static转为嵌套类,否则将无法序列化
public class Button {
private boolean isClickable;
public ButtonState getCurrentState() {
return new ButtonState(this.isClickable);
}
public void restoreState(ButtonState state) {
this.isClickable = state.canClick;
}
public static class ButtonState implements Serializable {
private boolean canClick;
public ButtonState(boolean isClickable) {
this.canClick = isClickable;
}
}
}
而在Kotlin中正好相反,默认为加上static的嵌套类,如果需要成为内部类则应加上inner修饰符,使用this@Outer访问外部类实例
class Button {
private var isClickable = false
val currentState: ButtonState
get() = ButtonState(isClickable)
fun restoreState(state: ButtonState) {
isClickable = state.canClick
}
class ButtonState(val canClick: Boolean) : Serializable
inner class TestState(val canClick: Boolean) {
fun getOuterReference(): Button = this@Button
}
}
受限的类继承结构
在如下结构和判断中,若新增了类,但却没有新增分支,会导致其调用走到else
interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr
fun eval(e: Expr): Int =
when (e) {
is Num -> {
e.value
}
is Sum -> {
eval(e.left) + eval(e.right)
}
else -> {
throw IllegalArgumentException("")
}
}
通过sealed修饰父类,将子类嵌套在父类,可避免额外的else分支,当新增子类时,when表达式会提示编译失败
sealed class Expr {
class Num(val value: Int) : Expr()
class Sum(val left: Expr, val right: Expr) : Expr()
}
fun eval(e: Expr): Int =
when (e) {
is Expr.Num -> {
e.value
}
is Expr.Sum -> {
eval(e.left) + eval(e.right)
}
}
主构造函数
class+类名+[修饰符]+constructor()表示主构造函数,init表示初始化语句块
class User private constructor(name: String) {
val name: String
init {
this.name = name
}
}
若主构造函数没有注解或可见性修饰符,上述还可以省略为
class User(name: String) {
val name: String = name
}
如果参数用相应的构造方法参数来初始化,还可以简化为
class User(val name: String)
也可以加上默认参数,若所有构造方法参数都有默认值,会生成一个额外的不带参数的构造方法来使用所有的默认值
class User(val name: String = "A")
若类有父类,可在继承列表中调用父类构造函数初始化父类,如下传入Person的参数name会被用于构造User
open class User(val name: String)
class Person(name: String) : User(name) {
}
从构造函数
当需要多个构造函数时,可使用从构造函数
open class User {
private val name: String
private var age: Int = 0
constructor(name: String) {
this.name = name
}
constructor(name: String, age: Int) {
this.name = name
this.age = age
}
}
可使用this和super调用自身及父类的构造从构造函数,若类没有主构造函数,那么每个从构造函数必须初始化父类
open class User {
private val name: String
private var age: Int = 0
constructor(name: String) {
this.name = name
}
constructor(name: String, age: Int) {
this.name = name
this.age = age
}
}
class Person : User {
constructor(name: String) : this(name, 18) {
}
constructor(name: String, age: Int) : super(name, age) {
}
}
通过setter/getter访问域的值
在setter中可以通过标识符field读写域的值,getter中只能读
class User(val name: String) {
var address: String = "unspecified"
set(value: String) {
println(
"""
|Address was changed for $name:
|"$field" -> "$name"
""".trimMargin()
)
field = value
}
}
如上,在更新address值时
val user = User("Tom")
user.address = "A Street"
额外打印一些信息
Address was changed for Tom:
"unspecified" -> "Tom"
修改getter/setter可见性
如下,使用内部计数字符个数,不能对其setter
class lengthCounter {
var counter: Int = 0
private set
fun addWord(word: String) {
counter += word.length
}
}
data类
data修饰符将会为类自动生成通用方法的实现,如toString()、equals()和hashCode(),会将主构造函数中申明的属性纳入考虑
data class Person(val name: String, val age: Int)
Kotlin还为data类新增了copy(),用于返回相同的对象,且互不影响
val A = Person("A", 18)
val B = A.copy();
println(A)
println(B)
通过by实现类委托
在新增对象功能时,原有功能通常会被委托给原对象,这样会出现大量样板代码
class MyCollection<T> : Collection<T> {
private val innerList = arrayListOf<T>()
override val size: Int
get() = innerList.size
override fun contains(element: T): Boolean {
return innerList.contains(element)
}
override fun containsAll(elements: Collection<T>): Boolean {
return innerList.containsAll(elements)
}
override fun isEmpty(): Boolean {
return innerList.isEmpty()
}
override fun iterator(): Iterator<T> {
return innerList.iterator()
}
}
可通过 by 将接口的实现委托到另一个对象,编译器会自动实现上面的方法,也可自行重写方法
class MyCollection<T>(innerList: Collection<T> = ArrayList<T>()) : Collection<T> by innerList {
}
object关键字
定义一个类并同上创建一个实例
使用对象声明创建单例
一个对象声明不允许有构造方法,在定义时即被创建,不需要再调用构造方法
object Person {
val name: String = ""
fun printName() {
}
}
可使用对象.属性/方法来访问
Person.name = "A"
Person.printName()
当实现一个接口但不包含任何状态时,通常使用对象声明,如实现一个比较器
object CaseInsensitiveFileComparator : Comparator<File> {
override fun compare(p0: File, p1: File): Int {
return p0.path.compareTo(p1.path, true)
}
}
在使用时可直接传递到接收Comparator的函数
println(CaseInsensitiveFileComparator.compare(File("/User"), File("/user")))
val files = listOf(File("/Z"), File("/a"))
println(files.sortedWith(CaseInsensitiveFileComparator))
在Java中被编译成通过静态字段持有单例,则通过如下调用
CaseInsensitiveFileComparator.INSTANCE.compare(File("/User"), File("/user"));
伴生对象代替静态方法和字段
创建工厂方法
class User {
val name: String
constructor(email: String) {
name = email.substringBefore('@')
}
constructor(id: Int) {
name = id.toString()
}
}
使用companion定义伴生对象,将上面改成工厂方法实现
class User private constructor(val name: String) {
companion object {
fun newUserByEmail(email: String) = User(email.substringBefore('@'))
fun newUserById(id: Int) = User(id.toString())
}
}
在使用时,可通过类名称访问对象的方法和属性
val user1 = User.newUserByEmail("xxx@gmail.com")
val user2 = User.newUserById(123)
伴生对象在Java中通过如下方式调用,若存在名字,则代替Companion
User.Companion.newUserById(123)
实现接口
伴生对象也可实现接口
interface Factory<T> {
fun newUserByEmail(email: String): T
fun newUserById(id: Int): T
}
class User(val name: String) {
companion object : Factory<User> {
override fun newUserByEmail(email: String) = User(email.substringBefore('@'))
override fun newUserById(id: Int) = User(id.toString())
}
}
对于如下方法
fun <T> createUser(factory: Factory<T>) {
}
可直接将伴生对象所在类名字当作实现了该接口的对象实例来使用
createUser(User)
扩展函数
当需要定义通过类调用的方法时,可以通过伴生对象函数来实现
class User(val name: String) {
companion object {
}
}
fun User.Companion.printName() {
}
使用如下
User.printName()
对象表达式创建匿名对象
object声明匿名对象代替Java的匿名内部类
interface OnClickListener {
fun onclick()
}
fun setOnClickListener(listener: OnClickListener) {
}
对于上面常用的监听事件,可传入声明匿名对象
setOnClickListener(object : OnClickListener {
override fun onclick() {
}
})
若需要命名则提取出来,匿名对象可以实现多个接口
val listener = object : OnClickListener {
override fun onclick() {
}
}
setOnClickListener(listener)