Java Beans as Kotlin Properties

Goal

Make Java get/set pairs accessible as properties in Kotlin

Examples

// simple getter
A getA()
->
val a: A
// getter and setter
A getA()
void setA(A)
->
var a: A
// only setter (write-only)
void setA(A)
->
var a: A
  // no get
  set(v) {...}
// private getter (effectively write-only)  
private A getA()
public void setA(A)
->
var a: A
  private get() = ...
  public set(v) = ...
// Different setter type  
A getA()
void setA(B)
->
var a: A
  get() = ...
  set(v: B) = ...
// Overloaded setters
A getA()
void setA(B)
void setA(C)
->
var a: A
  get() = ...
  set(v: B) = ...
  set(v: C) = ...

TODO

  • field access (may clash with property)
  • static field access (critical, syntax can't be changed)
  • How much old code does this break?
    • usages of Java APIs with getters/setters
    • inheritors of Java classes overriding getters/setters

Changes in Kotlin properties

  • allow many setters (overloaded)
  • allow no getter (no backing field + no explicit getter => no getter at all)
  • allow different modifiers on getters and properties
    • property modifiers are lub(get, set)
    • property is no less visible than its accessors
    • property is no “less final” than its accessors (final > open > abstract)

Changes in JavaDescriptorResolver

Definition. Getter of name N is // TODO: recognize “is” prefix?

Definition. Setter of name N is // TODO

  • All getters and setters of the same name are merged into (replaced by) a single property.
  • Its visibility/modality is computed as lub of all getters' and setters' visibilities/modalities.
  • (MAYBE) the respective functions (getters/setters) are also created as synthesized members (to enable calling them from Kotlin the Java way)
    • see difficult cases below (two abstracts in J1 and J2)

Difficult cases of inheritance

Two abstracts of incompatible shapes:

class J {
  abstract void setA(A)
}

class K {
  abstract fun setA(A)
}

class K1: J, K {
  // can't override both var J.a and fun K.setA
  // but the same problem exists in pure Kotlin code
  
  // but if we create a concrete synthetic setA() in J it will override K.setA()
  // but what if there are two setA()'s coming from J1 nad J2?
}

// Let's say we extend J and K with J1:

class J1 extend J, K {
  @Override
  void setA(A)
}

// and then extend it in K1

class K1 : J1 {
  // we can override either val a or fun setA(), but not both, which is fine
}

// If setA() were abstract in J1, we shouldn't be able to override only `var`, because it breaks the substitution principle

Abstract is J and concrete fun in K

class J {
  abstract void setA(A)
}

class K {
  fun setA(A) {}
}

class K1: J, K {
  override var a: A ...
}

Abstract is J and val in K is OK.

Abstract in J1 and J2:

class J1 {
  abstract void setA(A)
}

class J2 {
  abstract void setA(A)
}

class K : J1, J2 {
  // if there are two final setA(A) synthesized for supertypes, it's a conflict :(
}