Одной из самых классных особенностей Kotlin являются расширения. Они позволяют дополнить функционал существующего класса не наследуя его и не затрагивая его код.
Ранее, в языках, которые не имеют подобного функционала, для достижения того же мы использовали паттерн Decorator. Но расширения освобождают от шаблонного кода, позволяя определять сразу конкретные функции. И сегодня мы именно о них и поговорим!
1. IsNull для Kotlin
Как мы обычно делаем проверку на null?
1 |
if (something == null){} |
А теперь добавим расширение Kotlin.
1 |
fun Any?.isNull() = this == null |
И теперь наш код будет выглядеть куда симпатичнее!
1 |
if (something.isNull()){} |
2. Округляем цену
Вот вам еще одно расширение, которое будет форматировать строку подходящий десятичный формат. Также мы можем перегрузить эту функцию и для Double или Long.
Допустим у нас есть торговое приложение, и каждый продукт имеет свою собственную цену. Тогда это расширение позволит легко отформатировать цены к общему желаемому шаблону.
1 2 3 4 5 6 7 8 9 |
fun String.toPriceAmount(): String { val dec = DecimalFormat("###,###,###.00") return dec.format(this.toDouble()) } fun Double.toPriceAmount(): String { val dec = DecimalFormat("###,###,###.00") return dec.format(this) } |
Применить это можно так:
1 2 3 |
println("11".toPriceAmount()) println("05".toPriceAmount()) println(12.0.toPriceAmount()) |
3. Форматирование дат
Еще один пример, который поможет сделать код чище.
Скажем, у нас есть приложение-блокнот. Понятное дело, что нам нужно отображать дату под каждой созданной записью, а ещё мы можем преобразовать дату в формат UnitTime и загрузить её в базу данных.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
private const val TIME_STAMP_FORMAT = "EEEE, MMMM d, yyyy - hh:mm:ss a" private const val DATE_FORMAT = "yyyy-MM-dd" fun Long.getTimeStamp(): String { val date = Date(this) val simpleDateFormat = SimpleDateFormat(TIME_STAMP_FORMAT, Locale.getDefault()) simpleDateFormat.timeZone = TimeZone.getDefault() return simpleDateFormat.format(date) } fun Long.getYearMonthDay(): String { val date = Date(this) val simpleDateFormat = SimpleDateFormat(DATE_FORMAT, Locale.getDefault()) simpleDateFormat.timeZone = TimeZone.getDefault() return simpleDateFormat.format(date) } @Throws(ParseException::class) fun String.getDateUnixTime(): Long { try { val simpleDateFormat = SimpleDateFormat(DATE_FORMAT, Locale.getDefault()) simpleDateFormat.timeZone = TimeZone.getDefault() return simpleDateFormat.parse(this)!!.time } catch (e: ParseException) { e.printStackTrace() } throw ParseException("Please Enter a valid date", 0) } |
Использование:
1 2 3 4 |
val currentTime = System.currentTimeMillis() println(currentTime.getTimeStamp()) println(currentTime.getYearMonthDay()) println("2020-09-20".getDateUnixTime()) |
Результат:
1 2 3 |
Sunday, September 20, 2020 - 10:48:26 AM 2020-09-20 1600549200000 |
4. ObjectSerializer
На самом деле моё любимое расширение, ведь оно показывает всю мощь расширений Kotlin, который позволяет вам расширять интерфейсы! И да, все наследующие это расширение классы тоже смогут использовать эту функцию.
Допустим у нас есть приложение с карточками и нам нужно экспортировать их чтобы отправить другу или восстановить их позже. В этом нам и поможет ObjectSerializer. Сперва мы можем их сериализовать карточки в String или ByteArray, чтобы сохранить их в файле и восстановить позже, используя расширение deserialize.
В примере ниже можно увидеть, что мы сможем сериализовать каждый класс, который наследует Serializable. Но вся прелесть этого отображена не конкретно в этом примере, просто представьте, что это можно применить и с чем-то большим 😊
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@Throws(IOException::class) fun Serializable.serialize(): String { val serialObj = ByteArrayOutputStream() val objStream = ObjectOutputStream(serialObj) objStream.writeObject(this) objStream.close() return encodeBytes(serialObj.toByteArray()) } @Throws(IOException::class, ClassNotFoundException::class) fun String?.deserialize(): Any? { if (this.isNullOrEmpty()) return null val serialObj = ByteArrayInputStream(decodeBytes(this)) val objStream = ObjectInputStream(serialObj) return objStream.readObject() } |
Использование:
1 2 3 4 |
val kotlinExtention = "Kotlin Extensions ObjectSerializer" val serializedString = kotlinExtention.serialize() println(serializedString) println(serializedString.deserialize() as String) |
Результат:
1 2 |
kmonaaafheaaccelgphegmgjgocaefhihegfgohdgjgpgohdcaepgcgkgfgdhefdgfhcgjgbgmgjhkgfhc Kotlin Extensions ObjectSerializer |
Вот полная версия показанного выше кода:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
import java.io.* import kotlin.experimental.and @Throws(IOException::class) fun Serializable.serialize(): String { val serialObj = ByteArrayOutputStream() val objStream = ObjectOutputStream(serialObj) objStream.writeObject(this) objStream.close() return encodeBytes(serialObj.toByteArray()) } @Throws(IOException::class, ClassNotFoundException::class) fun String?.deserialize(): Any? { if (this.isNullOrEmpty()) return null val serialObj = ByteArrayInputStream(decodeBytes(this)) val objStream = ObjectInputStream(serialObj) return objStream.readObject() } private fun encodeBytes(bytes: ByteArray): String { val strBuf = StringBuffer() for (i in bytes.indices) { strBuf.append(((bytes[i].toInt() shr 4 and 0xF) + 'a'.toInt()).toChar()) strBuf.append(((bytes[i] and 0xF) + 'a'.toInt()).toChar()) } return strBuf.toString() } private fun decodeBytes(str: String): ByteArray { val bytes = ByteArray(str.length / 2) var i = 0 while (i < str.length) { var c = str[i] bytes[i / 2] = (c - 'a' shl 4).toByte() c = str[i + 1] bytes[i / 2] = bytes[i / 2].plus(((c - 'a'))).toByte() i += 2 } return bytes } |
5. Расширение дженериков (обобщений)
В этот раз мы еще и получим всю мощь дженериков. Создавая расширение, которое использует дженерик – мы сможем применить его к любому наследующему его классу.
Это очень удобно если нам нужно отредактировать какую-либо стороннюю библиотеку или API. Так что вместо наследования класса мы можем просто расширить его, что поможет сократить уйму шаблонного кода.
К примеру, если нам нужно найти чьё-то имя в списке или даже скопировать список имён, то это расширение поможет нам легко создать такой список.
В примере ниже я создал класс Student, который наследует класс User, и наше расширение будет собирать их имена.
1 2 3 4 5 6 7 |
abstract class User(open var id: Int, open var name: String) data class Student(override var id: Int, override var name: String) : User(id, name) fun <T : User> List<T>.getUserNameList(): List<String> { return this.map { it.name } } |
Применение:
1 2 3 4 5 6 |
val listOfUsers = mutableListOf<Student>() listOfUsers.add(Student(1, "Kotlin")) listOfUsers.add(Student(2, "Alex")) listOfUsers.add(Student(3, "Mohammed")) print(listOfUsers.getUserNameList()) |
И вывод:
1 |
[Kotlin, Alex, Mohammed] |
На этом всё! Спасибо за внимание, надеюсь вам понравилось и что-то вы примените и в своих проектах.
Интересен Kotlin? Посмотрите что у нас есть на эту тему!