Введение
В преддверии выхода языка 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.
Однако, стоит оговориться, что если вы на текущей момент счастливы со scala, с ее «сложностью» и временем компиляции, тогда вам скорее всего не нужен будет kotlin, для всех остальных читать дальше:
Для тех кто в танке впервые слышит о языке, ниже несколько примеров с официального сайта:
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]}!") }
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 пока реализован не очень удачно. Но тут авторы языка обещают в ближайшее время исправить ситуацию. А пока можно либо применять имеющееся решение, либо воспользоваться расширением языка:
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 можно ждать их появление в самом ближайшем будущем.
Перейдем к примеру, в котором будет продемонстрировано небольшое 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 на следующее:
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:
server.port = 8080
Создаем файл Application.kt в папке src/main/kotlin/test.kotlin.spring.project. В нем будут основные настройки для запуска spring boot:
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) } } }
mapperForKotlinTypes нужен для того чтобы подключить к jackson mapping для kotlin. Получается похожий аналог симбиоза scala и argonaut.
@Bean open fun mapperForKotlinTypes(): MappingJackson2HttpMessageConverter { return MappingJackson2HttpMessageConverter().apply { objectMapper = jacksonMapper } }
Также необходимо будет создать файл с настройками методов rest сервиса. Будет несколько методов:
— метод будет выдавать AckResponse на введенные с запроса данные об имени и фамилии.
— метод, на вход поступает массив строк, из которого выбирается наименьшая строка по длине, которая потом разбивается по ‘_’, сортируется и собирается в строку уже с символом ‘,’ (демонстрирует возможности языка)
Создаем файл ServiceController.kt в папке src/main/kotlin/test.kotlin.spring.project.
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.
Спасибо всем за внимание.
ссылка на оригинал статьи http://habrahabr.ru/post/274997/
Добавить комментарий