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 任務執行所編譯的一套文件。