×

Java vs. Kotlin для Android. День 2: А может ну его, этот Kotlin?

Необходимо сделать лирическое отступление и констатировать факт, что со времени написания оригинальной серии статей по Kotlin и публикацией их на данном ресурсе, прошло уже достаточное время. Время достаточное для того чтобы: а) вышла обнова на Kotlin 1.1; б) наш любимый Гугль решил-таки начать нативно поддерживать Java 8 под Android. Более того, второе событие произошло непосредственно в день выхода первой части этой серии статей — вот прям как звезды сошлись на небе.

Не знаю, какое из событий меня больше порадовало, но в любом случае пришлось кой-чего переписывать...к примеру, вот эти самые строки. Безусловно, я рад за платформу и благодарен Гуглу за такой ответственный шаг, НО Kotlin стал еще вкуснее и приятнее взору, пальцам и мозгам. Для тех, кого цепанула вторая новость, просим милости сюда изучать, чем нам это грозит. А для тех, кто, так же как и я, пропустил событие 1-ого марта в Котляндии, рекомендую не дочитывать эту фразу, а сразу пропустить следующий абзац...

Вы все еще здесь? А может ну его, этот Kotlin, и на боковую, а?;) Ах, да, чуть не забыл — дабы сократить поток негодования на тему «ах, да как ты/вы мог(ли) вот это не упомянуть или вот это?!», сразу ответственно заявляю, что данный артефакт не является мануалом к языку ни в коем случае. Я всего лишь описываю то, что МНЕ понравилось больше всего, когда я пропустил через себя Kotlin. То, что вот я бы непосредственно практически каждый день с радостью использовал в трудовых буднях.

Так-с...на чем я там остановился в прошлый раз? Ах, да, по диагонали изучил классы и хотел посмотреть аспекты of Functional Programming. Но я думаю, что правильнее все-таки будет сначала на базовый синтаксис взглянуть и узнать, что там есть вкусного. Есть у меня предчувствие, что вечерночь не пройдет зря. Поехали...

Null Safety

Начнем, пожалуй, с такой штуки как nullability. В отличие от Java переменная в Kotlin не может содержать null, если компилятор об этом не знает. Т.е. объявляя переменную, нужно явным образом указать может она быть nullable или нет. Такое простое решение — на уровне системы типов заставить разработчика заранее подумать о том, как должна вести себя переменная, какие значения она может принимать. Все сделано для того, чтобы максимально исключить возможность появления NPE (Null Pointer Exception...хотя кому это я объясняю???) в коде, и особенно в рантайме. Но все-таки NPE могут возникнуть в таких случаях:

  • явный вызов throw NullPointerException()
  • ошибка во внешнем Java-коде
  • использование специфичного оператора !!
  • Как же это все работает? Возьмем к примеру следующий код:
var strNotNull : String = "Hello"
strNotNull = null //compilation error

Во второй строке мы получим ошибку при попытке компиляции. Для того, чтобы иметь возможность записать null в переменную, нам нужно сделать следующее:

var strNull : String? = "Hello"

Теперь все сработает. Кстати, знание того, что в переменной НИКОГДА не будет null избавляет нас от дурацких проверок каждый раз, когда, к примеру, нам нужно получить длину строки. Можно смело делать вот так:

val length = strNotNull.length

и нам за это ничего не будет. А что же делать в случае, когда у нас может быть null в переменной? Просто использовать safe-call operator — ?.

val length = strNull?.length

И в данном случае в переменной length будет сохранена либо длина строки, либо null, в зависимости от состояния strNull. И тип переменной будет автоматически приведен к Int?. Если же мы не хотим возвращать null, то можно использовать Elvis operator:

val length = strNull?.length ?: -1 //length is instance of Int, but not Int?

Можно построить целую цепочку таких safe-calls, к примеру:

message?.sender?.birthdate?.year

Если же нам нужно выполнить блок операций для случая, когда переменная точно не равна null, то можно использовать функцию let:

//say good-bye to if (message != null) {}
message?.let{
..
}

Отличнейшая альтернатива заезженным блокам if/else. Настоятельно рекомендуется к использованию.

Как по мне, то очень интересное и элегантное решение проблемы с NPE в рантайме. По факту большинство NPEs вылазит именно из-за того, что мы забываем проверить на null или думаем, что вот в этом самом месте нам никогда не придет null или даже оставляем это «на потом» в спешке. Хотел было засчитать еще одно очко в пользу Kotlin, но решил, что данное решение заслуживает двух.;)

Smart casts & type checks

Как бы так поделикатнее описать свои мысли о реализации of smart casts & type checks in Kotlin и при этом не сильно обидеть Java? Одним словом — НАКОНЕЦ-ТО!!! Забудьте об этих дурацких дублирующих друг друга операциях с приведением типов как в примере ниже:

if (obj instanceOf String) len = ((String) obj).length();

Каждый раз, когда я пишу подобный код, мне хочется плакать....Ну почему компилятор не может догадаться, что если я проверяю переменную на принадлежность к какому-то типу, то при выполнении этого условия я хочу использовать именно этот интерфейс?! Ведь из-за этих ограничений Java-компилятора рождаются кошмарные конструкции и блоки кода, на который без слез смотреть невозможно.

Но ребята в JetBrains похоже услышали мольбы разработчиков и реализовали систему of smart casts. Вышеуказанный код на Kotlin выглядит следующим образом:

if (obj is String) len = obj.length

А теперь представьте, что вам нужно выполнить несколько вызовов переменной, и посмотрите, как это красиво можно сделать с помощью функции with:

with(obj) {
    val oldLen = length // what happens here is obj.length() is being called
    val newLen = replace(" ", "").length //and obj.replace(“ “, “”).length() here 	
}

Последний раз такую вкусность я использовал в Pascal/Delphi, и это было чертовски удобно, за такими вещами скучаешь. Особенно часто я вспоминаю об этом, когда инициализирую какой-нибудь объект типа Paint, и надо сделать несколько вызовов подряд, чтобы задать шрифт, цвет, тип заливки, антиалиасинг и прочее.

Тем не менее нужно помнить, что automatic smart cast работает в том случае, если компилятор точно уверен, что между проверкой и использованием переменной она не поменяет значение.

Ну а как быть, если мы хотим привести переменную к какому-то типу? Kotlin предлагает на выбор две опции:

  • unsafe cast (небезопасныйое секс, пардон, приведение) с помощью infix operator as
  • safe cast с помощью infix operator as?

В случае первого при попытке привести null value к какому-то типу выкинет исключение. Второй же оператор просто вернет null в результирующую переменную. Я бы рекомендовал использовать именно его.

Что мы имеем в итоге? Красивый, элегантный, читабельный код, не перегруженный синтаксисом. Прямо вот не могу удержаться и накидываю еще 2 очка в пользу Kotlin. Прости, Java, сегодня, видимо, не твой счастливый день.

Type aliases (доступно в версии 1.1)

Раз уж мы поговорили про приведение типов, то целесообразным будет упомянуть возможность объявления альтернативных имен для существующих типов. Что-то подобное я встречал давно в Pascal. Но в Kotlin эта фича работает и для функций, что делает ее особенно интересной! Что конкретно нам это дает?

  • мы можем объявлять «свои типы данных», к примеру, сократить длинный женерик до короткого дружелюбного имени
    typealias BestHashMapEver = HashMap<String, Int>
    val myMap = BestHashMapEver()

    Да, знаю, имя тут вышло не совсем короткое, но вы меня поняли.;)

    typealias Str = String
    val myStr:Str = “Hello DOU!”
  • мы можем объявлять псевдонимы для функций и использовать их при описании параметров
    typealias BestFunctionEver = (Int, Int) -> Boolean
    fun myFun(f: BestFunctionEver) : f(10, 11)
    myFun{x, y -> x > y}
    val bestFun: BestFunctionEver = {x, y -> y > x}
    myFun(bestFun)

Следует заметить, что type alias не генерирует нового типа данных. На уровне компилятора он расценивается как тот самый основной тип, что делает их взаимозаменяемыми. Я бы предложил следующие ситуации, когда можно использовать type aliases:

  • меняющиеся структуры данных;
  • меняющиеся сигнатуры функций;
  • тестирование;
  • создание flavour-билдов;
  • смысловое разделение типов данных и функций по именам/категориям.

Не могу сказать, что вот прямо конкретно этой возможностью я бы пользовался каждый день, но отторжения она у меня не вызвала, а скорее наоборот. Я использовал ее в Pascal/Delphi и не вижу, что может мне помешать сейчас! Однозначно +1 в корзинку Kotlin.

Ranges & Controlling Flow

Если мы говорим о логических структурах, то в Kotlin главной их фишкой является то, что их можно использовать как выражения. Все мы писали что-то наподобие этого не один раз:

if (a > b) {
   max = a;
} else {
   max = b;
}

Я специально привел самый простой пример, на Kotlin он будет выглядеть вот так:

val max = if (a > b) a else b

Чертовски удобно ведь! Если надо выполнить больше кода в блоках условия — нет проблем, но последняя строка должна возвращать результат работы в переменную. Этот же самый принцип работает для when expression. when — это тот же switch из Java и других С-подобных языков. Но вот только when дает намного больше свобод и вариативности использования. В сравнении с ним switch выглядит мелковато и абсолютно не юзабельно. Да, и забудьте про ненавистный break для каждой из веток условий.

Я не случайно объединил ranges и Flow Control Structures в один раздел. Нельзя рассказывать про блоки условий и циклы и не затронуть ranges, ведь именно они являются еще одним замечательным бонусом, который будет облегчать вам жизнь. Признаться, я еще со времен Паскаля очень любил ranges и считал их очень удобным решением, и все никак не мог понять, почему их нет как отдельного типа данных в том же С/С++ или в РНР(с которым в обнимку провел 9 лет) или в JS и, конечно же, в Java. И вот в Kotlin я снова с ними повстречался, но вот только варианты их использования стали шире и удобнее по сравнению с Паскалем. К примеру, range можно сохранить в переменную и использовать в блоке условия:

val myRange = 1..10
if (x in myRange) { //you might want to use !in to check if value IS NOT in range
…
}

Естественно, что вместо чисел 1 и 10, могут быть использованы переменные или даже вызовы функций. При использовании в цикле for можно указывать направление движения и величину шага:

for (i in 1..4 step 2) print(i) // prints "13"
for (i in 4 downTo 1 step 2) print(i) // prints "42"

Ах, да, еще в циклах можно использовать break & continue с метками для прыжков между циклами как и в Java.

Ниже приведен кусочек кода, где я попытался вынести все возможные варианты использования when:

//just a helper function that checks whether int value is even or not?
fun Int?.isEven() : Boolean = this?.mod(2) == 0

val range = 10..20;

//function with when-construction that takes a parameter of Any kind
fun test1(x:Any?) {
    when(x) { //here we pass in x to when
        null -> print("x is null")
        0,1,2 -> print("x = $x")            
        is CharRange -> print("x is a CharRange $x")
        in range -> print("x is in range $range, x = $x") 
        is String -> print("x is a String, x = \"$x\"")
        else -> print("x is of ${x?.javaClass}, x = $x")
    }
    print("\n")
}

//function with when-construction that does not take a parameter and may substitute if/else-if blocks completely
fun test2(x:Any?) {
    when { //no value is passed in
        x == null -> print("x is null")
        x in 0..2 -> print("x = $x")
        x is CharRange -> print("x is a CharRange $x")
        x in range -> print("x is in range $range, x = $x") 
        (x is Int? && x?.isEven() ?: false) -> print("x is even integer, x = $x")
        x is String -> print("x is a String, x = \"$x\"")
        else -> print("x is of ${x?.javaClass}, x = $x")
    }
    print("\n")
}

fun main(args: Array<String>) {
    test1(null) // prints "x is null"
    test1(2) // prints "x = 2"
    test1('A'..'Z') // prints "x is a CharRange A..Z"
    test1(15) // prints "x is in range 10..20, x = 15"
    test2(22) // prints "x is even integer, x = 22"
    test1("HELLO!") // prints "x is a String, x = "HELLO!""
    test1(101) // prints "x is of class java.lang.Integer, x = 101"
}

Сколько бы вы баллов накинули Kotlin за этот раздел?

Изначально на этом месте я подводил итоги по базовым вещам и говорил о том, какой же все-таки Kotlin классный, как лаконичнее код выглядит с применением вышеизложенного и прочее бла-бла-бла, НО, как мы знаем, 1-ого марта вышло обновление 1.1., и оно нам принесло:

Coroutines (доступно в версии 1.1)

Правильнее даже сказать: «доступно с версией 1.1», потому что корутины не входят в сам язык на данный момент, а являются экспериментальной функциональностью и вынесены в отдельный артефакт. Для использования оных нужно в build.gradle файле проекта указать:

kotlin {
    experimental {
        coroutines ‘enable’
    }
}
dependencies {
…
    compile "org.jetbrains.kotlinx:kotlinx-coroutines-core:0.14"
…
}
//по непонятным для меня причинам в один прекрасный момент gradle перестал
//находить артефакт с корутинами, и пришлось явным образом указать, где их стоит искать
repositories {
   maven { url "https://kotlin.bintray.com/kotlinx/" }
}

И теперь нам становятся доступны корутины. Что же это такое? В каком-то смысле — это легковесная нить, но очень дешевая в обслуживании, которую можно приостанавливать и возобновлять. Она может состоять из вызовов других корутин, которые в свою очередь могут выполняться в совершенно разных нитях. Одна нить (Thread) может обслуживать множество корутин. Kotlin предоставляет несколько механизмов использования корутин, но я рассмотрю только парочку наиболее интересных в контексте Android-разработки.

Первый вариант использования — это функция launch():

launch(CommonPool){
...
}

CommonPool представляет собой пул повторно используемых нитей и под капотом использует java.util.concurrent.ForkJoinPool. Ниже приведены два варианта одного и того же решения: блокирующие вызовы (никогда так не делайте!) и неблокирующие на основе корутин.

//this is a blocking example, isn’t it? ;)
println("Start")
for(i in  1..100) { //our main thread sleeps for 10 seconds...arghhh!!!
    Thread.sleep(100)
}
println("Hello!")
println("Stop")

И в консоли результат выполнения у нас будет такой:

11:22:41.250 419-419/com.test I/System.out: Start
11:22:51.269 419-419/com.test I/System.out: Hello!
11:22:51.269 419-419/com.test I/System.out: Stop

А вот неблокирующий вызов:

//this ain’t a blocking example, isn’t it?;)
println("Start")
launch(CommonPool) {
    for(i in  1..100) {
        delay(100) //hey, hey, what are we doing here?
    }
    println("Hello!")
}
println("Stop")

И в этом случае результат выполнения у нас будет такой:

11:27:32.824 6212-6212/com.test I/System.out: Start
11:27:32.838 6212-6212/com.test I/System.out: Stop
11:27:42.884 6212-6238/com.test I/System.out: Hello!

Зоркий глаз и пытливый ум уже заметили, что для задержки в 100 миллисекунд внутри функции launch() использовался вызов delay(). Он аналогичен Thread.sleep(), но используется в контексте корутины и блокирует ее, а не основной поток. Важно запомнить, что все конструкции, специфичные для корутин, могут быть использованы только в их пределах.

Ахх, я вижу как у вас загорелись глаза и начали роиться мысли о том, как с помощью одной такой конструкции можно заменить связку Thread-Handler(UI)? Как же нам данные из корутины корректно передать в UI-поток? Самые невнимательные могут предложить такое решение:

launch(CommonPool) {
    for(i in  1..100) {
        delay(100) 
    }
    myTextView.text = "Hello!" //can’t touch UI-stuff in non-UI thread!
}

и сразу получат по рукам — мы же находимся в рабочей нити. А внимательные сделают так:

launch(CommonPool) {
    for(i in  1..100) {
        delay(100)
    }
    runOnUiThread { myTextView.text = "Hello!" } //will work, but it looks weird
}

и это будет работать, но выглядит оно как-то не очень, согласитесь. И прежде чем показать, как это можно сделать «по красоте», необходимо разобраться с функцией async(). Она очень похожа на launch(), с одним лишь отличием, что возвращает результат работы экземпляром Deferred, а у него есть метод await(), который собственно и возвращает результат работы функции.

Давайте смотреть, как это выглядит. Предположим, нам нужно посчитать факториалы двух чисел и получить их сумму. Не спрашивайте меня, кому это может понадобиться, просто мне так захотелось.;) Для этого опишем следующую корявенькую функцию подсчета факториала (+1 в карму первому комментатору данной реализации, а все желающие углубить свои познания о факториале милости просим сюдой):

fun factorial(num : Int) : Deferred<Long> {
   return async(CommonPool) {
           var f:Long = 1
           for (i in 2..num) {
               f *= i
               delay(100) //this is just for purposes of an experiment
           }
           f //it's not a typo, it's a <strong>'return'</strong> statement!!!
       }
}

Внутри функции запускается async-корутина, которая вернет нам значение типа Long. Я поставил задержку в цикле, чтобы показать одну примечательную деталь работы корутин (о ней чуток позже). Теперь сделаем вызов этой функции и посмотрим, как оно отработает:

println("Start")
launch(CommonPool) {
   val f5 = factorial(5)
   val f25 = factorial(25)
   println((f5.await() + f25.await()))
}
println("Stop")

Результаты работы следующие:

11:42:35.050 26517-26517/? I/System.out: Start
11:42:35.061 26517-26517/? I/System.out: Stop
11:42:37.486 26517-26530/com.test I/System.out: 7034535277573963896

А теперь о том, на чем я бы хотел заострить ваше внимание. Если вы заметили, то разница между последними двумя выводами в консоли — около 2.5 секунд. Хотя 5! будет посчитан за ~0.5 секунды. А вот 25! как раз просчитается где-то за 2.5 секунды. Таким образом функция вывода println() не будет выполнена пока не вернутся результаты из всех корутин, т.е. по факту — пока не отработает самая долгая из них.

Отлично, с этим разобрались, и, собственно, теперь можем вернуться к поставленной выше задаче: как результаты работы корутины правильно передать в UI-поток. И в этом нам поможет модуль kotlinx-coroutines-android, который предоставляет UI контекст для корутин — замену тому самому CommonPool, что мы использовали в примерах выше. Для этого закидываем этот модуль в зависимости:

dependencies {
    …
    compile "org.jetbrains.kotlinx:kotlinx-coroutines-android:0.14"
}

Теперь мы смело можем сделать так:

println("Start")
launch(UI) {
   val f5 = factorial(5)
   val f25 = factorial(25)
   myTextView.text = (f5.await() + f25.await()).toString()
}
println("Stop")

Первым делом я проверил, как это дело работает в случае, если контекст умер — работает отлично, утечек я не пронаблюдал! Честно признаться: я и до этого времени AsyncTask совсем не часто использовал, а теперь не вижу необходимости вообще.

Отдельным пунктом хочу заметить, что с применением корутин отпадает необходимость использования колбеков для асинхронных вызовов. Я на дух не переношу ситуации, когда у меня флоу состоит из нескольких последовательных API-коллов. Эти громоздкие конструкции/ветвления переходящие одно в другое — бррр. Теперь же мы просто можем закинуть пачку API-коллов в нужной нам последовательности в корутину, обернуть их, к примеру, в try/catch (чтобы отлавливать различные исключения) и voilà, все готово! И, если я еще не сильно надоел, то хочу подкинуть еще чуток на корутинный вентилятор:

//fetching data for list/recyclerview
launch(UI) {
   try {
       val call = MyHttpClient.getLatestItems() //some call with async() inside
       itemsAdapter.setData(call.await())
       itemsAdapter.notifyDataSetChanged()
    } catch (exc : CustomException) {
           //logging exception  
    }
}

//fetching and processing images
launch(UI) {
   try {
       val imageCall1 = MyHttpClient.fetchImageByUrl(url1)
       val imageCall2 = MyHttpClient.fetchImageByUrl(url2)
       val resultImageFun = combineImages(imageCall1.await(), imageCall2.await()) //also an async() function
       imageView.setImageBitmap(resultImageFun.await())
    } catch (exc : CustomException) {
           //logging exception  
    }
}

Я специально остановился только на двух механизмах работы с корутинами, там их больше, и даже питонисты найдут свой любимый yield.;)

Пытливый ум может упрекнуть меня, что корутины не совсем тянут на базовые вещи в Kotlin, и отвести бы им место в следующей статье рядом с лямбдами, функциями и прочей вкусняшкой....и где-то я с ними соглашусь. Но уж больно хороши они мне показались, и я не смог не поделиться этой информацией с вами!;) Слишком уж сильно они упрощают жизнь и сокращают время от точки А (создание proof of concept) до точки Б (получение готовых результатов), а это дорогого стоит, не так ли?

Лично для себя я сделал уже выбор, и, боюсь, он не сильно в пользу Java. Так что можно дальше баллы не считать. Но о том, ЧТО еще Kotlin умеет, я постараюсь вменяемо рассказать в следующей (последней) части. А теперь можно и на боковую...и только бы не Йода мне приснился, только бы не эта зеленая марты...


Предыдущий выпуск: Java vs. Kotlin для Android. День 1: спрыгиваем с Java
Следующий выпуск: Java vs. Kotlin для Android. День 3: Android высшего порядка

Все про українське ІТ в телеграмі — підписуйтеся на канал DOU

👍ПодобаєтьсяСподобалось0
До обраногоВ обраному0
LinkedIn

Схожі статті




30 коментарів

Підписатись на коментаріВідписатись від коментарів Коментарі можуть залишати тільки користувачі з підтвердженими акаунтами.

Занимательная статья — интересно ознакомиться с восприятием языка разработчиками, особенно с точки зрения devtools. Котлин однозначно увеличивает скорость разработки и решает проблему медленной эволюции Java как языка.

P.S. Скорость разработки — показатель, в котором по моему личному мнению Python нет равных. И в статье несмотря на будоражащие душу сравнения с PHP и Pascal/Delphi, нет ни одного сравнения с Python, хотя многие идеи и конструкции ну очень похожи :-)

К своему стыду признаюсь, что только начал изучать Питончик, много лет назад я как-то было попробовал в него, но не пошло, и я забросил...а жаль(. И совершенно с вами соглашусь, что поразительный и лаконичный язык! Must know просто-таки)

Все равно не понимать. Изначано был Линукс Торвалдьс и на нем неплохо компилится С++ код и при том работает очень кипяточно быстро и надежно (если руки не кривые) .. нет решили написать на плюсах Далвик/АРТ .. ну намано (ток если шо нужно побыстрее — то будь добр юзай NDK) ... теперь мы яву как бы оборачиваем Котлином ... Котлин чем оборачивать будем??))) И вообще говорят «Солнышко встает, солнышко садиться — руками не трогай». Зачем еще менять то что и так нормально работало. А то мы из явы делаем какойто яваскрипт — как по мне, так сами придумываем проблемы, чтобы потом можно было их решать. Увеличиваем количество подводных камней, чтобы сэкономить 3 строчки. За статью спасибо.

Неплохая статья недавно вышла про то как следует и не следует писать на котлине:
blog.philipphauer.de/…​ic-kotlin-best-practices

Даёшь тылдыр!

Спасибо за статью.

В этом примере

println("Start")
launch(UI) {
   val f5 = factorial(5)
   val f25 = factorial(25)
   myTextView.text = (f5.await() + f25.await()).toString()
}
println("Stop")

f5 и f25 будут рассчитываться в отдельном потоке, но ожидание результата будет в UI. правильно я понимаю?

Нет, ожидания/блокировки не будет, если вы это имели в виду. Просто результаты работы этих функций вернутся в UI-thread.

Тобто у нас запускаються дві задачі паралельно чи дві задачі одна за одною? Маю на увазі власне задачі з вичисленням факторіалу.
А UI-Thread тим часом собі спокійно живе своїм життям і ми можемо клацати-цикати що душі заманеться?

— запускаются параллельно
— да, юай живет своей беззаботной жизнью)

Дякую Вам за відповідь і за Ваші статті. Саме вони зацікавили мене «пощупати» цю мову і після Swift’у вона мені здається ідеальним майбутнім для Android платформи :)

Я так понимаю, что котлиновское Deferred.await() это аналог джавишного Future.get().

Не понятно, в каком потоке выполняется ожидание (Deferred.await()), сложение, toString() и самое главное — финальное присвоение значения (myTextView.text = ).

Deffered.await() выполняется в отдельной рабочей(м) нити\потоке. Все остальное выполняется в контексте, который вы указали для корутины. Это может быть как еще одна рабочая нить ИЛИ, если на важно вернуть разультат в UI, то это будет UI-контекст, специально для этого реализованный ребятами из JetBrains

Спасибо за ответ. Яснее не стало. Видимо надо почитать что такое корутины. :)

Рассматривайте корутины как отдельные задачи, которые крутятся внутри рабочих нитей/потоков и их взаимодействие с «окружающим миром» хендлится на уровне SDK и компилятора. Т.е., когда компилятор Котлина видит описание корутины в коде, он создает все необходимые точки входа и выхода и всю инфраструктурку для ее работы.
Как-то так)

Глянул в офиц документацию. Картина прояснилась — delay/await в контексте корутины действительно не блокируют поток/нить, на котором исполняетя корутина, а саспендят саму корутину, давая другим корутинам поработать на этом же потоке, если таковые имеются.

Спасибо за статью, очень интересно!
Особенно порадовали

Coroutines
:)

Спасибо! Да, корутины зашли однозначно)

> легковесная нить

Якщо вже перекладати «thread», то потік.

«Поток — это исторически сложившийся, но неверный перевод, как Силиконовая Долина, например.
Правильный перевод будет «нить» и Кремниевая Долина.

P.S.
Более того, если переводить thread как поток, то возникнет дополнительная путанница с переводм stream.

Мы было бы трудно понять что мне пытается донести собеседник говоря о многонитиевом алгоритме обработки данных. Поток — устоявшийся термин понятный русскоговорящему сообществу.

P.S.: Омонимы никто не отменял.

Возможно термин «поток» легко приживается вне джава-эко-сферы, и человек, не знакомый с Java, действительно не поймет, что такое нить. Но у джавистов с этим проблем быть не должно, а если есть, то кто-то как минимум прогуливал уроки английского))
Имхо, перевод термина должен максимально передавать его смысл и обеспечивать узнаваемость/уникальность. И вот в данном случае «нить» — это довольно-таки уникальный термин, тогда как «поток» слишком размазан толстым слоем по Data Science.

Тоді з уроків англійської мови вам мабуть має бути відомо, що перекладати лише слова є помилкою. Завжди важливий контекст, в якому вони використовуються. Тому немає ніяких проблем з використанням «потоку» в обох випадках.

Я думаю, что у людей, которые вводили термин thread, были на то веские причины и они имели в виду именно нить/волокно вычислений. Имели бы они в виду потоки, то назвали бы Stream/Current или еще как-то так. Более того, потоки уже были в том же С++(откройте тех же Дейтелов).
«Поток» прижился у нас, но вас неправильно поймут, если вы в резюме у себя напишите что-то типа «experience in multi-streaming development», так ведь?
Я сам в быту употребляю «поток», когда говорю об инстансе нити, НО в документации, имхо, нужно придерживаться правильных терминов, а не бытовых.

ну поздно уже у нас пытаться переломить этот терминологический капец
хотя по смыслу таки поток = поток, а не нить
особенно в контексте современных процов, где чотко пишут : 16 core — 32 threads

Кому-то «Кремниевая Долина» тоже слух режет. Вы просто привыкли к неправильному варианту, и он вам нравится больше именно из-за того, что привычней.

Поток не только устоявшийся неправильный перевод, но еще и мешает правильно переводить stream. Особенно сейчас, когда в Java 8 появились stream на каждом шагу.

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

Отвечая Вам и Александеру, к Java я имею посредственное отношение, и мне чужды проблемы перевода stream. Со специалистами, которые стремятся давать новые имена устоявшимся названиям я препочел бы разговаривать на английком, наверное.

Thread так же переводится как струйка, у которой с потоком много больше общего, чем с нитью, если вдаваться в этимологию слова. Ведь thread представляет динамическую сущнось, как поток или струйка, а не статическую, как нить.

Поэтому, если и заниматься выдумываением новых терминов, я предпочел бы струйка. Не наю чем бы при этом струя (thread) отличалась от потока (stream). Не стоит игнорировать контекст.

«нить» — совершенно не новый термин. Нам еще в политехе 13 лет назад Thread давали именно как «нить», но не как поток. И было это на курсе по С++, если не ошибаюсь. «Потоки» как раз вот появились позже....

Программирую с 2008-го и за это время сталкивался только с многопоточным программированием, но не многонитиевым. Но вот только что проверил — гугл в курсе и так говорят. Ну, что ж, главное чтобы Вас понимали.

Підписатись на коментарі