Kotlin のバックアップ差分(No.71)
- バックアップ一覧
- 現在との差分 を表示
- 現在との差分 - 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)
- 追加された行はこの色です。
- 削除された行はこの色です。
---
keywords: [Kotlin, Swing, Java]
description: KotlinでSwingコンポーネントを使用するサンプルと、Javaからの変換に関するメモなど
author: aterai
pubdate: 2017-05-31T18:35:21+09:00
---
#contents
ラベル付きReturn
* 概要 [#summary]
`Kotlin`で`Swing`コンポーネントを使用するサンプルと、`Java`からの変換に関するメモです。
* 実行環境 [#s42e991c]
- [https://sdkman.io/ SDKMAN! the Software Development Kit Manager]で`kotlin`をインストール可能
$ curl -s "https://get.sdkman.io" | bash
$ sdk install kotlin
$ kotlinc -version
info: kotlinc-jvm 1.3.0 (JRE 1.8.0_192-b12)
$ kotlinc hello.kt -include-runtime -d hello.jar && "$JAVA_HOME/bin/java" -jar hello.jar
* `IntelliJ`の自動変換 [#IntelliJ]
- メニューの`Code`, `Convert Java File to Kotlin File`(KBD{Ctrl+Alt+Shift+K})で、`Java`のソースコードを`Ktolin`のソースコードに変換可能
-- `Hoge.java`を変換すると`Hoge.kt`が生成されて、`Hoge.java`は削除されてしまうので注意が必要
** 演算子の優先順位 [#precedence]
- 以下のようなソースコードを`IntelliJ`で自動変換した場合、論理演算子の左シフト(`<<`)は`shl`に変換され、また`and`の優先順位が異なるので括弧が減る
#twocolumn
`Java`
#code{{
btn.setSelected((i & (1 << 2)) != 0);
}}
#twocolumn
`Kotlin`(自動変換)
#code(lang-kotlin){{
btn.setSelected(i and (1 shl 2) != 0)
// 時々?に誤変換される場合がある?
// btn.setSelected(i and (1 shl 2 != 0))
}}
#twocolumn
- `bitwise AND`(`and`の優先順位)
-- [https://discuss.kotlinlang.org/t/bitwise-operators-are-not-in-the-precedence-table/2075 Bitwise operators are not in the precedence table - Support - Kotlin Discussions]
-- [https://kotlinlang.org/docs/reference/grammar.html#precedence Kotlinの演算子優先順位表]
--- [https://kotlinlang.org/docs/reference/basic-types.html#operations Basic Types: Numbers, Strings, Arrays - Kotlin Programming Language]
--- `Kotlin`の`bitwise and`は`infix function`のため、`Equality`の`!=`より優先順位が高い
-- `Java`の演算子優先順位表: [https://docs.oracle.com/javase/tutorial/java/nutsandbolts/operators.html Operators (The Java™ Tutorials > Learning the Java Language > Language Basics)]
--- `Java`は逆で、`equality`(`==`, `!=`)のほうが`bitwise AND`(`&`)より優先順位が高い
** 型パラメータ [#Arrays]
- `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(...)`
> [https://docs.oracle.com/javase/tutorial/java/generics/methods.html 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>(...)`
> [https://kotlinlang.org/docs/reference/generics.html 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`とエラーになる
#twocolumn
`Java`
#code{{
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(...)) { でも同じ変換結果になる
}}
#twocolumn
`Kotlin`(自動変換)
#code(lang-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`(手動修正)
#code(lang-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)) {
}}
#twocolumn
** 境界型パラメータ [#BoundedTypeParameter]
- `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 {`に修正する
#twocolumn
`Java`
#code{{
class CalendarViewTableModel<T extends LocalDate> extends DefaultTableModel {
public CalendarViewTableModel(T date) {
}}
#twocolumn
`Kotlin`(自動変換)
#code(lang-kotlin){{
class CalendarViewTableModel<T : LocalDate> public constructor(date: T) : DefaultTableModel() {
}}
#twocolumn
** クラスリテラル [#ClassLiterals]
- 総称型を持つクラスのクラスリテラル(`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`
- 逆に`Java`の`c instanceof JComboBox`を`Kotlin`に変換するときは、`c is JComboBox<*>`にしないとエラーになる
-- こちらは`IntelliJ`の自動変換で正常に変換可能
-- `One type argument expected. Use 'JComboBox<*>' if you don't want to pass type arguments`
#twocolumn
`Java`
#code{{
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());
}
});
}}
#twocolumn
`Kotlin`(自動変換)
#code(lang-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`(手動修正)
#code(lang-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)
}
}
}}
#twocolumn
** 型引数の省略 [#Diamond]
- `Java 7`以降で可能なジェネリッククラスのコンストラクタ呼び出しに必要な型引数を省略する記法(`<>, diamond operator`)([https://docs.oracle.com/javase/tutorial/java/generics/types.html#diamond 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`は存在せず?、型引数を省略したい場合は何も書く必要がない([https://kotlinlang.org/docs/reference/generics.html#generics 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)`には変換してくれない
#twocolumn
`Java`
#code{{
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);
}
// ...
}
}}
#twocolumn
`Kotlin`(自動変換)
#code(lang-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`(手動修正)
#code(lang-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)
}
// ...
}
}}
#twocolumn
** 匿名内部クラス [#AnonymousInnerClass]
- %%`IntelliJ`の自動変換では`error: this class does not have a constructor tree.addTreeWillExpandListener(object : TreeWillExpandListener() {`とエラーになる%%
-- `IntelliJ 2018.2.5`の自動変換では正しく変換されるようになっている
- インターフェイスから匿名内部クラスのインスタンスを生成する場合は`()`は不要
-- [https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SAM Conversions - Calling Java from Kotlin - Kotlin Programming Language]
-- `tree.addTreeWillExpandListener(object : TreeWillExpandListener() {`を`tree.addTreeWillExpandListener(object : TreeWillExpandListener {`に修正
#twocolumn
`Java`
#code{{
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");
}
});
}}
#twocolumn
`Kotlin`(自動変換)
#code(lang-kotlin){{
tree.addTreeWillExpandListener(object : TreeWillExpandListener() {
@Override
@Throws(ExpandVetoException::class)
fun treeWillExpand(e: TreeExpansionEvent) {
//throw new ExpandVetoException(e, "Tree expansion cancelled");
}
@Override
@Throws(ExpandVetoException::class)
fun treeWillCollapse(e: TreeExpansionEvent) {
throw ExpandVetoException(e, "Tree collapse cancelled")
}
})
}}
`Kotlin`(手動修正)
#code(lang-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")
}
})
}}
#twocolumn
** ラベル付きReturn [#ReturnAtLabels]
- 匿名内部クラスから`return`で抜ける`Java`コードを`IntelliJ`の自動変換で変換すると、`unresolved reference: @decode`とエラーになる`Kotlin`コードに変換される場合がある
-- 以下のような変換例の場合、`return@decode.addActionListener`を`return@addActionListener`のようにメソッド名のみのラベルに変更する必要がある
-- [https://kotlinlang.org/docs/reference/returns.html#return-at-labels Returns and Jumps: break and continue - Kotlin Programming Language]
#twocolumn
`Java`
#code{{
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();
}
});
}}
#twocolumn
`Kotlin`(自動変換)
#code(lang-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`(手動修正)
#code(lang-kotlin){{
val decode = JButton("decode")
decode.addActionListener {
val b64 = textArea.getText()
if (b64.isEmpty()) {
return@addActionListener
}
try {
// ...
} catch (ex: IOException) {
ex.printStackTrace()
}
}
}}
#twocolumn
** tryの戻り値 [#TryExpression]
- `Optional.map(...)`などから`return`で戻り値を返す`Java`コードを`IntelliJ`の自動変換で変換すると、`Return`のラベルがエラーになる`Kotlin`コードに変換される場合がある
- `Kotlin`では`try`は式で戻り値を持つことが可能なので、ラベル付き`Return`は不要
- [https://kotlinlang.org/docs/reference/exceptions.html#try-is-an-expression Try is an expression - Exceptions: try, catch, finally, throw, Nothing - Kotlin Programming Language]
#twocolumn
`Java`
#code{{
private static TexturePaint makeImageTexture() {
BufferedImage bi = Optional.ofNullable(A.class.getResource("a.png"))
.map(url -> {
try {
return ImageIO.read(url);
} catch (IOException ex) {
return makeMissingImage();
}
}).orElseGet(() -> makeMissingImage());
return new TexturePaint(bi, new Rectangle(bi.getWidth(), bi.getHeight()));
}
}}
#twocolumn
`Kotlin`(自動変換)
#code(lang-kotlin){{
private fun makeImageTexture(): TexturePaint {
val bi = Optional.ofNullable(A::class.java!!.getResource("a.png")).map({ url ->
try {
return@Optional.ofNullable(A.class. getResource "a.png").map ImageIO . read url
} catch (ex: IOException) {
return@Optional.ofNullable(A.class. getResource "a.png").map makeMissingImage ()
}
}).orElseGet({ makeMissingImage() })
return TexturePaint(bi, Rectangle(bi.getWidth(), bi.getHeight()))
}
}}
`Kotlin`(手動修正)
#code(lang-kotlin){{
private fun makeImageTexture(): TexturePaint {
val bi = Optional.ofNullable(A::class.java.getResource("a.png"))
.map { url ->
try {
ImageIO.read(url)
} catch (ex: IOException) {
makeMissingImage()
}
}.orElseGet { makeMissingImage() }
// Example without using Optional
// val bi = A::class.java.getResource("a.png")?.let {
// try {
// ImageIO.read(it)
// } catch (ex: IOException) {
// makeMissingImage()
// }
// } ?: makeMissingImage()
return TexturePaint(bi, Rectangle(bi.getWidth(), bi.getHeight()))
}
}}
#twocolumn
** メソッド参照 [#MethodReference]
- `IntelliJ`の自動変換ではメソッド参照の変換が%%苦手?%% いくらか修正されている?
-- `IntelliJ 2018.2.5`の自動変換では、`???`ではなく`Predicate<Component>`などに変換されるようになった
#twocolumn
`Java`
#code{{
public static Stream<TreeNode> children(TreeNode node) {
Class<TreeNode> clz = TreeNode.class;
return Collections.list((Enumeration<?>) node.children())
.stream().filter(clz::isInstance).map(clz::cast);
}
}}
#twocolumn
`Kotlin`(自動変換)
#code(lang-kotlin){{
fun children(node: TreeNode): Stream<TreeNode> {
val clz = TreeNode::class.java
return Collections.list(node.children() as Enumeration<*>)
.stream().filter(???({ clz!!.isInstance() })).map(clz!!.cast)
}
}}
`Kotlin`(手動修正)
#code(lang-kotlin){{
fun children(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()`が便利
#code(lang-kotlin){{
fun children(node: TreeNode): List<TreeNode> {
return (node.children() as Enumeration<*>)
.toList().filterIsInstance(TreeNode::class.java)
}
}}
#twocolumn
- 例えば`.filter(Container.class::isInstance)`のようなメソッド参照を、`IntelliJ 2018.2.5`の自動変換では`.filter(Predicate<Component> { Container::class.java!!.isInstance(it) })`のように変換するようになった?が、`import`の追加は対応していない
#twocolumn
`Java`
#code{{
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);
}
}}
#twocolumn
`Kotlin`(自動変換)
#code(lang-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`(手動修正)
#code(lang-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`(手動修正)
#code(lang-kotlin){{
// Stream を Iterable に変換してしまう方法もある
fun children(parent: Container): List<Component> {
return parent.getComponents().toList()
.filterIsInstance(Container::class.java)
.map { children(it) }
.fold(listOf<Component>(parent)) { a, b -> a + b }
}
// ...
children(fileChooser1)
.filterIsInstance(JTable::class.java)
// .firstOrNull()?.apply(JTable::removeEditor)
.firstOrNull()?.let { table ->
println(table)
table.removeEditor()
}
}}
#twocolumn
** メソッド参照(終端処理の戻り値) [#fd97ed55]
- [https://docs.oracle.com/javase/jp/8/docs/api/java/awt/Container.html#add-java.awt.Component- Container#add(Component)]メソッドは戻り値として`Component`を返す
-- `Java`の場合、終端処理の`forEach(p::add)`では戻り値を無視して`Container#add(Component)`のメソッド参照が可能
-- `Kotlin`の場合、`forEach(p::add)`とすると、`add`メソッドで引数が`Component!`、戻り値が`Unit`になるメソッドを探すためメソッド参照で以下のようなエラーになる
--- [https://discuss.kotlinlang.org/t/function-reference-ignoring-the-returned-value/2442 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)
^
#twocolumn
`Java`
#code{{
JPanel p = new JPanel(new GridLayout(0, 1, 2, 2));
Arrays.asList(button1, button2).forEach(p::add);
}}
#twocolumn
`Kotlin`(自動変換)
#code(lang-kotlin){{
val p = JPanel(GridLayout(0, 1, 2, 2))
Arrays.asList(button1, button2).forEach( ?? ? ({ p.add() }))
}}
`Kotlin`(手動修正)
#code(lang-kotlin){{
val p = JPanel(GridLayout(0, 1, 2, 2))
listOf(button1, button2).forEach { b -> p.add(b) }
//or listOf(button1, button2).map(p::add)
}}
#twocolumn
** コンストラクタ参照 [#ConstructorReferences]
- `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`に修正すればエラーを回避可能
--- [https://kotlinlang.org/docs/reference/reflection.html#constructor-references Constructor References - Reflection - Kotlin Programming Language]
#twocolumn
`Java`
#code{{
Stream.of("A", "B", "C").map(JToggleButton::new).forEach(r -> {
p.add(r);
bg.add(r);
});
}}
#twocolumn
`Kotlin`(自動変換)
#code(lang-kotlin){{
Stream.of("A", "B", "C").map(Function<String, JToggleButton> { JToggleButton(it) }).forEach({ r ->
p.add(r)
bg.add(r)
})
}}
`Kotlin`(手動修正)
#code(lang-kotlin){{
Stream.of("A", "B", "C").map(::JToggleButton).forEach { r ->
p.add(r)
bg.add(r)
}
}}
#twocolumn
** BigDecimal [#BigDecimal]
- `IntelliJ`の自動変換では`error: unresolved reference: intValue`とエラーになるので、手動で`toInt()`に修正する必要がある
#twocolumn
`Java`
#code{{
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);
}}
#twocolumn
`Kotlin`(自動変換)
#code(lang-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`(手動修正)
#code(lang-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}")
}}
#twocolumn
** Comparator [#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) })`に修正
#twocolumn
`Java`
#code{{
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);
}
}
}}
#twocolumn
`Kotlin`(自動変換)
#code(lang-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`(手動修正)
#code(lang-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`(手動修正)
#code(lang-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)
}
}}
#twocolumn
** 16進数数値リテラル内のアンダースコア [#UnderscoresInHexadecimalNumericLiterals]
- `IntelliJ`の自動変換では、頭に`0x`を付けて`16`進数表記した数値リテラルにアンダースコアを付けると、`IDE Fatal Errors`になり変換できない
-- `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"`とエラーになる
#twocolumn
`Java`
#code{{
editor1.setSelectionColor(new Color(0x64_88_AA_AA, true)); // IntelliJで自動変換できない
editor2.setSelectionColor(new Color(0x6488AAAA, true)); // IntelliJで自動変換可能
}}
#twocolumn
`Kotlin`(手動修正)
#code(lang-kotlin){{
editor1.setSelectionColor(Color(0x64_88_AA_AA, true)) // 問題なし
editor2.setSelectionColor(Color(0x6488AAAA, true)) // 問題なし
}}
#twocolumn
** int、Int、UInt [#u8a99b06]
- `Java`では例えば`-1`を`0xFFFFFFFF`のような`16`進数表記でも表現可能だが、`Kotlin`では`error: the integer literal does not conform to the expected type Int`とエラーになるため、`IntelliJ`の自動変換ではマイナス付の表記に自動変換される
- [https://stackoverflow.com/questions/47462793/java-integer-max-value-vs-kotlin-int-max-value Java Integer.MAX_VALUE vs Kotlin Int.MAX_VALUE - Stack Overflow]
#twocolumn
`Java`
#code{{
Color argb = new Color(0xffaabbcc, true);
Color rgb = new Color(0xaabbcc);
}}
#twocolumn
`Kotlin`(自動変換)
#code(lang-kotlin){{
val argb = Color(-0x554434, true)
val rgb = Color(0xaabbcc)
}}
#twocolumn
** @SuppressWarnings [#SuppressWarnings]
- `IntelliJ`の自動変換では、`@SuppressWarnings("unchecked")`などは削除されるので、`Unchecked cast: WatchEvent<*>! to WatchEvent<Path>`と警告される
-- 手動で`@Suppress("UNCHECKED_CAST")`などを付ける必要がある
#twocolumn
`Java`
#code{{
@SuppressWarnings("unchecked")
WatchEvent<Path> ev = (WatchEvent<Path>) event;
}}
#twocolumn
`Kotlin`(手動修正)
#code(lang-kotlin){{
@Suppress("UNCHECKED_CAST")
val ev = event as WatchEvent<Path>
}}
#twocolumn
* Swing + Kotlin サンプル [#swing]
** SwingUtilities.getAncestorOfClass(...) [#getAncestorOfClass]
- `SwingUtilities.getAncestorOfClass(...)`メソッドでよく使用する以下のような`Java`コードが、`Kotlin`ではセーフキャスト演算子`as?`(`safe cast operator`)とエルビス演算子`?:`を使って一行で記述可能
-- [https://kotlinlang.org/docs/reference/typecasts.html#safe-nullable-cast-operator "Safe" (nullable) cast operator - Type Checks and Casts: 'is' and 'as' - Kotlin Programming Language]
#twocolumn
`Java`
#code{{
Container p = SwingUtilities.getAncestorOfClass(JViewport.class, this);
if (!(p instanceof JViewport)) {
return;
}
}}
#twocolumn
`Kotlin`
#code(lang-kotlin){{
val p = SwingUtilities.getAncestorOfClass(JViewport::class.java, this) as? JViewport ?: return
}}
#twocolumn
** JTable [#JTable]
- `Class<?>`は%%`Class<Any>`か`Class<Object>`?%%、`Class<*>`か`Class<out Any>`に変換した方が良いかもしれない
-- `IntelliJ`の自動変換では`Class<*>`
- `Object#getClass()`は`o.javaClass`
-- [https://kotlinlang.org/docs/reference/java-interop.html#getclass 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`とエラーになる
-- [https://kotlinlang.org/docs/reference/generics.html#star-projections Star-projections - Generics - Kotlin Programming Language]
- オーバーライドの方法、`@Override`は`override`
- 配列、二次元配列
-- [https://kotlinlang.org/docs/reference/java-interop.html#java-arrays 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`に修正する必要がある
#twocolumn
`Java`
#code{{
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);
});
}
}
}}
#twocolumn
`Kotlin`
#code(lang-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)
}
}
}
}}
#twocolumn
** JTree [#JTree]
#twocolumn
`Java`
#code{{
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);
});
}
}
}}
#twocolumn
`Kotlin`
#code(lang-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)
}
}
}
}}
#twocolumn
** JCheckBox [#JCheckBox]
- `Smart Cast`、キャストで`if`のネストが深くなるのを避けたい
-- イベント発生元が`AbstractButton`なのは自明なので`as`を使用する
--- `val b = e.getSource() as AbstractButton`
-- 以下のような場合は、`apply`でも回避可能
#twocolumn
`Java`
#code{{
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);
});
}
}
}}
#twocolumn
`Kotlin`
#code(lang-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)
}
}
}
}}
#twocolumn
** TableCellEditor [#TableCellEditor]
- `extends`, `implements`
-- `IntelliJ`の自動変換では勝手に`open`は付けないようなので、以下のようなエラーが出る場合は、`internal class CheckBoxesPanel : JPanel() {`を`open class CheckBoxesPanel : JPanel() {`に修正する
#code{{
MainPanel.kt:81:37: error: this type is final, so it cannot be inherited from
internal class CheckBoxesRenderer : CheckBoxesPanel(), TableCellRenderer {
}}
- `IntelliJ`の自動変換では`static`変数がうまく変換できない?
-- 手動で`enum class`にして回避
#code(lang-kotlin){{
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)
}}
#twocolumn
`Java`
#code{{
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;
}
}
}}
#twocolumn
`Kotlin`
#code(lang-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
}
}
}}
#twocolumn
** ActionListener [#SAM]
- `IntelliJ`の自動変換では`ActionListener`などの関数型インターフェースのラムダ式をうまく変換できない場合がある
-- `AbstractCellEditor#stopCellEditing()`は`boolean`を返すので、`error: type mismatch: inferred type is (???) -> Boolean but ActionListener? was expected`とエラーになる
- [https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions SAM Conversions - Calling Java from Kotlin - Kotlin Programming Language]
#twocolumn
`Java`
#code{{
protected ActionListener handler;
//...
handler = e -> stopCellEditing();
}}
#twocolumn
`Kotlin`(自動変換)
#code(lang-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`(手動修正)
#code(lang-kotlin){{
protected var handler: ActionListener? = null
//...
handler = ActionListener { stopCellEditing() }
// or:
handler = object: ActionListener {
override fun actionPerformed(e: ActionEvent) : Unit { // Unitは無くても可
stopCellEditing()
}
}
}}
#twocolumn
- `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のみオーバーライド
#twocolumn
`Java`
#code{{
class CheckBoxNodeEditor extends AbstractCellEditor implements TreeCellEditor {
@Override public Object getCellEditorValue() {
return new CheckBoxNode(checkBox.getText(), checkBox.isSelected());
}
//...
}}
#twocolumn
`Kotlin`(自動変換)
#code(lang-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`(手動修正)
#code(lang-kotlin){{
override fun getCellEditorValue(): Any {
return CheckBoxNode(checkBox.getText(), checkBox.isSelected())
}
}}
#twocolumn
#twocolumn
`Java`
#code{{
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;
}
}
}}
#twocolumn
`Kotlin`
#code(lang-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
}
}
}}
#twocolumn
** Optional [#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?`の意味
-- [http://kotlinlang.org/docs/reference/java-interop.html#null-safety-and-platform-types Calling Java from Kotlin - Kotlin Programming Language]
-- `T! means "T or T?"`
#twocolumn
`Java`
#code{{
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);
});
}
}
}}
#twocolumn
`Kotlin`
#code(lang-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)
}
}
}
}}
#twocolumn
** SwingWorker [#SwingWorker]
- [https://www.pushing-pixels.org/2018/08/07/replacing-swingworker-with-kotlin-coroutines.html 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に置き換える>Kotlin/Coroutines]]に移動
** JComponent#updateUI() [#updateUI]
- `Swing`コンポーネントの`UI`プロパティを、現在の`LookAndFeel`で設定されている値にリセットする`updateUI()`メソッドは、コンストラクタから呼び出されるので、インスタンス変数が初期化されるまえに実行されることがある
- このため、`Kotlin`のコードに変換した`updateUI()`メソッド内で`null`チェックを行うと、不要な`null`チェックをしているとか、`!= null`が常に`true`であると警告される
- `null`チェックを外すと`NullPointerException`になる
- `Optional`や`Objects.nonNull(...)`で、警告をごまかす方法もある
#twocolumn
`Java`
#code{{
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;
// ...
}
}}
#twocolumn
`Kotlin`
#code(lang-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
}
}}
#twocolumn
* 手動変換 [#l4531741]
** IntStream [#IntStream]
- `java.util.stream.IntStream`は`IntRange`に置換可能
#twocolumn
#code(lang-kotlin){{
val indices = IntStream.range(0, l.getModel().getSize())
.filter { rb.intersects(l.getCellBounds(it, it)) }.toArray()
}}
#twocolumn
#code(lang-kotlin){{
val indices = (0 until l.getModel().getSize())
.filter { rb.intersects(l.getCellBounds(it, it)) }.toIntArray()
}}
#twocolumn
** List#get(0) [#firstOrNull]
- `java.util.List#.get(0)`は`List#firstOrNull()`などに置換可能
#twocolumn
#code(lang-kotlin){{
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
}
}
}}
#twocolumn
#code(lang-kotlin){{
val flag = table.getRowSorter().getSortKeys().firstOrNull()
?.takeIf { it.getColumn() == column && it.getSortOrder() == SortOrder.DESCENDING }
?.let { -1 } ?: 1
}}
#twocolumn
** 配列の最終要素 [#last]
- 配列の最終要素の取得は、`Array#last()`などに置換可能
#twocolumn
#code(lang-kotlin){{
p.setComponentZOrder(p.getComponent(p.getComponentCount() - 1), 0)
}}
#twocolumn
#code(lang-kotlin){{
p.setComponentZOrder(p.getComponents().last(), 0)
}}
#twocolumn
** Optional [#takeIf]
- `Optional.ofNullable(...).filter(...)`は、`?.takeIf {...}`などに置換可能
#twocolumn
#code(lang-kotlin){{
val clz = JTable::class.java
Optional.ofNullable(SwingUtilities.getAncestorOfClass(clz, e.getComponent()))
.filter(clz::isInstance)
.map(clz::cast)
.filter(JTable::isEditing)
.ifPresent(JTable::removeEditor)
}}
#twocolumn
#code(lang-kotlin){{
SwingUtilities.getAncestorOfClass(JTable::class.java, e.getComponent())
?.takeIf { it is JTable }
?.let { it as JTable }
?.takeIf { it.isEditing() }
?.also{ it.removeEditor() }
}}
#code(lang-kotlin){{
// takeIf {...}内のitにはスマートキャストが効くが、それを越えて作用はしない
SwingUtilities.getAncestorOfClass(JTable::class.java, e.getComponent())
?.takeIf { it is JTable && it.isEditing() }
?.also { (it as JTable).removeEditor() }
// ?.also { JTable::removeEditor } // コンパイルエラーにはならないが、removeEditor()メソッドは実行されない?
}}
#twocolumn
** Objects.nonNull(...) [#SafeCall]
- `Objects.nonNull(o)`などは、セーフコール演算子`?.`に置換可能
#twocolumn
#code(lang-kotlin){{
if (Objects.nonNull(colHead) && colHead.isVisible()) {
val colHeadHeight = Math.min(availR.height, colHead.getPreferredSize().height)
// ...
}
}}
#twocolumn
#code(lang-kotlin){{
colHead?.takeIf { it.isVisible() }?.let {
val colHeadHeight = Math.min(availR.height, it.getPreferredSize().height)
// ...
}
}}
#twocolumn
** Optional.orElse(...) [#elvis]
- `Optional.orElse(...)`などは、エルビス演算子`?:`に置換可能
#twocolumn
#code(lang-kotlin){{
val lv = Optional.ofNullable(spinner).map { it.getModel().getValue() }.orElse(1000L)
}}
#twocolumn
#code(lang-kotlin){{
val lv = spinner?.getModel()?.getValue() ?: 1000L
}}
#twocolumn
** Stream [#List]
- `Stream`は、`List`に置換可能な場合が多い
#twocolumn
#code(lang-kotlin){{
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) })
}}
#twocolumn
#code(lang-kotlin){{
fun children(parent: Container): List<Component> = parent.getComponents()
.filterIsInstance(Container::class.java)
.map { children(it) }
.fold(listOf<Component>(parent), { a, b -> a + b })
}}
#twocolumn
* コメント [#comment]
#comment
#comment