Der Nachgeschmack von Kotlin, Teil 2

    Im letzten Teil habe ich über die Riffe von Kotlin gesprochen, in diesem werde ich Ihnen sagen, dass sie überwiegen.

    Ich wurde mehrmals gefragt, aber was ist es in Kotlin, das den Übergang von Java fördern könnte, was ist seine Funktion? Ja, Kotlin brachte Null-Sicherheit und viel syntaktischen Zucker ein und deckte einige der Schwächen von Java ab. Dies wird jedoch kein Grund für den Übergang. Was könnte werden? Neue Möglichkeiten und neue Philosophie.



    Neue Funktionen


    1. Null Sicherheit


    Dies ist in erster Linie angegeben. Das macht den Code wirklich sicherer. Ja, es gibt ein Problem beim Aufruf von Java. Es läuft auf zwei Optionen hinaus:

    1. Aufruf von Bibliotheken von Drittanbietern. Es wird entweder durch eine explizite Deklaration des Typs der Variablen, der das Ergebnis zugewiesen wurde, oder durch das Schreiben einer Erweiterung behandelt, die Sie bereits häufig ausführen möchten, um die Aufrufkette zu begradigen (das Beispiel ist am Ende).
    2. Empfangen von Nachrichten von der Außenwelt (REST, MQ usw.). Spring Validate hilft hier.

    2. Koroutinen


    Ich habe es selbst noch nicht benutzt, aber anscheinend kann dies die Herangehensweise an Multithread-Programmierung stark verändern. UPD Ich habe einen Artikel über Koroutinen korrigiert und geschrieben .

    3. Kompilierung in JavaScript


    Leider habe ich es noch nicht ausprobiert. Zu Beginn meines Projekts war es nur in der Beta-Version und ich war mit Angular nicht vertraut. Probieren Sie es jetzt aus - ein einzelnes Modul mit DTO für Server und Client.

    Neue Philosophie


    Philosophie Kotlin - Concise , sicherer , interoperable , Tool wird freundlich (zum Büro Ort und Berichte.).

    1. Interoperabel


    Java-Kompatibilität ist wirklich 100% Hin- und Rückfahrt. Was auch immer ich tue, alles hat perfekt funktioniert.

    2. Werkzeugfreundlich


    Ich habe es auf Eclipse nicht ausprobiert, aber in Intellij ist alles großartig und verbessert sich weiter.

    3. Sicher


    Meiner pragmatischen Ansicht nach ist dies das Wichtigste. Prägnant, interoperabel und werkzeugfreundlich sind die Mindestvoraussetzungen für das Überleben von Sprachen in der JVM, da sie sonst Java verlieren.

    Null-Sicherheit + Veränderbarkeit


    Beispielsweise ist eine lokale Variable eine Liste von Zeichenfolgen. In Java kennt der Compiler nur zwei Optionen:

    List list1;
    final List list2;
    

    Die zweite Option ist in Einzelfällen zu finden. Normalerweise ist dies nicht das 8. Java, und diese Liste wird in einer anonymen Klasse benötigt.

    Und hier ist Kotlin:

    val list1: List?
    val list2: List?
    val list3: List
    val list4: List
    val list5: MutableList?
    val list6: MutableList?
    val list7: MutableList
    val list8: MutableList
    var list9: List?
    var list10: List?
    var list11: List
    var list12: List
    var list13: MutableList?
    var list14: MutableList?
    var list15: MutableList
    var list16: MutableList
    

    Was gibt es? Jeder Typ hat seine eigenen Garantien und eine Reihe von zulässigen Vorgängen. Daher kann + = null nur für var list12: List aufgerufen werdenund add (null) on val list8: MutableList und var list16: MutableList.

    Mit jeder Erklärung ist es unrentabel, den vollständigen Typ zu schreiben. Daher gibt es eine Typinferenz:

    val test = Random().nextBoolean()
    val list1 = if (test) null else listOf("")
    val list2 = if (test) null else listOf(null, "")
    val list3 = listOf("")
    val list4 = listOf(null, "")
    val list5 = if (test) null else mutableListOf("")
    val list6 = if (test) null else mutableListOf(null, "")
    val list7 = mutableListOf("")
    val list8 = mutableListOf(null, "")
    var list9 = list2?.filterNotNull()
    var list10 = list2
    var list11 = list2?.filterNotNull() ?: emptyList()
    var list12 = list2 ?: emptyList()
    var list13 = list2?.filterNotNull()?.toMutableList()
    var list14 = list2?.toMutableList()
    var list15 = list2?.filterNotNull()?.toMutableList() ?: mutableListOf()
    var list16 = list2?.toMutableList() ?: mutableListOf()
    

    Wenn Sie den Code schreiben, möchten Sie das zusätzliche Feld nicht als nullwertfähig deklarieren, um später nicht zu schreiben? und?: und die meisten Operationen führen zu unveränderlichen Sammlungen. Infolgedessen werden die engsten Zustände im Code deklariert, was strengere Verträge ergibt und die Komplexität des Programms verringert.

    Sichere Unterstützung durch andere Sprachfunktionen


    1. Natürlich möchte ich den Konstruktor ohne Parameter und Setter für Felder belassen, da ein Konstruktor ohne Parameter einen nicht kompilierten Zustand des Objekts erzeugt - zum Beispiel null Benutzeranmeldung.
    2. Die Sprache fördert das Fehlen lokaler Variablen - es gibt keine Zwischenzustände.
    3. Billigere DTO - Datenklasse . So können wir die Kontrolle über Zustände schwächen, wenn wir ein Objekt auf die Benutzeroberfläche übertragen, ohne die Verträge des Modells zu schwächen.
    4. Reduzierung der Kosten für die Überladung von Methoden - Standardparameter - Es gibt keine Versuchung, eine Reihe von Setzern zu schreiben.

      Beispiel
      data class Schedule(
          val delay: Int,
          val delayTimeUnit: TimeUnit = TimeUnit.SECONDS,
          val rate: Int? = null,
          val rateTimeUnit: TimeUnit = TimeUnit.SECONDS,
          val run: () -> Unit
      )
      fun usage() {
          Schedule(1) {
              println("Delay for second")
          }
          Schedule(100, TimeUnit.MILLISECONDS) {
              println("Delay for 100 milliseconds")
          }
          Schedule(1, rate = 1) {
              println("Delay for second, repeat every second")
          }
      }
      


    Was kann erscheinen


    1. Inline-Klassen / Wertklassen. Ermöglicht das Erstellen von Wrapper-Klassen um Grundelemente, während das Kompilieren ohne diese Klasse erfolgt. Es ist beispielsweise möglich, zwei Arten von Zeilen zu erstellen: Login und E-Mail, die nicht ineinander umgewandelt werden. Habe es auf Jpoint gehört.
    2. Wirklich unveränderliche Daten. Syntaxunterstützung für Objektveränderlichkeit. Ein unveränderliches Objekt darf keine Verweise auf nicht unveränderliche Objekte enthalten und in keiner Weise ändern. Platz drei bei der Abstimmung für neue Funktionen der Sprache.

    4. Prägnant (Beispiele aus meinem Projekt, fast wie es ist)


    Es gibt keine Einschränkung "eine Datei - eine Klasse"


    Die Arbeit mit den Frühlingsdaten sieht für mich so aus (alles in einer Datei):
    @Repository interface PayerRepository : CrudRepository {
        fun findByApprenticeId(id: Int): List
    }
    @Repository interface AttendanceRepository : CrudRepository {
        fun findByDateBetween(from: Date, to: Date): List
    }
    fun AttendanceRepository.byMonth(month: Date): List {
        val from = month.truncateToMonth()
        val to = month.addMonths(1).subtractDays(1)
        return findByDateBetween(from, to)
    }
    //ещё 10 репозиторий
    inline fun  CrudRepository.find(id: ID): T {
        return findOne(id) ?: throw ObjectNotFound(id, T::class.qualifiedName)
    }
    


    Erweiterungen


    Wir berichtigen den Einspruch an DateUtils
    Kotlin

    fun isJournalBlocked(date: Date, forMonth: Date) = forMonth <= date.subtractMonths(1).subtractDays(10)
    //используется ещё в 20 местах
    fun Date.subtractMonths(amount: Int): Date = DateUtils.addMonths(this, -amount)
    //используется ещё в 8 местах
    fun Date.subtractDays(amount: Int): Date = DateUtils.addDays(this, -amount)
    

    Java

    public boolean isJournalBlocked(Date date, Date forMonth) {
        return date.compareTo(DateUtils.addDays(DateUtils.addMonths(forMonth, -1), -1)) <= 0;
    }
    


    Es war notwendig, die Abfolge der Änderungen einiger Parameter zu speichern. Dazu habe ich die History-Schnittstelle zum Speichern eines solchen Parameters und der Erweiterung SortedMap geschrieben So richten Sie Inhalte nach Änderungen aus:

    Implementierung
    interface History {
        val begin: Date
        var end: Date?
        fun historyOf(): T
        fun containsMonth(date: Date): Boolean {
            val month = date.truncateToMonth()
            return begin <= month && (end == null || month < end)
        }
    }
    fun  SortedMap>.fix() {
        removeRepeatedNeighbors()
        val navigableMap = TreeMap>(this)
        values.forEach { it.end = navigableMap.higherEntry(it.begin)?.value?.begin }
    }
    private fun  SortedMap>.removeRepeatedNeighbors() {
        var previousHistory: T? = null
        for (history in values.toList()) {
            if (history.historyOf() == previousHistory) {
                remove(history.begin)
            } else {
                previousHistory = history.historyOf()
            }
        }
    }
    //usage:
    fun setGroup(from: Date, group: ClassGroup) {
        val history = GroupHistory(
                this, group, from.truncateToMonth(), null
        )
        groupsHistory[history.begin] = history
        groupsHistory.fix()
        this.group = groupsHistory.getValue(groupsHistory.lastKey()).group
    }
    


    Abholvorgänge


    Beispiel 1
    Kotlin

    val apprentices: List = apprenticeRepository.findAll()
               .map(::ApprenticeDTO)
               .sortedWith(compareBy({ it.lastName }, { it.firstName }))
    

    Java

    List apprentices = StreamSupport.stream(
                       apprenticeRepository.findAll().spliterator(),
                       false
               ).map(ApprenticeDTO::new)
               .sorted(Comparator.comparing(ApprenticeDTO::getLastName)
                       .thenComparing(Comparator.comparing(ApprenticeDTO::getFirstName)))
               .collect(Collectors.toList());
    


    Beispiel 2
    Kotlin

    val attendances: Map, Int> attendances = attendanceRepository
              .byMonth(month)
              .groupBy { it.date to it.group.id }
              .mapValues { it.value.count() }
              .toMap()
    

    Java

    Map, Integer> attendances = attendanceRepository
               .byMonth(month)
               .stream()
               .collect(Collectors.groupingBy((it) -> new Pair<>(it.getDate(), it.getGroup().getId())))
               .entrySet()
               .stream()
               .map(entry -> new Pair<>(entry.getKey(), entry.getValue().size()))
               .collect(Collectors.toMap(Pair::getFirst, Pair::getSecond));
    


    Es gibt ein filterNot (Sie können häufig die Methodenreferenz verwenden), ein first und ein firstOrNull usw. Wenn etwas fehlt, fügen Sie Ihre Erweiterung hinzu (zum Beispiel habe ich die Summe für das BigDecimal-Blatt hinzugefügt).

    Faul


    Bei der Arbeit mit jsf ist dies nur eine Erlösung. Jsf zieht oft das gleiche Feld (und ich gehe dafür in die Datenbank), und im Fall einer sortierten Tabelle erwartet es, dass es genau dasselbe Objekt wie beim letzten Mal zurückgibt. Und am wichtigsten ist, faul ist sehr einfach zu entfernen / einfügen.

    Smart Cast + Siegelklasse


    Beispiel
    Kotlin

    fun rentForGroup(month: Date, group: ClassGroup): Int {
        val hall = group.hall
        val hallRent = hall.rent(month)
        return when (hallRent) {
            is Monthly -> hallRent.priceForMonth() / hall.groups(month).size
            is PercentOfRevenue -> hallRent.priceForMonth(creditForGroup(month, group))
            is Hourly -> hallRent.priceForLessons(group.monthLessons(month))
        }
    }
    

    Java

    public int rentForGroup(Date month, ClassGroup group) {
        Hall hall = group.getHall();
        Rent hallRent = hall.rent(month);
        if (hallRent instanceof Monthly) {
            return ((Monthly) hallRent).priceForMonth() / hall.groups(month).size();
        } else if (hallRent instanceof PercentOfRevenue) {
            return ((PercentOfRevenue) hallRent).priceForMonth(creditForGroup(month, group));
        } else if (hallRent instanceof Hourly) {
            return ((Hourly) hallRent).priceForLessons(group.monthLessons(month));
        } else {
            throw new UnsupportedOperationException();
        }
    }
    


    Inline-Funktionen


    In Java ist dies einfach unmöglich (es sei denn, Sie fügen der Klasse einen expliziten Parameter hinzu).
    inline fun  assertFail(expression: () -> Unit) {
        try {
            expression()
            Assert.fail("expression must fail with ${E::class.qualifiedName}")
        } catch (e: Throwable) {
            if (e !is E) {
                throw e
            }
        }
    }
    @Test fun greenTest() {
        assertFail {
            arrayOf(1, 2)[3]
        }
    }
    

    inline fun  CrudRepository.find(id: ID): T {
        return findOne(id) ?: throw ObjectNotFound(id, T::class.qualifiedName)
    }
    


    String-Literale.


    Der Unterschied ist gering, aber die Wahrscheinlichkeit, einen Fehler zu machen, ist viel geringer. Sie können auch ohne Kopfschmerzen aus dem Internet kopieren und einfügen.

    reguläre Ausdrücke
    Kotlin

    val email = """^([_A-Za-z0-9-+]+(\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\.[A-Za-z0-9]+)*(\.[A-Za-z]{2,}))?$"""
    

    Java

    String email = "^([_A-Za-z0-9-+]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,}))?$"
    


    Außerdem sehr schöne Muster .

    Nachgeschmack


    Ich habe nur ein Projekt auf Kotlin, außer kleinen Handwerken. So kann ich mit Sicherheit eines sagen: Kotlin-, Spring- und DDD-Elemente unterstützen sich perfekt. Wenn Sie in Kotlin wie in Java schreiben, fühlen Sie nur syntaktischen Zucker (was schon nett ist), aber wenn Sie die klassischen Bohnen aufgeben, in die jeder etwas einfügen kann (was bedeutet, dass es praktisch keine Einschränkungen für den Staat gibt), dann wird Kotlin blühen.

    UPD
    Nachgeschmack von Kotlin, Teil 1
    Nachgeschmack von Kotlin, Teil 3. Coroutinen - Prozessorzeit teilen

    Jetzt auch beliebt: