Разработчик
0,0
рейтинг
13 января в 10:47

Разработка → Пишем простой RESTful сервис на kotlin и spring boot из песочницы

image

Введение


В преддверии выхода языка Kotlin из beta, хочется поделиться своим впечатлением от его использования.

Kotlin — это новый прекрасный язык от JetBrains (разработчиков IntelliJ Idea) для JVM, Android и браузера, который на первый взгляд выглядит как улучшенная java (или как упрощенная scala). Но это только на первый взгляд, язык не только впитал в себя многие интересные решения от других языков, но и представляет свои оригинальные:

— optional от swift, nullsafe в kotlin
— case классы из scala, data class в kotlin
— замена implicit методам из scala, extension функций
делегаты
null safely
smart cast
— и многое другое, подробнее можно посмотреть на официальном сайте kotlinlang.

Для тех кто знаком с java или scala, будет интересно сравнение kotlin & java, kotlin & scala.

Авторы языка стремятся добиться двух задач:
— сделать скорость компиляции сравнимой с java
— язык должен быть достаточно выразительным, и при этом быть простым насколько возможно
Поэтому, стоит оговориться, что если вы на текущей момент счастливы со scala, с ее «сложностью» и временем компиляции, тогда вам скорее всего не нужен будет kotlin, для всех остальных читать дальше:

Для тех кто в танке впервые слышит о языке, ниже несколько примеров с официального сайта:

Hello world
package hello

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


Чтение аргументов
fun main(args: Array<String>) {
   if (args.size() == 0) {
      println("Provide a name")
      return
   }
   println("Hello, ${args[0]}!")
}


hello world c ООП
class Greeter(val name: String) { 
   fun greet() { 
      println("Hello, $name")
   }
}

fun main(args: Array<String>) {
   Greeter(args[0]).greet()
}  


Из личного опыта применения kotlin особо хочется отметить несколько преимуществ языка:

— первое это конечно простоту взаимодействия с java. Все типы и коллекции из java преобразовываются в аналогичные из kotlin, и наоборот. Это особенно радует после всей той «анархии», которая творится в scala (да есть scala.collection.JavaConversions._ и scala.collection.JavaConverters._, но все же это не сравниться с полностью прозрачной конвертацией типов);
— также не может не радовать отличная поддержка от студии Intellij Idea, хоть язык и находится в Beta 4, уже на текущий момент плагин для студии позволяет комфортно работать;
— а для любителей implicit методов из scala, kotlin преподносит очень удобное решение в виде extension функций;
— помимо всего прочего разработчики языка ставят своей целью добиться времени компиляции сравнимой с java (привет scala), за что им только хочется пожать руки! Это особенно сильно радует после долгой работы в scala, когда редактирование одной строчки в достаточно небольшом файле компилируется с той же скоростью что и небольшой проект на java;
inline функции — отличное нововведение. С их помощью можно, например, расширить текущие возможности языка, или в некоторых ситуациях добиться повышения производительности;
— удобные функции стандартной библиотеки.
— удобные лямбды, в отличие от той же java 8. Очень похожи на реализацию из scala.

Тем не менее у языка есть и свои недостатки:

— не хватает pattern matching из scala, но в некоторых ситуациях спасает smart cast и Destructuring Declarations, в других же приходится выкручиваться другими средствами. Отсутствие pattern matching в целом понятно, разработчики стараются добиться максимального приближения к времени компиляции java, но его наличие позволило бы существенно упростить написание некоторых приложений, так что довольствуемся тем что есть;
try with resource пока реализован не очень удачно. Но тут авторы языка обещают в ближайшее время исправить ситуацию. А пока можно либо применять имеющееся решение, либо воспользоваться расширением языка:

try-with-resources
internal class ResourceHolder : AutoCloseable {
    val resources = ArrayList<AutoCloseable>()

    fun <T : AutoCloseable> T.autoClose(): T {
        resources.add(this)
        return this
    }

    override fun close() {
        resources.reverse()
        resources.forEach {
            try {
                it.close()
            } catch (e: Throwable) {
                e.printStackTrace()
            }
        }
    }
}

inline internal fun <R> using(block: ResourceHolder.() -> R): R {
    val holder = ResourceHolder()
    try {
        return holder.block()
    } finally {
        holder.close()
    }
}


Пример использования
fun copy(from: Path, to: Path) {
    using {
        val input = Files.newInputStream(from).autoClose()
        val output = Files.newOutputStream(to).autoClose()
        input.copyTo(output)
    }
}


— пока нет async и yield, но по словам авторов, после релиза 1.0 можно ждать их появление в самом ближайшем будущем.

Update В комментариях наибольший интерес вызывает сравнение со scala, и в частности преимущество kotlin:
Преимущества kotlin по сравнению со scala
— взаимодействие с java и обратно. На kotlin вообще можно забыть про то что ты вызываешь что-то из java, все предельно прозрачно и удобно. Тоже самое наоборот. Из java также удобно работать с kotlin. Тут даже не только сходство типов помогает, сколько сама ориентированность kotlin на прозрачную работу с java. Проще всего это продемонстрировать на примерах:
Примеры
1. Использование лямбд вместо анонимных классов (в scala правда тоже скоро это может появиться, но пока такой возможности нет).
Код в java:
ExecutorService executor = Executors.newFixedThreadPool(1);
    executor.execute(System.out::println);


В scala же уже не получится такое провернуть. А вот с kotlin проблем нет:
val executor = Executors.newFixedThreadPool(1)
    executor.execute { println() }


2. Есть тот же try-with-resources (да, можно сделать extension к языку, но из коробки этого нет, а значит придется тянуть решение из одного проекта в другой)
Это по крайней мере что первое приходит в голову

— отсутствие implicit. Сами по себе implicit конечно мощное решение, но без студии разобраться в чужом коде что к чем, почти нереально. Особенно если авторы «сильно увлеклись». А для добавления методов kotlin позволяет писать extension, которые на практике выглядят гораздо удобнее аналогичных решений в scala. Пример:
Scala:
implicit class RichInt(val x: Int) extends AnyVal {
  def square: Int = x * x
}
object App  {
   def print(): Unit = {
      val two = 2
      println(s"The square of 2 is ${two.square}")
  }
}

Kotlin:
fun Int.richInt(): Int = this * this

object App {
    fun print(): Unit {
        val two = 2
        println("The square of 2 is ${two.richInt()}")
    }
}


— nullable типы вместо Option.

— удобные функции стандартной библиотеки

— ну и конечно же скорость компиляции. На практике действительно все очень быстро компилируется, особенно по сравнению со scala.

— результирующий jar меньше аналогичного в scala.

— и что немаловажно, JetBrains обещают полную обратную совместимость (привет еще раз scala c либами под 2.10, 2.11, 2.12 и т.д.)


Перейдем к примеру, в котором будет продемонстрировано небольшое RESTful приложение на spring boot, со сборкой через gradle.

Настройка студии


Для работы необходимо поставить IntelliJ Idea Community (но можно использовать и Eclipse, под нее также есть плагин), в которой после установки обновить плагин kotlin. Обновить его необходимо вручную, через settings -> plugin, даже если вы перед этим выбрали обновление плагина через всплывающее окно (по крайней мере на данный момент, пока язык в beta).

Также лучше поставить локальный gradle, и прописать его в настройках в студии (settings -> build, execution, deployment -> gradle -> user local gradle distribution. После чего указать путь к gradle в gradle home).

Настройка проекта


Создаем проект gradle kotlin (new project -> gradle -> kotlin) и изменяем содержимое build.gradle на следующее:

Содержимое build.gradle
buildscript {
    ext.kotlin_version = '1.0.0-beta-4584'
    repositories {
        mavenCentral()
        maven { url "http://repo.spring.io/snapshot" }
        maven { url "http://repo.spring.io/milestone" }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.3.0.RELEASE")
        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
    }
}

apply plugin: 'idea'
apply plugin: 'spring-boot'
apply plugin: 'kotlin'

jar {
    baseName = 'test-spring-kotlin-project'
    version = '0.1.0'
}

repositories {
    mavenCentral()
    maven { url "http://repo.spring.io/snapshot" }
    maven { url "http://repo.spring.io/milestone" }

    maven { url "http://10.10.10.67:8081/nexus/content/groups/public" }
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web:1.3.0.RELEASE")
    compile("org.springframework:spring-jdbc:4.2.3.RELEASE")
    compile("com.fasterxml.jackson.module:jackson-module-kotlin:2.6.4")

    compile("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version")
}


Создаем файл application.properties в папке src/main/resources, в котором укажем порт для запуска spring boot:

application.properties
server.port = 8080


Создаем файл Application.kt в папке src/main/kotlin/test.kotlin.spring.project. В нем будут основные настройки для запуска spring boot:

Application.kt
package test.kotlin.spring.project

import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
import org.springframework.boot.builder.SpringApplicationBuilder
import org.springframework.boot.context.web.SpringBootServletInitializer
import org.springframework.context.annotation.Bean
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter

@SpringBootApplication
@EnableAutoConfiguration(exclude = arrayOf(DataSourceAutoConfiguration::class))
open class Application : SpringBootServletInitializer() {

    @Bean
    open fun mapperForKotlinTypes(): MappingJackson2HttpMessageConverter {
        return MappingJackson2HttpMessageConverter().apply { objectMapper = jacksonMapper }
    }

    override fun configure(application: SpringApplicationBuilder): SpringApplicationBuilder =
            application.sources(Application::class.java)

    companion object {

        val jacksonMapper = ObjectMapper().registerKotlinModule()
                .setSerializationInclusion(JsonInclude.Include.NON_ABSENT)
                .enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)

        @Throws(Exception::class)
        @JvmStatic fun main(args: Array<String>) {
            println("starting application...")
            SpringApplication.run(Application::class.java, *args)
        }
    }
}



Также необходимо будет создать файл с настройками методов rest сервиса. Будет несколько методов:

— метод будет выдавать AckResponse на введенные с запроса данные об имени и фамилии.
— метод, на вход поступает массив строк, из которого выбирается наименьшая строка по длине, которая потом разбивается по '_', сортируется и собирается в строку уже с символом ',' (демонстрирует возможности языка)

Создаем файл ServiceController.kt в папке src/main/kotlin/test.kotlin.spring.project.

ServiceController.kt
package test.kotlin.spring.project

import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestMethod
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController

data class AckResponse(val status: Boolean, val result: String, val message: String? = null)

@RestController
class ServiceController {
    @RequestMapping(
            path = arrayOf("/request"),
            method = arrayOf(RequestMethod.GET),
            produces = arrayOf(MediaType.APPLICATION_JSON_UTF8_VALUE))
    fun nameRequest(
            @RequestParam(value = "name") name: String,
            @RequestParam(value = "surname", required = false) surname: String?): AckResponse {
        return if (surname == null)
            AckResponse(status = true, result = "Hi $name", message = "surname is empty")
        else
            AckResponse(status = true, result = "Hi $surname,$name")
    }

    @RequestMapping(
            path = arrayOf("/sort_request"),
            method = arrayOf(RequestMethod.GET),
            produces = arrayOf(MediaType.APPLICATION_JSON_UTF8_VALUE))
    fun findMinimum(
            @RequestParam(value = "values") values: Array<String>): AckResponse {
        println("values:")
        values.forEach { println(it) }

        val minValue = values.apply { sortBy { it.length } }
            .firstOrNull()
            ?.split("_")
            ?.sorted()
            ?.joinToString(",") ?: ""

        return AckResponse(status = true, result = minValue)
    }
}



Запуск и проверка работы


Запускаем приложение из Application.kt. В случае успешного запуска в логе будет что-то вроде:

Логи приложения
starting application...

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.3.0.RELEASE)

2016-01-12 12:47:48.242  INFO 88 --- [           main] t.k.s.project.Application$Companion      : Starting Application.Companion on Lenovo-PC with PID 88 (D:\IDA_Projects\test\build\classes\main started by admin in D:\IDA_Projects\test)
2016-01-12 12:47:48.247  INFO 88 --- [           main] t.k.s.project.Application$Companion      : No profiles are active
2016-01-12 12:47:48.413  INFO 88 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@dbf57b3: startup date [Tue Jan 12 12:47:48 MSK 2016]; root of context hierarchy
2016-01-12 12:47:50.522  INFO 88 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Overriding bean definition for bean 'beanNameViewResolver' with a different definition: replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class]]
2016-01-12 12:47:51.066  INFO 88 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [class org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$ede1977c] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2016-01-12 12:47:51.902  INFO 88 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2016-01-12 12:47:51.930  INFO 88 --- [           main] o.apache.catalina.core.StandardService   : Starting service Tomcat
2016-01-12 12:47:51.937  INFO 88 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.0.28
2016-01-12 12:47:52.095  INFO 88 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2016-01-12 12:47:52.095  INFO 88 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 3688 ms
2016-01-12 12:47:52.546  INFO 88 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean        : Mapping servlet: 'dispatcherServlet' to [/]
2016-01-12 12:47:52.556  INFO 88 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'characterEncodingFilter' to: [/*]
2016-01-12 12:47:52.557  INFO 88 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2016-01-12 12:47:52.559  INFO 88 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2016-01-12 12:47:52.559  INFO 88 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'requestContextFilter' to: [/*]
2016-01-12 12:47:52.985  INFO 88 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@dbf57b3: startup date [Tue Jan 12 12:47:48 MSK 2016]; root of context hierarchy
2016-01-12 12:47:53.089  INFO 88 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/request],methods=[GET],produces=[application/json;charset=UTF-8]}" onto public final test.kotlin.spring.project.AckResponse test.kotlin.spring.project.ServiceController.pullUpdate(java.lang.String,java.lang.String)
2016-01-12 12:47:53.094  INFO 88 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2016-01-12 12:47:53.094  INFO 88 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest)
2016-01-12 12:47:53.138  INFO 88 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-01-12 12:47:53.139  INFO 88 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-01-12 12:47:53.195  INFO 88 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-01-12 12:47:53.512  INFO 88 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2016-01-12 12:47:53.612  INFO 88 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2016-01-12 12:47:53.620  INFO 88 --- [           main] t.k.s.project.Application$Companion      : Started Application.Companion in 6.076 seconds (JVM running for 7.177)
2016-01-12 12:47:57.874  INFO 88 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2016-01-12 12:47:57.874  INFO 88 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2016-01-12 12:47:57.897  INFO 88 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 23 ms


После успешного запуска, пробуем открыть страницу Запрос с именем. Ответ должен выглядеть следующем образом:
{
  status: true,
  result: "Hi Kris",
  message: "surname is empty"
}

И Запрос с именем и фамилией, тогда ответ будет немного другой:

{
  status: true,
  result: "Hi Eagle, Kris"
}

Вызов для проверки сортировки данных: Сортировка. В результате должно быть:

{
  status: true,
  result: "1,3,value,virst"
}

Тот же вызов, но с пустым массивом: Вызов
{
  status: true,
  result: ""
}

При необходимости можно собрать весь проект в один runnable jar, командой: gradle build. В результате проект будет собран в один архив, содержащий все зависимости без распаковки. При таком подходе существенно повышается время сборки проекта, по сравнению с тем же assemble, когда проект собирается в один архив с распаковкой всех зависимостей.

Заключение


В заключении хочется отметить что kotlin оказался весьма удобным языком для работы над любым проектом, где используется java, в качестве ее замены. Экосистема языка пока не такая обширная как та же scala, но уже сейчас можно использовать его в том же big data, где есть java api. К тому же из kotlin очень просто взаимодействовать с java, так что все что есть в java, можно использовать и в kotlin. К тому же из студии есть возможность легкой конвертации java файлов в аналогичные на kotlin (правда нужно будет немного руками подправить файл после конвертации). JetBrains проделали замечательную работу на пути создания идеального языка на смену java и scala. И надеюсь в будущем тенденция в сторону использования kotlin будет только расти.

Исходники доступны на github.

Спасибо всем за внимание.
Евгений @nerumb
карма
0,0
рейтинг 0,0
Разработчик
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (31)

  • 0
    Недавно публиковал статью, использовал там такое решение для Closeable:
    infix fun Closeable.and(other:Closeable):Closeable = Closeable {
        this@and.close()
        other.close()
    }
    
    fun incode() {
        val firstResource = create()
        val secondResource = create()
        (firstResource and secondResource).use {
           // do something
        }
    }
    

    Где use это экстеншн из стандартной библиотеки котлина. Для AutoCloseable пока приходится руками, да. А решение вроде ResourceHolder я использовал в другом проекте, для автоматического возвращения значений в пул — в этом смысле котлин дает большие возможности для собственных решений.
    • 0
      Для AutoCloseable пока приходится руками, да

      Поэтому вариант с ResourceHolder пока единственный нормальный. А так вся надежда на то, что JetBrains в ближайшее время исправят ситуацию.
  • 0
    Да и конвертр в json бут сконфигурировал мне сам, чем в этом смысле котлиновские классы хуже джавовских?
    • 0
      Да и конвертр в json бут сконфигурировал мне сам, чем в этом смысле котлиновские классы хуже джавовских?

      Они не хуже, просто из коробки spring boot c data и nullable типами хуже работает, поэтому приходится делать подмену маппера
      • 0
        Можете рассказать немного подробнее?
        • 0
          Похоже был не прав был со spring boot. Из коробки все нормально работает и с data class, и с обычными классами, да и с nullable типами. Сейчас проверил на практике.
          Но в любом случае неплохо иметь возможность кастомной настройки маппера:
          val jacksonMapper = ObjectMapper().registerKotlinModule()
                          .setSerializationInclusion(JsonInclude.Include.NON_ABSENT)
                          .enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)
          
          • 0
            Просто подумал, что вдруг я что-то упустил. А возможность кастомизации — дело безусловно благое.
  • +5
    fun main(args: Array<String>) {
       println("Hello World!")
    }
    

    заменяем «fun» на «def» и "< >" на "[ ]"

    def  main(args: Array[String]) {
      println("Hello World!")
    }
    

    и о чудо, Scala ты ли это!? )

    По мне так Kotlin это что-то между Java и Scala — т.е. и не такое многословный как Java, но и не такой «навороченный» как Scala. Имхо, но лучше бы JetBrains контрибьютили в Scala, чем пилили очередной язык под JVM…
    • 0
      По мне так Kotlin это что-то между Java и Scala

      Согласен, первое впечатление такое, но все же не совсем так, я бы сказал что более точно ему подходит «продуманная scala».

      Те же nullable типы, на практике удобнее чем Option, и к тому же сохраняется полная совместимость с Java.
      Или smart cast. От java ведь все равно никуда не деться, и когда что-нибудь вызываешь из нее, на выходе приходится проверять на null (ну или оборачивать в Option в scala). А kotlin позволяет всего лишь проверить на null, и дальше в коде уже сохраняется информация о том что эта переменная не может быть null. Не нужно никаких Option, и код становится понятнее.

      Вообще советую поиграться с kotlin, он в живую выглядит еще лучше
      • 0
        В чем «продуманность» относительно скалы? Про пример с nullable понятно.
        • 0
          Тут можно много говорить на эту тему. Лучше всего попробовать самим kotlin на практике :)

          Но если коротко, что лучше по сравнению со scala:
          — взаимодействие с java и обратно. На kotlin вообще можно забыть про то что ты вызываешь что-то из java, все предельно прозрачно и удобно. Тоже самое наоборот. Из java также удобно работать с kotlin. Тут даже не только сходство типов помогает, сколько сама ориентированность kotlin на прозрачную работу с java. Проще всего это продемонстрировать на примерах:
          Примеры
          1. Использование лямбд вместо анонимных классов (в scala правда тоже скоро это может появиться, но пока такой возможности нет).
          Код в java:
              ExecutorService executor = Executors.newFixedThreadPool(1);
              executor.execute(System.out::println);
              

          В scala же уже не получится такое провернуть. А вот с kotlin проблем нет:
              val executor = Executors.newFixedThreadPool(1)
              executor.execute { println() }
              

          2. Есть тот же try-with-resources (да, можно сделать extension к языку, но из коробки этого нет, а значит придется тянуть решение из одного проекта в другой)
          Это по крайней мере что первое приходит в голову


          — отсутствие implicit. Сами по себе implicit конечно мощное решение, но без студии разобраться в чужом коде что к чем, почти нереально. Особенно если авторы «сильно увлеклись». А для добавления методов kotlin позволяет писать extension, которые на практике выглядят гораздо удобнее аналогичных решений в scala. Пример:
          Scala:
          object MyExtensions {
            implicit def richInt(i: Int) = new {
              def square = i * i
            }
          }
          
          
          object App  {
            import MyExtensions._
          
            def print(): Unit = {
                val two = 2
                println(s"The square of 2 is ${two.square}")
            }
          }
          

          Kotlin:
          fun Int.richInt(): Int = this * this
          
          object App {
              fun print(): Unit {
                  val two = 2
                  println("The square of 2 is ${two.richInt()}")
              }
          }
          


          — есть inline функции. Очень мощный функционал. С его помощью можно, например, убрать оверхед для лямбд (иногда это критично).

          — удобные функции стандартной библиотеки

          — ну и конечно же скорость компиляции. На практике действительно все очень быстро компилируется, особенно по сравнению со scala.

          И опять же еще раз повторюсь, лучше самим пощупать Kotlin.
          • +3
            Вообще в scala давно уже добавлены implicit class'ы, которые позволяют писать более аккуратный код:
            implicit class RichInt(val x: Int) extends AnyVal {
              def square: Int = x * x
            }
            object App  {
               def print(): Unit = {
                  val two = 2
                  println(s"The square of 2 is ${two.square}")
              }
            }
            
            • +1
              Тут мой недочет, согласен. Но все же версия в kotlin выглядит лучше.
              • 0
                Но там разница в считанные символы, что никак нельзя назвать каким-то существенным улучшением. К тому же, чаще всего, добавляют сразу несколько функций, и в этом случае вараинт scala уже выглядит лучше, чем в kotlin.
                • 0
                  И даже если учитывать несколько функций, вариант из kotlin, имхо, будет выглядеть логичней и лучше.
                  • +1
                    Скажем так, при множестве функций вариант из scala в итоге станет короче, это то что объективно можно померить. Но по большому счету синтаксис здесь скорее дело вкуса. Однако надо учитывать, что в scala это не просто функция, а класс, в котором могут быть в приватные методы и даже переменные, хотя это уже отдельный случай, т.к. будет производится выделение памяти.
          • +3
            По остальным пунктам, которые вы приводите к качестве плюсов:
            — inline функции: в scala для этого есть аннотация @inline
            — удобные функции стандартной библиотеки: вполне присутствует в scala
            — скорость компиляции: здесь конечно scala медленне почти всех существующих языков, но инкрементальный компилятор, который будет перекомпилировать отдельные функции (или даже блоки кода, не помню точно), в значительной степени уберет эту проблему.

            Вообще хотелось развернутого сравнения от того, кто разбирается в обоих языках, потому что сейчас совершенно не понятно, зачем kotlin вообще нужен.
            • +1
              С inline функциями это я погорячился, но в остальном не согласен.

              На счет удобных функций стандартной библиотеки, можете привести аналоги в scala для apply, with, let, run?

              По поводу инкрементальной компиляции, вы часто в проекте меняете только одну функцию?

              Основными преимуществами kotlin, имхо, является следующее:
              — nullable типы
              — smart cast
              — null safely
              — быстрая компиляция
              — отсутствие implicit
              — лучшее взаимодействие с java
              — отличная поддержка от студии
              — готовые решения для работы в jvm, android, трансляции в js
              стабильная обратная совместимость

              Вообще темой публикации не было сравнение kotlin и scala, и вообще это тема достойна отдельной статьи. И в самом начале публикации указано, что если вы счастливы со scala, но вам не нужен kotlin.
              • +3
                Согласен с тем, что сравнение двух языков — это скорее для отдельной статьи. Но мы здесь это обсуждаем потому, что не обсуждать же REST-сервис? Понятно для чего статья была написана. К сожалению полноценно поспорить с вами не могу, т.к. kotlin не знаю и именно по этому считаю статью с честным сравнением и примерами кода наиболее интересным вариантом знакомства с языком. Однако попробую написать по поводу известных вещей.

                — По поводу инкрементальной компиляции, вы часто в проекте меняете только одну функцию?
                По идее инкрементальная компиляция может работать параллельно с написанием кода, сводя до минимума ожидание. Но даже если она отрабатывает по всем изменениям, то это обычно значительно меньше целого проекта. Т.к. она даже не будет целые файлы перекомпилировать.

                На счет работы с null. В scala достаточно удобно использовать Option, особенно в сочетании с for, хотя это не 100% защита от null. Однако не так давно было обсуждение, а не ввести ли в scala аналогичные nullable типы, так что возможно скоро здесь не будет различий.

                — отсутствие implicit
                Не сказал бы что это прямо плюс. Считаю, что при аккуратном использовании implicit дает больше преимуществ, чем недостатков.

                — лучшее взаимодействие с java
                Это палка о двух концах. Если в kotlin такая хорошая интероперабильность с java, то значит, что ряд косяков за java перенесся в kotlin. Кроме того, если где-то надо предоставить какое-то очень навороченно api из scala, то можно и несколько оберток написать, как например play делает. Но этот сценарий встречается гораздо реже, чем наоборот. А наоборот почти никаких проблем нет, а с выходом 2.12 будет ещё меньше.

                — отличная поддержка от студии
                Это не плюс kotlin'а, это — минус jetbrains'у )

                — готовые решения для работы в jvm, android, трансляции в js
                Для js в scala точно есть решение — scalajs. На счет android не знаю. Возможно здесь действительно есть объектичное преимущество kotlin.

                — стабильная обратная совместимость
                В scala совместимость на уровне исходных кодов до версии 3. Но сейчас идет активная работа над тем, чтобы компилировать исходники в ast-деревья и распространять пакеты с ними, а не с class-файлами. Тогда не будет зависимости от версии.
            • 0
              Некоторые моменты есть у них на сайте. Если субъективно, мне котлин нравится тем, что он простой в чтении, написании и изучении, при этом в нем есть всё, что мне хотелось бы иметь в языке. Конечно, котлин более бедный в плане возможностей язык, чем скала, но в этом же и его прелесть — видно, что при создании пытались сохранить простоту при наличии всех необходимых фич. Почти любой Java-программист без труда может писать на котлине, чего не всегда можно сказать о скале. Скала замечательный язык, котлин тоже, просто преследующий немного другие цели, сравнение по колчеству фич здесь очевидно не уместно.
        • +1
          Еще забыл добавить что результирующий jar меньше аналогичного в scala.
          Например тут приводятся такие цифры:
          -kotlin 2.2мб
          -scala 5.5мб
          Для android это является критичным фактором

          И еще что немаловажно, JetBrains обещают полную обратную совместимость (привет еще раз scala c либами под 2.10, 2.11, 2.12 и т.д.)
          • 0
            Весомые аргументы. Но я все же возлагаю на детище Одерки и его команды наибольшие надежды, чем на Kotlin(хотя Jetbrains конечно большой респект, думаю их труды тоже не пропадут даром). Особенно учитывая последний пост Одерски, в котором он упомянул, что в этом году он приложит усилия на упрощение Scala. Да и про Dotty не стоит забывать.
            • 0
              По поводу упрощения scala я бы особо не переживал. Язык может быть упрощен на концептуальном уровне, без особого ограничения возможностей программиста. Например, Clojure относительно простой язык, но все думаю прекрасно представляют, каких размеров горы данный язык позволяет ворочать.
              • +1
                Я не переживаю, я наоборот воодушевлен тем, что люди пилящие Scala очень тщательно следят за комьюнити/трендами/критикой и стараются вовремя реагировать. У команды Scala есть опыт и понимание того что и как нужно делать, крупные продукты на их стеке и амбициозные планы. А вот что есть у Kotlin команды кроме того, что они знают как делать хорошие инструменты для разработчиков — я не знаю. Вся эта вкусовщина и духе nullable лучше Option или у нас нет implicit'ов и это плюс — чистой воды маркетинг…
                Я вот знаю, что выйдет Scala 3.0 — и это будет прогресс и куча улучшений, еще большее комьюнити и увеличение спроса. А выйдет Kotlin 2.0 и будет ли на нем писать кто-то чуть больше чем люди уставшие от геттеров-сеттеров в Java — не ясно.
          • +2
            Полная обратная совместимость — весьма тревожный звоночек. У котлина есть все шансы породить свой java.util.Date и жить с этим наследством на протяжении всего жизненного цикла языка. Обратная совместимость позволяет поддерживать энтропию сохранять язык в чистоте. Только представьте законсервировавшуюся скалу. И ряд неудачных решений, которые в ближайшем времени будут устранены (view bounds, xml...)
            В случае, например со scala, (передаю так же привет всем ее библиотекам и их множественным версиям), смена версии не является большой проблемой. У меня были крупные проекты, внутри которых я проводил миграцию 2.9 -> 2.10, 2.10 -> 2.11 и проблем это не совершенно не вызвало.
            Запугивания миграцией, я зачастую слышу от людей которые эту самую миграцию не проводили.
            • 0
              Совсем вечной она конечно не будет. Разработчики лишь обещают, что обратная совместимость будет сохраняться как можно дольше. Они и сами говорят, что может наступить момент в далеком будущем, когда нужно будет что-то капитально изменить

              В случае, например со scala, (передаю так же привет всем ее библиотекам и их множественным версиям), смена версии не является большой проблемой. У меня были крупные проекты, внутри которых я проводил миграцию 2.9 -> 2.10, 2.10 -> 2.11 и проблем это не совершенно не вызвало.

              А если рассмотреть проект, у которого большое количество стороних библиотек на scala? Тут как раз и может получиться ситуация, что одна либа есть под новую версию scala, а других нет.

              Запугивания миграцией, я зачастую слышу от людей которые эту самую миграцию не проводили.

              В самой миграции проблем нет, сложность есть лишь в долгой поддержки проекта, особенно с большим количеством зависимостей.
    • +7
      def  main(args: Array[String]) {
        println("Hello World!")
      }
      

      заменяем ещё несколько символов
      (defun main (args)
        (print "Hello World!")
      )
      

      и о чудо, lisp, ты ли это?
  • –1
    Не нравится мне котлин. Ну не нравится.
    Совместимость с явой? Ок. Что ж тогда синтаксис поменяли там, где и явавский не был плох? Типы переставили в конец, fun — вот это вот всё.
    null-safety, насколько я знаю, сделан приворачиванием сотен метаданных к имеющимся либам. За ними следить вообще-то надо, глазами, тоже как-то не ахти.
    дженерики с use-site variance еще в яве более чем отстойны, стоило ли пилить новый язык, чтоб наступить на те же грабли?
    Складывается ощущение, что jetbrains сознательно забивают на другие языки, чтоб продвигать свой замечательный котлин.
    Некоторые траблы явы просто не решаются без введения своей стандартной библиотеки, как с скале или цейлоне. Как следствие котлин мало что может поменять, чуть понавороченнее, чем xtend, но с синтаксисом, зачем-то сделанным более отличным от привычного.
    • +1
      null-safety, насколько я знаю, сделан приворачиванием сотен метаданных к имеющимся либам.

      Нет, null-safety внутри Котлина реализуется прикручиванием метаданных в скомпиленный jar, а всё что за пределами, решается с помощью flexible types.

      дженерики с use-site variance еще в яве более чем отстойны, стоило ли пилить новый язык, чтоб наступить на те же грабли?

      Полагаю, иначе нельзя, ведь надо бесшовно интегрироваться с Java, а она пишет метаинфу к class-файлы с прицелом на use-site variance. Кроме того, никто не мешает использовать declaration-site variance в Kotlin :-)

      Некоторые траблы явы просто не решаются без введения своей стандартной библиотеки, как с скале или цейлоне.

      Может и не решаются, но Jetbrains умудрились неожиданно хорошо решить ряд проблем без введения стандартной библиотеки, например, те же List/MutableList. Формально оно находится в пакете kotlin, но фактически компилится в старый добрый java.util.List и полностью с ним совместими при интеропе.
  • 0
    Отличный язык. Но, боюсь, как бы он не встал на одну полку с Ceylon, GoSu, Fantom, Xtend, etc… На мой взгляд основные просчеты:
    — Долго тянут с релизом. Язык был анонсирован несколько лет назад, и вау эффект уже изрядно поутих. Самое время его было выпустить до Java 8. Сейчас внимание программеров переключилось на джавовские лямбды и стримы, и мощь котлина в их глазах уже поугасла.
    — Недостаточная огласка в СМИ. Взять хотя бы Одерски, который пиарит свою скалу везде, где только возможно. Несколько статей на хабре не решат проблемы.
    — Нет своего стека технологий. Красивый язык оценят, но без сопутствующих технологий им пользоваться не будут. Вот Groovy некрасивый язык, а им пользуются (про Scala толерантно помалкиваю). Нужен стек типа typesafe (или, упаси господи, JEE) и фреймворки типа play. Кроме того, на первое время нужно определиться с нишей решаемых задач, напр. scala — параллельное программирование, kotlin — андроид, клиентские веб приложения (прощай, GWT), микросервисы. На одной экосистеме Java далеко не уедешь.
    — Success stories. Типа «Twitter признал свою ошибку со scala и переписал свой стек на kotlin-е» :)

    P.S. Персональное ощущение: пробовал Kotlin несколько раз. Писал несколько проектов. В итоге, за пару часов все переписывал на java и избавлялся от котлина в проекте, ибо отличия в структуре несущественны. Да, код короче, элегантнее, но IDE позволяет быстро писать и на Java.
    • +1
      — Долго тянут с релизом. Язык был анонсирован несколько лет назад, и вау эффект уже изрядно поутих. Самое время его было выпустить до Java 8. Сейчас внимание программеров переключилось на джавовские лямбды и стримы, и мощь котлина в их глазах уже поугасла.

      Не соглашусь с вами. Даже сейчас, после выхода java 8, kotlin выглядит намного лучше. Посмотрите тоже сравнение лямбд в java и kotlin. Да и помимо этого в языке собраны многие интересные решения, которые в совокупности позволяют сделать код более читабельным и понятным.

      — Недостаточная огласка в СМИ. Взять хотя бы Одерски, который пиарит свою скалу везде, где только возможно. Несколько статей на хабре не решат проблемы.

      В android kotlin уже завоевал сердца разработчиков :)
      А так релиз еще впереди, да и, как мне кажется, все кто интересуются продуктами JetBrains знакомы с Kotlin.

      — Нет своего стека технологий. Красивый язык оценят, но без сопутствующих технологий им пользоваться не будут. Вот Groovy некрасивый язык, а им пользуются (про Scala толерантно помалкиваю). Нужен стек типа typesafe (или, упаси господи, JEE) и фреймворки типа play. Кроме того, на первое время нужно определиться с нишей решаемых задач, напр. scala — параллельное программирование, kotlin — андроид, клиентские веб приложения (прощай, GWT), микросервисы. На одной экосистеме Java далеко не уедешь.

      Для java есть просто огромное количество библиотек, и да, на ней можно выехать на первое время. При большом желании можно даже использовать библиотеки из scala.

      — Success stories. Типа «Twitter признал свою ошибку со scala и переписал свой стек на kotlin-е» :)

      Посмотрите подробнее информацию на их сайте (Who's using Kotlin). Да и сами JetBrains у себя его используют.
      На kotlin уже сейчас разрабатываются приложения под android, тот же avito, например.
      Не забывайте, что язык еще находится в Beta, пока нельзя гарантировать полную обратную совместимость, стандартная библиотека еще допиливается. Вот после релиза и появятся «Success stories»

      Да, код короче, элегантнее, но IDE позволяет быстро писать и на Java.

      Если бы вы сказали что писали до этого на scala и kotlin вам не понравился, я бы еще понял. Но по сравнению с java у языка такое огромное количество улучшений, что обратный переход с kotlin на java выглядит весьма странно.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.