Operatori

Un operatore è un simbolo (o una sequenza di simboli) che specifica quale legge applicare a uno o più operandi, per generare un risultato. Vediamo quali sono gli operatori del Kotlin e come si esegue l'overloading su di essi.

Kotlin ha dovuto superare le limitazioni della JVM nella materia degli operatori. Infatti la JVM non permette l'overloading, e quindi Kotlin ha realizzato un modo per by-passare queste limitazioni. Quanto il compilatore si trova di fronte aglio operatori (cioè dei simboli):

  • Determina il tipo di a (l'operando di sinistra, se non in un caso specifico) e cerca un metodo (dell'istanza e/o di estensione) che abbia il nome associato all’operatore e con il modificatore operator, e che il il tipo di b (l'operando di destra, se non è un operatore unario e non siamo in un determinato caso specifico) sia compatibile con gli overloading.
  • Se essa non si trova e/o è ambigua la chiamata viene lanciato un errore di compilazione.
  • Viene dedotto dal risultato della funzione il tipo di ritorno del suddetto operatore.

Si noti che queste operazioni, così come molte altre, sono ottimizzate per i tipi di base del Kotlin.

Operatori unari

Operatore Tradotto in:
+a a.unaryPlus()
-a a.unaryMinus()
!a a.not()

Ad esempio, ecco come puoi sovraccaricare l'operatore unario meno (-):

data class Point(val x: Int, val y: Int) {

operator fun unaryMinus() = Point(-x, -y)

}

val point = Point(10, 20)

fun main(args: Array<String>) {

println(-point)

}

Operatori di incremento e decremento

Operatore Tradotto in:
a++ e ++a a.inc()
a-- e --a a.dec()

Rispetto agli operatori unari c'è da precisare che:

  • Nel caso che sia post-fisso:
    • Kotlin si memorizza il valore di a in un archivio temporaneo;
    • Esegue inc() o dec();
    • Assegna il risultato di inc() o dec() ad a;
    • Ritorna il valore memorizzato nell’archivio.
  • Nel caso che sia pre-fisso:
    • Esegue inc() o dec();
    • Assegna il risultato di inc() o dec() ad a;
    • Ritorna a.

Ecco un esempio:

data class Counter(val dayIndex: Int = 0) {

operator fun inc(): Counter {

return Counter(dayIndex + 1)

}

}

fun main(args: Array<String>) {

var counter = Counter()

println(counter++) // print Counter(dayIndex=0)

println(++counter) // print Counter(dayIndex=2)

}

Operatori binari

Operatori aritmetici

Operatore Tradotto in:
a + b a.plus(b)
a - b a.minus(b)
a * b a.times(b)
a / b a.div(b)
a % b a.rem(b), a.mod(b) (deprecato)
a..b a.rangeTo(b)

Si noti che l'operatore rem() è supportato da Kotlin 1.1. Kotlin 1.0 utilizza l'operatore mod(), che è deprecato in Kotlin 1.1.

Di seguito è riportato un esempio di classe Contatore che inizia con un valore dato e può essere incrementato utilizzando overload dell' operatore +:

data class Counter(val dayIndex: Int = 0) {

operator fun plus(increment: Int): Counter {

return Counter(dayIndex + increment)

}

}

fun main(args: Array<String>) {

var counter = Counter() + 2

println() // print Counter(dayIndex=2)

}

Operatore in

Operatore Tradotto in:
a in b b.contains(a)
a !in b !b.contains(a)

Per in e !in la procedura è sempre la stessa, ma l'ordine degli argomenti è invertito. Ecco comunque un esempio:

data class MyArray<T>(val array: Array<T>) {

operator fun contains(value : T) = array.contains(value)

}

fun main(args: Array<String>) {

val numbers = MyArray(arrayOf(1, 4, 42, -3))

if (4 in numbers) {

println("numbers array contains 4.")

}

}

Operatore is e as

Questi operatore, non sono overloddabili. Il loro funzionamento viene spiegato in una lezione precedente.

Operatore di Indexed (array-like)

Operatore Tradotto in:
a[i] a.get(i)
a[i, j] a.get(i, j)
a[i_1, ..., i_n] a.get(i_1, ..., i_n)
a[i] = b a.set(i, b)
a[i, j] = b a.set(i, j, b)
a[i_1, ..., i_n] = b a.set(i_1, ..., i_n, b)

Viene scelto il get() o il set() in base se l’operatore = si trova prima o dopo di a[…] ed al numero di parametri passato:

class TwoDimensionalArray<T>(size: Int, init: (x: Int, y: Int) -> T) {

val array: Array<Array<Any?>> = Array(size, { x -> Array<Any?>(size, { y -> init(x, y) }) }) // viene utilizzato un array di Any? per una limitazione della JVM

operator fun get(x: Int, y: Int): T = array[x][y] as T

operator fun set(x: Int, y: Int, value: T) {

array[x][y] = value

}

operator fun get(x: Int) = array[x].map{it as T}.toList()

val size get() = array.size

}

fun main(args: Array<String>) {

val array2d = TwoDimensionalArray(10, { x, y -> 10 })

for (x in 0 until array2d.size) {

for (y in 0 until array2d.size) {

print(array2d[x, y].toString() + " ")

array2d[x, y] = x * y

}

println()

}

println()

for(x in 0 until array2d.size) {

for(item in array2d[x]) {

print(item.toString() + " ")

}

println()

}

}

Realizza questo output:

10 10 10 10 10 10 10 10 10 10

10 10 10 10 10 10 10 10 10 10

10 10 10 10 10 10 10 10 10 10

10 10 10 10 10 10 10 10 10 10

10 10 10 10 10 10 10 10 10 10

10 10 10 10 10 10 10 10 10 10

10 10 10 10 10 10 10 10 10 10

10 10 10 10 10 10 10 10 10 10

10 10 10 10 10 10 10 10 10 10

10 10 10 10 10 10 10 10 10 10

0 0 0 0 0 0 0 0 0 0

0 1 2 3 4 5 6 7 8 9

0 2 4 6 8 10 12 14 16 18

0 3 6 9 12 15 18 21 24 27

0 4 8 12 16 20 24 28 32 36

0 5 10 15 20 25 30 35 40 45

0 6 12 18 24 30 36 42 48 54

0 7 14 21 28 35 42 49 56 63

0 8 16 24 32 40 48 56 64 72

0 9 18 27 36 45 54 63 72 81

Operatore di invoke()

Operatore Tradotto in:
a() a.invoke()
a(i) a.invoke(i)
a(i, j) a.invoke(i, j)
a(i_1, ..., i_n) a.invoke(i_1, ..., i_n)

Viene scelto l’invoke() in base al numero di parametri:

class C {

operator fun invoke(): Unit = println("hello world")

}

fun main(args: Array<String>) {

val c = C()

c()

C()()

}

Operatori con assegnamento

Operatore Tradotto in: Non può coesistere con:
a += b a.plusAssign(b) a.plus(b)
a -= b a.minusAssign(b) a.minus(b)
a *= b a.timesAssign(b) a.times(b)
a /= b a.divAssign(b) a.div(b)
a %= b a.remAssign(b) a.rem(b)

Codeste funzioni non devono tornare nessun valore, inoltre non deve esistere l'overload dell'operatore + se esiste quello del += (o viceversa), stessa cosa vale per gli altri operatori. A differenza degli altri operatori in questo caso il compilatore:

  • Controlla se esiste l'operatore ⭐= (ove ⭐ è uno dei 5 operatori: +, -, *, / e %):
    • Se si controlla la validità dell'overload e del tipo di ritorno che deve essere Unit (leggasi void, per chi viene da Java).
  • Altrimenti lo trasforma in a = a ⭐ b (ove ⭐ è uno dei 5 operatori: +, -, *, / e %):
    • Se non esiste l'operatore viene generato un errore di compilazione.

class C {

operator fun plus(b : C) = C()


operator fun remAssign(b : C) {

println("remAssign")

}

}

fun main(args: Array<String>) {

var v1 = C()

var v2 = C()

var v3 = C()

v1 += v2

v3 %= v1

}

Operatori di identità (verifica il riferimento)

  • L'operatore === serve per verificare se due variabili hanno lo stesso riferimento, cioè se condividono l'oggetto puntato;
  • L'operatore !==, al contrario, serve per verificare se due variabili non hanno lo stesso riferimento, cioè se non condividono l'oggetto puntato.

fun main(args: Array<String>) {

val number1 = Integer(10) // create new instance

val number2 = Integer(10) // create new instance

val number3 = number1

// check if number1 and number2 are Structural equality

println(number1 == number2) // prints true

// check if number1 and number2 points to the same object

// in other words, checks for Referential equality

println(number1 === number2) // prints false

// check if number1 and number3 points to the same object

println(number1 === number3) // prints true

}

Operatori di uguaglianza e di disuguaglianza

Operatore Tradotto in:
a == b a?.equals(b) ?: (b === null)
a != b !(a?.equals(b) ?: (b === null))

L'operatore == e il relativo != sono speciali: viene tradotta in un'espressione complessa che esegue controlli per il null. Quindi non sempre si viene richiamata la funzione equals():


class Sample(var sample : Int) {

override fun equals(other: Any?): Boolean {

println("Sono dentro equals()")

if(other is Sample)

return this.sample == other.sample

else return false

}

}

fun main(args: Array<String>) {

var v1: Sample? = null

var v2: Sample? = null

var v3: Sample? = Sample(10)

var v4: Sample? = Sample(10)

println("v1 == v2")

v1 == v2

println("v2 == v3")

v2 == v3

println("v3 == v4")

v3 == v4

}

L'output a schermo è il seguente:

v1 == v2

v2 == v3

v3 == v4

Sono dentro equals()

Operatori di confronto

Espressione Tradotto a
a > b a.compareTo(b) > 0
a < b a.compareTo(b) < 0
a >= b a.compareTo(b) >= 0
a <= b a.compareTo(b) <= 0

Tutti i confronti sono tradotti in chiamate compareTo(), che restituisce degli Int:

class Data {

companion object {

private fun annoBisestile(anno: Int): Boolean {

return (((anno % 4 == 0) && (anno % 100 != 0)) || (anno % 400 == 0))

}

private fun dataValide(giorno: Int, mese: Int, anno: Int): Boolean {

if (anno < 0) return false

if (mese > 12 || mese < 1) return false

if (giorno > 31 || giorno < 1) return false

if ((giorno == 31 && (mese == 2 || mese == 4 || mese == 6 || mese == 9 || mese == 11))) return false

if (giorno == 30 && mese == 2) return false

if ((giorno == 29 && mese == 2) && annoBisestile(anno)) return false

return true

}

}

private var _giorno = 1

var giorno: Int

get() {

return _giorno

}

set(value) {

if (dataValide(value, mese, anno))

_giorno = value

}

private var _mese = 1

var mese: Int

get() {

return _mese

}

set(value) {

if (dataValide(giorno, value, anno))

_mese = value

}

private var _anno = 1900

var anno: Int

get() {

return _anno

}

set(value) {

if (dataValide(giorno, mese, value))

_anno = value

}

constructor()

constructor(giorno: Int, mese: Int, anno: Int) {

if (dataValide(giorno, mese, anno)) {

_giorno = giorno

_mese = mese

_anno = anno

}

}

constructor(giorni: Long) {

this.giorni = giorni

}

var giorni: Long

get() {

var giorni: Long = 0

val annopart = 1900

val mesi = arrayOf(31, if (annoBisestile(_anno)) 29 else 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)

for (i in annopart until _anno)

giorni += if (annoBisestile(i)) 366 else 365

for (i in 0..(_mese - 2))

giorni += mesi[i]

giorni += _giorno - 1

return giorni

}

set(value) {

val annopart = 1900

var anno = annopart

var mese = 0

var giorni = value

while (giorni >= if (annoBisestile(anno)) 366 else 365) {

giorni -= if (annoBisestile(anno)) 366 else 365

anno++

}

val mesi = arrayOf(31, if (annoBisestile(anno)) 29 else 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)

while (giorni >= mesi[mese]) {

giorni -= mesi[mese]

mese++

}

_giorno = giorni.toInt() + 1

_mese = mese + 1

_anno = anno

}

operator fun compareTo(other: Data) : Int {

return this.giorni.compareTo(other.giorni)

}

}

fun main(args: Array<String>) {

val d1 = Data()

val d2 = Data(10, 10, 2000)

val d3 = Data(30000)

val d4 = Data(40, 40, 2000)

val d5 = Data(10, 2, 2010)

println("d1 < d2: " + (d1 < d2))

println("d2 <= d3: " + (d2 <= d3))

println("d3 >= d4: " + (d3 >= d4))

println("d4 > d5: " + (d4 > d5))

}

L'output a schermo è il seguente:

d1 < d2: true

d2 <= d3: false

d3 >= d4: true

d4 > d5: false


Altri operatori letterali

In alcune lezioni abbiamo visto varie keyword che si comportano da operatore: downTo, until, to. Queste in realtà sono delle semplifici funzioni con il modificatore infix. Immagina di utilizzare delle enumerazioni per realizzare un mazzo di carte da Poker:

enum class Suit {

HEARTS,

SPADES,

CLUBS,

DIAMONDS

}

enum class Rank {

ACE, TWO, THREE, FOUR, FIVE,

SIX, SEVEN, EIGHT, NINE,

TEN, JACK, QUEEN, KING

}

data class Card(val rank: Rank, val suit: Suit)

È una struttura molto semplice, fondamentalmente si basa su tre parti:

Suit: Il tipo della carta (HEARTS, SPADES, CLUBS o DIAMONDS).

Rank: Il valore della carta (ACE, TWO, THREE, ..., TEN, JACK, QUEEN, KING).

Card: Una data class composta da Rank e Suit.

Per creare una carta bisogna fare quanto segue:

val card = Card(Rank.QUEEN, Suit.HEARTS)

Ma noi siamo abituati a dire un QUEEN of HEARTS oppure un JACK of SPADES. Ma l'operatore of non è un operatore esistente. Ma Kotlin ci permette di realizzare nuovi operatori, come è già stato fatto per alcune keyword (downTo, until, to...) tramite i metodi con il modificatore infix. Come per gli operatori, questi metodi devono essere metodi di istanza di una classe e/o metodi di estenzione:

enum class Rank {

TWO, THREE, FOUR, FIVE,

SIX, SEVEN, EIGHT, NINE,

TEN, JACK, QUEEN, KING, ACE;


infix fun of(suit: Suit) = Card(this, suit)

}

Grazie a questo modificatore infix siamo in grado di riscrivere lo Snippet 2.12 in maniera molto più naturale:

val card = Rank.ACE of Suit.HEARTS

Ma ancora non è naturale, come noi vogliamo e poi ci manca un metodo per generare le 40 carte. Adesso usiamo gli import per evitare di scrivere Rank. e Suit.:

import Rank.*

import Suit.*

enum class Suit {

HEARTS,

SPADES,

CLUBS,

DIAMONDS

}

enum class Rank {

TWO, THREE, FOUR, FIVE,

SIX, SEVEN, EIGHT, NINE,

TEN, JACK, QUEEN, KING, ACE;

infix fun of(suit: Suit) = Card(this, suit)

}

data class Card(val rank: Rank, val suit: Suit) {

companion object {

private var array: Array<Card>? = null

fun values() : Array<Card> {

if(array == null) {

val list = mutableListOf<Card>()

for (suit in Suit.values()) {

Rank.values().mapTo(list) { rank -> rank of suit }

}

array = list.toTypedArray()

}

return array ?: emptyArray()

}

}

}

fun main(args: Array<String>) {

val list = Card.values().toMutableList()

list.remove(ACE of HEARTS)

list.remove(TEN of DIAMONDS)

list.remove(JACK of CLUBS)

list.remove(SEVEN of SPADES)

}

results matching ""

    No results matching ""