Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Do you even Kotlin? (GDG Voronezh Meetup 2017)

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

Do you even Kotlin? (GDG Voronezh Meetup 2017)

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

В этом докладе мы расскажем:
* откуда появился этот язык, и чем он выделяется из череды JVM-языков;
* почему стоит писать на Kotlin, и как это правильно делать;
* чем Kotlin лучше Java;
* как убедить своего менеджера проекта попробовать этот язык;
* с какими подводными камнями столкнулись при миграции на Kotlin в коммерческом Android-проекте.

Video: https://youtu.be/n_G7N8wGBBc

Avatar for Sergey Ryabov

Sergey Ryabov

June 22, 2017
Tweet

More Decks by Sergey Ryabov

Other Decks in Programming

Transcript

  1. Builders public class Builder { private int servingSize; // required

    private int servings; // required private int calories = 0; private int fat = 0; private int sodium = 0; private int carbohydrate = 0; public Builder setServingSize(int servingSize) { this.servingSize = servingSize; return this; } public Builder setServings(int servings) { this.servings = servings; return this; } public Builder setCalories(int calories) {...} public Builder setFat(int fat) {...} public Builder setSodium(int sodium) {...} public Builder setCarbohydrate(int carbohydrate) {...} JNutritionFacts build() { return new JNutritionFacts(servingSize, servings, calories, fat, sodium, carbohydrate); } } public class JNutritionFacts { int servingSize; int servings; int calories; int fat; int sodium; int carbohydrate; public JNutritionFacts() {} public JNutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) { this.servingSize = servingSize; this.servings = servings; this.calories = calories; this.fat = fat; this.sodium = sodium; this.carbohydrate = carbohydrate; } } public static void main(String[] args) { JNutritionFacts facts = new Builder() .setServingSize(100) .setServings(2) .setCalories(200) .setCarbohydrate(80) .setFat(25) .setSodium(12) .build(); }
  2. Builders val jFacts = JNutritionFacts().apply { servingSize = 100 servings

    = 2 calories = 200 fat = 80 sodium = 25 carbohydrate = 12 } class KNutritionFacts( var servingSize: Int, var servings: Int, var calories: Int = 0, var fat: Int = 0, var sodium: Int = 0, var carbohydrate: Int = 0 ) val kFacts = KNutritionFacts( servingSize = 100, servings = 2, calories = 200, fat = 80, sodium = 25, carbohydrate = 12 )
  3. Singletons public enum Elvis { INSTANCE; public void sing() {

    /* Do anything that you want to do, but uh-uh, Honey, lay off of my shoes Don't you step on my blue suede shoes. You can do anything but lay off of my blue suede shoes. */ } } Elvis.INSTANCE.sing(); object ViktorTsoi { fun sing() { /* Красная-красная кровь - Через час уже просто земля, Через два на ней цветы и трава, Через три она снова жива. И согрета лучами звезды По имени Солнце. */ } } ViktorTsoi.sing()
  4. Utils classes public final class Utils { public static int

    doubleInt(int param) { return param * 2; } public static String reverse(String src) { return new StringBuilder(src).reverse().toString(); } private Utils() { throw new AssertionError("You shall not pass!"); } public static void main(String[] args) { System.out.println(doubleInt(5)); System.out.println(reverse("Test")); } }
  5. Utils classes package utils fun doubleInt(param: Int) = param *

    2 fun String.reverse() = StringBuilder(this).reverse().toString() fun main(args: Array<String>) { println(doubleInt(5)) println("Test".reverse()) } // Usage in Java. public static void main(String[] args) { System.out.println(UtilsKt.doubleInt(5)); System.out.println(UtilsKt.reverse("Test")); }
  6. Final classes, methods, locals public final class JComplex { private

    final double re; private final double im; private JComplex(double re, double im) { this.re = re; this.im = im; } public static JComplex valueOf(double re, double im) { return new JComplex(re, im); } public static final JComplex ZERO = new JComplex(0, 0); public static final JComplex ONE = new JComplex(1, 0); public static final JComplex I = new JComplex(0, 1); // Accessors with no corresponding mutators public double realPart() { return re; } public double imaginaryPart() { return im; } public JComplex add(JComplex c) { return new JComplex(re + c.re, im + c.im); } public JComplex subtract(JComplex c) { return new JComplex(re - c.re, im - c.im); } public JComplex multiply(JComplex c) { return new JComplex(re * c.re - im * c.im, re * c.im + im * c.re); } public JComplex divide(JComplex c) { final double tmp = c.re * c.re + c.im * c.im; return new JComplex((re * c.re + im * c.im) / tmp, (im * c.re - re * c.im) / tmp); } }
  7. Final classes, methods, locals class KComplex(val re: Double, val im:

    Double) { fun add(c: KComplex) = KComplex(re + c.re, im + c.im) fun subtract(c: KComplex) = KComplex(re - c.re, im - c.im) fun multiply(c: KComplex) = KComplex(re * c.re - im * c.im, re * c.im + im * c.re) fun divide(c: KComplex): KComplex { val tmp = c.re * c.re + c.im * c.im return KComplex((re * c.re + im * c.im) / tmp, (im * c.re - re * c.im) / tmp) } companion object { val ZERO = KComplex(0.0, 0.0) val ONE = KComplex(1.0, 0.0) val I = KComplex(0.0, 1.0) } } // This class is open for extension. open class Point(open val x: Int, open val y: Int, private var color: Int)
  8. Null-checks class JEmployee { String name; JCompany company; Date employedAt;

    public JEmployee(String name, JCompany company, Date employedAt) { this.name = name; this.company = company; this.employedAt = employedAt; } } class JCompany { String name; String address; public JCompany(String name, String address) { this.name = name; this.address = address; } } void print(JEmployee employee) { final StringBuilder sb = new StringBuilder(); sb.append(employee.name); sb.append(" works for "); if (employee.company != null) { sb.append(employee.company.name); } else sb.append("food"); sb.append(" in "); if (employee.company != null && employee.company.address != null) { sb.append(employee.company.address); } else sb.append("cyberspace"); sb.append(" since "); if (employee.employedAt != null) { sb.append(employee.employedAt.toGMTString()); } else sb.append("birth"); System.out.println(sb.toString()); }
  9. Null-checks class KEmployee(val name: String, val company: KCompany? = null,

    val employedAt: Date? = null) class KCompany(val name: String, val address: String? = null) fun print(employee: KEmployee) { println("${employee.name} works for ${employee.company?.name ?: "food"} " + "in ${employee.company?.address ?: "cyberspace"} " + "since ${employee.employedAt?.toGMTString() ?: "birth"}") }
  10. Java Beans public class JUser { private final String id;

    private String firstName; private @Nullable String lastName; private @Nullable Date birthday; public JUser(String id, String firstName, @Nullable String lastName, @Nullable Date birthday) { this.id = id; this.firstName = firstName; this.lastName = lastName; this.birthday = birthday; } public String getId() { return id; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } @Nullable public String getLastName() { return lastName; } public void setLastName(@Nullable String lastName) { this.lastName = lastName; } @Nullable public Date getBirthday() { return birthday; } public void setBirthday(@Nullable Date birthday) { this.birthday = birthday; } @Override public String toString() { return "User{" + "id='" + id + '\'' + ", firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + ", birthday=" + birthday + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; JUser user = (JUser) o; if (!id.equals(user.id)) return false; if (!firstName.equals(user.firstName)) return false; if (lastName != null ? !lastName.equals(user.lastName) : user.lastName != null) return false; return birthday != null ? birthday.equals(user.birthday) : user.birthday == null; } @Override public int hashCode() { int result = id.hashCode(); result = 31 * result + firstName.hashCode(); result = 31 * result + (lastName != null ? lastName.hashCode() : 0); result = 31 * result + (birthday != null ? birthday.hashCode() : 0); return result; } }
  11. Java Beans data class KUser(val id: String, var firstName: String,

    var lastName: String?, var birthday: Date?) // Yes, that’s all.
  12. Named parameters private static void composePerson(String firstName, String lastName, int

    age, int shoeSize, double height, double weight, boolean isVegetarian) { // Some usefull code here. } public static void main(String[] args) { composePerson("Hamato", "Yoshi", 42, 39, 149.5, 72.0, true); }
  13. Named parameters private fun composePerson(firstName: String, lastName: String, age: Int,

    shoeSize: Int, height: Double, weight: Double, isVegetarian: Boolean) { // Some usefull code here. } fun main(args: Array<String>) { composePerson( firstName = "Hamato", lastName = "Yoshi", age = 42, shoeSize = 39, height = 149.5, weight = 72.0, isVegetarian = true ) }
  14. Smart casts private void printLength(@Nullable Object obj) { if (obj

    == null) { System.out.println("null"); } else if (obj instanceof String) { System.out.println(((String) obj).length()); } else if (obj instanceof Collection) { System.out.println(((Collection) obj).size()); } else if (obj instanceof Date) { System.out.println(((Date) obj).getTime()); } else { System.out.println(obj.hashCode()); } } private fun printLength(obj: Any?) { if (obj == null) { println(null) } else if (obj is String) { println(obj.length) } else if (obj is Collection<*>) { println(obj.size) } else if (obj is Date) { println(obj.time) } else { println(obj.hashCode()) } }
  15. Smart switch private static String describe(@Nullable Integer i) { if

    (i == null) return "null"; switch (i) { case 1: case 2: case 3: case 4: return "less than FIVE"; case 5: return "FIVE"; default: return "Error: Unexpected number!"; } } public static void main(String[] args) { System.out.println(describe(42)); }
  16. Smart switch private fun describe(i: Number?) = when (i) {

    null -> "null" in 1..4, -3 -> "less than FIVE" 5 -> "FIVE" is Double -> "WOW Double" !in 1..100 -> { "not in FIRST HUNDRED" } else -> { "Error: Unexpected number!" } } private fun Int.isOdd() = this % 2 != 0 private fun Int.isEven() = !this.isOdd() // Just a list of conditions to be tested. fun advanced(x: Int) = when { x.isOdd() -> print("x is odd") x.isEven() -> print("x is even") else -> print("x is funny") } fun main(args: Array<String>) { println(describe(42.0)) advanced(42) }
  17. Compile-time vs runtime: Sealed classes enum NType { MESSAGE, LIKE,

    POST_UPDATES } interface JNotification { NType getType(); } class JMessageNotification implements JNotification {...} class JLikeNotification implements JNotification {...} class JPostUpdatesNotification implements JNotification {...} private static String describe(JNotification n) { switch (n.getType()) { case MESSAGE: return "New message: " + ((JMessageNotification) n).text; case LIKE: return "You were " + (!((JLikeNotification) n).wasLiked ? " un " : "") + "liked"; case POST_UPDATES: return "You have " + ((JPostUpdatesNotification) n).newCommentsCount + " new comments"; default: throw new IllegalArgumentException("Unsupported type"); // Try to comment this line. } }
  18. Compile-time vs runtime: Sealed classes sealed class KNotification data class

    KMessageNotification(val userId: Long, val text: String) : KNotification() data class KLikeNotification(val userId: Long, val wasLiked: Boolean) : KNotification() data class KPostUpdatesNotification(val postId: Long, val newCommentsCount: Int) : KNotification() private fun describe(n: KNotification) = when (n) { is KMessageNotification -> "New message: ${n.text}" is KLikeNotification -> "You were ${if (!n.wasLiked) "un" else ""}liked" is KPostUpdatesNotification -> "You have ${n.newCommentsCount} new comments" // else -> "... You don’t need this anymore" }
  19. Compile-time vs runtime: Nonnull parameters private static JUser getUser() {

    return null; } private static void handleUser(JUser user) { Objects.requireNonNull(user, "User should not be null."); System.out.println("User's name: " + user.getFirstName()); } public static void main(String[] args) { handleUser(getUser()); } Exception in thread "main" java.lang.NullPointerException: User should not be null. at java.util.Objects.requireNonNull(Objects.java:228) at compiletime.Nullability.handleUser(Nullability.java:16) at compiletime.Nullability.main(Nullability.java:21)
  20. Compile-time vs runtime: Nonnull parameters ❗Error:(19, 14) Kotlin: Type mismatch:

    inferred type is KUser? but KUser was expected private fun getUser(): KUser? { return null } private fun handleUser(user: KUser) { println("User's name: ${user.firstName}") } fun main(args: Array<String>) { handleUser(getUser()) }
  21. Compile-time vs runtime: Immutable Collections private static List<String> getProtectedTurtles() {

    return Collections.unmodifiableList(getTurtles()); } private static List<String> getTurtles() { return new ArrayList<>(asList("Leo", "Don", "Raf", "Mike")); } private static void infiltrate(List<String> src) { src.add("Shredder"); } public static void main(String[] args) { infiltrate(getTurtles()); infiltrate(getProtectedTurtles()); } Exception in thread "main" java.lang.UnsupportedOperationException at java.util.Collections$UnmodifiableCollection.add(Collections.java:1055) at compiletime.Collections.easterEgg(Collections.java:23) at compiletime.Collections.main(Collections.java:29)
  22. Compile-time vs runtime: Immutable Collections private fun getProtectedTurtles(): List<String> {

    return listOf("Leo", "Don", "Raf", "Mike") } private fun getTurtles(): MutableList<String> { return mutableListOf("Leo", "Don", "Raf", "Mike") } private fun infiltrate(src: MutableList<String>) { src.add("Shredder") } fun main(args: Array<String>) { infiltrate(getTurtles()) infiltrate(getProtectedTurtles()) } ❗Error:(23, 14) Kotlin: Type mismatch: inferred type is List<String> but MutableList<String> was expected
  23. How to start with Kotlin • Exceptional Java Interoperability •

    Kotlin Code Reviews • Java to Kotlin Convertor in IntelliJ • Start small - unit tests & util classes are your best choice • Migrate code that you refactor • Kotlin for all new features
  24. Problems • Java Annotations on Kotlin properties • Inferring @Nullable

    & @Nonnull from Java code • Lombok-unfriendly • Absence of best-practices
  25. Thank you • Kotlin Portal http://kotl.in • Kotlin Docs https://kotlinlang.org/docs/reference

    • Try online https://try.kotlinlang.org • Kotlin Koans https://kotlinlang.org/docs/tutorials/koans.html • Code from this talk https://github.com/colriot/talk-kotlin-vs-java • Kotlin in Action. Dmitry Jemerov, Svetlana Isakova • Kotlin for Android Developers. Antonio Leiva