2016-04-08 40 views
15

E 'facile scrivere metodi di estensione in Kotlin:campi di estensione in Kotlin

class A { } 
class B { 
    fun A.newFunction() { ... } 
} 

Ma c'è qualche modo per creare variabili estensione? Come:

class B { 
    var A.someCounter: Int = 0 
} 

risposta

24

No - l'documentation spiega questo:

estensioni in realtà non modificano le classi che estendono. Definendo un'estensione, non si inseriscono nuovi membri in una classe, ma si limitano a rendere nuove funzioni richiamabili con la notazione a punti sulle istanze di questa classe.

e

Si noti che, dal momento che le estensioni in realtà non inserire i membri in classi, non c'è modo efficace per una proprietà di estensione di avere un campo di supporto. Questo è il motivo per cui gli inizializzatori non sono consentiti per le proprietà di estensione. Il loro comportamento può essere definito solo fornendo getter/setter in modo esplicito.

Pensare alle funzioni/proprietà di estensione come semplice zucchero sintattico per chiamare una funzione statica e passare in un valore sperabilmente lo rende chiaro.

11

È possibile creare una proprietà di estensione con getter e setter override:

var A.someProperty: Int 
    get() = /* return something */ 
    set(value) { /* do something */ } 

Ma non si può creare una proprietà di estensione con un campo di supporto perché non è possibile aggiungere un campo a una classe esistente.

5

Non è possibile aggiungere un campo, ma è possibile aggiungere una proprietà che delega ad altre proprietà/metodi dell'oggetto per implementare i relativi accessor. Per esempio si supponga di voler aggiungere una proprietà secondsSinceEpoch alla classe java.util.Date, è possibile scrivere

var Date.secondsSinceEpoch: Long 
    get() = this.time/1000 
    set(value) { 
     this.time = value * 1000 
    } 
7

C'è no way to add extension properties with backing fields alle classi, perché le estensioni do not actually modify a class.

È possibile definire una proprietà di interno solo con getter personalizzato (e setter per var) o delegated property.


Tuttavia, se è necessario definire una proprietà di estensione che si comporti come se fosse un campo di supporto, le proprietà delegate sono utili. L'idea è di creare un delegato proprietà che avrebbe memorizzare il mappatura oggetto-to-value:

  • utilizzando l'identità, non equals()/hashCode(), effettivamente memorizzare i valori per ogni oggetto, come IdentityHashMap fa;

  • non impedire i chiave oggetti vengano rifiuti raccolti (usando weak references), come WeakHashMap fa.

Purtroppo, non c'è WeakIdentityHashMap nel JDK, quindi bisogna implementare il proprio (o prendere un complete implementation).

Quindi, in base a questa mappatura è possibile creare una classe delegato che soddisfi lo property delegates requirements. Ecco un esempio di attuazione non-thread-safe:

class FieldProperty<R, T : Any>(
    val initializer: (R) -> T = { throw IllegalStateException("Not initialized.") } 
) {  
    private val map = WeakIdentityHashMap<R, T>() 

    operator fun getValue(thisRef: R, property: KProperty<*>): T = 
      map[thisRef] ?: setValue(thisRef, property, initializer(thisRef)) 

    operator fun setValue(thisRef: R, property: KProperty<*>, value: T): T { 
     map[thisRef] = value 
     return value 
    } 
} 

Esempio di utilizzo:

var Int.tag: String by FieldProperty { "$it" } 

fun main(args: Array<String>) { 
    val x = 0 
    println(x.tag) // 0 

    val z = 1 
    println(z.tag) // 1 
    x.tag = "my tag" 
    z.tag = x.tag 
    println(z.tag) // my tag 
} 

Quando definita all'interno di una classe, la mappatura può essere memorizzato indipendentemente per istanze della classe o in una condiviso oggetto delegato:

private val bATag = FieldProperty<Int, String> { "$it" } 

class B() { 
    var A.someCounter: Int by FieldProperty { 0 } // independent for each instance of B 
    var A.tag: String by bATag // shared between the instances, but usable only inside B 
} 

Inoltre, si ricorda che l'identità is not guaranteed per PRI di Java tipi mitivi dovuti al pugilato.

E sospetto che le prestazioni di questa soluzione siano significativamente peggiori rispetto a quelle dei campi regolari, molto probabilmente vicino al normale Map, ma ciò richiede ulteriori test.

Per il supporto di proprietà nullable e l'implementazione thread-safe, fare riferimento a here.

+0

questo è un bel trucco creativo. È possibile aggiungere un file di licenza al github. Grazie! – MadDeveloper