Kotlin中的对象和接口

#### [kotlin官方文档 https://www.kotlincn.net/docs/reference/](https://www.kotlincn.net/docs/reference/) ####
Kotlin的类和接口与Java的类和接口是有一定的区别的。

Kotlin编译器能够生成有用的方法来避免冗余。比如将一个类声明为data类可以让编译器生成若干标准方法,同时也可以避免书写委托方法(委托模式kotlin原生支持)。
面向对象编程语言(kotlin、java)中接口的理解更像是一种协议和规范,类则相当于规范下的一种具体的产品(当然抽象类则更像是一种半成品),一般的把事物的特性和描述规定成接口,把实物本身定义成一个类,比如
一个漂亮的会做饭会讲笑话的小姑娘
可以这样提取 一个[漂亮的][会做饭][会讲笑话]的{小姑娘}
接口 接口 接口 接口
kotlin的接口
Kotlin
的接口可以既包含抽象方法的声明也包含实现。与抽象类不同的是,接口无法保存状态。它可以有属性但必须声明为抽象或提供访问器实现。
接口定义
-
用关键字 interface 来定义接口
/** * 使用interface来声明一个接口 */ interface Clickable { val prop: Int // 抽象的,不可以赋值 fun click(); //可以有方法体 fun select() { // 可选的方法体,但是变量无状态,变量的值需要子类实现 } } /** * 实现接口 *kotlin在类名后面使用:来代替Java中的extends和implements关键字 *和Java一样,一个类可以实现多个接口,但是只能继承一个类。 */ class Button :Clickable { override fun click()= println("this is clicked") } //调用 fun main(args: Array<String>) { val button:Button = Button(); println(button.click()); //this is clicked }
接口中的属性
interface MyInterface {
val prop: Int // 抽象的,不可以赋值
val propertyWithImplementation: String
get() = "foo"
fun foo() {
print(prop)
}
}
class ChildClass : MyInterface {
//实现接口,必须重写抽象属性
override val prop: Int = 122
}
fun main(args: Array<String>) {
val ch = ChildClass()
println(ch.prop)
println(ch.propertyWithImplementation)
println(ch.foo())
}
//打印结果
122
foo
122kotlin.Unit
接口继承
interface named {
val name: String
}
interface newPerson : named {
var firstName: String
var lastName: String
override val name: String get() = "$firstName $lastName"
}
data class Emplopee(
// 不必实现“name”
override var firstName: String,
override var lastName: String,
val persion: Int
) : newPerson {
}
fun main(args: Array<String>) {
Emplopee("zhou","bencheng",0).name.println()
}
//执行结果 zhou bencheng
解决覆盖冲突
实现多个接口时,可能会遇到同一方法继承多个实现的问题。例如
interface A {
fun foo():String {
println("A foo")
return "A foo"
}
fun bar()
}
interface B {
fun foo() :String {
println("B foo")
return "B foo"
}
fun bar(){
print("bar")
}
}
interface C:A {
override fun foo():String {
println("C foo")
return "C foo"
}
}
abstract class D {
open fun foo() :String {
println("方法D foo")
return "D foo"
}
open fun bar() {
println("方法D bar")
}
}
class E : D(), A, B {
override fun foo() :String {
//调用接口A中的同名实现
super<A>.foo()
//调用接口B中的同名实现
super<B>.foo()
//调用类D中的同名实现
super<D>.foo()
println("类 E 中的实现")
return "类 E 中的实现"
}
override fun bar() {
super<D>.bar()
}
}
fun main(args: Array<String>) {
E().foo()
}
// 返回结果
A foo
B foo
方法D foo
类 E 中的实现
上例中,接口 A 和 B 都定义了方法 foo() 和 bar()。 两者都实现了 foo(), 但是只有 B 实现了 bar() (bar() 在 A 中没有标记为抽象, 因为没有方法体时默认为抽象)。因为 C 是一个实现了 A 的具体类,所以必须要重写 bar() 并实现这个抽象方法。
然而,如果我们从 A 和 B 派生 D,我们需要实现我们从多个接口继承的所有方法,并指明 D 应该如何实现它们。这一规则既适用于继承单个实现(bar())的方法也适用于继承多个实现(foo())的方法。
- 思考一下,上面的示例是函数名相同,返回值相同 ,如果函数名相同返回值不同会怎样呢
结果很明显,那就没法完成实现关系了,这个冲突问题无解
接口代理
kotlin代理模式官方文档地址:http://kotlinlang.org/docs/reference/delegation.html
一.代理模式
代理是实现代码复用的一种方法。
在面向对象的语言中,代理模式是通过组合的方式来达到和继承一样的效果的。
代理模式定义:给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问,即客户不直接操控原对象,而是通过代理对象间接地操控原对象。
下面是代理模式UML图:

代理模式三要素:1.RealSubject 原对象 2.Proxy 代理对象 3.Subject 接口
RealSubject和Proxy都实现了Subject接口,这样两者就具有了公共方法Request。
通过执行Proxy中的Request方法,间接的执行RealSubject中的Request方法。
先来个🌰了解一下如何实现:
interface Subject {
void request();
}
class RealSubject implements Subject{
@Override
public void request() {
System.out.println("RealSubject");
}
}
class Proxy implements Subject{
private RealSubject realSubject;
public Proxy(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void request() {
System.out.println("Proxy start");
realSubject.request();
System.out.println("Proxy end");
}
}
public static void main(String[] args){
RealSubject realSubject = new RealSubject();
Proxy proxy=new Proxy(realSubject);
proxy.request();
}
以上代码就是代理模式的实现原理。
通过代理模式:
功能1. 我们可以复用RealSubject类的代码。
功能2. 在执行RealSubject的request方法执行之前和执行之后,插入一段代码(比如打印出来request方法的执行时间)。
对于功能1 接下来让我们思考一个问题:
假如Subject接口声明了2个方法。而我们需要复用RealSubject其中的1个方法:
interface Subject {
void request1();
void request2();
}
class RealSubject implements Subject{
@Override
public void request1() {
System.out.println("RealSubject request1");
}
@Override
public void request2() {
System.out.println("RealSubject request2");
}
}
class Proxy implements Subject{
private RealSubject realSubject;
public Proxy(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void request1() {
realSubject.request1();
}
@Override
public void request2() {
System.out.println("Proxy request2");
}
}
我们需要手动书写Proxy类,然后重载request1和request2方法,我们可以很快的把代码敲完。
如果Subject接口声明了100个方法,而我们想复用RealSubject类中的90个方法呢,这敲代码花费的时间不可忽视。
kotlin成功的解决了这个问题。
二.kotlin代理模式的实现
kotlin实现代理模式非常简单,看一个官网的🌰:
interface Base {
fun print()
}
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}
class Derived(b: Base) : Base by b
fun main(args: Array<String>) {
val b = BaseImpl(10)
Derived(b).print()
}
运行结果是:10
-
转为java代码看一下庐山真面目:
// Base.java import kotlin.Metadata; public interface Base { void print(); } // Derived.java import kotlin.Metadata; import kotlin.jvm.internal.Intrinsics; import org.jetbrains.annotations.NotNull; public final class Derived implements Base { private final Base $$delegate_0; public Derived(@NotNull Base b) { Intrinsics.checkParameterIsNotNull(b, "b"); super(); this.$$delegate_0 = b; } public void print() { this.$$delegate_0.print(); } } // TestKt.java import kotlin.Metadata; import kotlin.jvm.internal.Intrinsics; import org.jetbrains.annotations.NotNull; public final class TestKt { public static final void main(@NotNull String[] args) { Intrinsics.checkParameterIsNotNull(args, "args"); BaseImpl b = new BaseImpl(10); (new Derived((Base)b)).print(); } } // BaseImpl.java import kotlin.Metadata; public final class BaseImpl implements Base { private final int x; public void print() { int var1 = this.x; System.out.print(var1); } public final int getX() { return this.x; } public BaseImpl(int x) { this.x = x; } }
其实就是在编译期自动生成了Derived类,解放了双手。
我们可以按照需求复写print()方法
interface Base {
fun printMessage()
fun printMessageLine()
}
class BaseImpl(val x: Int) : Base {
override fun printMessage() { print(x) }
override fun printMessageLine() { println(x) }
}
class Derived(b: Base) : Base by b {
override fun printMessage() { print("abc") }
}
fun main(args: Array<String>) {
val b = BaseImpl(10)
Derived(b).printMessage()
Derived(b).printMessageLine()
}
输出:abc10
- Derived除了可以实现Base接口,还可以继承其他父类,方法名字遇到冲突怎么办
比如Derived继承了父类Parent,而父类同样拥有print方法:
interface Base {
fun print()
}
class BaseImpl(val x: Int) : Base {
override fun print() {
print(x)
}
}
open class Parent {
open fun print() {
println("Parent print")
}
}
class Derived(b: Base) : Parent(),Base by b
fun main(args: Array<String>) {
val b = BaseImpl(10)
Derived(b).print()
}
输出:10
可以看到父类print方法会被覆盖。
三.kotlin代理模式的总结
- 1.只能实现对接口方法的代理,即Base类不能为抽象类。
- 2.不方便对所有的代理方法进行统一处理。比如说在执行每个方法前都执行相同的逻辑,而java动态代理可以方便的实现这个功能。
- 3.方法名称有冲突时,代理类方法优先级较高。
- 4.编译期自动生成代理模式。不会影响运行效率。
四.继承和代理的选择
如果仅仅是代码复用和方法重写,继承能达到和代理一样的效果。
-
继承和代理的使用都存在条件限制:
-
如果使用继承的话,父类必须为可继承的,并且想要覆盖的方法也必须为可重写的,即java中类和方法都不能存在
final
修饰符,kotlin
中明确使用open
修饰符。 -
使用代理的话,两者需要存在公共接口,比如上面例子中类
Derived
和Parent
都需要实现Base
接口。 -
由于
kotlin
、java
存在单继承的约束(每个类只能存在一个父类),在使用继承或者代理均可的情况下,推荐使用代理。
-
可见性修饰符
类、对象、接口、构造函数、方法、属性和它们的 setter
都可以有 可见性修饰符。 (getter
总是与属性有着相同的可见性。) 在 Kotlin
中有这四个可见性修饰符:private
、 protected
、 internal
和 public
。 如果没有显式指定修饰符的话,默认可见性是 public。
kotlin修饰符与java修饰符对比
kotlin | java | 作用 |
---|---|---|
private | private | 类内部方法和成员可见,外部不可见 |
protected | protected | 继承他的子类可见 |
- | default | 包内可见 |
internal(模块内可见) | - | |
public | public | 公共可见 |
在本页可以学到这些修饰符如何应用到不同类型的声明作用域。
包
函数、属性和类、对象和接口可以在顶层声明,即直接在包内:
// 文件名:example.kt
package foo
fun baz() { …… }
class Bar { …… }
-
kotlin中的变量可见性
- 如果你不指定任何可见性修饰符,默认为 public,这意味着你的声明将随处可见;
- 如果你声明为 private,它只会在声明它的文件内可见;
- 如果你声明为 internal,它会在相同模块内随处可见;
- protected 不适用于顶层声明。
注意:要使用另一包中可见的顶层声明,仍需将其导入进来。
例如:
// 文件名:example.kt
package foo
private fun foo() { …… } // 在 example.kt 内可见
public var bar: Int = 5 // 该属性随处可见
private set // setter 只在 example.kt 内可见
internal val baz = 6 // 相同模块内可见
类和接口
-
对于类内部声明的成员:
- private 意味着只在这个类内部(包含其所有成员)可见;
- protected—— 和 private一样 + 在子类中可见。
- internal —— 能见到类声明的 本模块内 的任何客户端都可见其 internal 成员;
- public —— 能见到类声明的任何客户端都可见其 public 成员。
请注意在 Kotlin 中,外部类不能访问内部类的 private 成员。
如果你覆盖一个 protected 成员并且没有显式指定其可见性,该成员还会是 protected 可见性。
例子:
open class Outer {
private val a = 1
protected open val b = 2
internal val c = 3
val d = 4 // 默认 public
protected class Nested {
public val e: Int = 5
}
}
class Subclass : Outer() {
// a 不可见
// b、c、d 可见
// Nested 和 e 可见
override val b = 5 // “b”为 protected
}
class Unrelated(o: Outer) {
// o.a、o.b 不可见
// o.c 和 o.d 可见(相同模块)
// Outer.Nested 不可见,Nested::e 也不可见
}
构造函数可见性
要指定一个类的的主构造函数的可见性,使用以下语法(注意你需要添加一个显式 constructor 关键字):
//私有化类的构造
class C private constructor(a: Int) { …… }
这里的构造函数是私有的。默认情况下,所有构造函数都是 public,这实际上等于类可见的地方它就可见(即 一个 internal 类的构造函数只能在相同模块内可见).
局部声明
局部变量、函数和类不能有可见性修饰符。
模块可见性
-
可见性修饰符
internal
意味着该成员只在相同模块内可见。更具体地说, 一个模块是编译在一起的一套 Kotlin 文件:- 一个 IntelliJ IDEA 模块;
- 一个 Maven 项目;
- 一个 Gradle 源集(例外是 test 源集可以访问 main 的 internal 声明);
- 一次 Ant 任务执行所编译的一套文件。