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

Do you even Kotlin? (GDG Voronezh Meetup 2017)

Do you even Kotlin? (GDG Voronezh Meetup 2017)

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

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

Video: https://youtu.be/n_G7N8wGBBc

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