概要

KotlinSwingコンポーネントを使用するサンプルと、Javaからの変換に関するメモです。

実行環境

$ curl -s "https://get.sdkman.io" | bash
$ sdk install kotlin
$ kotlinc -version
info: kotlinc-jvm 1.3.0 (JRE 1.8.0_192-b12)
info: kotlinc-jvm 1.6.10 (JRE 1.8.0_322-b06)
$ kotlinc hello.kt -include-runtime -d hello.jar && "$JAVA_HOME/bin/java" -jar hello.jar

Gradle

#spanend
#spanadd
buildscript {
#spanend
  ext.kotlin_version = 'latest.release'
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
  }
#spanadd
}
#spanend
#spanadd

#spanend
#spanadd
apply plugin: 'kotlin'
#spanend
#spanadd
apply plugin: 'application'
#spanend
#spanadd

#spanend
#spanadd
// group 'KotlinSwingTips'
#spanend
#spanadd
// version '1.0-SNAPSHOT'
#spanend
#spanadd

#spanend
#spanadd
mainClassName = 'example.AppKt'
#spanend
#spanadd

#spanend
#spanadd
defaultTasks 'run'
#spanend
#spanadd

#spanend
#spanadd
repositories {
#spanend
  mavenCentral()
  jcenter()
#spanadd
}
#spanend
#spanadd

#spanend
#spanadd
dependencies {
#spanend
  // implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
  implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
  testImplementation 'junit:junit:4.11'
  testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
#spanadd
}
#spanend
#spanadd

#spanend
#spanadd
sourceCompatibility = 1.8
#spanend
#spanadd

#spanend
#spanadd
compileKotlin {
#spanend
  kotlinOptions.jvmTarget = "$sourceCompatibility"
#spanadd
}
#spanend
#spanadd

#spanend
#spanadd
compileTestKotlin {
#spanend
  kotlinOptions.jvmTarget = "$sourceCompatibility"
#spanadd
}
#spanend
#spanadd

#spanend
#spanadd
jar {
#spanend
  manifest { attributes 'Main-Class': "$mainClassName" }
  from { configurations.compileClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
#spanadd
}
#spanend
#spanadd

IntelliJの自動変換

  • メニューのCode, Convert Java File to Kotlin File(Ctrl+Alt+Shift+K)で、JavaのソースコードをKtolinのソースコードに変換可能
  • メニューのCode, Convert Java File to Kotlin File(Ctrl+Alt+Shift+K)で、JavaのソースコードをKotlinのソースコードに変換可能
    • Hoge.javaを変換するとHoge.ktが生成されて、Hoge.javaは削除されてしまうので注意が必要
      • Undo(Ctrl+Z)で変換は取り消されて削除されたJavaファイルは復活する
  • Fileメニュー、Project StructureパネルのProject SDK<No SDK>になっていると、@Overrideoverrideに変換されない場合がある
  • Ktolinプロジェクトを作成して適当なApp.ktを作成し、そこにJavaのソースコードをコピー・ペーストすると自動変換可能
    • Ctrl+Alt+Shift+Kでファイル変換するより高機能?

Improved Java to Kotlin converter

  • Kotlin 1.3.50 released | Kotlin Blog

    In the future, the new converter is going to become the default one. In this release, it’s available for preview. To turn it on, specify the Use New J2K (experimental) flag in settings.

  • Kotlin 1.3.50IntelliJ IDEAConvert Java File to Kotlin File(Ctrl+Alt+Shift+K)が実験的に改良されている
    • Use New J2K(experimental)フラグで試用可能になるが、設定をJ2Kで検索しても見つからない
    • Settingsダイアログ→Languages & FrameworksUse New Java to Kotlin Converterにチェックを入れる必要がある
    • Kotlin Plugin1.4.31-release-IJ2019.2-1から1.3.50-release-IJ2019.2-1にするとUse New Java to Kotlin Converterにチェックをしているかどうかに関わらず、KotlinNullPointerExceptionで変換に失敗する場合がある?
  • クリップボード経由の変換は1.3.50-release-IJ2019.2-1でもエラーにならず実行可能
  • 以下のほとんどがUse New J2K(experimental)をオンにすると修正されている

演算子の優先順位

  • 以下のようなソースコードをIntelliJで自動変換した場合、論理演算子の左シフト(<<)はshlに変換され、またandの優先順位が異なるので括弧が減る

Java

btn.setSelected((i & (1 << 2)) != 0);

Kotlin(自動変換)

btn.setSelected(i and (1 shl 2) != 0)
#spandel
// 時々?に誤変換される場合がある?
#spanend
#spanadd
// 以下のように誤変換される場合がある???
#spanend
#spanadd
// 再現しないので勘違いかもしれない
#spanend
// btn.setSelected(i and (1 shl 2 != 0))

型パラメータ

  • IntelliJの自動変換ではArrays.asList(...)の型パラメータを自動的に変換してくれない

型パラメータ(修正済)

  • 修正済 IntelliJの自動変換ではArrays.asList(...)の型パラメータを自動的に変換してくれない
    • Arrays.<Component>asList(...)と型パラメータを明示しても省略される
    • for (c in Arrays.asList(...)) {に変換されてerror: none of the following functions can be called with the arguments supplied:になる
    • for (c: Component in Arrays.asList<Component>(...)) {、またはfor (c in listOf<Component>(...)) {などに手動で修正(for (c: Component in Arrays<Component>.asList(...)) {も可能?)
  • 型引数
    • JavaKotlinでは型引数を付ける位置が異なるので注意
  • KotlinJava風にfor (c in Arrays.<Component>asList(...)) {を使用すると、error: expecting an elementとエラーになる

Java

Box box = Box.createVerticalBox();
for (Component c: Arrays.<Component>asList(textField1, textField2, combo3, combo4)) {
  box.add(c);
  box.add(Box.createVerticalStrut(5));
}
// for (Component c: Arrays.asList(...)) { でも同じ変換結果になる

Kotlin(自動変換)

val box = Box.createVerticalBox()
for (c in Arrays.asList(textField1, textField2, combo3, combo4)) {
  box.add(c)
  box.add(Box.createVerticalStrut(5))
}
// error: none of the following functions can be called with the arguments supplied:
// public open fun add(p0: Component!): Component! defined in javax.swing.Box
// public open fun add(p0: PopupMenu!): Unit defined in javax.swing.Box
//             box.add(c)
//                 ^

Kotlin(手動修正)

val box = Box.createVerticalBox()
for (c in Arrays.asList<Component>(textField1, textField2, combo3, combo4)) {
  box.add(c);
  box.add(Box.createVerticalStrut(5));
}
// or:
// for (c in listOf<Component>(textField1, textField2, combo3, combo4)) {
// for (c in Arrays<Component>.asList(textField1, textField2, combo3, combo4)) {

境界型パラメータ

  • IntelliJの自動変換で以下のような境界型パラメータをもつJavaクラスを変換し、kotlincでコンパイルすると無駄な境界型パラメータが存在すると警告してくれる
    • warning: 'LocalDate' is a final type, and thus a value of the type parameter is predetermined
    • LocalDateクラスはfinalで継承不可のため、境界型パラメータは無意味
    • ECJ(Eclipse Compiler for Java)もThe type parameter T should not be bounded by the final type LocalDate. Final types cannot be further extendedと警告してくれる
    • Java側をclass CalendarViewTableModel<T extends TemporalAdjuster> extends DefaultTableModel {か、class CalendarViewTableModel extends DefaultTableModel {に修正する

Java

class CalendarViewTableModel<T extends LocalDate> extends DefaultTableModel {
  public CalendarViewTableModel(T date) {

Kotlin(自動変換)

class CalendarViewTableModel<T : LocalDate> public constructor(date: T) : DefaultTableModel() {

クラスリテラル

  • 総称型を持つクラスのクラスリテラル(Class Literals)をIntelliJで自動変換すると失敗する場合がある

クラスリテラル(修正済)

  • 修正済 総称型を持つクラスのクラスリテラル(Class Literals)をIntelliJで自動変換すると失敗する場合がある
  • 逆にJavac instanceof JComboBoxKotlinに変換するときは、c is JComboBox<*>にしないとエラーになる
    • こちらはIntelliJの自動変換で正常に変換可能
    • One type argument expected. Use 'JComboBox<*>' if you don't want to pass type arguments

Java

enabledCheck.addActionListener(e -> {
  Container c = SwingUtilities.getAncestorOfClass(JComboBox.class, this);
  if (c instanceof JComboBox) {
    JComboBox<?> combo = (JComboBox<?>) c;
    ComboItem item = (ComboItem) combo.getItemAt(data.getIndex());
    item.setEnabled(((JCheckBox) e.getSource()).isSelected());
    editableCheck.setEnabled(item.isEnabled());
    textField.setEnabled(item.isEnabled());
  }
});

Kotlin(自動変換)

enabledCheck.addActionListener({ e ->
  val c = SwingUtilities.getAncestorOfClass(JComboBox<*>::class.java, this)
  if (c is JComboBox<*>) {
    val combo = c as JComboBox<*>
    val item = combo.getItemAt(data.index) as ComboItem
    item.isEnabled = (e.getSource() as JCheckBox).isSelected()
    editableCheck.setEnabled(item.isEnabled)
    textField.setEnabled(item.isEnabled)
  }
})

Kotlin(手動修正)

#spandel
enabledCheck.addActionListener({ e ->
#spanend
#spanadd
enabledCheck.addActionListener {
#spanend
  val c = SwingUtilities.getAncestorOfClass(JComboBox::class.java, this)
  if (c is JComboBox<*>) {
    val item = c.getItemAt(data.index) as ComboItem
    item.isEnabled = (e.getSource() as JCheckBox).isSelected()
    editableCheck.setEnabled(item.isEnabled)
    textField.setEnabled(item.isEnabled)
  }
#spandel
})
#spanend
#spanadd
}
#spanend

型引数の省略

型引数の省略(修正済)

  • 修正済 Java 7以降で可能なジェネリッククラスのコンストラクタ呼び出しに必要な型引数を省略する記法(<>, diamond operator)(Generic Types (The Java™ Tutorials > Learning the Java Language > Generics (Updated)))は、IntelliJの自動変換では正常に変換されない場合がある
  • 例えば、以下のJavaコードのrenderer = new CheckBoxCellRenderer<>();部分は、renderer = CheckBoxCellRenderer<example.CheckBoxNode>()に変換され、Type mismatch: inferred type is CheckBoxCellRenderer<CheckBoxNode> but CheckBoxCellRenderer<E>? was expectedとエラーになる
    • これをrenderer = CheckBoxCellRenderer<>()と空にすると、今度はType expectedとエラーになる
    • Kotlinには<>, diamond operatorは存在せず?、型引数を省略したい場合は何も書く必要がない(Generics: in, out, where - Kotlin Programming Language)
    • renderer = CheckBoxCellRenderer<E>()、またはrenderer = CheckBoxCellRenderer()にすれば正常に動作する
    • 変換元のJavaコードがrenderer = new CheckBoxCellRenderer<E>();なら、IntelliJの自動変換ではrenderer = CheckBoxCellRenderer()になり正常に動作する
  • JList<CheckBoxNode> list2 = new CheckBoxList<>(model);が、なぜかval list2 = CheckBoxList<E>(model)に変換されるバグも存在する?
    • 正しくval list2 = CheckBoxList<CheckBoxNode>(model)に変換される場合もあるが、条件が不明
    • どちらにしても、val list2 = CheckBoxList(model)には変換してくれない

Java

class CheckBoxList<E extends CheckBoxNode> extends JList<E> {
  private transient CheckBoxCellRenderer<E> renderer;
  protected CheckBoxList(ListModel<E> model) {
    super(model);
  }
  @Override public void updateUI() {
    setForeground(null);
    setBackground(null);
    setSelectionForeground(null);
    setSelectionBackground(null);
    removeMouseListener(renderer);
    removeMouseMotionListener(renderer);
    super.updateUI();
    renderer = new CheckBoxCellRenderer<>();
    setCellRenderer(renderer);
    addMouseListener(renderer);
    addMouseMotionListener(renderer);
    putClientProperty("List.isFileList", Boolean.TRUE);
  }
  // ...
}

Kotlin(自動変換)

internal class CheckBoxList<E : CheckBoxNode> protected constructor(model: ListModel<E>) : JList<E>(model) {
  @Transient
  private var renderer: CheckBoxCellRenderer<E>? = null

  override fun updateUI() {
    setForeground(null)
    setBackground(null)
    setSelectionForeground(null)
    setSelectionBackground(null)
    removeMouseListener(renderer)
    removeMouseMotionListener(renderer)
    super.updateUI()
    renderer = CheckBoxCellRenderer<example.CheckBoxNode>()
    // renderer = CheckBoxCellRenderer<CheckBoxNode>() // でもType mismatchでエラー
    setCellRenderer(renderer)
    addMouseListener(renderer)
    addMouseMotionListener(renderer)
    putClientProperty("List.isFileList", java.lang.Boolean.TRUE)
  }
  // ...
}

Kotlin(手動修正)

internal class CheckBoxList<E : CheckBoxNode> protected constructor(model: ListModel<E>) : JList<E>(model) {
  @Transient
  private var renderer: CheckBoxCellRenderer<E>? = null

  override fun updateUI() {
    setForeground(null)
    setBackground(null)
    setSelectionForeground(null)
    setSelectionBackground(null)
    removeMouseListener(renderer)
    removeMouseMotionListener(renderer)
    super.updateUI()
    renderer = CheckBoxCellRenderer<E>()
    // renderer = CheckBoxCellRenderer<>() // NG
    // renderer = CheckBoxCellRenderer() // OK
    setCellRenderer(renderer)
    addMouseListener(renderer)
    addMouseMotionListener(renderer)
    putClientProperty("List.isFileList", java.lang.Boolean.TRUE)
  }
  // ...
}

匿名内部クラス

  • IntelliJの自動変換ではerror: this class does not have a constructor tree.addTreeWillExpandListener(object : TreeWillExpandListener() {とエラーになる
    • IntelliJ 2018.2.5の自動変換では正しく変換されるようになっている
  • インターフェイスから匿名内部クラスのインスタンスを生成する場合は()は不要
  • kotlin 1.4.0からkotlinのインターフェイスでもラムダでの記述も可能になった

Java

tree.addTreeWillExpandListener(new TreeWillExpandListener() {
  @Override public void treeWillExpand(TreeExpansionEvent e)
      throws ExpandVetoException {
    //throw new ExpandVetoException(e, "Tree expansion cancelled");
  }
  @Override public void treeWillCollapse(TreeExpansionEvent e)
      throws ExpandVetoException {
    throw new ExpandVetoException(e, "Tree collapse cancelled");
  }
});

Kotlin(自動変換)

tree.addTreeWillExpandListener(object : TreeWillExpandListener() {
  @Override
  @Throws(ExpandVetoException::class)
  fun treeWillExpand(e: TreeExpansionEvent) {
    //throw new ExpandVetoException(e, "Tree expansion cancelled");
  override fun treeWillExpand(e: TreeExpansionEvent) {
    // throw new ExpandVetoException(e, "Tree expansion cancelled");
  }
  @Override
  @Throws(ExpandVetoException::class)
  fun treeWillCollapse(e: TreeExpansionEvent) {
  override fun treeWillCollapse(e: TreeExpansionEvent) {
    throw ExpandVetoException(e, "Tree collapse cancelled")
  }
})

Kotlin(手動修正)

tree.addTreeWillExpandListener(object : TreeWillExpandListener {
  @Throws(ExpandVetoException::class)
  override fun treeWillExpand(e: TreeExpansionEvent) {
    //throw new ExpandVetoException(e, "Tree expansion cancelled");
    // throw new ExpandVetoException(e, "Tree expansion cancelled");
  }
  @Throws(ExpandVetoException::class)
  override fun treeWillCollapse(e: TreeExpansionEvent) {
    throw ExpandVetoException(e, "Tree collapse cancelled")
  }
})

ラベル付きReturn

  • 匿名内部クラスからreturnで抜けるJavaコードをIntelliJの自動変換で変換すると、unresolved reference: @decodeとエラーになるKotlinコードに変換される場合がある
  • 匿名内部クラスからreturnで抜けるJavaコードをIntelliJの自動変換で変換すると、unresolved reference: @decodeとエラーになるKotlinコードに変換される場合がある

Java

JButton decode = new JButton("decode");
decode.addActionListener(e -> {
  String b64 = textArea.getText();
  if (b64.isEmpty()) {
    return;
  }
  try (InputStream is = ...) {
    label.setIcon(new ImageIcon(ImageIO.read(is)));
  } catch (IOException ex) {
    ex.printStackTrace();
  }
});

Kotlin(自動変換)

val decode = JButton("decode")
decode.addActionListener({ e ->
  val b64 = textArea.getText()
  if (b64.isEmpty()) {
    return@decode.addActionListener
  }
  try {
    // ...
  } catch (ex: IOException) {
    ex.printStackTrace()
  }
})

Kotlin(手動修正)

val decode = JButton("decode")
#spandel
decode.addActionListener({ e ->
#spanend
#spanadd
decode.addActionListener {
#spanend
  val b64 = textArea.getText()
  if (b64.isEmpty()) {
    return@addActionListener
  }
  try {
    // ...
  } catch (ex: IOException) {
    ex.printStackTrace()
  }
#spandel
})
#spanend
#spanadd
}
#spanend

tryの戻り値

  • Optional.map(...)などからreturnで戻り値を返すJavaコードをIntelliJの自動変換で変換すると、ReturnのラベルがエラーになるKotlinコードに変換される場合がある
    • @Optional.ofNullable(getClass().getResource("unkaku_w.png")).mapがラベルになっている?
  • Kotlinではtryは式で戻り値を持つことが可能なので、ラベル付きReturnは不要
  • Try is an expression - Exceptions: try, catch, finally, throw, Nothing - Kotlin Programming Language
Java
#spanend
#spanadd
BufferedImage bi = Optional.ofNullable(getClass().getResource("unkaku_w.png"))
#spanend
    .map(url -> {
      try {
        return ImageIO.read(url);
      } catch (IOException ex) {
        return makeMissingImage();
      }
    }).orElseGet(() -> makeMissingImage());
#spanadd
Kotlin(自動変換)
#spanend
#spanadd
val bi = Optional.ofNullable(javaClass.getResource("unkaku_w.png")).map({ url ->
#spanend
  try {
    return@Optional.ofNullable(getClass().getResource("unkaku_w.png")).map ImageIO . read url
  } catch (ex: IOException) {
    return@Optional.ofNullable(getClass().getResource("unkaku_w.png")).map makeMissingImage ()
  }
#spanadd
}).orElseGet({ makeMissingImage() })
#spanend
#spanadd
Kotlin(手動修正1)
#spanend
#spanadd
val bi = Optional.ofNullable(javaClass.getResource("unkaku_w.png")).map { url ->
#spanend
  try {
    ImageIO.read(url)
  } catch (ex: IOException) {
    makeMissingImage()
  }
#spanadd
}.orElseGet { makeMissingImage() }
#spanend
#spanadd
Kotlin(手動修正2)
#spanend
#spanadd
val bi = javaClass.getResource("unkaku_w.png")?.let {
#spanend
  try {
    ImageIO.read(it)
  } catch (ex: IOException) {
    makeMissingImage()
  }
#spanadd
} ?: makeMissingImage()
#spanend
#spanadd
Kotlin(手動修正3)
#spanend
#spanadd
// runCatching関数でResultを取得するようtry-catchを置き換える
#spanend
#spanadd
val bi = runCatching {
#spanend
  ImageIO.read(javaClass.getResource("unkaku_w.png"))
#spanadd
}.getOrNull() ?: makeMissingImage()
#spanend
#spanadd

メソッド参照

  • IntelliJの自動変換ではメソッド参照の変換が苦手? いくらか修正されている?
    • IntelliJ 2018.2.5の自動変換では、???ではなくPredicate<Component>などに変換されるようになった

Java

#spandel
public static Stream<TreeNode> children(TreeNode node) {
#spanend
#spanadd
public static Stream<TreeNode> descendants(TreeNode node) {
#spanend
  Class<TreeNode> clz = TreeNode.class;
  return Collections.list((Enumeration<?>) node.children())
    .stream().filter(clz::isInstance).map(clz::cast);
}

Kotlin(自動変換)

#spandel
fun children(node: TreeNode): Stream<TreeNode> {
#spanend
#spanadd
fun descendants(node: TreeNode): Stream<TreeNode> {
#spanend
  val clz = TreeNode::class.java
  return Collections.list(node.children() as Enumeration<*>)
    .stream().filter(???({ clz!!.isInstance() })).map(clz!!.cast)
}

Kotlin(手動修正)

#spandel
fun children(node: TreeNode): Stream<TreeNode> {
#spanend
#spanadd
fun descendants(node: TreeNode): Stream<TreeNode> {
#spanend
  val clz = TreeNode::class.java
  return Collections.list(node.children() as Enumeration<*>)
    .stream().filter(clz::isInstance).map(clz::cast)
    // .filterIsInstance(clz).stream()
}

Kotlin(手動修正): Iterable<*>.filterIsInstance()が便利

#spandel
fun children(node: TreeNode): List<TreeNode> {
#spanend
#spanadd
fun descendants(node: TreeNode): List<TreeNode> {
#spanend
  return (node.children() as Enumeration<*>)
    .toList().filterIsInstance(TreeNode::class.java)
}
  • 例えば.filter(Container.class::isInstance)のようなメソッド参照を、IntelliJ 2018.2.5の自動変換では.filter(Predicate<Component> { Container::class.java!!.isInstance(it) })のように変換するようになった?が、importの追加は対応していない
  • 例えば.filter(Container.class::isInstance)のようなメソッド参照を、IntelliJ 2018.2.5の自動変換では.filter(Predicate<Component> { Container::class.java!!.isInstance(it) })のように変換可能になった?が、importの追加は対応していない

Java

public static Stream<Component> stream(Container parent) {
  return Stream.of(parent.getComponents())
    .filter(Container.class::isInstance)
    .map(c -> stream(Container.class.cast(c)))
    .reduce(Stream.of(parent), Stream::concat);
  }

Kotlin(自動変換)

// 自動的に以下のimportは生成されないのでunresolved referenceになる
// import java.util.function.BinaryOperator
// import java.util.function.Predicate
fun stream(parent: Container): Stream<Component> {
  return Stream.of(*parent.getComponents())
    .filter(Predicate<Component> { Container::class.java!!.isInstance(it) })
    .map({ c -> stream(Container::class.java!!.cast(c)) })
    .reduce(Stream.of(parent), BinaryOperator<Stream<Component>> { a, b -> Stream.concat(a, b) })
}

Kotlin(手動修正)

import java.util.function.BinaryOperator
import java.util.function.Predicate
fun stream(parent: Container): Stream<Component> {
  return Stream.of(*parent.getComponents())
  // return Stream.of(*parent.getComponents())
  return Arrays.asList(parent.getComponents())
    .filter(Container::class.java::isInstance)
    .map({ c -> stream(Container::class.java.cast(c)) })
    .map { c -> stream(Container::class.java.cast(c)) }
    // OK: .reduce(Stream.of(parent), BinaryOperator<Stream<Component>>{ a, b -> Stream.concat(a, b) })
    // NG: .reduce(Stream.of(parent), Stream::concat)
    .reduce(Stream.of<Component>(parent), { a, b -> Stream.concat<Component>(a, b) }) // OK
}

Kotlin(手動修正)

// Stream を Iterable に変換してしまう方法もある
#spandel
fun children(parent: Container): List<Component> {
#spanend
#spanadd
fun descendants(parent: Container): List<Component> {
#spanend
  return parent.getComponents().toList()
    .filterIsInstance(Container::class.java)
    .map { children(it) }
    .map { descendants(it) }
    .fold(listOf<Component>(parent)) { a, b -> a + b }
}
// ...
#spandel
children(fileChooser1)
#spanend
#spanadd
descendants(fileChooser1)
#spanend
  .filterIsInstance(JTable::class.java)
  // .firstOrNull()?.apply(JTable::removeEditor)
  .firstOrNull()?.let { table ->
    println(table)
    table.removeEditor()
  }

メソッド参照(終端処理の戻り値)

  • Container#add(Component)メソッドは戻り値としてComponentを返す
    • Javaの場合、終端処理のforEach(p::add)では戻り値を無視してContainer#add(Component)のメソッド参照が可能
    • Kotlinの場合、forEach(p::add)とすると、addメソッドで引数がComponent!、戻り値がUnitになるメソッドを探すためメソッド参照で以下のようなエラーになる
hello.kt:38:66: error: none of the following functions can be called with the arguments supplied:
public open fun add(p0: Component!): Component! defined in javax.swing.JPanel
public open fun add(p0: Component!, p1: Any!): Unit defined in javax.swing.JPanel
public open fun add(p0: Component!, p1: Any!, p2: Int): Unit defined in javax.swing.JPanel
public open fun add(p0: Component!, p1: Int): Component! defined in javax.swing.JPanel
public open fun add(p0: PopupMenu!): Unit defined in javax.swing.JPanel
public open fun add(p0: String!, p1: Component!): Component! defined in javax.swing.JPanel
    listOf(button1, button2, button3, button4).forEach(p::add)
                                                          ^

Java

JPanel p = new JPanel(new GridLayout(0, 1, 2, 2));
Arrays.asList(button1, button2).forEach(p::add);

Kotlin(自動変換)

val p = JPanel(GridLayout(0, 1, 2, 2))
Arrays.asList(button1, button2).forEach( ?? ? ({ p.add() }))

Kotlin(手動修正)

val p = JPanel(GridLayout(0, 1, 2, 2))
#spandel
listOf(button1, button2).forEach({ b -> p.add(b) })
#spanend
#spanadd
listOf(button1, button2).forEach { b -> p.add(b) }
#spanend
//or listOf(button1, button2).map(p::add)

BigDecimal

  • IntelliJの自動変換ではerror: unresolved reference: intValueとエラーになるので、手動でtoInt()に修正する必要がある

コンストラクタ参照

  • IntelliJの自動変換ではコンストラクタ参照がうまく変換できない場合がある
    • 例えばStream.of("A", "B", "C").map(JToggleButton::new)Stream.of("A", "B", "C").map(Function<String, JToggleButton> { JToggleButton(it) })に変換されて、以下のようなエラーになる
e: App.kt: (13, 34): Interface Function does not have constructors
e: App.kt: (13, 82): Unresolved reference: it
e: App.kt: (13, 89): Cannot choose among the following candidates without completing type inference:
@HidesMembers public inline fun <T> Iterable<???>.forEach(action: (???) -> Unit): Unit defined in kotlin.collections
@HidesMembers public inline fun <K, V> Map<out ???, ???>.forEach(action: (Map.Entry<???, ???>) -> Unit): Unit defined in kotlin.collections
e: App.kt: (13, 99): Cannot infer a type for this parameter. Please specify it explicitly.

Java

#spanadd
Stream.of("A", "B", "C").map(JToggleButton::new).forEach(r -> {
#spanend
  p.add(r);
  bg.add(r);
#spanadd
});
#spanend
#spanadd
Kotlin(自動変換)
#spanend
#spanadd
Stream.of("A", "B", "C").map(Function<String, JToggleButton> { JToggleButton(it) }).forEach({ r ->
#spanend
  p.add(r)
  bg.add(r)
#spanadd
})
#spanend
#spanadd
Kotlin(手動修正)
#spanend
#spanadd
Stream.of("A", "B", "C").map(::JToggleButton).forEach { r ->
#spanend
  p.add(r)
  bg.add(r)
#spanadd
}
#spanend
#spanadd

Number#intValue()

  • Number#intValue()IntelliJで自動変換するとintValue()がそのまま使用されるが、コンパイルするとerror: unresolved reference: intValueとエラーになるので手動でtoInt()などを使用するよう修正する必要がある
    • 1.4.31-release-IJ2019.2-1の自動変換ではtoInt()になる
Java
#spanend
double d = delta.y * GRAVITY;
int ia = (int) d;
int ib = (int) Math.floor(d);
int ic = new BigDecimal(d).setScale(0, RoundingMode.DOWN).intValue();
System.out.format("%d %d %d%n", ia, ib, ic);

Kotlin(自動変換)

val d = delta.y * GRAVITY
val ia = d.toInt()
val ib = Math.floor(d) as Int
val ic = BigDecimal(d).setScale(0, RoundingMode.DOWN).intValue()
System.out.format("%d %d %d%n", ia, ib, ic)

Kotlin(手動修正)

val d = delta.y * GRAVITY
val ia = d.toInt()
val ib = Math.floor(d).toInt()
val ic = BigDecimal(d).setScale(0, RoundingMode.DOWN).toInt()
println("${ia} ${ib} ${ic}")

Comparator

  • IntelliJの自動変換ではerror: calls to static methods in Java interfaces are prohibited in JVM target 1.6. Recompile with '-jvm-target 1.8'とエラーになる
    • kotlinc hello.kt -jvm-target 1.8 -include-runtime -d hello.jarkotlincにオプションを追加
  • IntelliJの自動変換ではerror: using 'sort(kotlin.Comparator<in T> /* = java.util.Comparator<in T> */): Unit' is an error. Use sortWith(comparator) instead.とエラーになる
    • list.sort(...)list.sortWith(...)に修正
  • IntelliJの自動変換ではerror: type inference failed: Not enough information to infer parameter T in fun <T : Any!> comparingInt(p0: ((T!) -> Int)!): Comparator<T!>!とエラーになる
    • Comparator.comparingInt({ l -> l.get(0) })Comparator.comparingInt<List<Int>>({ l -> l.get(0) })に修正

Java

import java.util.*;
public class SortTest {
  public static void main(String[] args) {
    List<List<Integer>> list = Arrays.asList(
      Arrays.asList(15, 20, 35),
      Arrays.asList(30, 45, 72),
      Arrays.asList(15, 20, 31),
      Arrays.asList(27, 33, 59),
      Arrays.asList(27, 35, 77));
    list.sort(Comparator.<List<Integer>>comparingInt(l -> l.get(0))
      .thenComparingInt(l -> l.get(1))
      .thenComparingInt(l -> l.get(2))
      .reversed());
    System.out.println(list);
  }
}

Kotlin(自動変換)

fun main(args: Array<String>) {
  val list = Arrays.asList(
    Arrays.asList(15, 20, 35),
    Arrays.asList(30, 45, 72),
    Arrays.asList(15, 20, 31),
    Arrays.asList(27, 33, 59),
    Arrays.asList(27, 35, 77))
  list.sort(Comparator.comparingInt({ l -> l.get(0) })
    .thenComparingInt({ l -> l.get(1) })
    .thenComparingInt({ l -> l.get(2) })
    .reversed())
  System.out.println(list)
}

Kotlin(手動修正)

fun main(args: Array<String>) {
  val list = Arrays.asList(
    Arrays.asList(15, 20, 35),
    Arrays.asList(30, 45, 72),
    Arrays.asList(15, 20, 31),
    Arrays.asList(27, 33, 59),
    Arrays.asList(27, 35, 77))
  list.sortWith(Comparator.comparingInt<List<Int>>({ l -> l.get(0) })
    .thenComparingInt({ l -> l.get(1) })
    .thenComparingInt({ l -> l.get(2) })
  list.sortWith(Comparator.comparingInt<List<Int>> { l -> l.get(0) }
    .thenComparingInt { l -> l.get(1) }
    .thenComparingInt { l -> l.get(2) }
    .reversed())
  System.out.println(list)
}

Kotlin(手動修正)

fun main(args: Array<String>) {
  val list = listOf<List<Int>>(
    listOf(15, 20, 35),
    listOf(30, 45, 72),
    listOf(15, 20, 31),
    listOf(27, 33, 59),
    listOf(27, 35, 77))
  val l = list.sortedWith(compareBy({ it.get(0) }, { it.get(1) }, { it.get(2) })).reversed()
  println(l)
}

16進数数値リテラル内のアンダースコア

  • IntelliJの自動変換では、頭に0xを付けて16進数表記した数値リテラルにアンダースコアを付けると、IDE Fatal Errorsになり変換できない
    • IntelliJ 2018.2.5の自動変換では、前処理でアンダースコアを除去?して正しく変換されるようになっている
    • IntelliJで自動変換できないだけで、Kotlin16進数表記の数値にアンダースコアを付けても問題なく動作可能
    • アンダースコアがなければ、16進数表記でも問題なくIntelliJで自動変換可能
    • 例1: new Color(0xEE_EE_EE)は、IntelliJの自動変換ではjava.lang.NumberFormatException: For input string: "E_EE_EE"とエラーになる
    • 例2: new Color(0x64_88_AA_AA, true)は、IntelliJの自動変換ではjava.lang.NumberFormatException: For input string: "64_8"とエラーになる
Java
#spanend
#spanadd
editor1.setSelectionColor(new Color(0x64_88_AA_AA, true)); // IntelliJで自動変換できない
#spanend
#spanadd
editor2.setSelectionColor(new Color(0x6488AAAA, true)); // IntelliJで自動変換可能
#spanend
#spanadd
Kotlin(手動修正)
#spanend
#spanadd
editor1.setSelectionColor(Color(0x64_88_AA_AA, true)) // 問題なし
#spanend
#spanadd
editor2.setSelectionColor(Color(0x6488AAAA, true)) // 問題なし
#spanend
#spanadd

プライマリコンストラクタでのプロパティ定義

  • Javaのクラスで変数宣言にコメントが付いている場合、IntelliJの自動変換ではそのコメントがプライマリコンストラクタの引数でのプロパティ定義にそのままコピーされてしまう場合がある
    • たとえば以下の例では、 // = table.getTableHeader();class RowHeaderRenderer<E>(private val header: JTableHeader // = table.getTableHeader();) : JLabel(), ...とプライマリコンストラクタのプロパティ定義に入り込んでそれ以降がコメントアウトされてしまうため、コンパイルエラーになる
    • コメントの末尾で改行が入るよう修正された
Java
#spanend
#spanadd
class RowHeaderRenderer<E> extends JLabel implements ListCellRenderer<E> {
#spanend
  private final JTableHeader header; // = table.getTableHeader();
#spanadd

#spanend
  public RowHeaderRenderer(JTableHeader header) {
    super();
    this.header = header;
    this.setOpaque(true);
    // ...
#spanadd
Kotlin(旧自動変換)
#spanend
#spanadd
inner class RowHeaderRenderer<E>(private val header: JTableHeader // = table.getTableHeader();) : JLabel(), ListCellRenderer<E> {
#spanend
#spanadd

#spanend
  init {
    this.setOpaque(true)
    // ...
#spanadd
Kotlin(新自動変換)
#spanend
#spanadd
inner class RowHeaderRenderer<E>(private val header: JTableHeader // = table.getTableHeader();
#spanend
#spanadd
) : JLabel(), ListCellRenderer<E> {
#spanend
#spanadd

#spanend
  init {
    this.setOpaque(true)
    // ...
#spanadd

int、Int

  • Javaでは例えば-10xFFFFFFFFのような16進数表記でも表現可能だが、Kotlinではerror: the integer literal does not conform to the expected type Intとエラーになるため、IntelliJの自動変換ではマイナス付の表記に自動変換される
  • Java Integer.MAX_VALUE vs Kotlin Int.MAX_VALUE - Stack Overflow
Java
#spanend
#spanadd
Color argb = new Color(0xffaabbcc, true);
#spanend
#spanadd
Color rgb = new Color(0xaabbcc);
#spanend
#spanadd
Kotlin(自動変換)
#spanend
#spanadd
val argb = Color(-0x554434, true)
#spanend
#spanadd
val rgb = Color(0xaabbcc)
#spanend
#spanadd
Kotlin(手動修正)
#spanend
#spanadd
// LongからIntに変換する方法もある
#spanend
#spanadd
val argb = Color(0xff_aa_bb_cc.toInt(), true)
#spanend
#spanadd
println(argb == Color(-0x554434, true)) // true
#spanend
#spanadd

@SuppressWarnings

  • IntelliJの自動変換では、@SuppressWarnings("unchecked")などは削除されるので、Unchecked cast: WatchEvent<*>! to WatchEvent<Path>と警告される
    • 手動で@Suppress("UNCHECKED_CAST")などを付ける必要がある
Java
#spanend
#spanadd
@SuppressWarnings("unchecked")
#spanend
#spanadd
WatchEvent<Path> ev = (WatchEvent<Path>) event;
#spanend
#spanadd
Kotlin(手動修正)
#spanend
#spanadd
@Suppress("UNCHECKED_CAST")
#spanend
#spanadd
val ev = event as WatchEvent<Path>
#spanend
#spanadd

二次元配列

  • IntelliJの自動変換で二次元配列をクローンするコードを変換した場合、Type mismatch: inferred type is Array<Int?> but Array<Int> was expectedのようなエラーになる場合がある
Java
#spanend
#spanadd
private final Integer[][] mask;
#spanend
#spanadd
@SuppressWarnings("PMD.UseVarargs")
#spanend
#spanadd
protected SudokuCellRenderer(Integer[][] src) {
#spanend
  super();
  Integer[][] dest = new Integer[src.length][src[0].length];
  for (int i = 0; i < src.length; i++) {
    System.arraycopy(src[i], 0, dest[i], 0, src[0].length);
  }
  this.mask = dest;
#spanadd
}
#spanend
#spanadd
Kotlin(自動変換)
#spanend
#spanadd
private val mask: Array<Array<Int>>
#spanend
#spanadd
init {
#spanend
  val dest = Array<Array<Int>>(src.size) { arrayOfNulls(src[0].size) }
  for (i in src.indices) {
    System.arraycopy(src[i], 0, dest[i], 0, src[0].size)
  }
  this.mask = dest
#spanadd
}
#spanend
#spanadd
Kotlin(手動修正1)
#spanend
#spanadd
private val mask: Array<Array<Int?>>
#spanend
#spanadd
init {
#spanend
  val dest = Array<Array<Int?>>(src.size) { arrayOfNulls(src[0].size) }
  // もしくはval mask: Array<Array<Int>>のままで、適当なInt値で初期化
  // val dest = Array(src.size, { Array(src[0].size, { 0 }) }) // 0で初期化
  for (i in src.indices) {
    System.arraycopy(src[i], 0, dest[i], 0, src[0].size)
  }
  this.mask = dest
#spanadd
}
#spanend
#spanadd
Kotlin(手動修正2)
#spanend
#spanadd
private val mask: Array<Array<Int>>
#spanend
#spanadd
init {
#spanend
  // System.arraycopyなどは使用せず、コピー元の二次元配列を参照して初期化
  this.mask = Array(src.size, { i -> Array(src[i].size, { j -> src[i][j] }) })
#spanadd
}
#spanend
#spanadd

Ellipse2D.Double

  • IntelliJの自動変換でEllipse2D.Doublekotlin.Double競合する場合がある?
    • 再現できなくなった
Java
#spanend
#spanadd
import java.awt.geom.Ellipse2D;
#spanend
#spanadd

#spanend
#spanadd
enum ButtonLocation {
#spanend
  CENTER(0d),
  NORTH(45d),
  EAST(135d),
  SOUTH(225d),
  WEST(-45d);
  private final double degree;
  ButtonLocation(double degree) {
    this.degree = degree;
  }
#spanadd

#spanend
  public double getStartAngle() {
    return degree;
  }
#spanadd
}
#spanend
#spanadd
// ...
#spanend
#spanadd
Shape inner = new Ellipse2D.Double(xx, xx, ww, ww);
#spanend
#spanadd
Kotlin(自動変換)
#spanend
#spanadd
import java.awt.geom.Ellipse2D.Double
#spanend
#spanadd

#spanend
#spanadd
enum class ButtonLocation(val startAngle: kotlin.Double) {
#spanend
  CENTER(0.0), NORTH(45.0), EAST(135.0), SOUTH(225.0), WEST(-45.0);
#spanadd
}
#spanend
#spanadd
// ...
#spanend
#spanadd
val inner: Shape = Double(xx, xx, ww, ww)
#spanend
#spanadd
Kotlin(手動修正)
#spanend
#spanadd
import java.awt.geom.Ellipse2D
#spanend
#spanadd

#spanend
#spanadd
enum class ButtonLocation(val startAngle: Double) {
#spanend
  CENTER(0.0), NORTH(45.0), EAST(135.0), SOUTH(225.0), WEST(-45.0);
#spanadd
}
#spanend
#spanadd

#spanend
#spanadd
val inner: Shape = Ellipse2D.Double(xx, xx, ww, ww)
#spanend
#spanadd

手動修正が不要なIntelliJの自動変換

  • IntelliJの自動変換で変換後の手動修正が不要なケースのメモ

セーフキャスト

Java
#spanend
#spanadd
Set<?> f = v instanceof Set ? (Set<?>) v : EnumSet.noneOf(Permissions.class);
#spanend
#spanadd
Kotlin
#spanend
#spanadd
val f = v as? Set<*> ?: EnumSet.noneOf(Permissions::class.java)
#spanend
#spanadd

明示的に外側のクラス名で修飾されたthis

Java
#spanend
#spanadd
class FileListTable extends JTable {
#spanend
  private class RubberBandingListener extends MouseAdapter {
    @Override public void mousePressed(MouseEvent e) {
      FileListTable table = FileListTable.this
      // ...
#spanadd
Kotlin
#spanend
#spanadd
class FileListTable(model: TableModel) : JTable(model) {
#spanend
  private inner class RubberBandingListener : MouseAdapter() {
    override fun mousePressed(e: MouseEvent) {
      val table = this@FileListTable
      // ...
#spanadd

enumを参照等価性で比較する

  • 最近?のIntelliJの自動変換では、列挙子(enum)の比較を===演算子に変換する
Java
#spanend
#spanadd
checkBox.setSelected(node.getStatus() == Status.SELECTED);
#spanend
#spanadd
Kotlin
#spanend
#spanadd
checkBox.setSelected(it.status === Status.SELECTED)
#spanend
#spanadd

Swing + Kotlin サンプル

SwingUtilities.getAncestorOfClass(...)

Java
#spanend
#spanadd
Container p = SwingUtilities.getAncestorOfClass(JViewport.class, this);
#spanend
#spanadd
if (!(p instanceof JViewport)) {
#spanend
  return;
#spanadd
}
#spanend
#spanadd
Kotlin
#spanend
#spanadd
val p = SwingUtilities.getAncestorOfClass(JViewport::class.java, this) as? JViewport ?: return
#spanend
#spanadd

JTable

  • Class<?>Class<Any>Class<Object>Class<*>Class<out Any>に変換した方が良いかもしれない
    • IntelliJの自動変換ではClass<*>
  • Object#getClass()o.javaClass
  • オーバーライドの方法、@Overrideoverride
  • 配列、二次元配列
  • apply
  • switchwhen
  • DefaultTableModel#getColumnClass(...)メソッドをオーバーライドして、Boolean::class.javaを返してもセルレンダラーにJCheckBoxは適用されない
    • IntelliJの自動変換ではBoolean::class.javaになるが、java.lang.Boolean::class.javaに修正する必要がある

Java

import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;

public class JTableExample {
  public JComponent makeUI() {
    String[] cn = {"String", "Integer", "Boolean"};
    Object[][] data = {
      {"aaa", 12, true}, {"bbb", 5, false},
      {"CCC", 92, true}, {"DDD", 0, false}
    };
    TableModel m = new DefaultTableModel(data, cn) {
      @Override public Class<?> getColumnClass(int col) {
        return getValueAt(0, col).getClass();
      }
    };
    JTable table = new JTable(m);
    table.setAutoCreateRowSorter(true);

    JPanel p = new JPanel(new BorderLayout());
    p.add(new JScrollPane(table));
    return p;
  }
  public static void main(String... args) {
    EventQueue.invokeLater(() -> {
      JFrame f = new JFrame();
      f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      f.add(new JTableExample().makeUI());
      f.setSize(320, 240);
      f.setLocationRelativeTo(null);
      f.setVisible(true);
    });
  }
}

Kotlin

import java.awt.*
import javax.swing.*
import javax.swing.table.*

fun makeUI(): JComponent {
  val cn = arrayOf("String", "Integer", "Boolean")
  val data = arrayOf(
    arrayOf("aaa", 12, true), arrayOf("bbb", 5, false),
    arrayOf("CCC", 92, true), arrayOf("DDD", 0, false))
  val m = object: DefaultTableModel(data, cn) {
    override fun getColumnClass(col: Int): Class<Any> {
      return getValueAt(0, col).javaClass
    }
  }
  // val m = object: DefaultTableModel(data, cn) {
  //   override fun getColumnClass(col: Int): Class<*> {
  //     return when (col) {
  //       0 -> String::class.java
  //       1 -> Integer::class.java
  //       // XXX: 2 -> Boolean::class.java
  //       2 -> java.lang.Boolean::class.java
  //       else -> Object::class.java
  //     }
  //   }
  // }
  val table = JTable(m).apply {
    autoCreateRowSorter = true
  }
  return JPanel(BorderLayout(5, 5)).apply {
    add(JScrollPane(table))
  }
}
fun main(args: Array<String>) {
  EventQueue.invokeLater {
    JFrame("kotlin swing").apply {
      defaultCloseOperation = JFrame.EXIT_ON_CLOSE
      add(makeUI())
      size = Dimension(320, 240)
      setLocationRelativeTo(null)
      setVisible(true)
    }
  }
}

JTree

Java

import java.awt.*;
import java.util.*;
import javax.swing.*;

public class JTreeExample {
  public JComponent makeUI() {
    JTree tree = new JTree();

    JButton b1 = new JButton("expand");
    b1.addActionListener(e -> expandAll(tree));

    JButton b2 = new JButton("collapse");
    b2.addActionListener(e -> collapseAll(tree));

    JPanel pp = new JPanel(new GridLayout(1, 0, 5, 5));
    Arrays.asList(b1, b2).forEach(pp::add);
    JPanel p = new JPanel(new BorderLayout(5, 5));
    p.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
    p.add(new JScrollPane(tree));
    p.add(pp, BorderLayout.SOUTH);
    return p;
  }
  protected static void expandAll(JTree tree) {
    int row = 0;
    while (row < tree.getRowCount()) {
      tree.expandRow(row);
      row++;
    }
  }
  protected static void collapseAll(JTree tree) {
    int row = tree.getRowCount() - 1;
    while (row >= 0) {
      tree.collapseRow(row);
      row--;
    }
  }
  public static void main(String[] args) {
    EventQueue.invokeLater(() -> {
      JFrame f = new JFrame();
      f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      f.add(new JTreeExample().makeUI());
      f.setSize(320, 240);
      f.setLocationRelativeTo(null);
      f.setVisible(true);
    });
  }
}

Kotlin

import java.awt.*
import javax.swing.*

fun makeUI(): JComponent {
  val tree = JTree()
  val p = JPanel(GridLayout(1, 0, 5, 5)).apply {
    add(JButton("expand").apply {
      addActionListener { expandAll(tree) }
    })
    add(JButton("collapse").apply {
      addActionListener { collapseAll(tree) }
    })
  }
  return JPanel(BorderLayout(5, 5)).apply {
    setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5))
    add(JScrollPane(tree))
    add(p, BorderLayout.SOUTH)
  }
}
fun expandAll(tree: JTree) {
  var row = 0
  while (row < tree.getRowCount()) {
    tree.expandRow(row)
    row++
  }
}
fun collapseAll(tree : JTree) {
  var row = tree.getRowCount() - 1
  while (row >= 0) {
    tree.collapseRow(row)
    row--
  }
}
fun main(args: Array<String>) {
  EventQueue.invokeLater {
    JFrame("kotlin swing").apply {
      defaultCloseOperation = JFrame.EXIT_ON_CLOSE
      add(makeUI())
      size = Dimension(320, 240)
      setLocationRelativeTo(null)
      setVisible(true)
    }
  }
}

JCheckBox

  • Smart Cast、キャストでifのネストが深くなるのを避けたい
    • イベント発生元がAbstractButtonなのは自明なのでasを使用する
      • val b = e.getSource() as AbstractButton
    • 以下のような場合は、applyでも回避可能

Java

import java.awt.*;
import javax.swing.*;

public class JCheckBoxExample {
  public JComponent makeUI() {
    JCheckBox cb = new JCheckBox("Always On Top", true);
    cb.addActionListener(e -> {
      AbstractButton b = (AbstractButton) e.getSource();
      Container c = b.getTopLevelAncestor();
      if (c instanceof Window) {
        ((Window) c).setAlwaysOnTop(b.isSelected());
      }
    });
    JPanel p = new JPanel(new BorderLayout());
    p.add(cb, BorderLayout.NORTH);
    return p;
  }
  public static void main(String[] args) {
    EventQueue.invokeLater(() -> {
      JFrame f = new JFrame();
      f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      f.add(new JCheckBoxExample().makeUI());
      f.setSize(320, 240);
      f.setLocationRelativeTo(null);
      f.setVisible(true);
    });
  }
}

Kotlin

import java.awt.*
import javax.swing.*

fun makeUI(): JComponent {
  val cb = JCheckBox("Always On Top", true).apply {
//     addActionListener { e ->
//       val c = e.getSource()
//       if (c is AbstractButton) {
//         val w = c.getTopLevelAncestor()
//         if (w is Window) {
//           w.setAlwaysOnTop(c.isSelected())
//         }
//       }
    addActionListener { e ->
      val b = e.getSource() as AbstractButton
      val w = b.getTopLevelAncestor()
      if (w is Window) {
        w.setAlwaysOnTop(b.isSelected())
      }
    }
//     addActionListener {
//       val w = getTopLevelAncestor()
//       if (w is Window) {
//           w.setAlwaysOnTop(isSelected())
//       }
//     }
  }
  return JPanel(BorderLayout()).apply {
    add(cb, BorderLayout.NORTH)
  }
}

fun main(args: Array<String>) {
  EventQueue.invokeLater {
    JFrame("kotlin swing").apply {
      defaultCloseOperation = JFrame.EXIT_ON_CLOSE
      add(makeUI())
      size = Dimension(320, 240)
      setLocationRelativeTo(null)
      setVisible(true)
    }
  }
}

TableCellEditor

  • extends, implements
    • IntelliJの自動変換では勝手にopenは付けないようなので、以下のようなエラーが出る場合は、internal class CheckBoxesPanel : JPanel() {open class CheckBoxesPanel : JPanel() {に修正する
      MainPanel.kt:81:37: error: this type is final, so it cannot be inherited from
      internal class CheckBoxesRenderer : CheckBoxesPanel(), TableCellRenderer {
      
  • IntelliJの自動変換ではstatic変数がうまく変換できない?
    • 手動でenum classにして回避
      companion object {
        protected val TITLES = arrayOf("r", "w", "x")
      }
      #spandel
      //...
      #spanend
      #spanadd
      // ...
      #spanend
      MainPanel.kt:45:5: error: property must be initialized or be abstract
          var buttons: Array<JCheckBox>
          ^
      MainPanel.kt:55:19: error: type mismatch: inferred type is Array<JCheckBox?> but Array<JCheckBox> was expected
              buttons = arrayOfNulls<JCheckBox>(TITLES.size)
      

Java

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.*;
import javax.swing.table.*;

public final class MainPanel {
  public JComponent makeUI() {
    String[] columnNames = {"user", "rwx"};
    Object[][] data = {
      {"owner", 7}, {"group", 6}, {"other", 5}
    };
    TableModel model = new DefaultTableModel(data, columnNames) {
      @Override public Class<?> getColumnClass(int column) {
        return getValueAt(0, column).getClass();
      }
    };
    JTable table = new JTable(model) {
      @Override public void updateUI() {
        super.updateUI();
        getColumnModel().getColumn(1).setCellRenderer(new CheckBoxesRenderer());
        getColumnModel().getColumn(1).setCellEditor(new CheckBoxesEditor());
      }
    };
    table.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);

    JPanel p = new JPanel(new BorderLayout());
    p.add(new JScrollPane(table));
    return p;
  }
  public static void main(String[] args) {
    EventQueue.invokeLater(() -> {
      JFrame frame = new JFrame();
      frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
      frame.getContentPane().add(new MainPanel().makeUI());
      frame.setSize(320, 240);
      frame.setLocationRelativeTo(null);
      frame.setVisible(true);
    });
  }
}

class CheckBoxesPanel extends JPanel {
  protected static final String[] TITLES = {"r", "w", "x"};
  public JCheckBox[] buttons;
  @Override public void updateUI() {
    super.updateUI();
    setOpaque(false);
    setBackground(new Color(0x0, true));
    setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
    initButtons();
  }
  private void initButtons() {
    buttons = new JCheckBox[TITLES.length];
    for (int i = 0; i < buttons.length; i++) {
      JCheckBox b = new JCheckBox(TITLES[i]);
      b.setOpaque(false);
      b.setFocusable(false);
      b.setRolloverEnabled(false);
      b.setBackground(new Color(0x0, true));
      buttons[i] = b;
      add(b);
      add(Box.createHorizontalStrut(5));
    }
  }
  protected void updateButtons(Object v) {
    removeAll();
    initButtons();
    Integer i = v instanceof Integer ? (Integer) v : 0;
    buttons[0].setSelected((i & (1 << 2)) != 0);
    buttons[1].setSelected((i & (1 << 1)) != 0);
    buttons[2].setSelected((i & (1 << 0)) != 0);
  }
}

class CheckBoxesRenderer extends CheckBoxesPanel implements TableCellRenderer {
  @Override public void updateUI() {
    super.updateUI();
    setName("Table.cellRenderer");
  }
  @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
    updateButtons(value);
    return this;
  }
}

class CheckBoxesEditor extends AbstractCellEditor implements TableCellEditor {
  private final CheckBoxesPanel panel = new CheckBoxesPanel() {
    @Override public void updateUI() {
      super.updateUI();
      EventQueue.invokeLater(() -> {
        ActionMap am = getActionMap();
        for (int i = 0; i < buttons.length; i++) {
          String title = TITLES[i];
          am.put(title, new AbstractAction(title) {
            @Override public void actionPerformed(ActionEvent e) {
              Arrays.stream(buttons)
              .filter(b -> b.getText().equals(title))
              .findFirst()
              .ifPresent(JCheckBox::doClick);
              fireEditingStopped();
            }
          });
        }
        InputMap im = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0), TITLES[0]);
        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0), TITLES[1]);
        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_X, 0), TITLES[2]);
      });
    }
  };
  @Override public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
    panel.updateButtons(value);
    return panel;
  }
  @Override public Object getCellEditorValue() {
    int i = 0;
    i = panel.buttons[0].isSelected() ? 1 << 2 | i : i;
    i = panel.buttons[1].isSelected() ? 1 << 1 | i : i;
    i = panel.buttons[2].isSelected() ? 1 << 0 | i : i;
    return i;
  }
}

Kotlin

import java.awt.*
import java.awt.event.*
import java.util.*
import javax.swing.*
import javax.swing.event.*
import javax.swing.plaf.*
import javax.swing.table.*

fun makeUI(): JComponent {
  val columnNames = arrayOf("user", "rwx")
  val data = arrayOf(arrayOf<Any>("owner", 7), arrayOf<Any>("group", 6), arrayOf<Any>("other", 5))
  val model = object : DefaultTableModel(data, columnNames) {
    override fun getColumnClass(column: Int): Class<*> {
      return getValueAt(0, column).javaClass
    }
  }
  val table = object : JTable(model) {
    override fun updateUI() {
      super.updateUI()
      getColumnModel().getColumn(1).setCellRenderer(CheckBoxesRenderer())
      getColumnModel().getColumn(1).setCellEditor(CheckBoxesEditor())
    }
  }
  table.putClientProperty("terminateEditOnFocusLost", java.lang.Boolean.TRUE)
  return JPanel(BorderLayout(5, 5)).apply {
    add(JScrollPane(table))
  }
}

fun main(args: Array<String>) {
  EventQueue.invokeLater {
    JFrame("kotlin swing").apply {
      defaultCloseOperation = JFrame.EXIT_ON_CLOSE
      add(makeUI())
      size = Dimension(320, 240)
      setLocationRelativeTo(null)
      setVisible(true)
    }
  }
}

open class CheckBoxesPanel() : JPanel() {
  public val buttons = arrayOf(JCheckBox(Permission.READ.toString()), JCheckBox(Permission.WRITE.toString()), JCheckBox(Permission.EXECUTE.toString()))

  override fun updateUI() {
    super.updateUI()
    setOpaque(false)
    setBackground(Color(0x0, true))
    setLayout(BoxLayout(this, BoxLayout.X_AXIS))
    EventQueue.invokeLater({ initButtons() })
  }

  private fun initButtons() {
    for (b in buttons) {
      b.setOpaque(false)
      b.setFocusable(false)
      b.setRolloverEnabled(false)
      b.setBackground(Color(0x0, true))
      add(b)
      add(Box.createHorizontalStrut(5))
    }
  }

  public fun updateButtons(v: Any) {
    removeAll()
    initButtons()
    val i = v as ? Int ? : 0
    buttons[0].setSelected(i and (1 shl 2) != 0)
    buttons[1].setSelected(i and (1 shl 1) != 0)
    buttons[2].setSelected(i and (1 shl 0) != 0)
  }
}

enum class Permission(var str: String) {
  READ("r"), WRITE("w"), EXECUTE("x");
  override fun toString() = str
}

internal class CheckBoxesRenderer : CheckBoxesPanel(), TableCellRenderer {
  override fun updateUI() {
    super.updateUI()
    setName("Table.cellRenderer")
  }
  override fun getTableCellRendererComponent(table: JTable, value: Any, isSelected: Boolean, hasFocus: Boolean, row: Int, column: Int): Component {
    updateButtons(value)
    return this
  }
}

internal class CheckBoxesEditor : AbstractCellEditor(), TableCellEditor {
  private val panel = object : CheckBoxesPanel() {
    override fun updateUI() {
      super.updateUI()
      EventQueue.invokeLater({
        val am = getActionMap()
        for (i in buttons.indices) {
          val title = buttons[i].getText()
          am.put(title, object : AbstractAction(title) {
            override fun actionPerformed(e: ActionEvent) {
              Arrays.stream(buttons)
                .filter({ b -> b.getText() == title })
                .findFirst()
                .ifPresent({ it.doClick() })
              fireEditingStopped()
            }
          })
        }
        val im = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0), Permission.READ.toString())
        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0), Permission.WRITE.toString())
        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_X, 0), Permission.EXECUTE.toString())
      })
    }
  }

  override fun getTableCellEditorComponent(table: JTable, value: Any, isSelected: Boolean, row: Int, column: Int): Component {
    panel.updateButtons(value)
    return panel
  }

  override fun getCellEditorValue(): Any {
    var i = 0
    i = if (panel.buttons[0].isSelected()) 1 shl 2 or i else i
    i = if (panel.buttons[1].isSelected()) 1 shl 1 or i else i
    i = if (panel.buttons[2].isSelected()) 1 shl 0 or i else i
    return i
  }
}

ActionListener

  • IntelliJの自動変換ではActionListenerなどの関数型インターフェースのラムダ式をうまく変換できない場合がある
    • AbstractCellEditor#stopCellEditing()booleanを返すので、error: type mismatch: inferred type is (???) -> Boolean but ActionListener? was expectedとエラーになる
  • SAM Conversions - Calling Java from Kotlin - Kotlin Programming Language

Java

protected ActionListener handler;
#spandel
//...
#spanend
#spanadd
// ...
#spanend
handler = e -> stopCellEditing();

Kotlin(自動変換)

protected var handler: ActionListener? = null
#spandel
//...
#spanend
#spanadd
// ...
#spanend
handler = { e -> stopCellEditing() }
// error: type mismatch: inferred type is (???) -> Boolean but ActionListener? was expected
//             handler = { e -> stopCellEditing() }
// error: cannot infer a type for this parameter. Please specify it explicitly.
//             handler = { e -> stopCellEditing() }
// error: modifier 'override' is not applicable to 'local function'
//               override fun actionPerformed(e: ActionEvent) : Unit {

Kotlin(手動修正)

protected var handler: ActionListener? = null
#spandel
//...
#spanend
#spanadd
// ...
#spanend
handler = ActionListener { stopCellEditing() }
// or:
handler = object: ActionListener {
  override fun actionPerformed(e: ActionEvent) : Unit { // Unitは無くても可
    stopCellEditing()
  }
}
  • IntelliJの自動変換で、以下のようなエラーになる場合は、ObjectAnyに手動変換する
    error: class 'CheckBoxNodeRenderer' is not abstract and does not implement abstract member public abstract fun getTreeCellRendererComponent(p0: JTree!, p1: Any!, p2: Boolean, p3: Boolean, p4: Boolean, p5: Int, p6: Boolean): Component! defined in javax.swing.tree.TreeCellRenderer
    internal class CheckBoxNodeRenderer : TreeCellRenderer {
  • getterのみオーバーライド

Java

class CheckBoxNodeEditor extends AbstractCellEditor implements TreeCellEditor {
  @Override public Object getCellEditorValue() {
    return new CheckBoxNode(checkBox.getText(), checkBox.isSelected());
  }
  //...
  // ...

Kotlin(自動変換)

val cellEditorValue: Any
  override get() = CheckBoxNode(checkBox.getText(), checkBox.isSelected())
//error: modifier 'override' is not applicable to 'getter'
//       override get() = CheckBoxNode(checkBox.getText(), checkBox.isSelected())

Kotlin(手動修正)

  override fun getCellEditorValue(): Any {
    return CheckBoxNode(checkBox.getText(), checkBox.isSelected())
  }

Java

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;

public class LeafCheckBoxTreeTest {
  private JComponent makeUI() {
    JTree tree = new JTree();
    tree.setEditable(true);
    tree.setCellRenderer(new CheckBoxNodeRenderer());
    tree.setCellEditor(new CheckBoxNodeEditor());
    tree.setRowHeight(18);

    boolean b = true;
    TreeModel model = tree.getModel();
    DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
    Enumeration<?> e = root.breadthFirstEnumeration();
    while (e.hasMoreElements()) {
      DefaultMutableTreeNode node = (DefaultMutableTreeNode) e.nextElement();
      String s = Objects.toString(node.getUserObject(), "");
      node.setUserObject(new CheckBoxNode(s, b));
      b ^= true;
    }
    return new JScrollPane(tree);
  }
  public static void main(String[] args) {
    EventQueue.invokeLater(() -> {
      UIManager.put("swing.boldMetal", Boolean.FALSE);
      JFrame f = new JFrame();
      f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
      f.getContentPane().add(new LeafCheckBoxTreeTest().makeUI());
      f.setSize(320, 240);
      f.setLocationRelativeTo(null);
      f.setVisible(true);
    });
  }
}

class CheckBoxNodeRenderer implements TreeCellRenderer {
  private final JCheckBox checkBox = new JCheckBox();
  private final DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer();
  @Override public Component getTreeCellRendererComponent(
      JTree tree, Object value, boolean selected, boolean expanded,
      boolean leaf, int row, boolean hasFocus) {
    if (leaf && value instanceof DefaultMutableTreeNode) {
      checkBox.setEnabled(tree.isEnabled());
      checkBox.setFont(tree.getFont());
      checkBox.setOpaque(false);
      checkBox.setFocusable(false);
      Object userObject = ((DefaultMutableTreeNode) value).getUserObject();
      if (userObject instanceof CheckBoxNode) {
        CheckBoxNode node = (CheckBoxNode) userObject;
        checkBox.setText(node.text);
        checkBox.setSelected(node.selected);
      }
      return checkBox;
    }
    return renderer.getTreeCellRendererComponent(
        tree, value, selected, expanded, leaf, row, hasFocus);
  }
}

class CheckBoxNodeEditor extends AbstractCellEditor implements TreeCellEditor {
  private final JCheckBox checkBox = new JCheckBox() {
    protected transient ActionListener handler;
    @Override public void updateUI() {
      removeActionListener(handler);
      super.updateUI();
      setOpaque(false);
      setFocusable(false);
      handler = e -> stopCellEditing();
      addActionListener(handler);
    }
  };
  @Override public Component getTreeCellEditorComponent(
      JTree tree, Object value, boolean selected, boolean expanded,
      boolean leaf, int row) {
    if (leaf && value instanceof DefaultMutableTreeNode) {
      Object userObject = ((DefaultMutableTreeNode) value).getUserObject();
      if (userObject instanceof CheckBoxNode) {
        checkBox.setSelected(((CheckBoxNode) userObject).selected);
      } else {
        checkBox.setSelected(false);
      }
      checkBox.setText(value.toString());
    }
    return checkBox;
  }
  @Override public Object getCellEditorValue() {
    return new CheckBoxNode(checkBox.getText(), checkBox.isSelected());
  }
  @Override public boolean isCellEditable(EventObject e) {
    if (e instanceof MouseEvent && e.getSource() instanceof JTree) {
      MouseEvent me = (MouseEvent) e;
      JTree tree = (JTree) me.getComponent();
      TreePath path = tree.getPathForLocation(me.getX(), me.getY());
      Object o = path.getLastPathComponent();
      if (o instanceof TreeNode) {
        return ((TreeNode) o).isLeaf();
      }
    }
    return false;
  }
}

class CheckBoxNode {
  public final String text;
  public final boolean selected;
  protected CheckBoxNode(String text, boolean selected) {
    this.text = text;
    this.selected = selected;
  }
  @Override public String toString() {
    return text;
  }
}

Kotlin

import java.awt.*
import java.awt.event.*
import java.util.*
import javax.swing.*
import javax.swing.event.*
import javax.swing.tree.*

private fun makeUI(): JComponent {
  val tree = JTree()
  tree.setEditable(true)
  tree.setCellRenderer(CheckBoxNodeRenderer())
  tree.setCellEditor(CheckBoxNodeEditor())
  tree.setRowHeight(18)

  var b = true
  val model = tree.getModel()
  val root = model.getRoot() as DefaultMutableTreeNode
  val e = root.breadthFirstEnumeration()
  while (e.hasMoreElements()) {
    val node = e.nextElement() as DefaultMutableTreeNode
    val s = Objects.toString(node.getUserObject(), "")
    node.setUserObject(CheckBoxNode(s, b))
    b = b xor true
  }
  return JScrollPane(tree)
}

fun main(args: Array<String>) {
  EventQueue.invokeLater {
    UIManager.put("swing.boldMetal", false)
    JFrame("kotlin swing").apply {
      defaultCloseOperation = JFrame.EXIT_ON_CLOSE
      add(makeUI())
      size = Dimension(320, 240)
      setLocationRelativeTo(null)
      setVisible(true)
    }
  }
}

internal class CheckBoxNodeRenderer : TreeCellRenderer {
  private val checkBox = JCheckBox()
  private val renderer = DefaultTreeCellRenderer()
  override fun getTreeCellRendererComponent(
    tree: JTree, value: Any, selected: Boolean, expanded: Boolean,
    leaf: Boolean, row: Int, hasFocus: Boolean): Component {
    if (leaf && value is DefaultMutableTreeNode) {
      checkBox.setEnabled(tree.isEnabled())
      checkBox.setFont(tree.getFont())
      checkBox.setOpaque(false)
      checkBox.setFocusable(false)
      val userObject = value.getUserObject()
      if (userObject is CheckBoxNode) {
        val node = userObject // as CheckBoxNode
        checkBox.setText(node.text)
        checkBox.setSelected(node.selected)
      }
      return checkBox
    }
    return renderer.getTreeCellRendererComponent(
      tree, value, selected, expanded, leaf, row, hasFocus)
  }
}

internal class CheckBoxNodeEditor : AbstractCellEditor(), TreeCellEditor {
  private val checkBox = object : JCheckBox() {
    protected var handler: ActionListener? = null
    override fun updateUI() {
      removeActionListener(handler)
      super.updateUI()
      setOpaque(false)
      setFocusable(false)
      // handler = object: ActionListener {
      //   override fun actionPerformed(e: ActionEvent) : Unit {
      //     stopCellEditing()
      //   }
      // }
      handler = ActionListener { stopCellEditing() }
      addActionListener(handler)
    }
  }

  override fun getTreeCellEditorComponent(
      tree: JTree, value: Any, selected: Boolean, expanded: Boolean,
      leaf: Boolean, row: Int): Component {
    if (leaf && value is DefaultMutableTreeNode) {
      val userObject = value.getUserObject()
      if (userObject is CheckBoxNode) {
        checkBox.setSelected(userObject.selected)
      } else {
        checkBox.setSelected(false)
      }
      checkBox.setText(value.toString())
    }
    return checkBox
  }

  override fun getCellEditorValue(): Any {
    return CheckBoxNode(checkBox.getText(), checkBox.isSelected())
  }

  override fun isCellEditable(e: EventObject): Boolean {
    if (e is MouseEvent && e.getSource() is JTree) {
      val tree = e.getComponent() as JTree
      val path = tree.getPathForLocation(e.getX(), e.getY())
      val o = path.getLastPathComponent()
      if (o is TreeNode) {
        return o.isLeaf()
      }
    }
    return false
  }
}

internal class CheckBoxNode public constructor(val text: String, val selected: Boolean) {
  override fun toString(): String {
    return text
  }
}

Optional

  • Optional#orElse(...)は、let {...} ?: ...に変換
    • Integer stepSize = Optional.ofNullable(stepSizeMap.get(calendarField)).orElse(1);
    • val stepSize = stepSizeMap.get(calendarField).let { it } ?: 1

Null safety and platform types

  • 例えばSpinnerDateModel#getNextValue(...)メソッドをoverride fun getNextValue(): Object = Calendar.getInstance().apply { ...のように変換すると、error: type mismatch: inferred type is Date! but Object was expectedとエラーになる
  • override fun getNextValue(): Any = Calendar.getInstance().apply { ...Anyを使用するようオーバーライドする必要がある
  • Date!は、DateもしくはDate?の意味

Java

import java.awt.*;
import java.text.*;
import java.util.*;
import javax.swing.*;
import javax.swing.text.*;

public class OptionalExample {
  private JComponent makeUI() {
    SimpleDateFormat format = new SimpleDateFormat(
        "mm:ss, SSS", Locale.getDefault());
    DefaultFormatterFactory factory = new DefaultFormatterFactory(
        new DateFormatter(format));

    Calendar calendar = Calendar.getInstance();
    calendar.set(Calendar.HOUR_OF_DAY, 0);
    calendar.clear(Calendar.MINUTE);
    calendar.clear(Calendar.SECOND);
    calendar.clear(Calendar.MILLISECOND);
    Date d = calendar.getTime();

    JSpinner sp1 = new JSpinner(new SpinnerDateModel(
        d, null, null, Calendar.SECOND));
    JSpinner.DefaultEditor ed1 = (JSpinner.DefaultEditor) sp1.getEditor();
    ed1.getTextField().setFormatterFactory(factory);

    HashMap<Integer, Integer> stepSizeMap = new HashMap<>();
    stepSizeMap.put(Calendar.HOUR_OF_DAY, 1);
    stepSizeMap.put(Calendar.MINUTE,      1);
    stepSizeMap.put(Calendar.SECOND,      30);
    stepSizeMap.put(Calendar.MILLISECOND, 500);

    JSpinner sp2 = new JSpinner(new SpinnerDateModel(
        d, null, null, Calendar.SECOND) {
      @Override public Object getPreviousValue() {
        Calendar cal = Calendar.getInstance();
        cal.setTime(getDate());
        Integer calendarField = getCalendarField();
        Integer stepSize = Optional.ofNullable(
            stepSizeMap.get(calendarField)).orElse(1);
        cal.add(calendarField, -stepSize);
        return cal.getTime();
      }
      @Override public Object getNextValue() {
        Calendar cal = Calendar.getInstance();
        cal.setTime(getDate());
        Integer calendarField = getCalendarField();
        Integer stepSize = Optional.ofNullable(
            stepSizeMap.get(calendarField)).orElse(1);
        cal.add(calendarField, stepSize);
        return cal.getTime();
      }
    });
    JSpinner.DefaultEditor ed2 = (JSpinner.DefaultEditor) sp2.getEditor();
    ed2.getTextField().setFormatterFactory(factory);

    JPanel p = new JPanel();
    p.add(sp1);
    p.add(sp2);
    return p;
  }
  public static void main(String... args) {
    EventQueue.invokeLater(() -> {
      JFrame f = new JFrame();
      f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
      f.getContentPane().add(new OptionalExample().makeUI());
      f.setSize(320, 240);
      f.setLocationRelativeTo(null);
      f.setVisible(true);
    });
  }
}

Kotlin

import java.awt.*
import java.text.*
import java.util.*
import javax.swing.*
import javax.swing.text.*

fun makeUI(): JComponent {
  val format = SimpleDateFormat("mm:ss, SSS", Locale.getDefault())
  val factory = DefaultFormatterFactory(DateFormatter(format))

  val calendar = Calendar.getInstance().apply {
    set(Calendar.HOUR_OF_DAY, 0)
    clear(Calendar.MINUTE)
    clear(Calendar.SECOND)
    clear(Calendar.MILLISECOND)
  }
  val d = calendar.getTime()

  val sp1 = JSpinner(SpinnerDateModel(d, null, null, Calendar.SECOND))
  val ed1 = sp1.getEditor()
  if (ed1 is JSpinner.DefaultEditor) {
    ed1.getTextField().setFormatterFactory(factory)
  }

  val stepSizeMap: HashMap<Int, Int> = hashMapOf(
      Calendar.HOUR_OF_DAY to 1,
      Calendar.MINUTE      to 1,
      Calendar.SECOND      to 30,
      Calendar.MILLISECOND to 500)

  val sp2 = JSpinner(object: SpinnerDateModel(d, null, null, Calendar.SECOND) {
    override fun getPreviousValue(): Any = Calendar.getInstance().apply {
      time = getDate()
      val stepSize = stepSizeMap.get(calendarField).let { it } ?: 1
      add(calendarField, -stepSize)
    }.getTime()
    override fun getNextValue(): Any = Calendar.getInstance().apply {
        setTime(getDate())
        val stepSize = stepSizeMap.get(calendarField).let { it } ?: 1
        add(calendarField, stepSize)
    }.getTime()
  })
  val ed2 = sp2.getEditor()
  if (ed2 is JSpinner.DefaultEditor) {
    ed2.getTextField().setFormatterFactory(factory)
  }

  return JPanel().apply {
    add(sp1)
    add(sp2)
  }
}

fun main(args: Array<String>) {
  EventQueue.invokeLater {
    JFrame("kotlin swing").apply {
      defaultCloseOperation = WindowConstants.EXIT_ON_CLOSE
      add(makeUI())
      size = Dimension(320, 240)
      setLocationRelativeTo(null)
      setVisible(true)
    }
  }
}

SwingWorker

JComponent#updateUI()

  • SwingコンポーネントのUIプロパティを現在のLookAndFeelで設定されている値にリセットするupdateUI()メソッドは、コンストラクタから呼び出されるのでインスタンス変数が初期化される前に実行されることがある
  • このため、Kotlinのコードに変換したupdateUI()メソッド内でnullチェックを行うと不要なnullチェックをしているとか、!= nullが常にtrueであると警告されるが、これに従ってnullチェックを外すとNullPointerExceptionが発生する
  • OptionalObjects.nonNull(...)で、警告をごまかす方法もある
Java
#spanend
#spanadd
public final class MainPanel extends JPanel {
#spanend
  private final JSpinner spinner = new JSpinner();
  @Override public void updateUI() {
    super.updateUI();
    Long lv = Optional.ofNullable(spinner)
        .map(s -> (Long) s.getModel().getValue())
        .orElse(1000L);
    // Long lv = spinner != null ? (Long) spinner.getModel().getValue() : 1000L;
    // ...
  }
#spanadd
Kotlin
#spanend
#spanadd
class MainPanel : JPanel(BorderLayout()) {
#spanend
  private val spinner = JSpinner()
  private val spinner: JSpinner? = JSpinner()
  override fun updateUI() {
    super.updateUI()
    val lv = Optional.ofNullable(spinner)
        .map { it.getModel().getValue() }
        .orElse(1000L)
#spanadd

#spanend
    // Unnecessary safe call on a non-null receiver of type JSpinner
    // val lv = spinner
    //     ?.let { it.getModel().getValue() }
    //     ?: 1000L
#spanadd

#spanend
    // NullPointerException
    // val lv = spinner.getModel().getValue()
#spanadd

#spanend
    // Condition 'spinner != null' is always 'true'
    // val lv = if (spinner != null) spinner.getModel().getValue() else 1000L
#spanadd

#spanend
    // private val spinner: JSpinner? = JSpinner() なら(他の個所もspinner?にすれば)以下でOK
    // val lv = spinner?.getModel()?.getValue() ?: 1000L
  }
#spanadd

JComponent#createUI(...)

#spanend
#spanadd
e: C:\kst\SearchBarLayoutComboBox\src\main\kotlin\example\BasicSearchBarComboBoxUI.kt: (189, 5): Accidental override: The following declarations have the same JVM signature (createUI(Ljavax/swing/JComponent;)Ljavax/swing/plaf/ComponentUI;):
#spanend
    fun createUI(p0: JComponent!): ComponentUI! defined in javax.swing.plaf.basic.BasicComboBoxUI
    fun createUI(c: JComponent?): ComponentUI defined in example.BasicSearchBarComboBoxUI
#spanadd
  • 現状ではcreateUI(...)を使用せずupdateUI()をオーバーライドして直接独自UIを設定するなどで回避するしかない?

DefaultTreeCellEditor

  • JTree.startEditingAtPath(...)メソッドを実行すると以下のようなIllegalArgumentExceptionが発生する場合がある
    • このメソッドを辿るとBasicTreeUI内でイベントをnullTreeCellEditor#isCellEditable(event)を呼び出している箇所がある
    • その為、DefaultTreeCellEditor#isCellEditable(e: EventObject)のオーバーライドはDefaultTreeCellEditor#isCellEditable(e: EventObject?)に変更する必要がある
#spanend
#spanadd
Exception in thread "AWT-EventQueue-0" java.lang.IllegalArgumentException: Parameter specified as non-null is null: method example.MainPanel$1.isCellEditable, parameter e
#spanend
#spanadd
	at example.MainPanel$1.isCellEditable(App.kt)
#spanend
#spanadd
	at javax.swing.plaf.basic.BasicTreeUI.startEditing(BasicTreeUI.java:2129)
#spanend
#spanadd
	at javax.swing.plaf.basic.BasicTreeUI.startEditingAtPath(BasicTreeUI.java:620)
#spanend
#spanadd
	at javax.swing.JTree.startEditingAtPath(JTree.java:2405)
#spanend
#spanadd
	at example.TreePopupMenu$1.actionPerformed(App.kt:49)
#spanend
#spanadd
Kotlin 自動変換
#spanend
#spanadd
import java.awt.* // ktlint-disable no-wildcard-imports
#spanend
#spanadd
import java.awt.event.MouseEvent
#spanend
#spanadd
import java.util.EventObject
#spanend
#spanadd
import javax.swing.* // ktlint-disable no-wildcard-imports
#spanend
#spanadd
import javax.swing.event.AncestorEvent
#spanend
#spanadd
import javax.swing.event.AncestorListener
#spanend
#spanadd
import javax.swing.tree.DefaultMutableTreeNode
#spanend
#spanadd
import javax.swing.tree.DefaultTreeCellEditor
#spanend
#spanadd
import javax.swing.tree.DefaultTreeCellRenderer
#spanend
#spanadd
import javax.swing.tree.TreePath
#spanend
#spanadd

#spanend
#spanadd
class MainPanel : JPanel(BorderLayout()) {
#spanend
  init {
    val tree = JTree()
    tree.cellEditor = object : DefaultTreeCellEditor(tree, tree.cellRenderer as? DefaultTreeCellRenderer) {
      override fun isCellEditable(e: EventObject) = e !is MouseEvent && super.isCellEditable(e)
    }
    tree.isEditable = true
    tree.componentPopupMenu = TreePopupMenu()
    add(JScrollPane(tree))
    preferredSize = Dimension(320, 240)
  }
#spanadd
}
#spanend
#spanadd

#spanend
#spanadd
class TreePopupMenu : JPopupMenu() {
#spanend
  private var path: TreePath? = null
  private val editItem: JMenuItem
  private val editDialogItem: JMenuItem
#spanadd

#spanend
  override fun show(c: Component, x: Int, y: Int) {
    (c as? JTree)?.also { tree ->
      val tsp = tree.selectionPaths
      path = tree.getPathForLocation(x, y)
      val isEditable = tsp != null && tsp.size == 1 && tsp[0] == path
      editItem.isEnabled = isEditable
      editDialogItem.isEnabled = isEditable
      super.show(c, x, y)
    }
  }
#spanadd

#spanend
  init {
    val field = JTextField()
    field.addAncestorListener(FocusAncestorListener())
    editItem = add("Edit")
    editItem.addActionListener {
      path?.also {
        (invoker as? JTree)?.startEditingAtPath(it)
      }
    }
    editDialogItem = add("Edit Dialog")
    editDialogItem.addActionListener {
      (path?.lastPathComponent as? DefaultMutableTreeNode)?.also { node ->
        field.text = node.userObject.toString()
        (invoker as? JTree)?.also { tree ->
          val ret = JOptionPane.showConfirmDialog(
            tree, field, "Rename", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE
          )
          if (ret == JOptionPane.OK_OPTION) {
            tree.model.valueForPathChanged(path, field.text)
          }
        }
      }
    }
    add("dummy")
  }
#spanadd
}
#spanend
#spanadd

#spanend
#spanadd
class FocusAncestorListener : AncestorListener {
#spanend
  override fun ancestorAdded(e: AncestorEvent) {
    e.component.requestFocusInWindow()
  }
#spanadd

#spanend
  override fun ancestorMoved(e: AncestorEvent) {
    /* not needed */
  }
#spanadd

#spanend
  override fun ancestorRemoved(e: AncestorEvent) {
    /* not needed */
  }
#spanadd
}
#spanend
#spanadd

#spanend
#spanadd
fun main() {
#spanend
  EventQueue.invokeLater {
    runCatching {
      UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())
    }.onFailure {
      it.printStackTrace()
      Toolkit.getDefaultToolkit().beep()
    }
    JFrame().apply {
      setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
      getContentPane().add(MainPanel())
      pack()
      setLocationRelativeTo(null)
      setVisible(true)
    }
  }
#spanadd
}
#spanend
#spanadd
Kotlin 手動修正
#spanend
#spanadd
import java.awt.* // ktlint-disable no-wildcard-imports
#spanend
#spanadd
import java.awt.event.MouseEvent
#spanend
#spanadd
import java.util.EventObject
#spanend
#spanadd
import javax.swing.* // ktlint-disable no-wildcard-imports
#spanend
#spanadd
import javax.swing.event.AncestorEvent
#spanend
#spanadd
import javax.swing.event.AncestorListener
#spanend
#spanadd
import javax.swing.tree.DefaultMutableTreeNode
#spanend
#spanadd
import javax.swing.tree.DefaultTreeCellEditor
#spanend
#spanadd
import javax.swing.tree.DefaultTreeCellRenderer
#spanend
#spanadd
import javax.swing.tree.TreePath
#spanend
#spanadd

#spanend
#spanadd
class MainPanel : JPanel(BorderLayout()) {
#spanend
  init {
    val tree = JTree()
    tree.cellEditor = object : DefaultTreeCellEditor(tree, tree.cellRenderer as? DefaultTreeCellRenderer) {
      override fun isCellEditable(e: EventObject) = e !is MouseEvent && super.isCellEditable(e)
    }
    tree.isEditable = true
    tree.componentPopupMenu = TreePopupMenu()
    add(JScrollPane(tree))
    preferredSize = Dimension(320, 240)
  }
#spanadd
}
#spanend
#spanadd

#spanend
#spanadd
class TreePopupMenu : JPopupMenu() {
#spanend
  private var path: TreePath? = null
  private val editItem: JMenuItem
  private val editDialogItem: JMenuItem
#spanadd

#spanend
  override fun show(c: Component, x: Int, y: Int) {
    (c as? JTree)?.also { tree ->
      val tsp = tree.selectionPaths
      path = tree.getPathForLocation(x, y)
      val isEditable = tsp != null && tsp.size == 1 && tsp[0] == path
      editItem.isEnabled = isEditable
      editDialogItem.isEnabled = isEditable
      super.show(c, x, y)
    }
  }
#spanadd

#spanend
  init {
    val field = JTextField()
    field.addAncestorListener(FocusAncestorListener())
    editItem = add("Edit")
    editItem.addActionListener {
      path?.also {
        (invoker as? JTree)?.startEditingAtPath(it)
      }
    }
    editDialogItem = add("Edit Dialog")
    editDialogItem.addActionListener {
      (path?.lastPathComponent as? DefaultMutableTreeNode)?.also { node ->
        field.text = node.userObject.toString()
        (invoker as? JTree)?.also { tree ->
          val ret = JOptionPane.showConfirmDialog(
            tree, field, "Rename", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE
          )
          if (ret == JOptionPane.OK_OPTION) {
            tree.model.valueForPathChanged(path, field.text)
          }
        }
      }
    }
    add("JMenuItem")
  }
#spanadd
}
#spanend
#spanadd

#spanend
#spanadd
class FocusAncestorListener : AncestorListener {
#spanend
  override fun ancestorAdded(e: AncestorEvent) {
    e.component.requestFocusInWindow()
  }
#spanadd

#spanend
  override fun ancestorMoved(e: AncestorEvent) {
    /* not needed */
  }
#spanadd

#spanend
  override fun ancestorRemoved(e: AncestorEvent) {
    /* not needed */
  }
#spanadd
}
#spanend
#spanadd

#spanend
#spanadd
fun main() {
#spanend
  EventQueue.invokeLater {
    runCatching {
      UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())
    }.onFailure {
      it.printStackTrace()
      Toolkit.getDefaultToolkit().beep()
    }
    JFrame().apply {
      setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
      getContentPane().add(MainPanel())
      pack()
      setLocationRelativeTo(null)
      setVisible(true)
    }
  }
#spanadd
}
#spanend
#spanadd

手動変換

  • IntelliJの自動変換で正常に変換されるが、手動修正でもっと短いKotlinコードに変換が可能なケースのメモ
    • java.util.*のクラスを使用している箇所は変換可能な場合が多い

IntStream

  • java.util.stream.IntStreamIntRangeに置換可能
#spanend
#spanadd
val indices = IntStream.range(0, l.getModel().getSize())
#spanend
    .filter { rb.intersects(l.getCellBounds(it, it)) }.toArray()
#spanadd
#spanend
#spanadd
val indices = (0 until l.getModel().getSize())
#spanend
    .filter { rb.intersects(l.getCellBounds(it, it)) }.toIntArray()
#spanadd

Random

#spanend
#spanadd
val lengthOfTask = 10 + Random().nextInt(50)
#spanend
#spanadd
#spanend
#spanadd
val lengthOfTask = (10..60).random()
#spanend
#spanadd

List#get(0)

  • java.util.List#.get(0)List#firstOrNull()などに置換可能
#spanend
#spanadd
var flag = 1
#spanend
#spanadd
val keys = table.getRowSorter().getSortKeys()
#spanend
#spanadd
if (!keys.isEmpty()) {
#spanend
  val sortKey = keys.get(0)
  if (sortKey.getColumn() == column && sortKey.getSortOrder() == SortOrder.DESCENDING) {
    flag = -1
  }
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
val flag = table.getRowSorter().getSortKeys().firstOrNull()
#spanend
    ?.takeIf { it.getColumn() == column && it.getSortOrder() == SortOrder.DESCENDING }
    ?.let { -1 } ?: 1
#spanadd

配列の最終要素

  • 配列の最終要素の取得は、Array#last()などに置換可能
#spanend
#spanadd
p.setComponentZOrder(p.getComponent(p.getComponentCount() - 1), 0)
#spanend
#spanadd
#spanend
#spanadd
p.setComponentZOrder(p.getComponents().last(), 0)
#spanend
#spanadd

Optional

Optional.ofNullable(...)

  • Optional.ofNullable(...).filter(...)は、?.takeIf {...}などに置換可能
#spanend
#spanadd
val clz = JTable::class.java
#spanend
#spanadd
Optional.ofNullable(SwingUtilities.getAncestorOfClass(clz, e.getComponent()))
#spanend
    .filter(clz::isInstance)
    .map(clz::cast)
    .filter(JTable::isEditing)
    .ifPresent(JTable::removeEditor)
#spanadd
#spanend
#spanadd
SwingUtilities.getAncestorOfClass(JTable::class.java, e.getComponent())
#spanend
    ?.takeIf { it is JTable }
    ?.let { it as JTable }
    ?.takeIf { it.isEditing() }
    ?.also{ it.removeEditor() }
#spanadd
#spanend
#spanadd
// takeIf {...}内のitにはスマートキャストが効くが、それを越えて作用はしない
#spanend
#spanadd
SwingUtilities.getAncestorOfClass(JTable::class.java, e.getComponent())
#spanend
    ?.takeIf { it is JTable && it.isEditing() }
    ?.also { (it as JTable).removeEditor() }
    // ?.also { JTable::removeEditor } // コンパイルエラーにはならないが、removeEditor()メソッドは実行されない?
#spanadd

Optional#orElse(...)

  • Optional#orElse(...)はエルビス演算子?:に置換可能
#spanend
#spanadd
val lv = Optional.ofNullable(spinner).map { it.getModel().getValue() }.orElse(1000L)
#spanend
#spanadd
#spanend
#spanadd
val lv = spinner?.getModel()?.getValue() ?: 1000L
#spanend
#spanadd

Objects

Objects.nonNull(...)

  • Objects.nonNull(o)などは、セーフコール演算子?.に置換可能
#spanend
#spanadd
if (Objects.nonNull(colHead) && colHead.isVisible()) {
#spanend
  val colHeadHeight = Math.min(availR.height, colHead.getPreferredSize().height)
  // ...
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
colHead?.takeIf { it.isVisible() }?.let {
#spanend
  val colHeadHeight = Math.min(availR.height, it.getPreferredSize().height)
  // ...
#spanadd
}
#spanend
#spanadd

Objects.requireNonNull(...)

  • Objects.requireNonNull(o)requireNotNullに置換可能
  • NonではなくNotになることに注意
#spanend
#spanadd
Objects.requireNonNull(url)
#spanend
#spanadd
#spanend
#spanadd
requireNotNull(url)
#spanend
#spanadd

Stream

  • Streamは、List(Iterable)に置換可能な場合が多い
  • 配列もIterableなのでStreamにする必要はあまりない
#spanend
#spanadd
fun stream(parent: Container): Stream<Component> = Arrays.stream(parent.getComponents())
#spanend
    .filter(Container::class.java::isInstance)
    .map { c -> stream(Container::class.java.cast(c)) }
    .reduce(Stream.of<Component>(parent), { a, b -> Stream.concat<Component>(a, b) })
#spanadd
#spanend
#spanadd
fun descendants(parent: Container): List<Component> = parent.getComponents()
#spanend
    .filterIsInstance(Container::class.java)
    .map { descendants(it) }
    .fold(listOf<Component>(parent), { a, b -> a + b })
#spanadd

Stream.flatMap(...)

  • Stream#flatMap(Function<? super T,? extends Stream<? extends R>> mapper)はそのままIterable<T>.flatMap(transform: (T) -> Iterable<R>)に置換可能
#spanend
#spanadd
// // Java
#spanend
#spanadd
// private Stream<MenuElement> descendants(MenuElement me) {
#spanend
#spanadd
//   return Stream.of(me.getSubElements())
#spanend
#spanadd
//     .flatMap(m -> Stream.concat(Stream.of(m), descendants(m)));
#spanend
#spanadd
// }
#spanend
#spanadd
fun descendants(me: MenuElement): Stream<MenuElement> {
#spanend
  return Stream.of(*me.subElements)
    .flatMap { Stream.concat(Stream.of(it), descendants(it)) }
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
// StreamではなくIterableが使用可能
#spanend
#spanadd
private fun descendants(me: MenuElement): List<MenuElement> =
#spanend
  me.getSubElements().flatMap { listOf(it) + descendants(it) }
#spanadd

Stream.reduce(...)

  • 初期値有りのStream#reduce(T identity, BinaryOperator<T> accumulator)は、Iterable<T>.fold(initial: R, operation: (acc: R, T) -> R)に置換可能
#spanend
#spanadd
// // Java
#spanend
#spanadd
// int max = group.stream()
#spanend
#spanadd
//     .map(AlignedLabel::getSuperPreferredWidth)
#spanend
#spanadd
//     .reduce(0, Integer::max);
#spanend
#spanadd
val max = group.stream()
#spanend
    .map(Function<AlignedLabel, Int> { it.getSuperPreferredWidth() })
    .reduce(0, BinaryOperator<Int> { a, b -> Integer.max(a, b) })
#spanadd
#spanend
#spanadd
val max = group.stream()
#spanend
    .map { it.getSuperPreferredWidth() }
    .reduce(0, Integer::max)
#spanadd
#spanend
#spanadd
val max = group
#spanend
    .map { it.getSuperPreferredWidth() }
    .fold(0) { a, b -> maxOf(a, b) }
#spanadd
#spanend
#spanadd
val max = group.map { it.getSuperPreferredWidth() }.fold(0, ::maxOf)
#spanend
#spanadd
#spanend
#spanadd
// この例だとIntなのでmax()とエルビス演算子`?:`が使用可能
#spanend
#spanadd
val max = group.map { it.getSuperPreferredWidth() }.max() ?: 0
#spanend
#spanadd

Collections.nCopies(...).joinToString(...)

#spanend
#spanadd
val text = Collections.nCopies(2000, "aaaaaaaaaaaaa").joinToString("\n")
#spanend
#spanadd
#spanend
#spanadd
val text = "aaaaaaaaaaaaa\n".repeat(2000)
#spanend
#spanadd

joinToString(...)

#spanend
#spanadd
String msg = jlist.getSelectedValuesList().stream()
#spanend
    .map(it -> it.title)
    .collect(Collectors.joining(", "));
#spanadd
#spanend
#spanadd
val msg = jlist.selectedValuesList
#spanend
    .map { it.title }
    .joinToString(", ")
#spanadd

maxOf(...)

#spanend
#spanadd
// private List<AlignedLabel> group;
#spanend
#spanadd
int max = group.stream()
#spanend
    .map(AlignedLabel::getSuperPreferredWidth)
    .reduce(0, Integer::max);
#spanadd
#spanend
#spanadd
// private var group = mutableListOf<AlignedLabel>()
#spanend
#spanadd
// val max = group.map { it.getSuperPreferredWidth() }.fold(0, ::maxOf)
#spanend
#spanadd
// val max = group.map { it.getSuperPreferredWidth() }.maxOrNull() ?: 0
#spanend
#spanadd
// group.isNotEmpty()
#spanend
#spanadd
val max = group.maxOf(AlignedLabel::getSuperPreferredWidth)
#spanend
#spanadd

複数行文字列

#spanend
#spanadd
// private String makeTestHtml() {
#spanend
#spanadd
//   return String.join("\n", strarray);
#spanend
#spanadd
// }
#spanend
#spanadd
// を自動変換すると以下のようなコードになる
#spanend
#spanadd
private fun makeTestHtml(): String {
#spanend
  return arrayOf(
    "<html><body>",
    "<div>2222222222</div>",
    "</body></html>").joinToString("\n")
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
// が、三重引用符で囲めば改行などをエスケープシーケンスで記入する必要はない
#spanend
#spanadd
private fun makeTestHtml() = """
#spanend
  <html>
    <body>
      <div>2222222222</div>
    </body>
  </html>
#spanadd
"""
#spanend
#spanadd

Comparator.comparing(...).thenComparing(...)

  • 複数キーでソートするComparator<T>は、JavaではComparator.comparing(Function<? super T,? extends U> keyExtractor).thenComparing(Function<? super T,? extends U> keyExtractor)で作成可能
  • KotlinではcompareBy(vararg selectors: (T) -> Comparable<*>?)Comparator<T>.thenBy(...)で同等のComparator<T>が作成可能
#spanend
#spanadd
// val tnc = Comparator.comparing(Function<DefaultMutableTreeNode, Boolean> { it.isLeaf() })
#spanend
#spanadd
val tnc = Comparator.comparing<DefaultMutableTreeNode, Boolean> { it.isLeaf() }
#spanend
    .thenComparing { n -> n.getUserObject().toString() }
#spanadd
val children = parent.children().toList()
#spanend
    .filterIsInstance(DefaultMutableTreeNode::class.java)
    .sortedWith(tnc)
#spanadd
#spanend
#spanadd
val tnc = compareBy<DefaultMutableTreeNode> { it.isLeaf() }
#spanend
    .thenBy { it.getUserObject().toString() }
#spanadd
val children = parent.children().toList()
#spanend
    .filterIsInstance(DefaultMutableTreeNode::class.java)
    .sortedWith(tnc)
    // .sortedWith(compareBy(DefaultMutableTreeNode::isLeaf, { it.getUserObject().toString() }))
#spanadd

filterIsInstance

#spanend
#spanadd
// Only classes are allowed on the left hand side of a class literal
#spanend
#spanadd
// とエラーになる
#spanend
#spanadd
listOf(c0, c1, c2, c3)
#spanend
  .mapNotNull { it.getModel() }
  .filterIsInstance(MutableComboBoxModel<String>::class.java)
  .forEach { it.insertElementAt(str, it.getSize()) }
#spanadd
#spanend
#spanadd
// Type mismatch. でエラーになる
#spanend
#spanadd
listOf(c0, c1, c2, c3)
#spanend
  .mapNotNull { it.getModel() }
  .filterIsInstance(MutableComboBoxModel::class.java)
  .forEach { it.insertElementAt(str, it.getSize()) }
#spanadd
#spanend
#spanadd
// reified type parameter を使用する
#spanend
#spanadd
listOf(c0, c1, c2, c3)
#spanend
  .mapNotNull { it.getModel() }
  .filterIsInstance<MutableComboBoxModel<String>>()
  .forEach { it.insertElementAt(str, it.getSize()) }
#spanadd
#spanend
#spanadd
listOf(c0, c1, c2, c3)
#spanend
  .mapNotNull { it.getModel() as? MutableComboBoxModel<String> }
  .forEach { it.insertElementAt(str, it.getSize()) }
#spanadd

Functional (SAM) interfaces

  • kotlin 1.4.0からKotlinインターフェイスでもSAM(Single Abstract Method)変換を利用可能になったが、まだIntelliJの自動変換ではFunctional interfacesではなく普通のinterfacesに変換されるのでこれを使用する場合は手動で置換する必要がある
#spanend
#spanadd
interface ExpansionListener : EventListener {
#spanend
   fun expansionStateChanged(e: ExpansionEvent)
 }
#spanadd
}
#spanend
#spanadd
// ...
#spanend
#spanadd
p.addExpansionListener(object : ExpansionListener {
#spanend
  override fun expansionStateChanged(e: ExpansionEvent) {
    (e.source as? Component)?.also {
      // ...
    }
  }
#spanadd
})
#spanend
#spanadd
#spanend
#spanadd
fun interface ExpansionListener : EventListener {
#spanend
   fun expansionStateChanged(e: ExpansionEvent)
 }
#spanadd
}
#spanend
#spanadd
// ...
#spanend
#spanadd
p.addExpansionListener { e ->
#spanend
  (e.source as? Component)?.also {
    // ...
  }
#spanadd
}
#spanend
#spanadd

Jetpack Compose for Desktop

コメント