Kotlin: джависты, завидуйте

Около года назад, в подкасте радио-т я впервые услышал о инициативе JetBrains, новом языке программирования kotlin. С тех пор внимательно слежу за его развитием.
Они позиционируют котлин как “better java” и, надо сказать, у это получается. Это статический типизированный, язык со вшитой nullable-проверкой Так же он поддерживает функции высшего порядка(замыкания), extension functions и trait. Может немного напоминать scala – но, достаточно далеко от неё.


Впринципе, языков, в том числе и jvm-based достаточно много, но я обратил внимание именно на kotlin, потому что:

  • java – слишком проста. Как ассемблер. Её скудность синтаксиса вгоняет меня в уныние. Я хочу решать конкретные задачи, а не писать километры кода.
  • scala – слишком сложна. Она не только вносит свою систему типов, но и вводит непонятки в стиль написания кода. Те, кто пробовал её знают, что можно писать на скале как scala-style так и java-style.
  • scala – оооочень долго собирается. Не может язык, который призван решать практические задачи столько компилироваться. Или ради этого нужно поднимать свой кластер? Есть даже шутка: “Почему скала так долго собирается? В это время Одерский майнит биткоины” :D
  • groovy – классный, но медленный. И динамически типизированный. И почти не работает под андроидом
  • kotlin не тащит за собой всю систему типов – их небольшой рантайм расширяет стандартные коллекции и классы.

Код!

Пока мне больше видится использование kotlin на android (далее все примеры из мира андроид разработки). Всякие aop, groovy упрощяют жизнь в java-мире на серверах.

Доступ к компонентам интерфейса

При обращении к свойству происходит вычисление значения функции

class RiddleActivity: FragmentActivity() {
    val answer: TextView? get() = findView<TextView>(R.id.riddleAnswer)
    val question: TextView? get() = findView<TextView>(R.id.riddleQuestion)
    val nextQuestion: Button? get() = findView<Button>(R.id.riddleNextQuestion)
    . . . .
}

Регистрируем обработчики

private fun bind() {
    . . .
    array(barCounter, findView(R.id.riddleConfigureCategories)).forEach { v ->
        v?.setOnClickListener {
            CategoriesChooserFragment({ isChanged ->
                if (isChanged) currentOffset = 0
            }).show(getSupportFragmentManager(), "categories")
        }
    }
}

При этом добавляем методы ко всем классам View (extension functions)

public fun View.findView<T: View>(id: Int): T? = findViewById(id) as? T


public fun onClickListener(action: (View?) -> Unit): OnClickListener {
    return object : OnClickListener {
        override fun onClick(v: View) {
            action(v)
        }
    }
}

public fun View.setOnClickListener(action: (View?) -> Unit): Unit {
    setOnClickListener(onClickListener(action))
}

Просто описываем класс

open class Riddle(
    var id: Int = 0,
    var number: Int = 0,
    var text: String = "",
    var answer: String = "",
    val solved: Boolean = false,
    val purchased: Date? = null,
    val favorited: Date? = null
) {
}

Вытаскиваем данные в DAO

class RiddleDAO(val context: Context) {
    . . . .

    private fun transaction<T>(action: (database: SQLiteDatabase) -> T): T {
        var result: T
        open()
        val db = this.database
        if (db == null) throw SQLException("No database")
        db.beginTransaction()
        try {
            result = action(db)
            db.setTransactionSuccessful()
        } finally {
            db.endTransaction()
        }
        close()
        return result
    }

    fun setCategoryEnabled(categoryId: Int, isEnabled: Boolean) {
        transaction {
            val cv = ContentValues()
            cv.put(FIELD_ENABLED, if (isEnabled) 1 else 0)
            database?.update(TABLE_CATEGORIES, cv, "id = " + categoryId, null)
        }
    }

    fun count(onlyEnabled: Boolean = false): Int {
        return transaction {
            val query = "SELECT COUNT(*) FROM ${TABLE_RIDDLES} WHERE ${if (!onlyEnabled) "1=1" else ONLY_ENABLED_CONDITION}"
            val cursor = database?.rawQuery(query, null)
            cursor?.moveToFirst()
            cursor?.getInt(0) ?: 0
        }
    }
}

Иногда код выглядит очень эмоциональным!!

private fun getView(inflater: LayoutInflater?): View {
    val view = inflater!!.inflate(R.layout.categories, null)!!

java конструкции выглядят более человечными

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

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog? =
    AlertDialog.Builder(getActivity()!!)
        .setView(getView(getActivity()!!.getLayoutInflater()))
        .setTitle("Select categories")
        .setNeutralButton("Select all", object: DialogInterface.OnClickListener {
            override fun onClick(dialog: DialogInterface, which: Int) {
            }
        })
        .setPositiveButton("Ok", object : DialogInterface.OnClickListener {
            override fun onClick(dialog: DialogInterface, which: Int) {
            }
        })
        .create()

Унифицированный доступ к настройкам

Сам определяет тип по дефолтному аргументу, цепляется к любому Context (Activity, Service, ..)

public fun Context.loadPref<T>(key: String, default: T): T {
    val sp = getSharedPreferences(getClass().getCanonicalName(), 0)
    return when (default) {
        is Int -> sp.getInt(key, default)
        is Boolean -> sp.getBoolean(key, default)
        is String -> sp.getString(key, default)
        is Float -> sp.getFloat(key, default)
        is Long -> sp.getLong(key, default)
        else -> default
    } as T
}

....

val lastVersion = loadPref("version", 0);

Скачать бесплатно и без смс

Само приложние Riddles, куски кода которого я разместил можно скачать с Google Play

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