Idiomatic Interop Kevin Most Doesn't Kotlin already have 100% - - PowerPoint PPT Presentation

idiomatic interop
SMART_READER_LITE
LIVE PREVIEW

Idiomatic Interop Kevin Most Doesn't Kotlin already have 100% - - PowerPoint PPT Presentation

Idiomatic Interop Kevin Most Doesn't Kotlin already have 100% interop? Yes, but the interop can either be pleasant or clumsy And some features from Kotlin won't work in Java Who should be thinking about this? Java library developers


slide-1
SLIDE 1

Kevin Most

Idiomatic Interop

slide-2
SLIDE 2

Doesn't Kotlin already have 100% interop?

  • Yes, but the interop can either be pleasant or clumsy
  • And some features from Kotlin won't work in Java
slide-3
SLIDE 3

Who should be thinking about this?

  • Java library developers
  • Kotlin library developers
  • Anyone working in a mixed codebase
slide-4
SLIDE 4

Your interop story should be

  • Safe
  • Performant
  • Readable
  • Discoverable
slide-5
SLIDE 5

@JvmHappiness

slide-6
SLIDE 6

@Jvm Annotations

  • Annotations that tell the Kotlin compiler how to expose code

to Java

slide-7
SLIDE 7

@JvmOverloads

fun Date.format( formatString: String, locale: Locale = defaultLocale() ): String format(date, "yyyyMMdd");

slide-8
SLIDE 8

@JvmOverloads

fun Date.format( formatString: String, locale: Locale = defaultLocale() ): String format( date, "yyyyMMdd", defaultLocale()) );;

slide-9
SLIDE 9

@JvmOverloads

@JvmOverloads fun Date.format( formatString: String, locale: Locale = defaultLocale() ): String format( date, "yyyyMMdd", defaultLocale()) );;

slide-10
SLIDE 10

@JvmOverloads

@JvmOverloads fun Date.format( formatString: String, locale: Locale = defaultLocale() ): String format(date, "yyyyMMdd"); defaultLocale()

slide-11
SLIDE 11

Don't get too carried away

data class User @JvmOverloads constructor( val id: String? = null, val name: String? = null, val username: String? = null, val gender: String? = null, val points: Int = 0 )

slide-12
SLIDE 12

"Consider a builder when faced with many constructor parameters"

  • Joshua Bloch
slide-13
SLIDE 13

@JvmStatic

  • Only for use in companion and named objects
  • On fun and val:
  • Generates a static method in the bytecode that delegates

through to that function/property

  • On var:
  • Also generates a static setter that delegates through
slide-14
SLIDE 14

@JvmStatic

User.Companion.create(); class User {A companion object {B fun create(): User }C }D

slide-15
SLIDE 15

@JvmStatic

class User {A companion object {B @JvmStatic fun create(): User }C }D User.Companion.create();

slide-16
SLIDE 16

@JvmStatic

User.create(); class User {A companion object {B @JvmStatic fun create(): User }C }D

slide-17
SLIDE 17

A less nested way?

User.create(); class User {A companion object {B @JvmStatic fun create(): User }C }D

slide-18
SLIDE 18

A less nested way?

User.create(); fun create(): User class User {A }D

slide-19
SLIDE 19

A less nested way?

User.create(); fun create(): User class User {A }D

slide-20
SLIDE 20

A less nested way?

User.create(); fun create(): User class User {A }D

slide-21
SLIDE 21

A less nested way?

User.create(); @file:JvmName("User") fun create(): User class User {A }D

slide-22
SLIDE 22

@Throws

slide-23
SLIDE 23
  • For Kotlin functions, property getters/setters, constructors
  • Adds throws FooException to generated method header

@Throws

slide-24
SLIDE 24

@Throws

interface Repository<T> { /** * @throws IOException if can't save */ fun save(obj: T) }D

slide-25
SLIDE 25

@Throws

interface Repository<T> { /** * @throws IOException if can't save */ fun save(obj: T) }D

slide-26
SLIDE 26

@Throws

interface Repository<T> { /** * @throws IOException if can't save */ fun save(obj: T) }D

class UserRepository implements Repository<User> { @Override public void save(User obj) { throw new IOException(""); } }

slide-27
SLIDE 27

@Throws

interface Repository<T> { /** * @throws IOException if can't save */ fun save(obj: T) }D

class UserRepository implements Repository<User> {A @OverrideB public void save(User obj) {C throw new IOException("");D }E }F

slide-28
SLIDE 28

@Throws

interface Repository<T> { /** * @throws IOException if can't save */ fun save(obj: T) }D

class UserRepository implements Repository<User> {A @OverrideB public void save(User obj) throws IOException {C throw new IOException("");D }E }F

slide-29
SLIDE 29

@Throws

interface Repository<T> { /** * @throws IOException if can't save */ fun save(obj: T) }D

class UserRepository implements Repository<User> {A @OverrideB public void save(User obj) throws IOException {C throw new IOException("");D }E }F

slide-30
SLIDE 30

@Throws

interface Repository<T> { /** * @throws IOException if can't save */ fun save(obj: T) }D

class UserRepository implements Repository<User> {A @OverrideB public void save(User obj) throws IOException {C throw new IOException("");D }E }F

slide-31
SLIDE 31

@Throws

interface Repository<T> { /** * @throws IOException if can't save */ @Throws(IOException::class) fun save(obj: T) }D

class UserRepository implements Repository<User> {A @OverrideB public void save(User obj) throws IOException {C throw new IOException("");D }E }F

slide-32
SLIDE 32

Extension functions

slide-33
SLIDE 33

Extension functions

  • You have a huge Java codebase with many Utils classes
  • You add Kotlin 🎊
  • You still have all these Utils 😨
  • Awesome new Kotlin code has to call into old Java utils 😣
slide-34
SLIDE 34

Extension functions

  • Can we convert our Utils classes to Kotlin extensions
  • While preserving their signatures in Java so existing call-sites

can stay as they are?

slide-35
SLIDE 35

Extension functions

class UserUtils { static boolean hasName(User user) {} static String getDisplayableName(User user) {} static boolean isAnonymous(User user) {} static boolean isFriendsWith(User subject, User other) {} }

UserUtils.isAnonymous(aUser); UserUtils.hasName(aUser); UserUtils.isAnonymous(aUser) UserUtils.hasName(aUser)

slide-36
SLIDE 36

Extension functions

val User.hasName: Boolean get() {} val User.displayableName: String get() {} val User.isAnonymous: Boolean get() {} fun User.isFriendsWith(other: User): Boolean {}

UserUtils.isAnonymous(aUser); UserUtils.hasName(aUser); UserUtils.isAnonymous(aUser) UserUtils.hasName(aUser)

slide-37
SLIDE 37

Extension functions

val User.hasName: Boolean get() {} val User.displayableName: String get() {} val User.isAnonymous: Boolean get() {} fun User.isFriendsWith(other: User): Boolean {}

UserUtils.isAnonymous(aUser); UserUtils.hasName(aUser); UserUtils.isAnonymous(aUser) UserUtils.hasName(aUser)

slide-38
SLIDE 38

Extension functions

val User.hasName: Boolean get() {} val User.displayableName: String get() {} val User.isAnonymous: Boolean get() {} fun User.isFriendsWith(other: User): Boolean {}

UserUtils.isAnonymous(aUser); UserUtils.hasName(aUser); aUser.isAnonymous aUser.hasName

slide-39
SLIDE 39

Extension functions

UserUtils.isAnonymous(aUser); UserUtils.hasName(aUser); aUser.isAnonymous aUser.hasName @file:JvmName("UserUtils") // default is UserUtilsKt val User.hasName: Boolean get() {} val User.displayableName: String get() {} val User.isAnonymous: Boolean get() {} fun User.isFriendsWith(other: User): Boolean {}

slide-40
SLIDE 40

Extension functions

UserUtils.isAnonymous(aUser); UserUtils.hasName(aUser); aUser.isAnonymous aUser.hasName @file:JvmName("UserUtils") // default is UserUtilsKt val User.hasName: Boolean get() {} val User.displayableName: String get() {} val User.isAnonymous: Boolean get() {} fun User.isFriendsWith(other: User): Boolean {}

slide-41
SLIDE 41

Extension functions

UserUtils.isAnonymous(aUser); UserUtils.hasName(aUser); aUser.isAnonymous aUser.hasName @file:JvmName("UserUtils") // default is UserUtilsKt val User.hasName: Boolean @JvmName("hasName") get() {} val User.displayableName: String get() {} val User.isAnonymous: Boolean get() {} fun User.isFriendsWith(other: User): Boolean {}

slide-42
SLIDE 42

Extension functions

UserUtils.isAnonymous(aUser); UserUtils.hasName(aUser); aUser.isAnonymous aUser.hasName @file:JvmName("UserUtils") // default is UserUtilsKt val User.hasName: Boolean @JvmName("hasName") get() {} val User.displayableName: String get() {} val User.isAnonymous: Boolean get() {} fun User.isFriendsWith(other: User): Boolean {}

slide-43
SLIDE 43

Inline functions

slide-44
SLIDE 44

Inline functions

  • Java compiler doesn't support inlining
  • Can still use inline functions, but they won't be inlined
  • Be mindful of performance
  • Cannot call inline functions with reified generics from Java
slide-45
SLIDE 45

Reified generics workaround

inline fun <reified T> View.firstViewOfType(): T? {}

slide-46
SLIDE 46

Reified generics workaround

inline fun <reified T> View.firstViewOfType(): T? = firstViewOfType(T::class.java) fun <T> View.firstViewOfType(type: Class<T>): T? {}

slide-47
SLIDE 47

Visibility

slide-48
SLIDE 48

Visibility

  • public, protected, private behave as expected
  • Java package-local has no equivalent in Kotlin
  • Kotlin internal has no equivalent in Java
slide-49
SLIDE 49

internal

  • Kotlin module-level visibility
  • Module: IDEA, Maven, Gradle, or Ant compilation unit
  • So external Java shouldn't be able to see it, right?
  • Unfortunately, internal -> public in bytecode
slide-50
SLIDE 50

Package-local

  • Java default modifier
  • Only visible within the same package
  • Kotlin doesn't (currently?) have a way to restrict members to

the same package

slide-51
SLIDE 51

private

  • Java classes can access an inner class' private member
  • Kotlin classes cannot
slide-52
SLIDE 52

!

slide-53
SLIDE 53

!

  • The dreaded platform type
  • Blows up when dereferenced
  • Most calls into Java will return a platform type
  • You should try to eliminate most/all of these in your own code
  • Solution: Nullability Annotations
slide-54
SLIDE 54

Nullability Annotations!

interface Request<T> {A Response<T> execute(); }B val request: Request<User> = getUser(id) request.execute().value.displayableName interface Response<T> {C T getValue(); }D

NPE

slide-55
SLIDE 55

Nullability Annotations!

interface Request<T> {A Response<T> execute(); }B val request: Request<User> = getUser(id) request.execute().value.displayableName interface Response<T> {C @Nullable T getValue(); }D

slide-56
SLIDE 56

Nullability Annotations!

kmost@kmost: ~/work/foursquare-android dev $ rg "@NonNull" --count --no-filename | paste -d+ -s - | bc 759 kmost@kmost: ~/work/foursquare-android dev $ rg "@Nullable" --count --no-filename | paste -d+ -s - | bc 475

  • Annotating everything is super-tedious
slide-57
SLIDE 57

Nullability Annotations!

static List<String> getUsers(); getUsers() // (Mutable)List<String!>!

slide-58
SLIDE 58

Nullability Annotations!

@NonNull static List<String> getUsers(); getUsers() // (Mutable)List<String!>!

slide-59
SLIDE 59

Nullability Annotations!

@NonNull static List<String> getUsers(); getUsers() // (Mutable)List<String!>

slide-60
SLIDE 60

Nullability Annotations!

@NonNull static List<@NonNull String> getUsers(); getUsers() // (Mutable)List<String!>

slide-61
SLIDE 61

Nullability Annotations!

@NonNull static List<@NonNull String> getUsers(); getUsers() // (Mutable)List<String>

slide-62
SLIDE 62

Nullability Annotations!

interface Request<T> {A Response<T> execute(); }B val request: Request<User> = getUser(id) request.execute().value.displayableName interface Response<T> {C @Nullable T getValue(); }D

slide-63
SLIDE 63

Nullability Annotations!

interface Request<T> {A Response<A@Nullable T> execute(); }B val request: Request<User> = getUser(id) request.execute().value.displayableName interface Response<T> {C T getValue(); }D

slide-64
SLIDE 64

Nullability Annotations!

interface Request<T> {A Response<T> execute(); }B val request: Request<User> = getUser(id) request.execute().value.displayableName interface Response<@Nullable T> {C T getValue(); }D

slide-65
SLIDE 65

Nullability Annotations!

interface Request<T> {A Response<T> execute(); }B val request: Request<User> = getUser(id) request.execute().value.displayableName interface Response<T> { @Nullable T getValue(); }D

slide-66
SLIDE 66

Nullability Annotations!

interface Request<T> {A Response<T> execute(); }B val request: Request<User> = getUser(id) request.execute().value.displayableName interface Response<T> { @Nullable T getValue(); @Nullable Error<T> getError(); @Nullable HttpResponse getRawResponse(); }D

slide-67
SLIDE 67

Nullability Annotations!

interface Request<T> {A Response<T> execute(); }B val request: Request<User> = getUser(id) request.execute().value.displayableName interface Response<T> { @Nullable T getValue(); @Nullable Error<@Nullable T> getError(); @Nullable HttpResponse getRawResponse(); }D

slide-68
SLIDE 68

Nullability Annotations

slide-69
SLIDE 69

Default Nullability Annotations

  • Added in Kotlin 1.1.50
  • Specify default annotations:
  • Per package
  • Per class
  • Per method
slide-70
SLIDE 70

Default Nullability Annotations

dependencies { compile "com.google.code.findbugs:jsr305:3.0.2" } compileKotlin.kotlinOptions.freeCompilerArgs = [ "-Xjsr305-annotations=enable" ]

slide-71
SLIDE 71

Default Nullability Annotations

  • JSR-305 comes with:
  • @ParametersAreNonnullByDefault
  • @ParametersAreNullableByDefault
  • Annotate a package to make all parameters for all functions

non-null or nullable by default

slide-72
SLIDE 72

Annotating a package

package-info.java @ParametersAreNonnullByDefault package com.example.kotlinconf;

slide-73
SLIDE 73

DIY Default Nullability Annotations

  • You're not limited to @ParametersAreNonnullByDefault
  • You can make your own nullability annotations
  • Let's look at the source for

@ParametersAreNonnullByDefault

slide-74
SLIDE 74

ParametersAreNonnullByDefault.java

@Nonnull @TypeQualifierDefault(ElementType.PARAMETER) public @interface ParametersAreNonnullByDefault {}

DIY Default Nullability Annotations

slide-75
SLIDE 75

ParametersAreNonnullByDefault.java

@Nonnull @TypeQualifierDefault(ElementType.PARAMETER) public @interface ParametersAreNonnullByDefault {}

DIY Default Nullability Annotations

slide-76
SLIDE 76

FieldsAreNonnullByDefault.java

@Nonnull @TypeQualifierDefault(ElementType.FIELD) public @interface FieldsAreNonnullByDefault {}

DIY Default Nullability Annotations

slide-77
SLIDE 77

FieldsAreNonnullByDefault.java

@Nonnull @TypeQualifierDefault(ElementType.FIELD) public @interface FieldsAreNonnullByDefault {}

DIY Default Nullability Annotations

slide-78
SLIDE 78

FieldsAreNullableByDefault.java

@Nullable @TypeQualifierDefault(ElementType.FIELD) public @interface FieldsAreNullableByDefault {}

DIY Default Nullability Annotations

slide-79
SLIDE 79

FieldsAreNullableByDefault.java

@Nullable @TypeQualifierDefault(ElementType.FIELD) public @interface FieldsAreNullableByDefault {} // not quite

DIY Default Nullability Annotations

slide-80
SLIDE 80

FieldsAreNullableByDefault.java

@CheckForNull @TypeQualifierDefault(ElementType.FIELD) public @interface FieldsAreNullableByDefault {}

// not quite

DIY Default Nullability Annotations

slide-81
SLIDE 81

Living the dream

package-info.java @ParametersAreNonnullByDefault @FieldsAreNullableByDefault @MethodsReturnNullableByDefault package com.example.kotlinconf;

slide-82
SLIDE 82

Nulls in libraries

  • These solutions only work if you control the code in question
  • How do you deal with Java libs that have null everywhere?
  • ex: Android
slide-83
SLIDE 83

Nulls in libraries

  • Ask your library maintainers
slide-84
SLIDE 84

Nulls in libraries

  • Submit your own PR
  • Annotations are easy and low-risk
  • Even if you "know" the nullability of members, letting the

compiler enforce it for you is better

slide-85
SLIDE 85

Lambdas and SAMs

slide-86
SLIDE 86

Lambdas and SAMs

  • Kotlin lambdas compile to a functional interface in Java
  • () -> R becomes Function0<R>
  • (T) -> R becomes Function1<T, R>
  • Java SAMs compile to a special syntax in Kotlin
  • SAMName { ... }
slide-87
SLIDE 87

SAMs

  • Unfortunately, Kotlin SAMs currently don't offer SAM syntax

in Kotlin

  • Right now, it's best to keep your SAM types in Java
slide-88
SLIDE 88

SAMs

public interface JavaSAM {
 void onClick(View view);
 } val sam = JavaSAM { view -> ... }

slide-89
SLIDE 89

SAMs

interface KotlinSAM {
 fun onClick(view: View)
 }B val sam = JavaSAM { view -> ... }

slide-90
SLIDE 90

SAMs

interface KotlinSAM {
 fun onClick(view: View)
 }B val sam = KotlinSAM { view -> ... }

slide-91
SLIDE 91

SAMs

interface KotlinSAM {
 fun onClick(view: View)
 }B val sam = KotlinSAM { view -> ... }

slide-92
SLIDE 92

SAMs

interface KotlinSAM {
 fun onClick(view: View)
 }B val sam = object : KotlinSAM {


  • verride fun onClick(view: View) {

...
 }
 }

slide-93
SLIDE 93
slide-94
SLIDE 94

"The other two [collection literals and SAM conversions] seem tractable in the foreseeable future"

slide-95
SLIDE 95
slide-96
SLIDE 96

Lambda signatures

  • Lambdas with receivers exported with receiver as 1st param
  • Nullability of types is lost in Java!
  • (Foo) -> Unit is equivalent to Foo?.() -> Unit from

Java's perspective

slide-97
SLIDE 97

Special Types

slide-98
SLIDE 98

Special Types

  • Most types are mapped between Java and Kotlin
  • There are exceptions:
  • Unit
  • Nothing
slide-99
SLIDE 99

Unit

  • Unit can be mapped to void in most cases in Java
  • It cannot, however, if Unit is the selected type for a generic
  • Ex: Lambdas. Java signature FunctionN<Args, Unit>
  • Java has to: return Unit.INSTANCE;
slide-100
SLIDE 100

Nothing

  • Nothing is the subtype of all other types
  • No instances exist, not even null
  • So a Nothing function can never return; must throw/loop
  • No type exists like this in Java
slide-101
SLIDE 101

Nothing

  • Generics of Nothing become raw types
  • List<Nothing> in Kotlin becomes List in Java
  • Actual Nothings become Void
  • Consumers just have to know the method will never return
slide-102
SLIDE 102

Wildcards

slide-103
SLIDE 103

Wildcards

  • In Java, all use-sites of a generic need to say if they are:
  • Covariant: ? extends Foo
  • Contravariant: ? super Foo
  • In Kotlin, you just put that declaration on the type param itself:
  • Covariant: out T
  • Contravariant: in T
slide-104
SLIDE 104

Wildcards

class Box<T> { T foo; }B <T> Box<T> box(T unboxed) { return new Box<>(unboxed); }D <T> T unbox(Box<T> box) { return box.foo; }F Box<Integer> boxedInt = box(3); List<Box<Number>> boxes = new ArrayList<>(); boxes.add(boxedInt);

slide-105
SLIDE 105

Wildcards

class Box<T> { T foo; }B <T> Box<T> box(T unboxed) { return new Box<>(unboxed); }D <T> T unbox(Box<T> box) { return box.foo; }F Box<Integer> boxedInt = box(3); List<Box<Number>> boxes = new ArrayList<>(); boxes.add(boxedInt);

slide-106
SLIDE 106

Wildcards

class Box<T> { T foo; }B <T> Box<T> box(T unboxed) { return new Box<>(unboxed); }D <T> T unbox(Box<T> box) { return box.foo; }F Box<Integer> boxedInt = box(3); List<Box<? extends Number>> boxes = new ArrayList<>(); boxes.add(boxedInt);

slide-107
SLIDE 107

Wildcards

class Box<T> { T foo; }B <T> Box<T> box(T unboxed) { return new Box<>(unboxed); }D <T> T unbox(Box<T> box) { return box.foo; }F Box<Integer> boxedInt = box(3); List<Box<? extends Number>> boxes = new ArrayList<>(); boxes.add(boxedInt);

slide-108
SLIDE 108

Wildcards

class Box<T> { T foo; }B <T> Box<T> box(T unboxed) { return new Box<>(unboxed); }D <T> T unbox(Box<T> box) { return box.foo; }F Box<Integer> boxedInt = box(3); List<Box<? extends Number>> boxes; boxes.add(boxedInt);

slide-109
SLIDE 109

Wildcards

class Box<T> { T foo; }B <T> Box<T> box(T unboxed) { return new Box<>(unboxed); }D <T> T unbox(Box<T> box) { return box.foo; }F Box<Integer> boxedInt = box(3); List<Box<? extends Number>> boxes; boxes.add(boxedInt); class Box<T>(val foo: T) fun <T> box(unboxed: T) = Box<T>(unboxed) fun <T> unbox(box: Box<T>) = box.foo val boxedInt: Box<Int> = box(3) val boxed = mutableListOf<Box<Number>>() boxed.add(boxedInt)

slide-110
SLIDE 110

Wildcards

class Box<T> { T foo; }B <T> Box<T> box(T unboxed) { return new Box<>(unboxed); }D <T> T unbox(Box<T> box) { return box.foo; }F Box<Integer> boxedInt = box(3); List<Box<? extends Number>> boxes; boxes.add(boxedInt); class Box<T>(val foo: T) fun <T> box(unboxed: T) = Box<T>(unboxed) fun <T> unbox(box: Box<T>) = box.foo val boxedInt: Box<Int> = box(3) val boxed = mutableListOf<Box<Number>>() boxed.add(boxedInt)

slide-111
SLIDE 111

Wildcards

class Box<T> { T foo; }B <T> Box<T> box(T unboxed) { return new Box<>(unboxed); }D <T> T unbox(Box<T> box) { return box.foo; }F Box<Integer> boxedInt = box(3); List<Box<? extends Number>> boxes; boxes.add(boxedInt); class Box<out T>(val foo: T) fun <T> box(unboxed: T) = Box<T>(unboxed) fun <T> unbox(box: Box<T>) = box.foo val boxedInt: Box<Int> = box(3) val boxed = mutableListOf<Box<Number>>() boxed.add(boxedInt)

slide-112
SLIDE 112

Wildcards

  • So out is roughly equivalent to extends
  • And in is roughly equivalent to super
  • Kotlin "fakes" declaration-site variance for Java by

generating wildcards for all variant generics in parameters

  • Return types remain invariant
  • Final covariant types remain invariant
slide-113
SLIDE 113

Wildcards

  • To override the default generic behavior:
  • @JvmWildcard if you want variance where there is none
  • @JvmSuppressWildcards if you don't want variance
slide-114
SLIDE 114

Data classes

slide-115
SLIDE 115

Data classes

  • Tuple-like classes; properties declared in constructor
  • Auto-generation of hashCode(), equals(), toString()
  • These work perfectly in Java
  • Auto-generation of copy(...)
  • This works okay in Java
  • Lack of default + named params makes it clunky
slide-116
SLIDE 116

Data classes

data class User( val id: String? = null, val name: String? = null, val username: String? = null, val gender: String? = null, val points: Int = 0 ) u.copy(u.getId(), u.getName(), u.getUsername(), u.getGender(), u.getPoints() + 1);

slide-117
SLIDE 117

Data classes

data class User( val id: String? = null, val name: String? = null, val username: String? = null, val gender: String? = null, val points: Int = 0 )

slide-118
SLIDE 118

data class User( val id: String? = null, val name: String? = null, val username: String? = null, val gender: String? = null, val points: Int = 0 ) { fun toBuilder() = Builder(this) class Builder(private var user: User = User()) { fun id(id: String?) = apply { user = user.copy(id = id) } fun name(name: String?) = apply { user = user.copy(name = name) } // ... fun build() = user } }

Data classes

slide-119
SLIDE 119

"Consider a builder when faced with many constructor parameters"

  • Joshua Bloch
slide-120
SLIDE 120

data class User( val id: String? = null, val name: String? = null, val username: String? = null, val gender: String? = null, val points: Int = 0 ) { fun toBuilder() = Builder(this) class Builder(private var user: User = User()) { fun id(id: String?) = apply { user = user.copy(id = id) } fun name(name: String?) = apply { user = user.copy(name = name) } // ... fun build() = user } }

Data classes

slide-121
SLIDE 121

data class User( val id: String? = null, val name: String? = null, val username: String? = null, val gender: String? = null, val points: Int = 0 ) { fun toBuilder() = Builder(this) class Builder(private var user: User = User()) { fun id(id: String?) = apply { user = user.copy(id = id) } fun name(name: String?) = apply { user = user.copy(name = name) } // ... fun build() = user } }

Data classes

slide-122
SLIDE 122

data class User( val id: String? = null, val name: String? = null, val username: String? = null, val gender: String? = null, val points: Int = 0 ) { fun toBuilder() = Builder(this) class Builder(private var user: User = User()) { fun id(id: String?) = apply { user = user.copy(id = id) } fun name(name: String?) = apply { user = user.copy(name = name) } // ... fun build() = user } }

Data classes

slide-123
SLIDE 123

data class User( val id: String? = null, val name: String? = null, val username: String? = null, val gender: String? = null, val points: Int = 0 ) { fun toBuilder() = Builder(this) class Builder(private var user: User = User()) { fun id(id: String?) = apply { user = user.copy(id = id) } fun name(name: String?) = apply { user = user.copy(name = name) } // ... fun build() = user } }

Data classes

slide-124
SLIDE 124

Overall Interop Thoughts

slide-125
SLIDE 125

Overall Interop Thoughts

  • kt ❤ java
  • Most of the time, interop Just Works™
  • But when writing non-private members, say to yourself:
  • When writing Kotlin: "How will this look in Java?"
  • When writing Java: "How will this look in Kotlin?"
slide-126
SLIDE 126

Quick Tip

slide-127
SLIDE 127

Quick Tip

  • Write (at least some) tests in the other language
  • If you use Java, write some Kotlin tests
  • If you write Kotlin, write some Java tests
  • Gives you insight into the ergonomics of your public API
slide-128
SLIDE 128

Resources

  • Calling Kotlin from Java: https://kotlinlang.org/docs/

reference/java-to-kotlin-interop.html

  • Calling Java from Kotlin: https://kotlinlang.org/docs/

reference/java-interop.html

slide-129
SLIDE 129

#kotlinconf17 Kevin Most

Thank you!