Отличия Kotlin от Java | OTUS

Отличия Kotlin от Java

Как и Java, Kotlin является статически типизированным языком. Он поддерживает и процедурное, и объектно-ориентированное программирование, а также имеет ряд преимуществ, о которых мы и поговорим.

Null-безопасность

При попытке возвращения либо присваивания null код не скомпилируется, то есть Kotlin не допустит возникновения NullPointerException и выдаст ошибку компиляции.

Тем не менее в языке существует поддержка Nullable-типов. Задать такую переменную либо функцию мы можем, приписав ? к названию типа:

val name: String? = null // присваиваем null, код компилируется.
fun getName() : String? = null // возвращается null, код компилируется.
/* неправильно */   
val name: String? = null
val len = name.length   

/* правильно */ 
val name: String? = null    
val len = name?.length

Классы данных (Data Classes)

В Kotlin есть специальные классы, которые предназначены специально для хранения данных. С их помощью можно генерировать разные шаблоны: hashCode(), equals(), toString(), сеттеры, геттеры и т. д. Посмотрим код на Джава:

class Book {
   private String title;
   private Author author;
   public String getTitle() {
       return title;
   }
   public void setTitle(String title) {
       this.title = title;
   }
   public Author getAuthor() {
       return author;
   }
   public void setAuthor(Author author) {
       this.author = author;
   }
}

А теперь посмотрим на Kotlin-вариант:

/* Kotlin */
data class Book(var title:String,var author:Author)

Кроме того, можно легко создавать копии классов данных посредством метода copy():

val book = Book("Kotlin", "JetBrains")
val copy = book.copy()

Функции-расширения

Kotlin даёт возможность расширять функциональность существующих классов и не прибегать при этом к наследованию. Для этого существуют функции-расширения. Чтобы объявить такую функцию, надо к её имени приписать префикс в виде расширяемого типа. Давайте добавим функцию swap в MutableList:

fun MutableList<Int>.swap(index1:Int,index2:Int){
   val tmp=this[index1]
   this[index1]=this[index2]
   this[index2]=tmp
}

Ключевое слово this внутри такой функции-расширения относится к объекту-получателю, передаваемому перед точкой. В результате можно применить функцию swap к любому изменяемому списку:

val abc = mutableListOf(1, 2, 3)
abc.swap(0, 2)

Умные приведения типов

Если речь заходит о приведениях типов, компилятор Kotlin весьма умён, т. к. во многих случаях нет необходимости явно указывать операторы приведения, ведь в языке присутствует оператор is, делающий всю работу за вас:

fun demo(x:Any) {
   if(x is String) {
      print(x.length)  // x автоматически приводится к типу данных String
   }
}

Вывод типов

В языке программирования Kotlin совсем необязательно указывать тип переменной явно:

/* неявное определение */
fun main(args: Array<String>) {
   val text = 10
   println(text)
}

/* явное определение */
fun main(args: Array<String>) {
   val text: Int = 10
   println(text)
}

Функциональное программирование

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

Рассмотрим пример работы Kotlin с коллекциями:

fun main(args: Array<String>) {
   val numbers = arrayListOf(15, -5, 11, -39)
   val nonNegativeNumbers = numbers.filter { it >= 0 }
   println(nonNegativeNumbers)
}
// Вывод: 15, 11

Говоря о функциях высшего порядка, мы говорим о функциях, принимающих другие функции в качестве аргументов и возвращающих функции. Например:

fun alphaNum(func: () -> Unit) {}

Здесь func — имя аргумента, а ( ) -> Unit — тип функции. Мы говорим, что func будет функцией, ничего не возвращающей и не принимающей аргументов.

Лямбда-выражения — это анонимные функции, которые не объявляются, а передаются в виде выражений:

val sum: (Int, Int) -> Int = { x, y -> x + y }

Здесь мы объявили переменную sum, берущую 2 числа, складывающую их и принимающую значение суммы, приведённое к целому. При этом для вызова достаточно простого sum(2,2).

Гибкий и простой синтаксис

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

Для сравнения приведём код на Java:

/* Программа на Java */ 
public class Address {
   private String street;
   private int streetNumber;
   private String postCode;
   private String city;
   private Country country;
   public Address(String street, int streetNumber, String postCode, String city, Country country) {
       this.street = street;
       this.streetNumber = streetNumber;
       this.postCode = postCode;
       this.city = city;
       this.country = country;
   }
   @Override
   public boolean equals(Object o) {
       if (this == o) return true;
       if (o == null || getClass() != o.getClass()) return false;
       Address address = (Address) o;
       if (streetNumber != address.streetNumber) return false;
       if (!street.equals(address.street)) return false;
       if (!postCode.equals(address.postCode)) return false;
       if (!city.equals(address.city)) return false;
       return country == address.country;
   }
   @Override
   public int hashCode() {
       int result = street.hashCode();
       result = 31 * result + streetNumber;
       result = 31 * result + postCode.hashCode();
       result = 31 * result + city.hashCode();
       result = 31 * result + (country != null ? country.hashCode() : 0);
       return result;
   }
   @Override
   public String toString() {
       return "Address{" +
               "street='" + street + '\'' +
               ",     streetNumber=" + streetNumber +
               ",     postCode='" + postCode + '\'' +
               ",     city='" + city + '\'' +
               ",     country=" + country +
               '}';
   }
   public String getStreet() {
       return street;
   }
   public void setStreet(String street) {
       this.street = street;
   }

   public int getStreetNumber() {
       return streetNumber;
   }
   public void setStreetNumber(int streetNumber) {
       this.streetNumber = streetNumber;
   }
   public String getPostCode() {
       return postCode;
   }
   public void setPostCode(String postCode) {
       this.postCode = postCode;
   }
   public String getCity() {
       return city;
   }
   public void setCity(String city) {
       this.city = city;
   }
   public Country getCountry() {
       return country;
   }
   public void setCountry(Country country) {
       this.country = country;
   }
}

А теперь то же самое, но уже на Kotlin:

/* Та же программа на Kotlin */
data class Address(var street:String,
                   var streetNumber:Int,
                   var postCode:String,
                   var city:String,
                   var country:Country)

Источник: «Overview of Kotlin & Comparison With Java».

Не пропустите новые полезные статьи!

Спасибо за подписку!

Мы отправили вам письмо для подтверждения вашего email.
С уважением, OTUS!

Автор
0 комментариев
Для комментирования необходимо авторизоваться
Популярное
Сегодня тут пусто
Запланируй обучение с выгодой!
Получи скидку 10% на все курсы ноября и декабря до 17.11 →