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

Kotlin unit как убрать

  • автор:

Как заставить код возвращать Unit, а не String (Kotlin)

Есть такой код, собранный из кусков кода Kotlin по гуглу. По идее, в итоге я должен получать файл jjj.txt (предпоследняя строка) c записанными построчно путями ко всем файлам в папке /0/storage/emulated/tmp . Споткнулся на том, что первый фрагмент выводит то, что мне надо записать в jjj.txt, через println. Как сделать, чтобы то, что выводится через println, записывалось в jjj.txt (т. е. передавалось в FileWriter(«/0/storage/emulated/jjj.txt»).use < it.write(paths) >)?

fun getAllFilesInResources() < val projectDirAbsolutePath = Paths.get("").toAbsolutePath().toString() val resourcesPath = Paths.get(projectDirAbsolutePath, "/0/storage/emulated/tmp") val paths = Files.walk(resourcesPath) .forEach < item ->println("filename: $item") > FileWriter("/0/storage/emulated/jjj.txt").use < it.write(paths) >> 

Речь идёт об Android

Отслеживать

задан 15 окт 2021 в 15:07

23 4 4 бронзовых знака

Вам нужно переосмыслить подход. Пусть в этом коде будет создаваться объект того фрагмента, в котором выводится, а в том фрагменте создайте метод, который будет возвращать строку для записи. Таким образом в этом коде, используя созданный объект, мы будем обращаться к методу, возвращающему строку и записывать эту строку.

15 окт 2021 в 15:34

Что-то заголовок никак не стыкуется с телом вопроса.

Магия функций в Kotlin

Android_Deep_16.3-5020-d224fb.png

Kotlin — простой и совместимый с Java язык программирования, который сокращает время написания кода за счет более коротких конструкций. В этой статье мы расскажем про несколько популярных способов «магического» применения функций в Kotlin.

Extension Functions

Первый способ — расширение классов без наследования. Он позволяет, не меняя класс String и использующие его пакеты, расширить данный класс путём добавления нового метода или свойства.

Представьте, что у нас есть метод deleteSpaces() :

 
private fun String.deleteSpaces(): String  return this.replace(" ", "") > 

Мы можем использовать этот метод таким образом, как будто он — это часть класса String. Пользователь увидит это вот так:

 
println("Hel lo, Wor ld".deleteSpaces()) // Hello,World 

После выполнения компиляции этот метод уже будет выглядеть приблизительно так (код упрощён, главное — понять суть):

 
private static final String deleteSpaces(@NotNull String receiver)  return receiver.replace(" ", ""); > 

Следовательно, делаем вывод, что внутри метода deleteSpaces() у нас присутствует доступ лишь к публичным полям и методам класса, в результате чего инкапсуляция не нарушается.

Кроме Extension Functions, в Котлин по аналогии могут быть и Extension Properties:

 
private val String.first: Char get()  return this[0] > print("Kotlin".first) // K 

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

Анонимные функции и лямбда-функции

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

Рассмотрим синтаксис лямбда-выражения:

 
 аргументы -> возвращаемый_тип тело_функции > 

Что касается анонимной функции, то синтаксис её декларации абсолютно совпадает с синтаксисом обычной функции, просто у первой нет имени.

 
fun defaultFun(x: Int, y: Int): Int = x + y // Именованная функция val f = fun(x: Int, y: Int): Int = x + y // Анонимная функция 

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

Функции высшего порядка

Речь идёт о функции, принимающей в виде одного из аргументов другую функцию, включая лямбду либо анонимную функцию. Ярчайший пример применения – callback.

Представьте, что у нас есть функция высшего порядка longWork() :

 
private fun longWork(callback: (arg1: Int, arg2: String) -> Unit)  val result = doSomething() callback(result, "Kotlin > Java") // вызов callback > 

Эта функция принимает в виде аргумента функцию callback() , однако вызывает её лишь после функции doSomething() :

 
longWork( arg1, arg2 -> Unit print("callback runned") >) 

Как видите, мы вызываем функцию longWork() , передавая ей в виде аргумента лямбда-функцию, которую она вызовет позже. Язык программирования Котлин даёт возможность выносить лямбду за скобки в том случае, если она является последним аргументом функции, а также вообще убирать скобки, если лямбда — единственный аргумент. Кроме этого, чаще всего мы можем убрать тип возвращаемого значения, заменив аргументы на _ в том случае, если они не используются. Вот как выглядит укороченный вариант кода:

 
longWork  _, _ -> print("callback runned") > 

Всё это внешне уже напоминает не функцию высшего порядка, а языковую конструкцию, как, к примеру synchronized в Java. Кстати, synchronized в Котлин построен именно как функция высшего порядка.

Всё это весьма удобно при создании DSL – предметно-ориентированных языков. Допустим, того же Anko (Android UI прямо в Kotlin с сохранением удобства XML-разметки), или kotlinx.html (по аналогии с Anko), или Gradle Kotlin DSL (Gradle-скрипты на Котлин).

Давайте посмотрим на HTML-страницу на Kotlin:

 
System.out.appendHTML().html  body  div  a("http://kotlinlang.org")  target = ATarget.blank +"Main site" > > > > 

Вывод в stdout будет следующим:

 
html> body> div>a href="http://kotlinlang.org" target="_blank">Main sitea>div> body> html> 

Главный плюс этого DSL заключается в том, что в Kotlin существуют переменные, которые можно использовать для генерации динамической страницы (в отличие, скажем, от декларативного HTML). И всё это красивее и удобнее классической генерации страниц посредством конкатенации множества строк. Но вообще на практике для генерации HTML-разметки применяют иные методы, а этот мы показали лишь в качестве примера DSL в Kotlin.

Кроме того, возможно использование функций высшего порядка в качестве аналога Streams API из Java:

 
listOf(1, 2, 3, 4, 5) .filter  n -> n % 2 == 1 > // 1, 3, 5 .map  n -> n * n > // 1, 9, 25 .drop(1) // 9, 25 .take(1) // 9 

Ещё несколько слов о Kotlin

В Kotlin, в отличие от Джавы, присутствуют перегружаемые операторы. К примеру, если у класса есть метод plus() , мы можем вызвать его оператором +, а метод get() – с помощью оператора []. Это во-первых.

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

В inline-функциях return будет произведен из ближайшей по области видимости noinline-функции. А в noinline – непосредственно из самой функции. Данную проблему решают «именованные return».

Чтобы сделать return из лямбды, которую мы передаём в вышеописанном примере в apply() , мы можем задействовать return@apply.

Кстати, именованными могут быть не только return, но и break, continue. Кроме того, есть возможность создавать и свои метки:

 
loop@ for (i in 1..100)  for (j in 1..100)  if (. ) break@loop > > 

Также есть модификатор функции tailrec, который сообщает компилятору заменить рекурсию в функции на цикл, если функция написана в функциональном формате return if-then-else:

 
tailrec fun findFixPoint(x: Double = 1.0): Double = when  x == cos(x) -> x else -> findFixPoint(cos(x)) > // При компиляции будет заменена на fun findFixPoint(): Double  var x = 1.0 while (true)  val y = cos(x) if (x == y) return x x = y > > 

В-третьих, в ситуации, когда метод требует в виде аргументов объект, который должен быть унаследован от класса/интерфейса с одним абстрактным методом, в эту функцию мы можем передать лямбду либо анонимную функцию, а компилятор самостоятельно создаст анонимный класс с переопределением абстрактного метода на нашу лямбду. К примеру, в стандартной Android-библиотеке существует метод public void setOnClickListener(View.OnClickListener l) , в котором OnClickListener представляет собой интерфейс с единственным методом onClick(View v) .

Кстати, лямбда, переданная в качестве setOnClickListener < doSomething() >, скомпилируется в анонимный класс, реализующий интерфейс OnClickListener, в котором лямбда превратится в метод onClick(View v) .

Выводы

Язык программирования Kotlin с помощью своих «магических» функций существенно упрощает написание и, что не менее важно, чтение кода. А удобство и безопасность – наиболее важные отличия Kotlin от созданной в далёком 1995 году Java, когда о безопасности и удобстве мы только мечтали.

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 всё является объектом, в том смысле, что пользователь может вызвать функцию или получить доступ к свойству любой переменной. Некоторые типы являются встроенными, т.к. их реализация оптимизирована, хотя для пользователя они выглядят как обычные классы. В данном разделе описываются основные типы: числа, логические переменные, символы, строки и массивы.

Числа

Целочисленные типы

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

Тип Размер (биты) Минимальное значение Максимальное значение
Byte 8 -128 127
Short 16 -32768 32767
Int 32 -2,147,483,648 (-2 31 ) 2,147,483,647 (2 31 - 1)
Long 64 -9,223,372,036,854,775,808 (-2 63 ) 9,223,372,036,854,775,807 (2 63 - 1)

Все переменные, инициализированные целыми значениями, не превышающими максимальное значение Int , имеют предполагаемый тип Int . Если начальное значение превышает это значение, то тип Long . Чтобы явно указать тип Long , добавьте после значения L .

val one = 1 // Int val threeBillion = 3000000000 // Long val oneLong = 1L // Long val oneByte: Byte = 1 

Типы с плавающей точкой

Для действительных чисел в Kotlin есть типы с плавающей точкой Float и Double . Согласно стандарту IEEE 754, типы с плавающей точкой различаются своим десятичным разрядом, то есть количеством десятичных цифр, которые они могут хранить. С точки зрения IEEE 754 Float является одинарно точным, а Double обеспечивает двойную точность.

Тип Размер (биты) Значимые биты Биты экспоненты Разряды
Float 32 24 8 6-7
Double 64 53 11 15-16

Вы можете инициализировать переменные Double и Float числами, имеющими дробную часть. Она должна быть отделена от целой части точкой ( . ). Для переменных, инициализированных дробными числами, компилятор автоматически определяет тип Double .

val pi = 3.14 // Double // val one: Double = 1 // Ошибка: несоответствие типов val oneDouble = 1.0 // Double 

Чтобы явно указать тип Float , добавьте после значения f или F . Если такое значение содержит более 6-7 разрядов, оно будет округлено.

val e = 2.7182818284 // Double val eFloat = 2.7182818284f // Float, фактическое значение 2.7182817 

Обратите внимание, что в отличие от некоторых других языков, в Kotlin нет неявных преобразований для чисел. Например, функция с Double параметром может вызываться только для Double , но не для Float , Int или других числовых значений.

fun main() < fun printDouble(d: Double) < print(d) >val i = 1 val d = 1.0 val f = 1.0f printDouble(d) // printDouble(i) // Ошибка: несоответствие типов // printDouble(f) // Ошибка: несоответствие типов > 

Чтобы преобразовать числовые значения в различные типы, используйте Явные преобразования.

Символьные постоянные

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

  • Десятичные числа: 123
    • Тип Long обозначается заглавной L : 123L

    ВНИМАНИЕ: Восьмеричные литералы не поддерживаются.

    Также Kotlin поддерживает числа с плавающей запятой:

    • Тип Double по умолчанию: 123.5 , 123.5e10
    • Тип Float обозначается с помощью f или F : 123.5f

    Вы можете использовать нижние подчеркивания, чтобы сделать числовые константы более читаемыми:

    val oneMillion = 1_000_000 val creditCardNumber = 1234_5678_9012_3456L val socialSecurityNumber = 999_99_9999L val hexBytes = 0xFF_EC_DE_5E val bytes = 0b11010010_01101001_10010100_10010010 

    Представление чисел в JVM

    Обычно платформа JVM хранит числа в виде примитивных типов: int , double и так далее. Если же вам необходима ссылка, которая может принимать значение null (например, Int? ), то используйте обёртки. В этих случаях числа помещаются в Java классы как Integer , Double и так далее.

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

    val a: Int = 100 val boxedA: Int? = a val anotherBoxedA: Int? = a val b: Int = 10000 val boxedB: Int? = b val anotherBoxedB: Int? = b println(boxedA === anotherBoxedA) // true println(boxedB === anotherBoxedB) // false 

    Все nullable-ссылки на a на самом деле являются одним и тем же объектом из-за оптимизации памяти, которую JVM применяет к Integer между "-128" и "127". Но b больше этих значений, поэтому ссылки на b являются разными объектами.

    Однако, равенство по значению сохраняется.

    val b: Int = 10000 println(b == b) // Prints 'true' val boxedB: Int? = b val anotherBoxedB: Int? = b println(boxedB == anotherBoxedB) // Prints 'true' 

    Явные преобразования

    Из-за разницы в представлениях меньшие типы не являются подтипами бОльших типов. В противном случае возникли бы сложности.

    // Возможный код, который на самом деле не скомпилируется: val a: Int? = 1 // "Обёрнутый" Int (java.lang.Integer) val b: Long? = a // неявное преобразование возвращает "обёрнутый" Long (java.lang.Long) print(b == a) // Нежданчик! Данное выражение выведет "false" т. к. метод equals() типа Long предполагает, что вторая часть выражения также имеет тип Long 

    Таким образом, будет утрачена не только тождественность (равенство по ссылке), но и равенство по значению.

    Как следствие, неявное преобразование меньших типов в большие НЕ происходит. Это значит, что мы не можем присвоить значение типа Byte переменной типа Int без явного преобразования.

    val b: Byte = 1 // всё хорошо, литералы проверяются статически // val i: Int = b // ОШИБКА val i1: Int = b.toInt() 

    Каждый численный тип поддерживает следующие преобразования:

    • toByte(): Byte
    • toShort(): Short
    • toInt(): Int
    • toLong(): Long
    • toFloat(): Float
    • toDouble(): Double
    • toChar(): Char

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

    val l = 1L + 3 // Long + Int => Long 

    Операции

    Котлин поддерживает стандартный набор арифметических операций над числами: + , - , * , / , % . Они объявляются членами соответствующих классов.

    println(1 + 2) println(2_500_000_000L - 1L) println(3.14 * 2.71) println(10.0 / 3) 

    Вы также можете переопределить эти операторы для пользовательских классов. См. Перегрузка операторов для деталей.

    Деление целых чисел

    Деление целых чисел всегда возвращает целое число. Любая дробная часть отбрасывается.

    val x = 5 / 2 // println(x == 2.5) // ОШИБКА: Оператор '==' не может быть применен к 'Int' и 'Double' println(x == 2) // true 

    Это справедливо для деления любых двух целочисленных типов.

    val x = 5L / 2 println(x == 2L) // true 

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

    val x = 5 / 2.toDouble() println(x == 2.5) // true 
    Побитовые операции

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

    val x = (1 shl 2) and 0x000FF000 

    Ниже приведён полный список битовых операций:

    • shl(bits) – сдвиг влево с учётом знака (
    • shr(bits) – сдвиг вправо с учётом знака ( >> в Java)
    • ushr(bits) – сдвиг вправо без учёта знака ( >>> в Java)
    • and(bits) – побитовое И
    • or(bits) – побитовое ИЛИ
    • xor(bits) – побитовое исключающее ИЛИ
    • inv() – побитовое отрицание

    Сравнение чисел с плавающей точкой

    В этом разделе обсуждаются следующие операции над числами с плавающей запятой:

    • Проверки на равенство: a == b и a != b
    • Операторы сравнения: a < b , a >b , a = b
    • Создание диапазона и проверка диапазона: a..b , x in a..b , x !in a..b

    Когда статически известно, что операнды a и b являются Float или Double или их аналогами с nullable-значением (тип объявлен или является результатом умного приведения), операции с числами и диапазоном, который они образуют, соответствуют стандарту IEEE 754 для арифметики с плавающей точкой.

    `, a type parameter), the operations use the `equals` and `compareTo` implementations for `Float` and `Double`, which disagree with the standard, so that: -->

    Однако для поддержки общих вариантов использования и обеспечения полного упорядочивания, когда операнды статически не объявлены как числа с плавающей запятой (например, Any , Comparable <. >, параметр типа), операции используют реализации equals и compareTo для Float и Double , которые не согласуются со стандартом, так что:

    • NaN считается равным самому себе
    • NaN считается больше, чем любой другой элемент, включая "POSITIVE_INFINITY"
    • -0.0 считается меньше, чем 0.0

    Целые беззнаковые числа

    В дополнение к целочисленным типам, в Kotlin есть следующие типы целых беззнаковых чисел:

    • UByte : беззнаковое 8-битное целое число, в диапазоне от 0 до 255
    • UShort : беззнаковое 16-битное целое число, в диапазоне от 0 до 65535
    • UInt : беззнаковое 32-битное целое число, в диапазоне от 0 до 2 32 - 1
    • ULong : беззнаковое 64-битное целое число, в диапазоне от 0 до 2 64 - 1

    Беззнаковые типы поддерживают большинство операций своих знаковых аналогов.

    Changing type from unsigned type to signed counterpart (and vice versa) is a *binary incompatible* change. -->

    Изменение типа с беззнакового типа на его знаковый аналог (и наоборот) является двоично несовместимым изменением.

    Беззнаковые массивы и диапазоны

    Unsigned arrays and operations on them are in [Beta](components-stability.md). They can be changed incompatibly at any time. > Opt-in is required (see the details below). -->

    Беззнаковые массивы и операции над ними находятся в стадии бета-тестирования. Они могут быть несовместимо изменены в любое время.

    Как и в случае с примитивами, каждому типу без знака соответствует тип массивов знаковых типов:

    • UByteArray : массив беззнаковых byte
    • UShortArray : массив беззнаковых short
    • UIntArray : массив беззнаковых int
    • ULongArray : массив беззнаковых long

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

    При использовании массивов без знака вы получите предупреждение, что эта функция еще не стабильна. Чтобы удалить предупреждение используйте аннотацию @ExperimentalUnsignedTypes . Вам решать, должны ли ваши пользователи явно соглашаться на использование вашего API, но имейте в виду, что беззнаковый массив не является стабильной функцией, поэтому API, который он использует, может быть нарушен изменениями в языке. Узнайте больше о требованиях регистрации.

    Диапазоны и прогрессии поддерживаются для UInt и ULong классами UIntRange , UIntProgression , ULongRange и ULongProgression . Вместе с целочисленными беззнаковыми типами эти классы стабильны.

    Литералы

    Чтобы целые беззнаковые числа было легче использовать, в Kotlin можно помечать целочисленный литерал суффиксом, указывающим на определенный беззнаковый тип (аналогично Float или Long ):

    • u и U помечают беззнаковые литералы. Точный тип определяется на основе ожидаемого типа. Если ожидаемый тип не указан, компилятор будет использовать UInt или ULong в зависимости от размера литерала.
    val b: UByte = 1u // UByte, есть ожидаемый тип val s: UShort = 1u // UShort, есть ожидаемый тип val l: ULong = 1u // ULong, есть ожидаемый тип val a1 = 42u // UInt: ожидаемого типа нет, константе подходит тип UInt val a2 = 0xFFFF_FFFF_FFFFu // ULong: ожидаемого типа нет, тип UInt не подходит константе 
    • uL and UL явно помечают литерал как unsigned long .
    val a = 1UL // ULong, даже несмотря на то, что ожидаемого типа нет и константа вписывается в UInt 
    Дальнейшее обсуждение

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

    Логический тип

    Тип Boolean представляет логический тип данных и принимает два значения: true и false .

    При необходимости использования nullable-ссылок логические переменные оборачиваются Boolean? .

    Встроенные действия над логическими переменными включают:

    • || – ленивое логическое ИЛИ
    • && – ленивое логическое И
    • ! – отрицание
    val myTrue: Boolean = true val myFalse: Boolean = false val boolNull: Boolean? = null println(myTrue || myFalse) println(myTrue && myFalse) println(!myTrue) 

    **On JVM**: nullable references to boolean objects are boxed similarly to [numbers](#numbers-representation-on-the-jvm). -->

    В JVM: nullable-ссылки на логические объекты заключены в рамки аналогично числам.

    Символы

    Символы в Kotlin представлены типом Char . Символьные литералы заключаются в одинарные кавычки: '1' .

    Специальные символы начинаются с обратного слеша \ . Поддерживаются следующие escape-последовательности: \t , \b , \n , \r , \' , \" , \\ и \$ .

    Для кодирования любого другого символа используйте синтаксис escape-последовательности Юникода: '\uFF00' .

    val aChar: Char = 'a' println(aChar) println('\n') // выводит дополнительный символ новой строки println('\uFF00') 

    Если значение символьной переменной – цифра, её можно явно преобразовать в Int с помощью функции digitToInt() .

    **On JVM**: Like [numbers](#numbers-representation-on-the-jvm), characters are boxed when a nullable reference is needed. >Identity is not preserved by the boxing operation. -->

    В JVM: Подобно числам, символы оборачиваются при необходимости использования nullable-ссылки. При использовании обёрток тождественность (равенство по ссылке) не сохраняется.

    Строки

    Строки в Kotlin представлены типом String . Как правило, строка представляет собой последовательность символов в двойных кавычках ( " ).

    val str = "abcd 123" 

    Строки состоят из символов, которые могут быть получены по порядковому номеру: s[i] . Проход по строке выполняется циклом for .

    for (c in str)

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

    val str = "abcd" println(str.uppercase()) // Создается и выводится новый объект String println(str) // исходная строка остается прежней 

    Для объединения строк используется оператор + . Это работает и для объединения строк с другими типами, если первый элемент в выражении является строкой.

    val s = "abc" + 1 println(s + "def") // abc1def 

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

    Строковые литералы

    В Kotlin представлены два типа строковых литералов:

    • экронированные строки с экранированными символами
    • обычные строки, которые могут содержать символы новой строки и произвольный текст

    Вот пример экранированной строки:

    val s = "Hello, world!\n" 

    Экранирование выполняется общепринятым способом, а именно с помощью обратного слеша ( \ ). Список поддерживаемых escape-последовательностей см. в разделе Символы выше.

    Обычная строка выделена тройной кавычкой ( """ ), не содержит экранированных символов, но может содержать символы новой строки и любые другие символы:

    val text = """ for (c in "foo") print(c) """ 

    Чтобы удалить пробелы в начале обычных строк, используйте функцию trimMargin() .

    val text = """ |Tell me and I forget. |Teach me and I remember. |Involve me and I learn. |(Benjamin Franklin) """.trimMargin() 

    По умолчанию | используется в качестве префикса поля, но вы можете выбрать другой символ и передать его в качестве параметра, например, trimMargin(">") .

    Строковые шаблоны

    Строки могут содержать шаблонные выражения, т.е. участки кода, которые выполняются, а полученный результат встраивается в строку. Шаблон начинается со знака доллара ( $ ) и состоит либо из простого имени (например, переменной),

    val i = 10 println("i = $i") // выведет "i = 10" 

    либо из произвольного выражения в фигурных скобках.

    val s = "abc" println("$s.length is $") // выведет "abc.length is 3" 

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

    val price = """ $_9.99 """ 

    Массивы

    Массивы в Kotlin представлены классом Array , обладающим функциями get и set (которые обозначаются [] согласно соглашению о перегрузке операторов), и свойством size , а также несколькими полезными встроенными функциями.

    class Array private constructor() < val size: Int operator fun get(index: Int): T operator fun set(index: Int, value: T): Unit operator fun iterator(): Iterator// . > 

    Для создания массива используйте функцию arrayOf() , которой в качестве аргумента передаются элементы массива, т.е. выполнение arrayOf(1, 2, 3) создаёт массив [1, 2, 3] . С другой стороны функция arrayOfNulls() может быть использована для создания массива заданного размера, заполненного значениями null .

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

    // создаёт массив типа Array со значениями ["0", "1", "4", "9", "16"] val asc = Array(5) < i ->(i * i).toString() > asc.forEach

    Как отмечено выше, оператор [] используется вместо вызовов встроенных функций get() и set() .

    ` to an `Array `, which prevents a possible runtime failure (but you can use `Array `, see [Type Projections](generics.md#type-projections)). -->

    Обратите внимание: в отличие от Java массивы в Kotlin являются инвариантными. Это значит, что Kotlin запрещает нам присваивать массив Array переменной типа Array , предотвращая таким образом возможный отказ во время исполнения (хотя вы можете использовать Array , см. Проекции типов).

    Массивы примитивных типов

    Также в Kotlin есть особые классы для представления массивов примитивных типов без дополнительных затрат на оборачивание: ByteArray , ShortArray , IntArray и т.д. Данные классы не наследуют класс Array , хотя и обладают тем же набором методов и свойств. У каждого из них есть соответствующая фабричная функция:

    val x: IntArray = intArrayOf(1, 2, 3) x[0] = x[1] + x[2] 
    // int массив, размером 5 со значениями [0, 0, 0, 0, 0] val arr = IntArray(5) // инициализация элементов массива константой // int массив, размером 5 со значениями [42, 42, 42, 42, 42] val arr = IntArray(5) < 42 >// инициализация элементов массива лямбда-выражением // int массив, размером 5 со значениями [0, 1, 2, 3, 4] (элементы инициализированы своим индексом) var arr = IntArray(5)

    © 2015—2024 Open Source Community

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

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