Septembre 2022

Introduction à Kotlin

Pourquoi Kotlin

Pour l’auto-apprentissage : https://kotlinlang.org/docs/home.htmlhttps://play.kotlinlang.org/

Règles de base

fun main(args: Array<String>) {
  println("Hello Kotlin")
}

Types

val name = "Laurent"
val greetings = "Salut $name !"
val scream = "EH HO… ${name.toUpperCase()} !"

Références

Nullable types

val l: Int = if (b != null) b.length else -1
// équivalent
val l = b?.length ?: -1

Les intervalles (Range)

1..10              // de 1 inclus jusqu'à 10 inclus
1 until 10         // de 1 inclus jusqu'à 10 exclus
4..1               // vide
4 downto 1         //  4, 3, 2, 1
10 downto 1 step 2 // 10, 8, 6, 4, 2

Le transtypage (cast)

if (obj is String) {
  print(obj.trim())
}

if (obj !is String) { // equivalent à !(obj is String)
  print("Not a String")
}
else {
  print(obj.length)
}
// x est automatiquement casté en String à droite du ||
if (x !is String || x.length == 0) return

// x est automatiquement casté en String à droite du && et dans le if
if (x is String && x.length > 0) {
  print(x.length)
}
val x: String = y as String
val x: String? = y as? String

Contrôle du flot d’exécution

loop@ for (i in 1..100) {
  for (j in 1..100) {
    if (…) break@loop
  }
}
when (x) {
  0, 1 -> print("peu")
  in 2..10 -> print("moyen")
  is String -> print("${x.trim()} est une String")
  else -> {
    println("rien de tout ça")
    print("on peu mettre un block")
  }
}
println(
  when {
    x.isOdd() -> "impair"
    x.isEven() -> "pair"
    else -> "bizarre"
})
for (i in 0..9) {
  println(i)
}
for (c in "Hello") {
  println(c)
}
for (i in array.indices) {
  println(array[i])
}

for ((index, value) in array.withIndex()) {
  println("The element at $index is $value")
}

Les fonctions

fun sum(a: Int, b: Int): Int {
  return a + b
}
fun sum(a: Int, b: Int) = a + b
fun say(text: String = "Something") = println(text)
say()                     // Affiche Something
say("Hi guys")            // Affiche Hi guys
fun Box.setMargins(left: Int, top: Int, right: Int, bottom: Int) { … }

myBox.setMargins(10,10,20,20)   // haut ? bas ? gauche ? droite ?
myBox.setMargins(left = 10, right = 10, top = 20, bottom = 20)
fun foo(vararg strings: String) { … }
foo("s1", "s2")
foo(strings = *arrayOf("a", "b", "c"))
fun dfs(graph: Graph) {
  val visited = HashSet<Vertex>()

  fun dfs(current: Vertex) {
    if (!visited.add(current)) return
    for (v in current.neighbors)
      dfs(v)
  }

  dfs(graph.vertices[0])
}

Classes et objets

class Person {
  private String name;
  private int age;

  public Person(String name, int age) {
    this.name = name;
    this.age = age;
  }

  public String getName() { return name; }
  public void setName(String name) { this.name = name; }
  public String getAge() { return age; }
  public void setAge(int age) { this.age = age; }

  public int hashCode() { … }
  public boolean equals() { … }

  @Override
  public toString() {
    return "Person(name=" + name + ", age=" + age + ")";
  }
}
data class Person(var name: String, var age: Int)
val moi = Person("Laurent Provot", 27)

Visibilités

Modifieur Package Classe
public partout à tout ceux qui voient la classe
private au sein du fichier contenant la déclaration au sein de la classe uniquement
internal dans le même module dans le même module à qui voit la classe
protected comme private + dans les classes dérivées

Constructeur (Ctor)

class Person constructor(firstName: String) {…}
class Person(firstName: String) {…}
class Person(name: String) {
  val nameUpper = name.toUpperCase();

  init {
    println("My name is: $name");
  }
}
class Person(val firstName: String, var age: Int) { … }
class Person private constructor(val firstName: String) { … }
class Person @Inject constructor(val firstName: String) { … }
class Person(val name: String) {
  constructor(name: Streing, parent: Person) : this(name) {
    parent.children.add(this)
  }
}

Propriétés & getter / setter

class Person {
  var name = "John Doe";
}
john = Person();
var <propertyName>[: PropertyType] [= <initializer>]
    [<getter>]  
    [<setter>]
var speed: Int
    get() = field * 100;  
    set(value) {
      if (value >= 0) field = value;
    }
val isEmpty: Boolean
    get() = this.size == 0
var devOnly: String
    @NotNull get
    private set

Héritage

open class Base(arg: String)
class Derived(num: Int) : Base(arg.toString())
class Derived : Base {
  constructor(arg: String, num: Int) : super(arg) {…}
  constructor(arg: String) : this(arg, 42)
}
open class Base {
  open fun fo() {…}
  fun fc() {…}
}
class Derived() : Base() {
  override fun fo() {…}
}
open class Base {
  open val x: Int = 0 
}
class Derived : Base() {
  override var x: Int = 42
}
class AnotherDerived(override val x: Int) : Base()

Interfaces

interface Named {
  val name: String
}

interface FullNamed : Named {
  val firstName: String
  val lastName: String
    
  override val name: String get() = "$firstName $lastName"
}

data class Person(
  override val firstName: String,
  override val lastName: String
) : FullNamed
interface A {
  fun foo() { print("A") }
}

interface B {
  fun foo() { print("B") }
}

class C : A, B {
  override fun foo() {
    super<A>.foo()
    super<B>.foo()
  }
}

Classes de données

data class User(val name: String, val age: Int)

object & Companion object

button.addMouseListener(object : MouseAdapter() {
  override fun mouseClicked(e: MouseEvent) { … }
  override fun mouseEntered(e: MouseEvent) { … }
})
fun foo() {
  val local = object {
    var x: Int = 0
    var y: Int = 0
  }
  print(local.x + local.y)
  print(local::class.simpleName)  // affiche null
}
class C {
  // Private function : the return type is the anonymous object type
  private fun foo() = object { val x: String = "x"}

  // Public function : so the return type is Any
  fun publicFoo() = object { val x: String = "x" }

  fun bar() {
    val x1 = foo().x        // OK
    val x2 = publicFoo().x  // ERROR: Unresolved reference 'x'
  }
}
object DatabaseManager { 
  fun connect(conn: Connector) { … }
  fun fetchData() : Data { … }
}
class MyClass {
  companion object Factory {
    fun create(): MyClass = MyClass()
  }
}

val instance = MyClass.create()

Retour sur les fonctions

val square = { num: Int -> num * num }; // (Int) -> Int
val more : (String, Int) -> String = { str, num -> str + num }
val noReturn : Int -> Unit = { num -> println(num) }
val noReturn : Int -> Unit = { println(it) }
val concatInt : String.(Int) -> String = { this + it }
fun <T, R> Collection<T>.fold(
    initial: R, 
    combine: (acc: R, nextElement: T) -> R
): R {
    var accumulator: R = initial
    for (element: T in this) {
        accumulator = combine(accumulator, element)
    }
    return accumulator
}

val items = listOf(1, 2, 3, 4, 5)
items.(0, {acc, i -> acc + i})   // 15
items.("Elts :", {res, i -> res + " " + i})  // "Elts : 1 2 3 4 5"
items.(1, Int::times)   // 120
items.fold(0) {sum, i -> sum + i}
fun String.reverse() = StringBuilder(this).reverse.toString()
"That's cool !".reverse()   // "! looc s'tahT"
infix fun String.open(rights: Acces): File { … }

"/home/provot/lecture" open Access.WRITE
// équivalent à
"/home/provot/lecture".open(Access.WRITE)

Surcharge d’opérateur

Documentation

/**
 * A group of *members*.
 *
 * This class has no useful logic; it's just a documentation example.
 *
 * @param T the type of a member in this group.
 * @property name the name of this group.
 * @constructor Creates an empty group.
 */
class Group<T>(val name: String) {
    /**
     * Adds a [member] to this group.
     * @return the new size of the group.
     */
    fun add(member: T): Int { ... }
}