Kotlin のバックアップ(No.118)
- バックアップ一覧
- 差分 を表示
- 現在との差分 を表示
- 現在との差分 - Visual を表示
- ソース を表示
- Kotlin へ行く。
- 1 (2017-05-31 (水) 18:35:21)
- 2 (2017-05-31 (水) 19:53:14)
- 3 (2017-05-31 (水) 20:59:47)
- 4 (2017-05-31 (水) 23:55:03)
- 5 (2017-06-01 (木) 10:59:15)
- 6 (2017-06-01 (木) 12:23:00)
- 7 (2017-06-01 (木) 16:03:15)
- 8 (2017-06-01 (木) 17:20:46)
- 9 (2017-06-04 (日) 13:23:23)
- 10 (2017-06-05 (月) 18:47:29)
- 11 (2017-06-08 (木) 14:12:31)
- 12 (2017-06-08 (木) 19:03:39)
- 13 (2017-06-13 (火) 14:53:44)
- 14 (2017-06-26 (月) 15:09:29)
- 15 (2017-09-04 (月) 15:51:42)
- 16 (2017-09-04 (月) 17:12:49)
- 17 (2017-10-03 (火) 14:50:20)
- 18 (2017-11-07 (火) 16:44:56)
- 19 (2017-11-08 (水) 16:37:18)
- 20 (2017-11-13 (月) 18:11:40)
- 21 (2017-11-20 (月) 18:51:07)
- 22 (2017-12-12 (火) 19:07:44)
- 23 (2017-12-12 (火) 20:36:03)
- 24 (2017-12-14 (木) 15:57:09)
- 25 (2017-12-15 (金) 20:15:22)
- 26 (2017-12-15 (金) 21:32:06)
- 27 (2017-12-18 (月) 16:30:34)
- 28 (2017-12-23 (土) 15:30:50)
- 29 (2017-12-25 (月) 13:58:48)
- 30 (2017-12-25 (月) 17:40:04)
- 31 (2017-12-25 (月) 20:12:05)
- 32 (2017-12-28 (木) 22:22:38)
- 33 (2018-01-09 (火) 18:02:52)
- 34 (2018-03-02 (金) 18:20:39)
- 35 (2018-03-05 (月) 17:52:19)
- 36 (2018-03-26 (月) 21:25:15)
- 37 (2018-08-09 (木) 18:17:44)
- 38 (2018-10-30 (火) 17:00:35)
- 39 (2018-10-31 (水) 15:46:41)
- 40 (2018-10-31 (水) 17:07:25)
- 41 (2018-11-05 (月) 18:05:10)
- 42 (2018-11-06 (火) 18:51:01)
- 43 (2018-11-07 (水) 13:55:45)
- 44 (2018-11-07 (水) 19:30:57)
- 45 (2018-11-07 (水) 21:39:08)
- 46 (2018-11-08 (木) 13:58:44)
- 47 (2018-11-08 (木) 14:59:11)
- 48 (2018-11-08 (木) 19:22:44)
- 49 (2018-11-09 (金) 17:05:26)
- 50 (2018-11-13 (火) 14:49:29)
- 51 (2018-11-15 (木) 15:36:23)
- 52 (2018-11-16 (金) 18:22:36)
- 53 (2018-12-07 (金) 19:39:18)
- 54 (2018-12-09 (日) 21:57:42)
- 55 (2018-12-10 (月) 16:52:25)
- 56 (2018-12-10 (月) 18:05:19)
- 57 (2018-12-17 (月) 17:23:20)
- 58 (2018-12-20 (木) 13:56:07)
- 59 (2019-01-07 (月) 16:14:59)
- 60 (2019-01-17 (木) 17:40:32)
- 61 (2019-01-18 (金) 19:53:07)
- 62 (2019-02-01 (金) 21:34:36)
- 63 (2019-02-02 (土) 23:27:16)
- 64 (2019-02-12 (火) 16:09:08)
- 65 (2019-02-26 (火) 19:20:13)
- 66 (2019-02-26 (火) 21:11:09)
- 67 (2019-02-27 (水) 15:48:09)
- 68 (2019-02-27 (水) 19:31:05)
- 69 (2019-02-27 (水) 21:18:44)
- 70 (2019-02-28 (木) 22:09:03)
- 71 (2019-03-11 (月) 15:32:26)
- 72 (2019-03-14 (木) 17:38:07)
- 73 (2019-03-25 (月) 19:57:36)
- 74 (2019-03-27 (水) 20:28:44)
- 75 (2019-04-02 (火) 19:32:48)
- 76 (2019-04-04 (木) 20:13:39)
- 77 (2019-04-05 (金) 14:32:20)
- 78 (2019-04-05 (金) 18:05:49)
- 79 (2019-04-09 (火) 16:53:20)
- 80 (2019-04-17 (水) 16:40:32)
- 81 (2019-04-23 (火) 18:47:09)
- 82 (2019-04-23 (火) 20:15:22)
- 83 (2019-04-24 (水) 18:46:55)
- 84 (2019-05-02 (木) 17:57:41)
- 85 (2019-05-20 (月) 19:24:10)
- 86 (2019-05-22 (水) 17:19:10)
- 87 (2019-05-22 (水) 19:35:38)
- 88 (2019-05-23 (木) 17:54:37)
- 89 (2019-05-27 (月) 18:26:22)
- 90 (2019-05-28 (火) 15:22:38)
- 91 (2019-06-10 (月) 19:46:56)
- 92 (2019-06-11 (火) 19:41:28)
- 93 (2019-06-14 (金) 16:18:02)
- 94 (2019-06-14 (金) 20:35:44)
- 95 (2019-06-24 (月) 14:43:23)
- 96 (2019-06-24 (月) 18:26:16)
- 97 (2019-07-02 (火) 16:23:12)
- 98 (2019-07-29 (月) 16:16:17)
- 99 (2019-08-08 (木) 22:52:05)
- 100 (2019-08-13 (火) 17:47:36)
- 101 (2019-08-15 (木) 16:09:14)
- 102 (2019-08-26 (月) 16:39:57)
- 103 (2019-08-26 (月) 18:12:51)
- 104 (2019-08-26 (月) 20:23:07)
- 105 (2019-08-27 (火) 14:22:16)
- 106 (2019-08-28 (水) 19:38:13)
- 107 (2019-08-29 (木) 19:11:00)
- 108 (2019-09-12 (木) 16:08:13)
- 109 (2019-11-12 (火) 19:55:32)
- 110 (2019-11-13 (水) 13:29:33)
- 111 (2019-12-05 (木) 22:21:26)
- 112 (2019-12-16 (月) 17:39:21)
- 113 (2019-12-17 (火) 14:36:42)
- 114 (2020-01-06 (月) 18:30:19)
- 115 (2020-01-16 (木) 20:26:27)
- 116 (2020-03-19 (木) 20:05:04)
- 117 (2020-08-24 (月) 14:44:31)
- 118 (2020-08-26 (水) 12:18:57)
- 119 (2020-11-12 (木) 10:10:09)
- 120 (2021-09-28 (火) 12:56:54)
- 121 (2021-12-13 (月) 15:11:40)
- 122 (2022-03-29 (火) 15:20:50)
- 123 (2023-07-10 (月) 11:14:56)
- 124 (2024-02-21 (水) 02:08:19)
- 125 (2025-01-03 (金) 08:57:02)
- 126 (2025-01-03 (金) 09:04:02)
- keywords: [Kotlin, Swing, Java] description: KotlinでSwingコンポーネントを使用するサンプルと、Javaからの変換に関するメモなど author: aterai pubdate: 2017-05-31T18:35:21+09:00
概要
Kotlin
でSwing
コンポーネントを使用するサンプルと、Java
からの変換に関するメモです。
実行環境
- SDKMAN! the Software Development Kit Managerで
kotlin
のインストールが可能
$ curl -s "https://get.sdkman.io" | bash $ sdk install kotlin $ kotlinc -version info: kotlinc-jvm 1.3.70 (JRE 1.8.0_242-b07) $ kotlinc hello.kt -include-runtime -d hello.jar && "$JAVA_HOME/bin/java" -jar hello.jar
Gradle
- Using Gradle - Kotlin Programming Language
- kotlin-examples/gradle/hello-world at master ・ Kotlin/kotlin-examples
- 上記のサンプル
build.gradle
が参考になる
- 上記のサンプル
- Kotlin Basics: Create Executable Kotlin JARs, using Gradle
jar
作成は同じくこちらの方法が使用可能- Configuring Dependencies - Using Gradle - Kotlin Programming Language
kotlin-stdlib
の場合Java 6
以上が対象になるので、Java 7
で導入されたAutoCloseable
でuse
を使用すると@InlineOnly public inline fun <T : Closeable?, R> ???.use(block: (???) -> ???): ??? defined in kotlin.io
とコンパイルエラーになるkotlin-stdlib-jdk7
かkotlin-stdlib-jdk8
を使用すれば回避可能
buildscript {
ext.kotlin_version = 'latest.release'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'kotlin'
apply plugin: 'application'
// group 'KotlinSwingTips'
// version '1.0-SNAPSHOT'
mainClassName = 'example.AppKt'
defaultTasks 'run'
repositories {
mavenCentral()
jcenter()
}
dependencies {
// 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"
}
sourceCompatibility = 1.8
compileKotlin {
kotlinOptions.jvmTarget = "$sourceCompatibility"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "$sourceCompatibility"
}
jar {
manifest { attributes 'Main-Class': "$mainClassName" }
from { configurations.compileClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
}
IntelliJ
の自動変換
- メニューの
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>
になっていると、@Override
がoverride
に変換されない場合がある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.50
でIntelliJ IDEA
のConvert Java File to Kotlin File
(Ctrl+Alt+Shift+K)が実験的に改良されているUse New J2K(experimental)
フラグで試用可能になるが、設定をJ2K
で検索しても見つからないSettings
ダイアログ→Languages & Frameworks
→Use New Java to Kotlin Converter
にチェックを入れる必要があるKotlin Plugin
を1.4.31-release-IJ2019.2-1
から1.3.50-release-IJ2019.2-1
にするとUse New Java to Kotlin Converter
にチェックをしているかどうかに関わらず、KotlinNullPointerException
で変換に失敗する場合がある?- KotlinNullPointerException on J2K convertion of constructor reference as Function type argument : KT-33431
kotlin.KotlinNullPointerException at org.jetbrains.kotlin.idea.actions.JavaToKotlinAction.actionPerformed(JavaToKotlinAction.kt:221) ...
- KotlinNullPointerException on J2K convertion of constructor reference as Function type argument : KT-33431
- クリップボード経由の変換は
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)
// 以下のように誤変換される場合がある???
// 再現しないので勘違いかもしれない
// btn.setSelected(i and (1 shl 2 != 0))
bitwise AND
(and
の優先順位)- Bitwise operators are not in the precedence table - Support - Kotlin Discussions
- Kotlinの演算子優先順位表
- Basic Types: Numbers, Strings, Arrays - Kotlin Programming Language
Kotlin
のbitwise and
はinfix function
のため、Equality
の!=
より優先順位が高い
Java
の演算子優先順位表: Operators (The Java™ Tutorials > Learning the Java Language > Language Basics)Java
は逆で、equality
(==
,!=
)のほうがbitwise AND
(&
)より優先順位が高い
型パラメータ(修正済)
- 修正済
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(...)) {
も可能?)
- 型引数
Java
とKotlin
では型引数を付ける位置が異なるので注意
Java
:Arrays.<Component>asList(...)
Generic Methods (The Java™ Tutorials > Learning the Java Language > Generics (Updated))
For static generic methods, the type parameter section must appear before the method's return type.
Kotlin
:Arrays.asList<Component>(...)
Generics: in, out, where - Kotlin Programming Language
To call a generic function, specify the type arguments at the call site after the name of the function:
Kotlin
でJava
風に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
で自動変換すると失敗する場合がある- 例えば
JComboBox.class
はIntelliJ
の自動変換でJComboBox<*>::class.java
に変換されるが、JComboBox::class.java
に修正しないとコンパイルエラーになる Only classes are allowed on the left hand side of a class literal
- J2K converts class literals including redundant generic <*> : KT-15791
- 例えば
- 逆に
Java
のc instanceof JComboBox
をKotlin
に変換するときは、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
(手動修正)
enabledCheck.addActionListener {
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)
}
}
型引数の省略(修正済)
- 修正済
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
の自動変換では正しく変換されるようになっている
- インターフェイスから匿名内部クラスのインスタンスを生成する場合は
()
は不要- SAM Conversions - Calling Java from Kotlin - Kotlin Programming Language
tree.addTreeWillExpandListener(object : TreeWillExpandListener() {
をtree.addTreeWillExpandListener(object : TreeWillExpandListener {
に修正
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() {
@Throws(ExpandVetoException::class)
override fun treeWillExpand(e: TreeExpansionEvent) {
// throw new ExpandVetoException(e, "Tree expansion cancelled");
}
@Throws(ExpandVetoException::class)
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");
}
@Throws(ExpandVetoException::class)
override fun treeWillCollapse(e: TreeExpansionEvent) {
throw ExpandVetoException(e, "Tree collapse cancelled")
}
})
ラベル付きReturn
匿名内部クラスからreturn
で抜けるJava
コードをIntelliJ
の自動変換で変換すると、unresolved reference: @decode
とエラーになるKotlin
コードに変換される場合があるIntelliJ 2018.2.5
の自動変換では正しく変換されるようになっている- 以下のような変換例の場合、
return@decode.addActionListener
をreturn@addActionListener
のようにメソッド名のみのラベルに変更する必要がある - Returns and Jumps: break and continue - Kotlin Programming Language
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")
decode.addActionListener {
val b64 = textArea.getText()
if (b64.isEmpty()) {
return@addActionListener
}
try {
// ...
} catch (ex: IOException) {
ex.printStackTrace()
}
}
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
BufferedImage bi = Optional.ofNullable(getClass().getResource("unkaku_w.png"))
.map(url -> {
try {
return ImageIO.read(url);
} catch (IOException ex) {
return makeMissingImage();
}
}).orElseGet(() -> makeMissingImage());
Kotlin
(自動変換)
val bi = Optional.ofNullable(javaClass.getResource("unkaku_w.png")).map({ url ->
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 ()
}
}).orElseGet({ makeMissingImage() })
Kotlin
(手動修正1)
val bi = Optional.ofNullable(javaClass.getResource("unkaku_w.png")).map { url ->
try {
ImageIO.read(url)
} catch (ex: IOException) {
makeMissingImage()
}
}.orElseGet { makeMissingImage() }
Kotlin
(手動修正2)
val bi = javaClass.getResource("unkaku_w.png")?.let {
try {
ImageIO.read(it)
} catch (ex: IOException) {
makeMissingImage()
}
} ?: makeMissingImage()
Kotlin
(手動修正3)
// runCatching関数でResultを取得するようtry-catchを置き換える
val bi = runCatching {
ImageIO.read(javaClass.getResource("unkaku_w.png"))
}.getOrNull() ?: makeMissingImage()
メソッド参照
IntelliJ
の自動変換ではメソッド参照の変換が苦手?いくらか修正されている?IntelliJ 2018.2.5
の自動変換では、???
ではなくPredicate<Component>
などに変換されるようになった
Java
public static Stream<TreeNode> descendants(TreeNode node) {
Class<TreeNode> clz = TreeNode.class;
return Collections.list((Enumeration<?>) node.children())
.stream().filter(clz::isInstance).map(clz::cast);
}
Kotlin
(自動変換)
fun descendants(node: TreeNode): Stream<TreeNode> {
val clz = TreeNode::class.java
return Collections.list(node.children() as Enumeration<*>)
.stream().filter(???({ clz!!.isInstance() })).map(clz!!.cast)
}
Kotlin
(手動修正)
fun descendants(node: TreeNode): Stream<TreeNode> {
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()
が便利
fun descendants(node: TreeNode): List<TreeNode> {
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
の追加は対応していない
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 Arrays.asList(parent.getComponents())
.filter(Container::class.java::isInstance)
.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 に変換してしまう方法もある
fun descendants(parent: Container): List<Component> {
return parent.getComponents().toList()
.filterIsInstance(Container::class.java)
.map { descendants(it) }
.fold(listOf<Component>(parent)) { a, b -> a + b }
}
// ...
descendants(fileChooser1)
.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
になるメソッドを探すためメソッド参照で以下のようなエラーになる- Function reference: ignoring the returned value - Support - Kotlin Discussions
map(p::add)
にすればエラーは回避できる
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))
listOf(button1, button2).forEach { b -> p.add(b) }
//or listOf(button1, button2).map(p::add)
コンストラクタ参照
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.
::JToggleButton
に修正すればエラーを回避可能
Java
Stream.of("A", "B", "C").map(JToggleButton::new).forEach(r -> {
p.add(r);
bg.add(r);
});
Kotlin
(自動変換)
Stream.of("A", "B", "C").map(Function<String, JToggleButton> { JToggleButton(it) }).forEach({ r ->
p.add(r)
bg.add(r)
})
Kotlin
(手動修正)
Stream.of("A", "B", "C").map(::JToggleButton).forEach { r ->
p.add(r)
bg.add(r)
}
Number#intValue()
Number#intValue()
をIntelliJ
で自動変換するとintValue()
がそのまま使用されるが、コンパイルするとerror: unresolved reference: intValue
とエラーになるので手動でtoInt()
などを使用するよう修正する必要がある1.4.31-release-IJ2019.2-1
の自動変換ではtoInt()
になる
Java
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.jar
とkotlinc
にオプションを追加
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) }
.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
で自動変換できないだけで、Kotlin
で16
進数表記の数値にアンダースコアを付けても問題なく動作可能- アンダースコアがなければ、
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
editor1.setSelectionColor(new Color(0x64_88_AA_AA, true)); // IntelliJで自動変換できない
editor2.setSelectionColor(new Color(0x6488AAAA, true)); // IntelliJで自動変換可能
Kotlin
(手動修正)
editor1.setSelectionColor(Color(0x64_88_AA_AA, true)) // 問題なし
editor2.setSelectionColor(Color(0x6488AAAA, true)) // 問題なし
プライマリコンストラクタでのプロパティ定義
Java
のクラスで変数宣言にコメントが付いている場合、IntelliJ
の自動変換ではそのコメントがプライマリコンストラクタの引数でのプロパティ定義にそのままコピーされてしまう場合があるたとえば以下の例では、// = table.getTableHeader();
がclass RowHeaderRenderer<E>(private val header: JTableHeader // = table.getTableHeader();) : JLabel(), ...
とプライマリコンストラクタのプロパティ定義に入り込んでそれ以降がコメントアウトされてしまうため、コンパイルエラーになる- コメントの末尾で改行が入るよう修正された
Java
class RowHeaderRenderer<E> extends JLabel implements ListCellRenderer<E> {
private final JTableHeader header; // = table.getTableHeader();
public RowHeaderRenderer(JTableHeader header) {
super();
this.header = header;
this.setOpaque(true);
// ...
Kotlin
(旧自動変換)
inner class RowHeaderRenderer<E>(private val header: JTableHeader // = table.getTableHeader();) : JLabel(), ListCellRenderer<E> {
init {
this.setOpaque(true)
// ...
Kotlin
(新自動変換)
inner class RowHeaderRenderer<E>(private val header: JTableHeader // = table.getTableHeader();
) : JLabel(), ListCellRenderer<E> {
init {
this.setOpaque(true)
// ...
int、Int
Java
では例えば-1
を0xFFFFFFFF
のような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
Color argb = new Color(0xffaabbcc, true);
Color rgb = new Color(0xaabbcc);
Kotlin
(自動変換)
val argb = Color(-0x554434, true)
val rgb = Color(0xaabbcc)
Kotlin
(手動修正)
// LongからIntに変換する方法もある
val argb = Color(0xff_aa_bb_cc.toInt(), true)
println(argb == Color(-0x554434, true)) // true
@SuppressWarnings
IntelliJ
の自動変換では、@SuppressWarnings("unchecked")
などは削除されるので、Unchecked cast: WatchEvent<*>! to WatchEvent<Path>
と警告される- 手動で
@Suppress("UNCHECKED_CAST")
などを付ける必要がある
- 手動で
Java
@SuppressWarnings("unchecked")
WatchEvent<Path> ev = (WatchEvent<Path>) event;
Kotlin
(手動修正)
@Suppress("UNCHECKED_CAST")
val ev = event as WatchEvent<Path>
二次元配列
IntelliJ
の自動変換で二次元配列をクローンするコードを変換した場合、Type mismatch: inferred type is Array<Int?> but Array<Int> was expected
のようなエラーになる場合がある- Array - Kotlin Programming Language
Array
のコンストラクタで第二引数のinit
関数を使用して初期値を指定するなどの修正が必要
Java
private final Integer[][] mask;
@SuppressWarnings("PMD.UseVarargs")
protected SudokuCellRenderer(Integer[][] src) {
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;
}
Kotlin
(自動変換)
private val mask: Array<Array<Int>>
init {
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
}
Kotlin
(手動修正1)
private val mask: Array<Array<Int?>>
init {
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
}
Kotlin
(手動修正2)
private val mask: Array<Array<Int>>
init {
// System.arraycopyなどは使用せず、コピー元の二次元配列を参照して初期化
this.mask = Array(src.size, { i -> Array(src[i].size, { j -> src[i][j] }) })
}
Ellipse2D.Double
IntelliJ
の自動変換でEllipse2D.Double
とkotlin.Double
競合する場合がある?- 再現できなくなった
Java
import java.awt.geom.Ellipse2D;
enum ButtonLocation {
CENTER(0d),
NORTH(45d),
EAST(135d),
SOUTH(225d),
WEST(-45d);
private final double degree;
ButtonLocation(double degree) {
this.degree = degree;
}
public double getStartAngle() {
return degree;
}
}
// ...
Shape inner = new Ellipse2D.Double(xx, xx, ww, ww);
Kotlin
(自動変換)
import java.awt.geom.Ellipse2D.Double
enum class ButtonLocation(val startAngle: kotlin.Double) {
CENTER(0.0), NORTH(45.0), EAST(135.0), SOUTH(225.0), WEST(-45.0);
}
// ...
val inner: Shape = Double(xx, xx, ww, ww)
Kotlin
(手動修正)
import java.awt.geom.Ellipse2D
enum class ButtonLocation(val startAngle: Double) {
CENTER(0.0), NORTH(45.0), EAST(135.0), SOUTH(225.0), WEST(-45.0);
}
val inner: Shape = Ellipse2D.Double(xx, xx, ww, ww)
手動修正が不要なIntelliJ
の自動変換
IntelliJ
の自動変換で変換後の手動修正が不要なケースのメモ
セーフキャスト
IntelliJ
の自動変換では、instanceof
と三項演算子を使ったJava
コードはセーフキャスト演算子as?
(safe cast operator
)を使ったコードになる
Java
Set<?> f = v instanceof Set ? (Set<?>) v : EnumSet.noneOf(Permissions.class);
Kotlin
val f = v as? Set<*> ?: EnumSet.noneOf(Permissions::class.java)
明示的に外側のクラス名で修飾されたthis
- 明示的に
this
を外側のクラス名で修飾したJava
コードはIntelliJ
の自動変換で@label
で修飾されたコードになる
Java
class FileListTable extends JTable {
private class RubberBandingListener extends MouseAdapter {
@Override public void mousePressed(MouseEvent e) {
FileListTable table = FileListTable.this
// ...
Kotlin
class FileListTable(model: TableModel) : JTable(model) {
private inner class RubberBandingListener : MouseAdapter() {
override fun mousePressed(e: MouseEvent) {
val table = this@FileListTable
// ...
enumを参照等価性で比較する
最近?のIntelliJ
の自動変換では、列挙子(enum
)の比較を===
演算子に変換する以前は==
演算子に変換していた???- Equality - Kotlin Programming Language
Java
checkBox.setSelected(node.getStatus() == Status.SELECTED);
Kotlin
checkBox.setSelected(it.status === Status.SELECTED)
Swing + Kotlin サンプル
SwingUtilities.getAncestorOfClass(...)
SwingUtilities.getAncestorOfClass(...)
メソッドでよく使用する以下のようなJava
コードが、Kotlin
ではセーフキャスト演算子as?
(safe cast operator
)とエルビス演算子?:
を使って一行で記述可能
Java
Container p = SwingUtilities.getAncestorOfClass(JViewport.class, this);
if (!(p instanceof JViewport)) {
return;
}
Kotlin
val p = SwingUtilities.getAncestorOfClass(JViewport::class.java, this) as? JViewport ?: return
JTable
Class<?>
は、Class<Any>
かClass<Object>
?Class<*>
かClass<out Any>
に変換した方が良いかもしれないIntelliJ
の自動変換ではClass<*>
Object#getClass()
はo.javaClass
- getClass() - Calling Java from Kotlin - Kotlin Programming Language
Integer::class.java
はClass<Integer>
になるので、以下のサンプルで使用すると、error: type inference failed. Expected type mismatch: inferred type is Class<Integer> but Class<Any> was expected
とエラーになる- Star-projections - Generics - Kotlin Programming Language
- オーバーライドの方法、
@Override
はoverride
- 配列、二次元配列
- Java Arrays - Calling Java from Kotlin - Kotlin Programming Language
IntelliJ
の自動変換ではarrayOf(arrayOf<Any>("aaa", 12, true), ...
apply
switch
はwhen
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") } // ... 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;
// ...
handler = e -> stopCellEditing();
Kotlin
(自動変換)
protected var handler: ActionListener? = null
// ...
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
// ...
handler = ActionListener { stopCellEditing() }
// or:
handler = object: ActionListener {
override fun actionPerformed(e: ActionEvent) : Unit { // Unitは無くても可
stopCellEditing()
}
}
IntelliJ
の自動変換で、以下のようなエラーになる場合は、Object
をAny
に手動変換する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) ?: 1
- 例えば
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?
の意味- Calling Java from Kotlin - Kotlin Programming Language
T! means "T or T?"
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
- Replacing SwingWorker with Kotlin coroutines ・ Pushing Pixelsを参考に、
SwingWorker
をkotlin-coroutines
に置き換えるテストをしているが、unresolved reference
になるkotlin 1.3.0
、kotlinx.coroutines 1.0.0
で動作確認 - JavaのSwingWorkerをKotlinのCoroutinesに置き換えるに移動
JComponent#updateUI()
Swing
コンポーネントのUI
プロパティを現在のLookAndFeel
で設定されている値にリセットするupdateUI()
メソッドは、コンストラクタから呼び出されるのでインスタンス変数が初期化される前に実行されることがある- このため、
Kotlin
のコードに変換したupdateUI()
メソッド内でnull
チェックを行うと不要なnull
チェックをしているとか、!= null
が常にtrue
であると警告されるが、これに従ってnull
チェックを外すとNullPointerException
が発生する Optional
やObjects.nonNull(...)
で、警告をごまかす方法もある
Java
public final class MainPanel extends JPanel {
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;
// ...
}
Kotlin
class MainPanel : JPanel(BorderLayout()) {
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)
// Unnecessary safe call on a non-null receiver of type JSpinner
// val lv = spinner
// ?.let { it.getModel().getValue() }
// ?: 1000L
// NullPointerException
// val lv = spinner.getModel().getValue()
// Condition 'spinner != null' is always 'true'
// val lv = if (spinner != null) spinner.getModel().getValue() else 1000L
// private val spinner: JSpinner? = JSpinner() なら(他の個所もspinner?にすれば)以下でOK
// val lv = spinner?.getModel()?.getValue() ?: 1000L
}
JComponent#createUI(...)
Kotlin 1.3.21
では@JvmStatic
を付けてstatic
なメソッドを上書き不可- "Accidental override" reported when a @JvmStatic method in a Kotlin class has the same signature as a static method in a Java base class : KT-12993
- たとえば
BasicComboBoxUI
を継承する独自UI
を作成するために、static
なcreateUI(...)
メソッドを上書きする必要があるが、@JvmStatic fun createUI(c: JComponent): ComponentUI {
は、以下のようにエラーになる
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;):
fun createUI(p0: JComponent!): ComponentUI! defined in javax.swing.plaf.basic.BasicComboBoxUI
fun createUI(c: JComponent?): ComponentUI defined in example.BasicSearchBarComboBoxUI
- 現状では
createUI(...)
を使用せずupdateUI()
をオーバーライドして直接独自UI
を設定するなどで回避するしかない?
DefaultTreeCellEditor
JTree.startEditingAtPath(...)
メソッドを実行すると以下のようなIllegalArgumentException
が発生する場合がある- このメソッドを辿ると
BasicTreeUI
内でイベントをnull
でTreeCellEditor#isCellEditable(event)
を呼び出している箇所がある - その為、
DefaultTreeCellEditor#isCellEditable(e: EventObject)
のオーバーライドはDefaultTreeCellEditor#isCellEditable(e: EventObject?)
に変更する必要がある
- このメソッドを辿ると
Exception in thread "AWT-EventQueue-0" java.lang.IllegalArgumentException: Parameter specified as non-null is null: method example.MainPanel$1.isCellEditable, parameter e
at example.MainPanel$1.isCellEditable(App.kt)
at javax.swing.plaf.basic.BasicTreeUI.startEditing(BasicTreeUI.java:2129)
at javax.swing.plaf.basic.BasicTreeUI.startEditingAtPath(BasicTreeUI.java:620)
at javax.swing.JTree.startEditingAtPath(JTree.java:2405)
at example.TreePopupMenu$1.actionPerformed(App.kt:49)
Kotlin
自動変換
import java.awt.* // ktlint-disable no-wildcard-imports
import java.awt.event.MouseEvent
import java.util.EventObject
import javax.swing.* // ktlint-disable no-wildcard-imports
import javax.swing.event.AncestorEvent
import javax.swing.event.AncestorListener
import javax.swing.tree.DefaultMutableTreeNode
import javax.swing.tree.DefaultTreeCellEditor
import javax.swing.tree.DefaultTreeCellRenderer
import javax.swing.tree.TreePath
class MainPanel : JPanel(BorderLayout()) {
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)
}
}
class TreePopupMenu : JPopupMenu() {
private var path: TreePath? = null
private val editItem: JMenuItem
private val editDialogItem: JMenuItem
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)
}
}
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")
}
}
class FocusAncestorListener : AncestorListener {
override fun ancestorAdded(e: AncestorEvent) {
e.component.requestFocusInWindow()
}
override fun ancestorMoved(e: AncestorEvent) {
/* not needed */
}
override fun ancestorRemoved(e: AncestorEvent) {
/* not needed */
}
}
fun main() {
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)
}
}
}
Kotlin
手動修正
import java.awt.* // ktlint-disable no-wildcard-imports
import java.awt.event.MouseEvent
import java.util.EventObject
import javax.swing.* // ktlint-disable no-wildcard-imports
import javax.swing.event.AncestorEvent
import javax.swing.event.AncestorListener
import javax.swing.tree.DefaultMutableTreeNode
import javax.swing.tree.DefaultTreeCellEditor
import javax.swing.tree.DefaultTreeCellRenderer
import javax.swing.tree.TreePath
class MainPanel : JPanel(BorderLayout()) {
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)
}
}
class TreePopupMenu : JPopupMenu() {
private var path: TreePath? = null
private val editItem: JMenuItem
private val editDialogItem: JMenuItem
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)
}
}
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")
}
}
class FocusAncestorListener : AncestorListener {
override fun ancestorAdded(e: AncestorEvent) {
e.component.requestFocusInWindow()
}
override fun ancestorMoved(e: AncestorEvent) {
/* not needed */
}
override fun ancestorRemoved(e: AncestorEvent) {
/* not needed */
}
}
fun main() {
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)
}
}
}
手動変換
IntelliJ
の自動変換で正常に変換されるが、手動修正でもっと短いKotlin
コードに変換が可能なケースのメモjava.util.*
のクラスを使用している箇所は変換可能な場合が多い
IntStream
java.util.stream.IntStream
はIntRange
に置換可能
val indices = IntStream.range(0, l.getModel().getSize())
.filter { rb.intersects(l.getCellBounds(it, it)) }.toArray()
val indices = (0 until l.getModel().getSize())
.filter { rb.intersects(l.getCellBounds(it, it)) }.toIntArray()
Random
java.util.Random
は1.3
からIntRange.ramdom()
などに置換可能
val lengthOfTask = 10 + Random().nextInt(50)
val lengthOfTask = (10..60).random()
List#get(0)
java.util.List#.get(0)
はList#firstOrNull()
などに置換可能
var flag = 1
val keys = table.getRowSorter().getSortKeys()
if (!keys.isEmpty()) {
val sortKey = keys.get(0)
if (sortKey.getColumn() == column && sortKey.getSortOrder() == SortOrder.DESCENDING) {
flag = -1
}
}
val flag = table.getRowSorter().getSortKeys().firstOrNull()
?.takeIf { it.getColumn() == column && it.getSortOrder() == SortOrder.DESCENDING }
?.let { -1 } ?: 1
配列の最終要素
- 配列の最終要素の取得は、
Array#last()
などに置換可能
p.setComponentZOrder(p.getComponent(p.getComponentCount() - 1), 0)
p.setComponentZOrder(p.getComponents().last(), 0)
Optional
Optional.ofNullable(...).filter(...)
は、?.takeIf {...}
などに置換可能
val clz = JTable::class.java
Optional.ofNullable(SwingUtilities.getAncestorOfClass(clz, e.getComponent()))
.filter(clz::isInstance)
.map(clz::cast)
.filter(JTable::isEditing)
.ifPresent(JTable::removeEditor)
SwingUtilities.getAncestorOfClass(JTable::class.java, e.getComponent())
?.takeIf { it is JTable }
?.let { it as JTable }
?.takeIf { it.isEditing() }
?.also{ it.removeEditor() }
// takeIf {...}内のitにはスマートキャストが効くが、それを越えて作用はしない
SwingUtilities.getAncestorOfClass(JTable::class.java, e.getComponent())
?.takeIf { it is JTable && it.isEditing() }
?.also { (it as JTable).removeEditor() }
// ?.also { JTable::removeEditor } // コンパイルエラーにはならないが、removeEditor()メソッドは実行されない?
Objects.nonNull(...)
Objects.nonNull(o)
などは、セーフコール演算子?.
に置換可能
if (Objects.nonNull(colHead) && colHead.isVisible()) {
val colHeadHeight = Math.min(availR.height, colHead.getPreferredSize().height)
// ...
}
colHead?.takeIf { it.isVisible() }?.let {
val colHeadHeight = Math.min(availR.height, it.getPreferredSize().height)
// ...
}
Optional.orElse(...)
Optional.orElse(...)
などは、エルビス演算子?:
に置換可能
val lv = Optional.ofNullable(spinner).map { it.getModel().getValue() }.orElse(1000L)
val lv = spinner?.getModel()?.getValue() ?: 1000L
Stream
Stream
は、List
(Iterable
)に置換可能な場合が多い- 配列も
Iterable
なのでStream
にする必要はあまりない
fun stream(parent: Container): Stream<Component> = Arrays.stream(parent.getComponents())
.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) })
fun descendants(parent: Container): List<Component> = parent.getComponents()
.filterIsInstance(Container::class.java)
.map { descendants(it) }
.fold(listOf<Component>(parent), { a, b -> a + b })
Stream.flatMap(...)
Stream#flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
はそのままIterable<T>.flatMap(transform: (T) -> Iterable<R>)
に置換可能
// // Java
// private Stream<MenuElement> descendants(MenuElement me) {
// return Stream.of(me.getSubElements())
// .flatMap(m -> Stream.concat(Stream.of(m), descendants(m)));
// }
fun descendants(me: MenuElement): Stream<MenuElement> {
return Stream.of(*me.subElements)
.flatMap { Stream.concat(Stream.of(it), descendants(it)) }
}
// StreamではなくIterableが使用可能
private fun descendants(me: MenuElement): List<MenuElement> =
me.getSubElements().flatMap { listOf(it) + descendants(it) }
Stream.reduce(...)
- 初期値有りの
Stream#reduce(T identity, BinaryOperator<T> accumulator)
は、Iterable<T>.fold(initial: R, operation: (acc: R, T) -> R)
に置換可能
// // Java
// int max = group.stream()
// .map(AlignedLabel::getSuperPreferredWidth)
// .reduce(0, Integer::max);
val max = group.stream()
.map(Function<AlignedLabel, Int> { it.getSuperPreferredWidth() })
.reduce(0, BinaryOperator<Int> { a, b -> Integer.max(a, b) })
val max = group.stream()
.map { it.getSuperPreferredWidth() }
.reduce(0, Integer::max)
val max = group
.map { it.getSuperPreferredWidth() }
.fold(0) { a, b -> maxOf(a, b) }
val max = group.map { it.getSuperPreferredWidth() }.fold(0, ::maxOf)
// この例だとIntなのでmax()とエルビス演算子`?:`が使用可能
val max = group.map { it.getSuperPreferredWidth() }.max() ?: 0
Collections.nCopies(...).joinToString(...)
CharSequence#repeat(n)
でn
回繰り返した文字列を生成可能
val text = Collections.nCopies(2000, "aaaaaaaaaaaaa").joinToString("\n")
val text = "aaaaaaaaaaaaa\n".repeat(2000)
複数行文字列
- 複数行文字列は三重引用符
"""
を使って生成可能
// private String makeTestHtml() {
// return String.join("\n", strarray);
// }
// を自動変換すると以下のようなコードになる
private fun makeTestHtml(): String {
return arrayOf(
"<html><body>",
"<div>2222222222</div>",
"</body></html>").joinToString("\n")
}
// が、三重引用符で囲めば改行などをエスケープシーケンスで記入する必要はない
private fun makeTestHtml() = """
<html>
<body>
<div>2222222222</div>
</body>
</html>
"""
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>
が作成可能
// val tnc = Comparator.comparing(Function<DefaultMutableTreeNode, Boolean> { it.isLeaf() })
val tnc = Comparator.comparing<DefaultMutableTreeNode, Boolean> { it.isLeaf() }
.thenComparing { n -> n.getUserObject().toString() }
val children = parent.children().toList()
.filterIsInstance(DefaultMutableTreeNode::class.java)
.sortedWith(tnc)
val tnc = compareBy<DefaultMutableTreeNode> { it.isLeaf() }
.thenBy { it.getUserObject().toString() }
val children = parent.children().toList()
.filterIsInstance(DefaultMutableTreeNode::class.java)
.sortedWith(tnc)
// .sortedWith(compareBy(DefaultMutableTreeNode::isLeaf, { it.getUserObject().toString() }))
filterIsInstance
// Only classes are allowed on the left hand side of a class literal
// とエラーになる
listOf(c0, c1, c2, c3)
.mapNotNull { it.getModel() }
.filterIsInstance(MutableComboBoxModel<String>::class.java)
.forEach { it.insertElementAt(str, it.getSize()) }
// Type mismatch. でエラーになる
listOf(c0, c1, c2, c3)
.mapNotNull { it.getModel() }
.filterIsInstance(MutableComboBoxModel::class.java)
.forEach { it.insertElementAt(str, it.getSize()) }
// reified type parameter を使用する
listOf(c0, c1, c2, c3)
.mapNotNull { it.getModel() }
.filterIsInstance<MutableComboBoxModel<String>>()
.forEach { it.insertElementAt(str, it.getSize()) }
listOf(c0, c1, c2, c3)
.mapNotNull { it.getModel() as? MutableComboBoxModel<String> }
.forEach { it.insertElementAt(str, it.getSize()) }
Functional (SAM) interfaces
kotlin 1.4.0
からKotlin
インターフェイスでもSAM
(Single Abstract Method
)変換を利用可能になったが、まだIntelliJ
の自動変換ではFunctional interfaces
ではなく普通のinterfaces
に変換されるのでこれを使用する場合は手動で置換する必要がある
interface ExpansionListener : EventListener {
fun expansionStateChanged(e: ExpansionEvent)
}
}
// ...
p.addExpansionListener(object : ExpansionListener {
override fun expansionStateChanged(e: ExpansionEvent) {
(e.source as? Component)?.also {
// ...
}
}
})
fun interface ExpansionListener : EventListener {
fun expansionStateChanged(e: ExpansionEvent)
}
}
// ...
p.addExpansionListener {
(e.source as? Component)?.also {
// ...
}
}