Kotlin unit как убрать
Перейти к содержимому

Kotlin unit как убрать

  • автор:

Kotlin unit как убрать

Статья проплачена кошками — всемирно известными производителями котят.

Если статья вам понравилась, то можете поддержать проект.

  • Именованные параметры
  • Параметры по умолчанию
  • Unit. Если функция ничего не возвращает
  • Ключевое слово vararg — переменное число параметров
  • Вложенные (локальные) функции
  • Функции верхнего уровня
  • Функция TODO()
  • infix
  • Имена функций в обратных кавычках

Коты забавные, поэтому ввели ключевое слово fun (есть спорное мнение, что на самом деле это сокращение от «function» для обозначения функций, которые являются аналогами методов в Java).

Объявление функции начинается с ключевого слова fun, затем идёт имя функции, в круглых скобках указываются параметры. Тип возвращаемого значения указывается после списка параметров и отделяется от него двоеточием. Функция всегда возвращает значение. Если вы сами не указали возвращаемое значение, то функция вернёт Unit, который схож с void, но является объектом.

Параметры в функциях объявляется немного иначе, чем в Java — сначала имя параметра, потом его тип.

 fun add(x: Int, y: Int): Int

С функциями можно работать как с значениями — можно сохранить в переменной, передать в качестве параметра, возвратить из другой функции.

Стандартный вывод «Hello Kitty» для Kotlin-программы (Desktop, не Android):

 fun main()

Данная функция ничего не возвращает. Напишем другую функцию, возвращающую результат.

 fun max(a: Int, b: Int): Int < return if (a >b) a else b > 
 println(max(7, 2)) // выводит 7 

Обратите внимание, что if является выражением в Kotlin, а не Java-оператором и соответствует тернарному оператору в Java:

 (a > b) ? a : b 

Простую функцию, в которой блок состоит из одной строки кода, можно переписать в одну строчку.

 fun max(a: Int, b: Int): Int = if (a > b) a else b 

Можно даже убрать возвращаемый тип. Гулять так гулять.

 fun max(a: Int, b: Int) = if (a > b) a else b 

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

В других случаях (тело-блок) вы обязаны указывать возвращаемый тип и использовать инструкцию return.

Функции верхнего уровня можно импортировать для сокращения кода.

 import strings.lastChar val cat = "Cat".lastChar() 

Доступен вариант со звёздочкой.

 import strings.* val c = "Cat".lastChar() 

Можно даже изменить имя и создать псевдоним при помощи ключевого слова as. Этот вариант может оказаться полезным, если имеются несколько одинаковых названий функций из разных пакетов и хочется избежать путаницы и конфликтов.

 import strings.lastChar as last val c = "Cat".last() 

Именованные параметры

Мы привыкли, что при вызове метода следует соблюдать очерёдность параметров. С именованными параметрами такая необходимость отпала. Создадим новую функцию из двух параметров.

 fun sayHelloByName(firstName: String, secondName: String)

Вызывая функцию, мы можем не соблюдать порядок параметров, если явно будем прописывать имена параметров.

 sayHelloByName(secondName = "Котофеевич", firstName = "Котофей") 

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

Данный приём не сработает при работе с методами, написанными на Java. Поддержка именованных аргументов есть в Java 8, но Kotlin поддерживает совместимость с Java 6, поэтому приходится смириться. Возможно, в будущем, эта проблема решится автоматически, когда откажутся от поддержки старых версий.

Параметры по умолчанию

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

Добавим в класс активности новую функцию для вывода всплывающего сообщения (в примере используется функция-расширение).

 fun Context.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT)

Второй параметр использует значение по умолчанию и мы можем его не указывать при вызове. Вызываем функцию.

 toast("Meow") // просто и аккуратно toast("Meow-w-w", Toast.LENGTH_LONG) // используем второй параметр 

С параметрами по умолчанию нужно быть внимательными, возможна ситуация, когда Kotlin не поймёт, что вы от него хотите. Создадим функцию из трёх параметров, один из них будет иметь значение по умолчанию.

 fun sayHello(firstWord: String, secondWord: String = "Kitty", thirdWord: String)

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

 sayHello("Hello", "Kitty") // не компилируется 

В этом случае на помощь приходят именованные параметры.

 sayHello("Hello", thirdWord = "Kitty") 

Третий параметр теперь нам известен, опущенный параметр относится ко второму, оставшийся относится к первому.

У класса Thread имеется восемь конструкторов! Вы можете создавать гораздо удобные решения с параметрами по умолчанию.

Поскольку в Java нет понятия параметров по умолчанию, вам придётся явно указывать все значения при вызове функции Kotlin из Java-кода. В этом случае добавьте аннотацию @JvmOverloads, который создаст перегруженные версии методов, опуская каждый из параметров по одному, начиная с последнего.

Unit. Если функция ничего не возвращает

Стоит немного рассказать о функциях, которые не возвращают никаких значений. В Java мы используем ключевое слово void для подобных случаев. В Kotlin был придуман новый тип Unit для подобных ситуаций. Получается, что функция всегда что-то возвращает, в нашем случае Unit, который мы никак не используем.

 fun sayHello(name: String): Unit < println("Hello $name") >button.setOnClickListener

Но Kotlin достаточно умен и понимает, что мы не хотим ничего возвращать. Поэтому мы можем сократить код.

 fun sayHello(name: String)

Можно сократить код, убрав фигурные скобки, так как у функции только одно выражение.

 fun sayHello(name: String) = println("Hello $name") 

Ключевое слово vararg — переменное число параметров

В Java при вызове методов с разным числом аргументов использовалось троеточие (. ). В Kotlin существует другой подход — ключевое слово vararg.

 fun printNumbers(vararg integers: Int) < for (number in integers) < println("$number") >> 

Вызываем функцию с любым количеством аргументов.

 printNumbers(1, 2, 3, 4, 5) printNumbers(4) 

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

По сути vararg работает с массивом, но простое добавление массива Kotlin не пропустит. Следует использовать специальный оператор *.

 val intArray: IntArray = intArrayOf(6, 7, 8, 9) printNumbers(1, 2, 3, 4, 5, *intArray) 

Вложенные (локальные) функции

Внутри одной функции можно создать ещё одну локальную функцию.

 fun doIt(param: String) < // fun justDoIt(innerParam: String) < println(innerParam) println(param) >> 

Вложенная функция имеет доступ к переменным своей родительской функции.

Создадим функцию, которая выводим имя кота в верхнем регистре. Заодно создадим вложенную функцию, которая подсчитывает длину имени кота.

 fun getCat(name: String) < fun makeStrange(): Int < return name.length * 2 >println(name.uppercase() + makeStrange()) > // вызываем функцию getCat("barsik") // BARSIK12 getCat("vaska") // VASKA10 

Функции верхнего уровня

Функцию можно объявить в начале файла, не обязательно размещать его в теле класса. Это удобно, когда вам нужны методы, которые не относятся к конкретному классу или вы не хотите перезагружать имеющийся класс лишним кодом. Часто для этих целей программисты создавали отдельные классы со словом Util.

Размещая код метода за пределами класса, вы избегаете лишней вложенности. Они по-прежнему являются членами пакета, прописанного в файле и могут импортироваться при использовании в других пакетах.

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

 // файл cats.kt package kitten fun someFun(. ): String

Kotlin незаметно для вас создаст класс CatsKt по имени файла и все функции скомпилирует в статические методы. Если будете вызывать функцию в Java-коде, то это будет выглядеть следующим образом.

 import kitten.CatsKt; . CatsKt.someFun(. ); 

Если имя класса вас не устраивает, то добавьте аннотацию @JvmName перед именем пакета.

 @file:JmvName("CatFunctions") package kitten 

Тогда вызов в Java-коде будет другим.

 import kitten.CatFunctions; . CatFunctions.someFun(. ); 

Функция TODO()

В стандартную библиотеку Kotlin входит функция TODO() (надо сделать). Её описание выглядит следующим образом.

 /** * Всегда возбуждает [NotImplementedError], сигнализируя, что операция не реализована. */ public inline fun TODO(): Nothing = throw NotImplementedError() 

Функция TODO() возбуждает исключение, т.е. вызов функции гарантированно завершится ошибкой — она возвращает тип Nothing. Считайте функцию временной заглушкой. Разработчик знает, что некоторая функция должна вернуть строку или другой объект, но пока отсутствуют другие функции, необходимые для ее реализации. Создадим для примера две функции.

 fun shouldReturnAString(): String < TODO("implement the string building functionality here to return a string") >fun shouldReturnACat(): Cat

Обратите внимание, что возвращаемое значение для shouldReturnAString() — это String, но на самом деле функция ничего не возвращает. Аналогично у shouldReturnACat().

Возвращаемый тип Nothing у TODO() показывает компилятору, что функция гарантированно вызовет ошибку, поэтому проверять возвращаемое значение после TODO() не имеет смысла, так как shouldReturnAString() и shouldReturnACat() ничего не вернут. Компилятор не будет ругаться, а разработчик может продолжать разработку, отложив на потом реализацию функции-заглушки.

Функцию можно вызвать без аргументов. Код, который будет следовать за функцией, будет недостижим.

 fun shouldReturnACat(): Cat < TODO() println("миссия невозможна") // этот код не будет вызван >

infix

Существует специальная форма вызова метода — инфиксный вызов. В инфиксном вызове имя метода помещается между именем целевого объекта и параметром без дополнительный разделителей.

Например, в ассоциативных списках часто используют следующий приём.

 // инфиксная нотация val map = mapOf(1 to "one", 3 to "three", 9 to "nine") println(map) 

Пример можно заменить на более традиционный.

 // обычный способ val map2 = mapOf(1.to("one"), 2.to("two"), 5.to("five")) println(map2) 

Инфиксную форму можно применять к обычным методам и функциям-расширениям, имеющим один обязательный параметр. Для этого в объявление функции нужно добавить модификатор infix.

Имена функций в обратных кавычках

Можно объявить или вызвать функцию с именем, содержащим нестандартные символы. Для этого достаточно заключить имя в обратные кавычки `. Например, объявим функцию:

 fun `12!cat`() = println("I am a cat!") 
 `12!cat`() 

Данная возможность нужна, чтобы поддерживать совместимость с Java в тех моментах, когда встречаются зарезервированные ключевые слова. Использование обратных кавычек позволяют избежать несовместимости в случаях, если это необходимо. На практике такое почти не встречается.

На данный момент под Android такой способ не работает, студия будет ругаться.

Kotlin unit как убрать

Для установки отступов внутри компонента применяется модификатор padding() , которые имеет несколько вариантов:

// устанавливает отступы от каждой стороны по отдельности fun Modifier.padding(start: Dp = 0.dp, top: Dp = 0.dp, end: Dp = 0.dp, bottom: Dp = 0.dp): Modifier // устанавливает отступы по вертикали и по горизонтали fun Modifier.padding(horizontal: Dp = 0.dp, vertical: Dp = 0.dp): Modifier // устанавливает одно значение для отступов от всех четырех сторон fun Modifier.padding(all: Dp): Modifier // устанавливает отступы в виде объекта PaddingValues fun Modifier.padding(paddingValues: PaddingValues): Modifier

В качестве значения отступов применяются единицы dp . Применим данный модификатор:

package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.padding import androidx.compose.material.Text import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp class MainActivity : ComponentActivity() < override fun onCreate(savedInstanceState: Bundle?) < super.onCreate(savedInstanceState) setContent< Text( "Hello METANIT.COM", fontSize=22.sp, modifier = Modifier.padding(30.dp) ) >> >

В данном случае устанавливается отступ в 30 единиц между границами компонента Text и его содержимым.

Отступы и модификатор padding в Jetpack Compose и Kotlin в Android

Установка отступов по отдельности:

Modifier.padding(start = 30.dp, top=30.dp, bottom = 25.dp)

Причем необязательно устанавливать отступы от всех четырех сторон, можно задать только те отступы, которые нужны.

Установка отступов по горизонтали и вертикали:

Modifier.padding(horizontal = 25.dp, vertical = 25.dp)
PaddingValues

Тип PaddingValues применяет значения для установки отступов:

public fun PaddingValues( start: Dp, top: Dp, end: Dp, bottom: Dp ): PaddingValues
package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.material.Text import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp class MainActivity : ComponentActivity() < override fun onCreate(savedInstanceState: Bundle?) < super.onCreate(savedInstanceState) setContent< val innerPadding = PaddingValues(top = 20.dp, start = 15.dp) Text( "Hello METANIT.COM", fontSize=22.sp, modifier = Modifier.padding(innerPadding) ) >> >
Порядок применения отступов

При использовании отступов следует учитывать очередность их применения, например:

package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.background import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material.Text import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp class MainActivity : ComponentActivity() < override fun onCreate(savedInstanceState: Bundle?) < super.onCreate(savedInstanceState) setContent< Text( "Hello METANIT.COM", fontSize=22.sp, modifier = Modifier.fillMaxSize() .padding(20.dp) // отступы перед закрашенной областью .background(color= Color.Yellow) .padding(30.dp) // отступы перед текстом ) >> >

Здесь компонент Text растягивается по всей длине и ширине экрана. При этом к компоненту применяются два отступа. Но один отступ применяется до установки фонового цвета, а другой отступ — после.

Порядок применения отступов и модификатор padding в Jetpack Compose и Kotlin в Android

В итоге получится, что сначала применяется модификатор padding(20.dp) , который устанавливает отступ в 20 единиц между границами компонента и закрашиваемой областью. Затем применяется модификатор padding(30.dp) , который устанавливает отступ в 30 единиц между границами закрашенной области и содержимым — текстом.

Сдвиг

Для сдвига содержимого компонента по горизонтали и вертикали применяется модификатор offset . Он имеет следующие версии:

fun Modifier.offset(x: Dp = 0.dp, y: Dp = 0.dp): Modifier fun Modifier.offset(offset: Density.() -> IntOffset): Modifier

Посмотрим на использование первой версии модификатора:

package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.background import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.size import androidx.compose.material.Text import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp class MainActivity : ComponentActivity() < override fun onCreate(savedInstanceState: Bundle?) < super.onCreate(savedInstanceState) setContent< Text( "Hello METANIT.COM", fontSize=22.sp, modifier = Modifier.size(width=300.dp, height=200.dp) .background(color= Color.Yellow) .offset(x=30.dp, y=50.dp) ) >> >

В данном случае сдвигаем содержимое компонента Text на 30 единиц по горизонтали и на 50 единиц по вертикали.

Сдвиг и модификатор offset в Jetpack Compose и Kotlin в Android

Как и в случае с отступами следует учитывать порядок применения модификаторов. Так, изменим из предыдущего примера компонент Text следующим образом:

Text( "Hello METANIT.COM", fontSize=22.sp, modifier = Modifier.size(width=300.dp, height=200.dp) .offset(x=30.dp, y=50.dp) .background(color= Color.Yellow) )

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

Порядок смещения компонента и модификатор offset в Jetpack Compose и Kotlin в Android

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

Почему Kotlin отстой

Эта статья родилась по мотивам вот этой статьи в виде полу-шутки. В той статье большая часть «проблем» является либо синтетическими и крайне редко используемыми, либо притянутыми за уши из-за ожидания соответствия языка теоретической парадигме которой, по мнению автора, язык должен соответствовать. С другой стороны не упомянуты вещи, которые мне лично действительно усложняют жизнь.

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

Убогий for

Самую могучую конструкция для реализации любых циклических действий for в Kotlin превратили в самую бесполезную вещь, реализуемую самим же Kotlin всего одной строкой.

inline fun For(it : Iterator, cb : (T) -> Unit) < while (it.hasNext()) cb(it.next()) >fun main(a : Array) < val list = listOf(1, 3, 4, 12) println("for"); for (it in list) println(it) println("FOR"); For(list.iterator()) < println(it) >val arr = arrayOf(1, 3, 4, 12) println("a-for"); for (it in arr) println(it) println("a-FOR"); For(arr.iterator()) < println(it) >println("r-for"); for (it in 0..10) println(it) println("r-FOR"); For((0..10).iterator()) < println(it) >>

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

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

Собственно, бог бы с ним с этим недоделанным for -ом, если бы была альтернатива. Но ее нет. К сожалению, жизнь на итераторах не заканчивается, а когда приходится писать какие-то сложные циклы, то приходится жестоко страдать с убогим while -ом.

Истерично-бессмысленная война с null-абле

Может быть из-за того, что я стар, а может быть из-за того, что уже лет 25 успешно пишу на С , где (sic!) есть такая вещь как void* , я не испытываю никакого экстаза от повторения вслух шаблонных: «стрельба в ногу» и «ошибка на миллион». В результате, я просто не понимаю с чем воюют. Какая разница, когда хлопнется программа, на проверке аргументов или на их использовании?

В чем соль декларирования null-safety Kotlin -ом, если он ее даже теоретически обеспечить не может? Значение null есть в самом языке, оно есть в Java , без инфраструктуры которой Kotlin , скажем прямо, не представляет никакого интереса. Как можно защититься от того, что используется за пределами языка и никак им не контролируется? Да никак. Это не более чем модная тенденция, уродование исходных текстов и регулярный геморой.

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

var value : Int? = null fun F() : Int < if ( value != null ) return 0 return value // Ошибка >

Ошибка Smart cast to ‘Int’ is impossible, because ‘value’ is a mutable property that could have been changed by this time просто задалбывает. Хочется кого-то убить или что-то сломать.

Где, как и кем эта проперть может быть модифицирована между двумя строчками. Соседним тредом? Откуда взялась эта абсолютно бредовая уверенность компилятора в том, что каждая буква моей программы — это элемент многопоточной конкуренции? Даже в случае написания жестокого многопоточного кода пересечения тридов случаются на очень малом объеме текста программы, но из-за репрессивной заботы компилятора о такой возможности я имею гиморрой с клинописью постоянно.

Кто придумал два восклицательных знака? Неуд! Два еще недостаточно взрывают мозг. Надо было пять. Или десять. И с обоих сторон. Так уж точно было бы понятно, где тут самый не кошерный и «небезопасный» код.

var value : Int? = null fun F() : Int < if (value == null) return 0 return when (Random().nextInt()) < 3 ->value!! + 2 12 -> value!! + 1 5 -> value!! * 4 else -> 0 > >

Самое пакостное, что жизнь никак не укладывается в красивые представления о «безопасном» коде тех, кому нужно каждый год продавать новую книгу о свежих тенденциях. К сожалению, null — это нормальное «неизвестное» состояние множества объектов, но при работе с ними постоянно приходится писать абсолютно ненужную клинопись.

Смешно же во всей этой чепухе вокруг null то, что это не работает. Я уже почти смирился с писаниной бездарной клинописи в своем коде с надеждой, что «зато когда-нибудь это спасет».

Java

public class jHelper < public static jHelper jF() < return null; >public void M() <> >

Kotlin

fun F() < val a = jHelper.jF() a.M() //Упс! >

Это замечательно компилируется без каких-либо ошибок или предупреждений, запускается и с грохотом схлопытвается cо стандартным NullPointerException т.к. тут Kotlin не проверяет ничего и нигде. И где обеща. тьфу, декларируемая безопасность?

В общем, в сухом остатке я имею следующее:

  • регулярный гиморой с преодолением надуманных проблем в моем коде;
  • постоянные приседания с !! при работе с nullable типами в моем коде;
  • постоянный оверхед, генерируемый компилятором на проверках всех параметров функций и при установке любых значений в моем коде;
  • нулевую безопасность для любых данных пришедших извне;

Т.е. весь гиморрой только в той части, которую я знаю и контролирую, а все наружнее молча посыпется при первой же возможности. Зашибись, зачет!

Зато все красиво и по феншую.

Почему присваивание — это не выражение?

Даже if это убогое, но выражение, а присваиванию эту возможность отрезали. Почему я не могу написать так?

var value = 10 fun F() : Int < return value = 0 // Ошибка >
var v1 = 1 var v2 = 1 var v3 = 1 fun F() < v1 = v2 = v3 = 0 // Ошибка >

Что в этом коде криминального? Хотя, я наверное, догадаюсь. Защищаем пользователя от if (v=20) . Но, врядли, т.к. это просто не соберется без автоматического приведения типов, которого у Kotlin , опять-же, нет. Сдаюсь. Кто знает ответ?

Чем не угодил оператор «?:»?

За что ампутировали оператор «?:»?

Что усмотрели вредного в таких конструкциях?

value != 0 ? "Y" : "N"

С if все замечательно:

if (value != 0) "Y" else "N"

кроме полной альтернативности (где такое еще есть?) и того, что часто побочная писанина if () else места занимает больше, чем само выражение.

За что убили автоматическое приведение типов?

Да, тотальное приведение типов друг к другу — это чистое и незамутненное зло. Я обоими руками против того, чтобы плодить ребусы, к которым приводит взаимное преобразование чисел и строк. В принципе, я даже за то, чтобы различать целочисленное и плавучку. Но зачем было совсем все-то вырезать?! Почему нельзя использовать стандартные и общепринятые правила приведения типов, которые существуют в подавляющем большинстве языков?

Ну ладно, пусть даже отрезали. Привет Pascal . Но зачем в документации-то врать про «there are no implicit widening conversions for numbers»? Где оно «are no», если такое замечательно собирается?

val i = 10 val l = 12L val f = 12.1 val l1 = i+100/l-f

Где ожидаемый хардкор?!

val l1 = i.toDouble() + 100.toDouble() / l.toDouble() - f

Т.е. авто-приведения типов нет… хотя… оно как-бы есть… но только для выражений… и еще для констант. А вот если передать в качестве параметра надо переменную или там присвоить в переменную без вычислений — тут уже ручная гребля в санях. Ведь это так принципиально, и нужно акцентировать все внимание на том, что вот из этого Int получается именно Long , а из этого Float именно Double .

Я прямо чувствую, как количество ошибок в моей программе стремительно тает от такой заботы обо мне.

Хотел бы я еще заикнуться про крайне желательное:

val c : SomeClass? = null if ( c ) "not-null" if ( !c ) "is-null"

но не буду т.к. опасаюсь за свою жизнь.

Недо-typedef

Давно просили прикрутить к Kotlin псевдонимы. Прикрутили. Я не знаю в каких случаях люди это планируют использовать но, на мой взгляд, толку от такой реализации примерно ноль. Назвали бы эту конструкцию макросом — у меня притензий бы не было, а так… обман какой-то.

Давайте разберемся в каких ситуациях вообще нужны псевдонимы в каком-нибудь языке. Я могу предположить следующее их применение:

  1. Создание альтернативного имени для существующего класса. Задача довольно бестолковая но, возможно, кому-то пригодится. С этим существующие псевдонимы справляются полностью.
  2. Создание нового типа без создания нового класса. Эту задачу существующие псевдонимы решить не способны вообще т.к. они не являются самостоятельным типом. Различить два псевдонима, отличающихся только именем невозможно.
  3. Уменьшение писанины при использовании шаблонных типов. Эта задача самая полезная и часто используемая. Существующие псевдонимы могут решить только описательную ее часть (см п.1), т.е. их можно использовать для описания типа переменных, параметров, возвращаемого значения и создать объект такого (базового) типа. Певдоним для шаблонного типа нельзя использовать для приведения или проверки типа объекта.

На практике мы имеем следующее:

typealias aI = SuperPuperClassA typealias pSI = Pair typealias pIS = Pair typealias pOTHER = Pair typealias aS = List class SuperPuperClassA < fun F() = pSI("",10) >fun main(a : Array) < val a = aI() val i1 = a.F() val i2 : Pair= a.F() val i3 : Any = a.F() //Этот код собирается и условие выполняется if ( i1 is pSI ) println("ok") if ( i1 is pOTHER ) println("ok") //Этот код НЕ собирается if ( i1 is pIS ) println("not compile") if ( i2 is pSI ) println("not compile") if ( i2 is pIS ) println("not compile") if ( i3 is pSI ) println("not compile") if ( i3 is pIS ) println("not compile") >

Обратите внимание на то, что в обоих строках где код собирается условие выполнится. Т.к. псевдоним не является полноценным типом, то различить их невозможно. Собственно, Kotlin мог бы их различать хотя бы в случаях, как в этом примере (весь код с явными и известными типами), но, видимо, нет желания.

Код, которые не собирается, имеет одну и ту же проблему: «Cannot check for instance of erased type». Проблема в недоразвитости (попросту отсутствии) шаблонов в рантайме JVM .

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

А, да, я говорил что псевдонимы можно описывать только глобальные, вне любого класса?

Nested and local type aliases are not supported

В результате их неудобно использовать и как макросы, для уменьшения писанины внутри одного класса, т.к. даже с модификатором private они «светятся» на весь текущий проект.

Убогие шаблоны

Шаблоны (generics) в Java вообще и в Kotlin в частности убоги и причина абсолютно одна и та же: JVM ничего не знает о шаблонах и все эти треугольные скобки в языке не более чем навесное синтаксическое украшательство.

Бог с ней с Java т.к. меня лично ее проблемы не волнуют. Меня волнует ущербность шаблонов конкретно в Kotlin , который позиционируется как другой язык, а не препроцессор для Java и, в результате, кивать на ее недостатки по меньшей мере бессмысленно.

То что шаблонные типы нельзя (бессмысленно) использовать для проверки типа или его приведения еще как-то можно пережить т.к. об этом компилятор хотя бы ошибку выдаст, но это не все проблемы.

Как Вам такой ребус:

/*00*/ class C(val value : Any) < /*01*/ fun F() : T < /*02*/ try < /*03*/ val v = value as T //Предупреждение компилятора "Unchecked cast: Any to T" /*04*/ return v /*05*/ >catch(ex : RuntimeException) < /*06*/ println("Incompatible") /*07*/ // Хак для иллюстрации того, что эксепшин будет съеден и не пойдет дальше /*08*/ return 0 as T /*09*/ >/*10*/ > /*11*/ > /*12*/ /*13*/ fun fTest() < /*14*/ val a = C( 12.789 ) /*15*/ println( "rc: $" ) /*16*/ /*17*/ val b = C( "12.123" ) /*18*/ println( "rc: $" ) /*19*/ >

В этом коде, в классе «С» делается попытка проверить совместим ли тип объекта с типом шаблона.

Внимание, вопрос: как отработает этот код?

  1. Не соберется вообще
  2. Соберется, выполнится и напечатает «12», «12»
  3. Соберется, выполнится и напечатает «12», «Incompatible»
  4. Соберется, выполнится и напечатает «12.789», «12.123»
  5. Хлопнется при запуске внутри функции «C::F» (на какой строке?)
  6. Хлопнется при запуске внутри функции «fTest» (на какой строке?)

Правильный ответ

Правильный ответ: хлопнется при запуске внутри функции «fTest» на строке 18

rc: 12 Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number at jm.test.ktest.KMainKt.fT(kMain.kt:18) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

Следующий конкурс: кто может объяснить почему это происходит?

  1. Почему не упало на первом вызове, где передается Double вместо Int ?
  2. Почему не отработал блок try / catch ?
  3. Как ошибка кастинга с ПРАВИЛЬНЫМИ типами смогла вообще доехать до кода используещего функцию «C::F»?

Под капотом

Кратеньно, выводы делайте сами.

Вот код, который генерирует Kotlin для проверки типа внутри «C::F»:

// val v = value as T GETFIELD jm/test/ktest/C.value : Ljava/lang/Object; CHECKCAST java/lang/Object ASTORE 1

Если очень сильно подумать (или заранее знать что оно неработоспособно), объяснить почему именно CHECKCAST Object можно. Сложнее объяснить зачем вообще этот код генерировать т.к. он абсолютная пустышка всегда, но это вопрос уже совсем к другой части компилятора.

А вот код, который генерируется при вызове функции «C::F»:

LINENUMBER 18 L6 ALOAD 1 INVOKEVIRTUAL jm/test/ktest/C.F ()Ljava/lang/Object; CHECKCAST java/lang/Number

Опять-же, если очень сильно думать (или знать заранее), то можно объяснить наличие правильных типов в этом месте, но лично для меня сам факт наличия проверки типов после вызова функции был неожиданностью. И да: Kotlin , оказывается, генерирует проверку типа снаружи при каждом использовании шаблонного результата для любого класса.

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

Я все понимаю: шаблонов в Java нет и все подобное. Этот пункт, скорее всего, не появился бы вообще, если бы нормальную работу с шаблонами нельзя было бы реализовать в принципе никогда и нигде… Но вот у меня перед глазами яркий пример — VCL . Фирма Borland в богом забытом году умудрилась прикрутить не к чему-нибудь, а к С и Pascal настолько мощное RTTI , что альтернатив ему не существует до сих пор. А тут не машинный код, тут Java и обеспечить в ней полнофункциональное использование шаблонов в своем, Kotlin -овском коде можно. Но его нет. В результате, язык вроде бы и другой, а ситуация, из-за синтаксического разнообразия, еще хуже чем в самой Java .

Напишем аналог шаблона из ребуса на Java .

public class jTest  < Object value; jTest( Object v ) < value = v; >public T F() < return (T)value; >//Предупреждение компилятора "Unchecked cast" public static void Test() < jTesta = new jTest( 12.123 ); System.out.print( "rcA: " ); System.out.print( a.F() ); jTest b = new jTest( "12.789" ); System.out.print( "\nrcB: " ); System.out.print( b.F() ); System.out.print( "\n" ); > >

И попробуем его вызвать из Kotlin и Java .

fun fTJ_1() < val a = jTest( 12.123 ) println( "rc: $" ) val b = jTest( "12.789" ) println( "rc: $" ) > fun fTJ_2()

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

  1. и шаблон и его использование реализовано на Kotlin ;
  2. шаблон на Java , а его использование на Kotlin ;
  3. и шаблон и реализацая на Java ;

и какие будут результаты выполнения программы в каждом случае?

  • Все примеры отработают одинаково.
  • Все примеры отработают по разному.
  • Все примеры, где реализация написана на Kotlin отработают одинаково, а с Java по другому.

Правильный ответ

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

rc: 12 Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number
Exception in thread "main" java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.Integer
rcA: 12.123 rcB: 12.789

Почему так а не иначе? А это будет домашнее задание.

Нет синтаксиса для описания структур

Если взять абсолютно любой сравнымый язык (хоть саму Java, хоть Scala, Groovy и множество прочих от Lua до, даже, С++) то в них во всех сделано так, чтобы было удобно описывать структуры данных в коде программы.

Kotlin — это единственный известный мне язык, где синтаксиса для описания структур данных нет вообще. Есть (грубо говоря) всего три функции: listOf, mapOf и arrayOf.

Если с массивами и спискамми синтаксис громоздок, но как-то структурируется зрительно:

 val iArr1 = arrayOf(1, 2, 3) val iArr2 = arrayOf( arrayOf(1, 2, 3), arrayOf(1, 2, 3), arrayOf(1, 2, 3) ) val iArr3 = arrayOf( arrayOf(arrayOf(1, 2, 3), arrayOf(1, 2, 3), arrayOf(1, 2, 3)), arrayOf(arrayOf(1, 2, 3), arrayOf(1, 2, 3), arrayOf(1, 2, 3)), arrayOf(arrayOf(1, 2, 3), arrayOf(1, 2, 3), arrayOf(1, 2, 3)) )

то с картами все значительно печальнее:

 val tree = mapOf( Pair("dir1", mapOf(Pair("file1", 0), Pair("file2", 1))), Pair("dir2", mapOf( Pair("dir21", mapOf(Pair("file1", 0), Pair("file2", 1))), Pair("dir22", mapOf(Pair("file1", 0), Pair("file2", 1))))) )

Я не знаю как именно другие пользователи описывают константные данные, но лично я испытываю жестокий дискомфорт при попытке использовать что-то сложнее одномерного списка.

Понятно, что можно засунуть данные в любой подходящий формат (хотя бы тот же JSON и оттуда ее прочитать), однако.

  1. Засовывать каждый десяток строк в отдельный файл только для того, чтобы иметь возможность ими наглядно манипулировать — это как-то избыточно (хотя именно так и приходится делать).
  2. Усилия по написанию структуры кода прямо в программе и во внешнем файле, с последующим их чтением, просто несопоставимы.
  3. В случае изменения структуры данных приходится, помимо кода, править гору совершенно лишнего текста по обслуживанию его загрузки.

В общем, концепция минимализма — это круто, но аццки неудобно.

ПС: В виде отдельного гвоздя в голову я пожелаю кому-нибудь написать библиотеку для работы с матрицами. Зато научитесь понимать отличать Array>>> и Array>> с первого взгляда и с любого расстояния.

Вылезает Null объект, не знаю как его убрать

Принимаю данные из другой Activity, чтобы потом вывести их в EditText. Это данные переменных Speed_Priem4, Time_Priem4, Way_Priem4. В этих строках пытался избавиться от null, но всё тщетно.

val Time_Priem4 = Time_Priem ?: return null val Speed_Priem4 = Speed_Priem ?: return null val Way_Priem4 = Way_Priem ?: return null 

Знаю, что ошибка распространенная и глупая, но решить ёё пока никак не могу Logcat вылетает вот такой:

E/AndroidRuntime: FATAL EXCEPTION: main Process: com.example.cars2, PID: 25424 java.lang.RuntimeException: Unable to start activity ComponentInfo: kotlin.KotlinNullPointerException at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2758) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2819) at android.app.ActivityThread.-wrap12(ActivityThread.java) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1558) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:163) at android.app.ActivityThread.main(ActivityThread.java:6396) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:904) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:794) Caused by: kotlin.KotlinNullPointerException at com.example.cars2.Activity_Information.Priem_Dannye_Vyvod(Activity_Information.kt:28) at com.example.cars2.Activity_Information.onCreate(Activity_Information.kt:15) at android.app.Activity.performCreate(Activity.java:6875) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1119) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2711) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2819) at android.app.ActivityThread.-wrap12(ActivityThread.java) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1558) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:163) at android.app.ActivityThread.main(ActivityThread.java:6396) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:904) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:794) 

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *