interface Clickable {
//抽象方法
fun click()
//非抽象方法
fun showOff() = println(" i am clickable")
}
class Button : Clickable{
override fun click() {
//在Kotlin中override修饰符是强制要求的
TODO("Not yet implemented")
}
override fun showOff() {
super.showOff()
}
}
如上为接口的定义和它的实现类
如果一个类实现了两个接口,并且两个接口中有相同的方法(无论是抽象方法还是非抽象方法),这时必须在类中显式实现这个方法。
如下:
interface Clickable {
//抽象方法
fun click()
//非抽象方法
fun showOff() = println(" i am clickable")
}
interface Focusable {
//非抽象方法
fun showOff() = println(" i am Focusable")
}
class Button : Clickable,Focusable{
override fun click() {
//在Kotlin中override修饰符是强制要求的
println("in click")
}
override fun showOff() {
//此时必须显式实现此方法,否则编译错误。
super<Focusable>.showOff() //使用尖括号加上父类型名字的“super”表面你想要调用哪一个父类的方法
}
}
注意:
注意:没有特别需要在子类中被重写的类和方法应该被显式的标注为final
open:
open class RichButton : Clickable{
fun disable(){
} // 默认为final,子类不能重写
open fun animate(){
} // 子类可以重写
override fun click() {
// 如果重写了一个基类或者接口的成员,重写的成员同样默认是open的
TODO("Not yet implemented")
}
}
abstract:
abstract class Animated{
//抽象类
abstract fun animate() //抽象方法
open fun openFun(){
} //抽象类中标注为open的非抽象方法
}
类中的修饰符总结
修饰符 | 相关成员 | 注解 |
---|---|---|
final | 不能被重写 | 类中成员默认使用 |
open | 可以被重写 | 需要明确的表明 |
abstract | 必须被重写 | 只能在抽象类中使用;抽象成员不能有实现 |
override | 重写父类或接口中的成员 | 如果没有使用final表明,重写的成员默认是开放的 |
枚举类,enum在Kotlin中是一个软关键字:只有当它出现在class前面时才有特殊的意义
enum class Color {
RED,ORANGE,YELLOW,GREEN,BLUE,INDIGO,VIOLET
}
enum class Colorful(val r : Int, val g : Int, val b : Int){
RED(255,0,0),
ORANGE(255,165,0),
YELLOW(255,255,0),
GREEN(0,255,0),
BLUE(0,0,255),
INDIGO(75,0,130),
VIOLET(238,130,238); //这里必须要有';'
fun rgb() = (r * 256 + g) * 256 + b //给枚举类定义一个方法
}
注意:上面的例子是Kotlin语法中唯一使用分号的地方:如果要在枚举类中定义任何方法,需要用分号分割枚举常量列表和方法定义
修饰符 | 类成员|顶层声明 |
---|---|
public(默认) | 所以地方可见 |
internal | 模块中可见 |
protected | 子类中可见 |
private | 类中可见 |
注意:
默认的可见性和java中不同,java中的默认可见性——包私有,在Kotlin中并没有将其作为可见性控制
internal:模块中可见,一个模块就是一组一起编译的文件。
Kotlin允许在顶层声明中使用private可见性(比如类可以私有)。
类的基础类型和类型函数列表中用到的所有类,或者函数签名都要有与这个类或者函数本身相同的可见性。
protected修饰符在java和Kotlin中有不同的行为。在java中,可以从一个包中访问一个protected成员。但在Kotlin中不允许这样做。
类的扩展函数不能访问这个类的private和protected成员。
类A在另一个类B中声明 | 在java中 | 在Kotlin中 |
---|---|---|
嵌套类(不存储外部类的引用) | static class A | class A |
内部类(存储外部类的引用) | class A | inner class A |
在Kotlin中引用外部类实例的语法也与java不同,在Kotlin中要使用this@Outer
class Outer{
inner class Inner{
fun getOuter() : Outer = this@Outer
}
}
demo:
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.right) + eval(e.left)
}
sealed关键字:定义密封类
注意:
枚举和密封Sealed类之间的区别:
java中的构造函数是与类名相同即可,kotlin里面的构造函数是用constructor关键字表示。
kotlin里面的构造函数分为主构造函数和次构造函数。主构造函数只能有一个,次构造函数个数不限制,可以有一个或者多个
//主构造方法如下,跟在类名后面
class Person constructor(name:String){
}
class Person constructor(){
}
//当主构造方法没有任何注解或者可见性修饰符时,可以省略,写成下面这样
class Person {
}
//这种就是有注解标记的主构造方法,不能省略
class Person @Inject internal constructor(){
}
//次构造方法,一个无参的次构造方法,一个有一个参数的次构造方法
class Person {
constructor(){
}
constructor(name:String){
}
}
以看到主构造方法是没有方法体的,那么,我们需要初始化的数据应该放到哪呢?kotlin提供了init方法,给我们初始化数据。
class Person constructor(){
init{
print("111")
}
init{
println()
print("222")
}
}
var p = Person()
//这里我们会看到打印台打印:111,换行打印222
//这里构造方法是按顺序执行的
对于次构造函数:
class Person{
constructor(){
println()
print("111")
}
init{
print("222")
}
}
//这里我们会看到打印台打印:222,换行打印111
结论:不管是什么构造方法,先执行init模块逻辑,后执行构造方法的逻辑
注意:如果一个类有主构造函数,只要他除了主构造函数还有其他次构造函数,那么这些次构造函数就必须调用主构造函数,方式可以不同
方式1:每个次构造函数都调用主构造函数 :即直接委托
class Parent(name: String) {
var age = 0;
var sex = "man"
constructor(name: String, age: Int) : this("Main name 1") {
this.age = age;
println("constructor 1 $name , $age , $sex")
}
constructor(nickName: String, sex: String) : this("Main name 2") {
this.sex = sex;
println("constructor 2 $nickName , $age , $sex")
}
open fun learn() {
println(" learn ")
}
}
fun main() {
Parent("lucy", "woman").learn()
}
方式2: 次构造函数2 ————> 次构造函数1 ,而 次构造函数1 ————> 主构造函数 ,,即间接委托
class Parent(name: String) {
var age = 0;
var sex = "man"
//次级构造函数1
constructor(name: String, age: Int) : this("Main name 1") {
this.age = age;
println("constructor 1 $name , $age , $sex")
}
//次级构造函数2
constructor(nickName: String, sex: String) : this("nickName jj", 12) {
this.sex = sex;
println("constructor 2 $nickName , $age , $sex")
}
open fun learn() {
println(" learn ")
}
}
fun main() {
Parent("lucy", "woman").learn()
}
如果一个类没有主构造函数,次级构造函数就不必显示调用主构造函数
class Parent {
init {
// 初始化代码块本质就是主构造函数的方法体
// 因为主构造函数在类名头部声明不能带有方法体
println("Main constructor")
}
constructor(sex: String) {
println("constructor , $sex")
}
open fun learn() {
println(" learn ")
}
}
fun main() {
Parent("woman").learn()
}
val:
如下所示的类中的属性定义:
class Users (_nickname : String){
val nickname : String
init {
nickname = _nickname
}
}
可以简化为如下:
class Users (val _nickname : String){
//相当于有了get方法
}
fun main(args : Array<String>){
val ure = Users("adc")
println(ure._nickname)
}
var:
Kclass Users (var _nickname : String){
//相当于有了get和set方法
}
fun main(args : Array<String>){
val ure = Users("adc")
ure._nickname = "d"
println(ure._nickname)
}
open class ClassUser (val className : String) {
}
class Users (var _nickname : String) : ClassUser(_nickname) {
}
注意:即使父类没有有参构造方法,也要显式的调用父类的构造方法
open class ClassUser () {
}
class Users (var _nickname : String) : ClassUser() {
}
在java中,默认的get,set方法非常复杂,且全是重写。在Kotlin中可以更方便的表达
//People.java
public class People {
private String name;
private int isAge;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getIsAge() {
return isAge;
}
public void setIsAge(int isAge) {
this.isAge = isAge;
}
}
//People.kt
class People {
var name: String? = null
var isAge = 0
}
注意:从java到Kotlin的转换过程中public修饰符消失了,在Kotlin中public是默认的可见性,所以可以省略
在java中调用:
//Kotlin的属性 name 会把name对应的getter方法暴露给java(即就是getName()),
People people = new People();
people.setAge(12);
people.setName("liSuo");
System.out.println(people.getName() + " "+ people.isAge());
//getter和setter的命名规则有一个例外:如果属性名称以is开头,
//对应的getter方法不会加任何的前缀
在Kotlin中调用
val people1 = People()
people1.isAge = 12
people1.name = "liSuo"
println(people1.name + " "+ people1.isAge)
如果对如上的类加上构造函数:
//People.java
public class People {
private String name;
private int isAge;
public People(String name, int isAge) {
this.name = name;
this.isAge = isAge;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getIsAge() {
return isAge;
}
public void setIsAge(int isAge) {
this.isAge = isAge;
}
}
//People.kt
class People(var name: String, var isAge: Int)
注意:从java到Kotlin的转换过程中public修饰符消失了,在Kotlin中public是默认的可见性,所以可以省略
在java中调用:
//Kotlin的属性 name 会把name对应的getter方法暴露给java(即就是getName()),
People people = new People("bob",12);
System.out.println(people.getName());
//getter和setter的命名规则有一个例外:如果属性名称以is开头,对应的getter方法不会加任何的前缀
System.out.println(people.isAge());
在Kotlin中调用
val people = People("bob",12)
people.isAge = 8
println(people.name)
println(people.isAge)
class Users (_nickname : String) {
var nickname : String = "unspecified"
set(value : String) {
field = value
println("cheang nickname to $field")
}
}
此外还可以修改访问器的可见性。
var nickname : String = "unspecified"
private set(value : String) {
field = value
println("cheang nickname to $field")
}
此时无法通过setter方法修改nickname的值。
class Client( val name : String, val postalCode : Int){
override fun toString(): String {
return "Client(name = $name , postalCode = $postalCode)"
}
override fun equals(other: Any?): Boolean {
if( other !is Client){
return false
}
return other.name == name && other.postalCode == postalCode
}
override fun hashCode(): Int {
return name.hashCode() * 31 + postalCode;
}
}
在Kotlin中不必在重写生成这些方法了,只要为你的类添加data修饰符,必要的方法将会自动生成好。
data class Client( val name : String, val postalCode : Int){
//作用和上面完全一样
}
无论什么时候实现一个接口,你都可以使用by关键字将接口的实现委托到另一个对象。
class CountingSet<T>(private val innerSet : MutableCollection<T> = HashSet<T>())
: MutableCollection<T> by innerSet{
var objectsAdded = 0
override fun add(element: T): Boolean {
objectsAdded++
return innerSet.add(element)
}
override fun addAll(elements: Collection<T>): Boolean {
objectsAdded+=elements.size;
return innerSet.addAll(elements)
}
}
object关键字在多种情况下出现,它的核心理念为:这个关键字 定义一个类并同时创建一个实例,下面我们介绍它的三个应用场景:
在Java中,单例模式通常是使用private构造方法,并且用静态字段来持有这个类仅有的实例。
而在Kotlin中,通过使用对象声明功能,将类声明与该类的单一实例声明结合到了一起。
object Person2{
var name : String = "default Name"
var age : Int = 15
}
//调用
print(Person2.name)
继承自接口的对象声明
对象声明可以继承自类和接口,这通常在你使用的框架需要去实现一个接口,但是你的实现不包含任何状态的时候很有用。
在类中声明对象
在类中使用对象声明时,这样的对象同样只有一个单一实例:它们在每个容器类的实例中具有相同的实例。
运行结果为:
在 Java 中使用 Kotlin 对象
如果要在Java中使用Kotlin中的声明对象,可以通过访问静态的INSTANCE字段:
Kotlin的类不能拥有静态成员,作为替代,Kotlin依赖包级别函数(在大多数情形下能够替代Java的静态方法)和对象声明(在其他情况下替代Java的静态方法,同时还包括静态字段),在大多数情况下,推荐使用顶层函数,但是顶层函数不能访问类的private变量。
因此,如果你需要写一个 在没有类实例的情况下 调用但是需要 访问类内部的函数,可以将其写成那个类中的 对象声明的成员。
在类中定义的对象之一可以使用一个特殊的关键字来标记 companion,如果这样做,就获得了直接 通过容器类名称来访问这个对象的方法和属性的能力,不再需要显示地指明对象的名称,下面是一个基础的示例:
伴生对象是一个声明在类中的普通对象,它可以有名字,实现一个接口或者有扩展函数或属性。假设我们需要在对象和JSON之间进行序列化和反序列化,可以将序列化的逻辑放在伴生对象中。
在大多数情况下,通过包含伴生对象的类的名字(也就是例子中的Person类)来引用伴生对象,所以不必关心它的名字,如果省略了伴生对象的名字,默认的名字将会分配为Companion。
在伴生对象中实现接口
就像其它对象声明一样,伴生对象也可以实现接口,可以将包含它的类的名字当做实现了该接口的对象实例来使用。
运行结果为:
伴生对象扩展
举例来说,如果类有一个伴生对象,可以通过在其上定义扩展函数来做到这一点,即类C有一个伴生对象,并且在C.Companion上定义了一个扩展函数func,则可以通过C.fun()来调用它。
下面,我们为Person类的伴生对象定义一个扩展函数:
当调用toJson就像是它是一个伴生对象定义的方法一样,但是实际上它是作为扩展函数在外部定义的。而为了能够为你的类定义扩展,必须在其中声明一个对象,即使是空的。
object关键字不仅能够用来表明单例式的对象,还能用来声明 匿名对象,它替代了Java中匿名内部类的用法。例如,让我们来看看怎样将一个典型的匿名内部类用法转换成Kotlin:
将匿名对象存储到变量中
除了去掉对象的名字外,语法与对象声明相同的。对象表达式声明了一个类并创建了该类的一个实例,但是没有给这个类或是实例分配一个名字。通常来说,它们都是不需要名字的,因为你会将这个对象用作一个函数调用的参数。如果你需要给对象分配一个名字,可以将其存储到一个变量中:
在对象表达式中修改变量的值
与Java的匿名类一样,在对象表达式中的代码可以访问创建它的函数中的变量,但是与Java不同,访问并被限制在final变量,还可以在对象表达式中修改变量的值。
运行结果为: