Kotlin の変更点
- 追加された行はこの色です。
- 削除された行はこの色です。
- Kotlin へ行く。
- Kotlin の差分を削除
--- keywords: [Kotlin, Swing, Java] description: KotlinでSwingコンポーネントを使用するサンプルと、Javaからの変換に関するメモなど author: aterai pubdate: 2017-05-31T18:35:21+09:00 --- #contents * Summary [#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.6.10 (JRE 1.8.0_322-b06) $ kotlinc hello.kt -include-runtime -d hello.jar && "$JAVA_HOME/bin/java" -jar hello.jar ** Gradle [#gradle] - [https://kotlinlang.org/docs/reference/using-gradle.html Using Gradle - Kotlin Programming Language] - [https://github.com/Kotlin/kotlin-examples/tree/master/gradle/hello-world kotlin-examples/gradle/hello-world at master ・ Kotlin/kotlin-examples] -- 上記のサンプル`build.gradle`が参考になる - [https://medium.com/@preslavrachev/kotlin-basics-create-executable-kotlin-jars-using-gradle-d17e9a8384b9 Kotlin Basics: Create Executable Kotlin JARs, using Gradle] -- `jar`作成は同じくこちらの方法が使用可能 -- [https://kotlinlang.org/docs/reference/using-gradle.html#configuring-dependencies 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`を使用すれば回避可能 #code{{ 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`の自動変換 [#IntelliJ] - メニューの`Code`, `Convert Java File to Kotlin File`(KBD{Ctrl+Alt+Shift+K})で、`Java`のソースコードを`Kotlin`のソースコードに変換可能 -- `Hoge.java`を変換すると`Hoge.kt`が生成されて、`Hoge.java`は削除されてしまうので注意が必要 --- `Undo`(KBD{Ctrl+Z})で変換は取り消されて削除された`Java`ファイルは復活する - `File`メニュー、`Project Structure`パネルの`Project SDK`が`<No SDK>`になっていると、`@Override`が`override`に変換されない場合がある - `Ktolin`プロジェクトを作成して適当な`App.kt`を作成し、そこに`Java`のソースコードをコピー・ペーストすると自動変換可能 -- KBD{Ctrl+Alt+Shift+K}でファイル変換するより高機能? ** Improved Java to Kotlin converter [#ba11f89c] - [https://blog.jetbrains.com/kotlin/2019/08/kotlin-1-3-50-released/ 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`(KBD{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`で変換に失敗する場合がある? --- [https://youtrack.jetbrains.com/issue/KT-33431 KotlinNullPointerException on J2K convertion of constructor reference as Function type argument : KT-33431] #code{{ kotlin.KotlinNullPointerException at org.jetbrains.kotlin.idea.actions.JavaToKotlinAction.actionPerformed(JavaToKotlinAction.kt:221) ... }} - クリップボード経由の変換は`1.3.50-release-IJ2019.2-1`でもエラーにならず実行可能 - 以下のほとんどが`Use New J2K(experimental)`をオンにすると修正されている ** 演算子の優先順位 [#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`(`&`)より優先順位が高い // ** シフト論理演算子 [#shrshl] // - 論理演算子を`IntelliJ`で自動変換した場合、括弧の対応がおかしくなる場合がある? // // #twocolumn // `Java` // #code{{ // @Override public int filterRGB(int x, int y, int argb) { // int r = (int) Math.min(0xFF, ((argb >> 16) & 0xFF) * SCALE); // int g = (int) Math.min(0xFF, ((argb >> 8) & 0xFF) * SCALE); // int b = (int) Math.min(0xFF, (argb & 0xFF) * SCALE); // return (argb & 0xFF000000) | (r << 16) | (g << 8) | b; // } // }} // // #twocolumn // `Kotlin`(自動変換) // #code(lang-kotlin){{ // // - 0xFF が 0xFFf と float に変換される? // // - Math.min の括弧がおかしくなっている? // override fun filterRGB(x: Int, y: Int, argb: Int): Int { // val r = Math.min(0xFFf, (argb shr 16 and 0xFF) * SCALE).toInt() // val g = Math.min(0xFFf, (argb shr 8 and 0xFF) * SCALE).toInt() // val b = Math.min(0xFFf, (argb and 0xFF) * SCALE).toInt() // return argb and -0x1000000 or (r shl 16) or (g shl 8) or b // } // }} // // `Kotlin`(手動修正) // #code(lang-kotlin){{ // override fun filterRGB(x: Int, y: Int, argb: Int): Int { // val r = Math.min(0xFF, ((argb shr 16 and 0xFF) * SCALE).toInt()) // val g = Math.min(0xFF, ((argb shr 8 and 0xFF) * SCALE).toInt()) // val b = Math.min(0xFF, ((argb and 0xFF) * SCALE).toInt()) // return argb and -0x1000000 or (r shl 16) or (g shl 8) or b // } // }} // #twocolumn ** 型パラメータ(修正済) [#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` -- [https://youtrack.jetbrains.com/issue/KT-15791 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` #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 {`に修正 - `kotlin 1.4.0`から`kotlin`のインターフェイスでもラムダでの記述も可能になった #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() { @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`(手動修正) #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`コードに変換される場合がある%% -- `IntelliJ 2018.2.5`の自動変換では正しく変換されるようになっている -- 以下のような変換例の場合、`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`コードに変換される場合がある -- `@Optional.ofNullable(getClass().getResource("unkaku_w.png")).map`がラベルになっている? - `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{{ BufferedImage bi = Optional.ofNullable(getClass().getResource("unkaku_w.png")) .map(url -> { try { return ImageIO.read(url); } catch (IOException ex) { return makeMissingImage(); } }).orElseGet(() -> makeMissingImage()); }} #twocolumn `Kotlin`(自動変換) #code(lang-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) #code(lang-kotlin){{ val bi = Optional.ofNullable(javaClass.getResource("unkaku_w.png")).map { url -> try { ImageIO.read(url) } catch (ex: IOException) { makeMissingImage() } }.orElseGet { makeMissingImage() } }} `Kotlin`(手動修正2) #code(lang-kotlin){{ val bi = javaClass.getResource("unkaku_w.png")?.let { try { ImageIO.read(it) } catch (ex: IOException) { makeMissingImage() } } ?: makeMissingImage() }} `Kotlin`(手動修正3) #code(lang-kotlin){{ // runCatching関数でResultを取得するようtry-catchを置き換える val bi = runCatching { ImageIO.read(javaClass.getResource("unkaku_w.png")) }.getOrNull() ?: makeMissingImage() }} #twocolumn ** メソッド参照 [#MethodReference] - `IntelliJ`の自動変換ではメソッド参照の変換が%%苦手?%% いくらか修正されている? -- `IntelliJ 2018.2.5`の自動変換では、`???`ではなく`Predicate<Component>`などに変換されるようになった #twocolumn `Java` #code{{ 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); } }} #twocolumn `Kotlin`(自動変換) #code(lang-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`(手動修正) #code(lang-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()`が便利 #code(lang-kotlin){{ fun descendants(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 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() } }} #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 ** Number#intValue() [#intValue] - %%`Number#intValue()`を`IntelliJ`で自動変換すると`intValue()`がそのまま使用されるが、コンパイルすると`error: unresolved reference: intValue`とエラーになるので手動で`toInt()`などを使用するよう修正する必要がある%% -- `1.4.31-release-IJ2019.2-1`の自動変換では`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 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"`とエラーになる #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 ** プライマリコンストラクタでのプロパティ定義 [#PrimaryConstructor] - %%`Java`のクラスで変数宣言にコメントが付いている場合、`IntelliJ`の自動変換ではそのコメントがプライマリコンストラクタの引数でのプロパティ定義にそのままコピーされてしまう場合がある%% -- %%たとえば以下の例では、` // = table.getTableHeader();`が`class RowHeaderRenderer<E>(private val header: JTableHeader // = table.getTableHeader();) : JLabel(), ...`とプライマリコンストラクタのプロパティ定義に入り込んでそれ以降がコメントアウトされてしまうため、コンパイルエラーになる%% -- コメントの末尾で改行が入るよう修正された #twocolumn `Java` #code{{ 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); // ... }} #twocolumn `Kotlin`(旧自動変換) #code(lang-kotlin){{ inner class RowHeaderRenderer<E>(private val header: JTableHeader // = table.getTableHeader();) : JLabel(), ListCellRenderer<E> { init { this.setOpaque(true) // ... }} `Kotlin`(新自動変換) #code(lang-kotlin){{ inner class RowHeaderRenderer<E>(private val header: JTableHeader // = table.getTableHeader(); ) : JLabel(), ListCellRenderer<E> { init { this.setOpaque(true) // ... }} #twocolumn ** int、Int [#IntegerLiteral] - `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) }} `Kotlin`(手動修正) #code(lang-kotlin){{ // LongからIntに変換する方法もある val argb = Color(0xff_aa_bb_cc.toInt(), true) println(argb == Color(-0x554434, true)) // true }} #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 ** 二次元配列 [#TwoDimensionalArray] - `IntelliJ`の自動変換で二次元配列をクローンするコードを変換した場合、`Type mismatch: inferred type is Array<Int?> but Array<Int> was expected`のようなエラーになる場合がある -- [https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-array/index.html Array - Kotlin Programming Language] -- `Array`のコンストラクタで第二引数の`init`関数を使用して初期値を指定するなどの修正が必要 #twocolumn `Java` #code{{ 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; } }} #twocolumn `Kotlin`(自動変換) #code(lang-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) #code(lang-kotlin){{ 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) #code(lang-kotlin){{ private val mask: Array<Array<Int>> init { // System.arraycopyなどは使用せず、コピー元の二次元配列を参照して初期化 this.mask = Array(src.size, { i -> Array(src[i].size, { j -> src[i][j] }) }) } }} #twocolumn ** Ellipse2D.Double [#Double] - `IntelliJ`の自動変換で`Ellipse2D.Double`と`kotlin.Double`競合する場合がある? -- 再現できなくなった #twocolumn `Java` #code{{ 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); }} #twocolumn `Kotlin`(自動変換) #code(lang-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`(手動修正) #code(lang-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) }} #twocolumn * 手動修正が不要な`IntelliJ`の自動変換 [#PerfectConversion] - `IntelliJ`の自動変換で変換後の手動修正が不要なケースのメモ ** セーフキャスト [#SafeCast] - `IntelliJ`の自動変換では、`instanceof`と三項演算子を使った`Java`コードはセーフキャスト演算子`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{{ Set<?> f = v instanceof Set ? (Set<?>) v : EnumSet.noneOf(Permissions.class); }} #twocolumn `Kotlin` #code(lang-kotlin){{ val f = v as? Set<*> ?: EnumSet.noneOf(Permissions::class.java) }} #twocolumn ** 明示的に外側のクラス名で修飾されたthis [#NestedInnerClassThis] - 明示的に`this`を外側のクラス名で修飾した`Java`コードは`IntelliJ`の自動変換で`@label`で修飾されたコードになる -- [https://kotlinlang.org/docs/reference/this-expressions.html This expressions - Kotlin Programming Language] #twocolumn `Java` #code{{ class FileListTable extends JTable { private class RubberBandingListener extends MouseAdapter { @Override public void mousePressed(MouseEvent e) { FileListTable table = FileListTable.this // ... }} #twocolumn `Kotlin` #code(lang-kotlin){{ class FileListTable(model: TableModel) : JTable(model) { private inner class RubberBandingListener : MouseAdapter() { override fun mousePressed(e: MouseEvent) { val table = this@FileListTable // ... }} #twocolumn ** enumを参照等価性で比較する [#ReferentialEquality] - %%最近?の%%`IntelliJ`の自動変換では、列挙子(`enum`)の比較を`===`演算子に変換する -- %%以前は`==`演算子に変換していた???%% -- [https://kotlinlang.org/docs/reference/equality.html Equality - Kotlin Programming Language] #twocolumn `Java` #code{{ checkBox.setSelected(node.getStatus() == Status.SELECTED); }} #twocolumn `Kotlin` #code(lang-kotlin){{ checkBox.setSelected(it.status === Status.SELECTED) }} #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 ** Null safety and platform types [#null-safety-and-platform-types] - 例えば`SpinnerDateModel#getNextValue(...)`メソッドを`override fun getNextValue(): Object = Calendar.getInstance().apply { ...`のように変換すると、`error: type mismatch: inferred type is Date! but Object was expected`とエラーになる - `override fun getNextValue(): Any = Calendar.getInstance().apply { ...`と`Any`を使用するようオーバーライドする必要がある - `Date!`は、`Date`もしくは`Date?`の意味 -- [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 ** JComponent#createUI(...) [#createUI] - `Kotlin 1.3.21`では`@JvmStatic`を付けて`static`なメソッドを上書き不可 - [https://youtrack.jetbrains.com/issue/KT-12993 "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] -- via: [https://stackoverflow.com/questions/44607112/kotlin-jvmstatic-and-accidental-override-in-a-companion-object swing - Kotlin @JvmStatic and accidental override in a companion object - Stack Overflow] - たとえば`BasicComboBoxUI`を継承する独自`UI`を作成するために、`static`な`createUI(...)`メソッドを上書きする必要があるが、`@JvmStatic fun createUI(c: JComponent): ComponentUI {`は、以下のようにエラーになる #code{{ 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 [#DefaultTreeCellEditor] - `JTree.startEditingAtPath(...)`メソッドを実行すると以下のような`IllegalArgumentException`が発生する場合がある -- このメソッドを辿ると`BasicTreeUI`内でイベントを`null`で`TreeCellEditor#isCellEditable(event)`を呼び出している箇所がある -- その為、`DefaultTreeCellEditor#isCellEditable(e: EventObject)`のオーバーライドは`DefaultTreeCellEditor#isCellEditable(e: EventObject?)`に変更する必要がある #code{{ 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) }} #twocolumn `Kotlin` 自動変換 #code{{ 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) } } } }} #twocolumn `Kotlin` 手動修正 #code(lang-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("JMenuItem") } } 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) } } } }} #twocolumn * 手動変換 [#ManualConversion] - `IntelliJ`の自動変換で正常に変換されるが、手動修正でもっと短い`Kotlin`コードに変換が可能なケースのメモ -- `java.util.*`のクラスを使用している箇所は変換可能な場合が多い ** 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 ** Random [#random] - `java.util.Random`は`1.3`から`IntRange.ramdom()`などに置換可能 -- [https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.ranges/random.html random - Kotlin Programming Language] #twocolumn #code(lang-kotlin){{ val lengthOfTask = 10 + Random().nextInt(50) }} #twocolumn #code(lang-kotlin){{ val lengthOfTask = (10..60).random() }} #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 [#Optional] *** Optional.ofNullable(...) [#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 *** 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 ** Objects [#Objects] *** 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 *** Objects.requireNonNull(...) [#requireNotNull] - `Objects.requireNonNull(o)`は[https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/require-not-null.html requireNotNull]に置換可能 - `Non`ではなく`Not`になることに注意 #twocolumn #code(lang-kotlin){{ Objects.requireNonNull(url) }} #twocolumn #code(lang-kotlin){{ requireNotNull(url) }} #twocolumn ** Stream [#List] - `Stream`は、`List`(`Iterable`)に置換可能な場合が多い - 配列も`Iterable`なので`Stream`にする必要はあまりない #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 descendants(parent: Container): List<Component> = parent.getComponents() .filterIsInstance(Container::class.java) .map { descendants(it) } .fold(listOf<Component>(parent), { a, b -> a + b }) }} #twocolumn ** Stream.flatMap(...) [#flatMap] - `Stream#flatMap(Function<? super T,? extends Stream<? extends R>> mapper)`はそのまま`Iterable<T>.flatMap(transform: (T) -> Iterable<R>)`に置換可能 #twocolumn #code(lang-kotlin){{ // // 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)) } } }} #twocolumn #code(lang-kotlin){{ // StreamではなくIterableが使用可能 private fun descendants(me: MenuElement): List<MenuElement> = me.getSubElements().flatMap { listOf(it) + descendants(it) } }} #twocolumn ** Stream.reduce(...) [#reduce] - 初期値有りの`Stream#reduce(T identity, BinaryOperator<T> accumulator)`は、`Iterable<T>.fold(initial: R, operation: (acc: R, T) -> R)`に置換可能 #twocolumn #code(lang-kotlin){{ // // 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) }) }} #code(lang-kotlin){{ val max = group.stream() .map { it.getSuperPreferredWidth() } .reduce(0, Integer::max) }} #twocolumn #code(lang-kotlin){{ val max = group .map { it.getSuperPreferredWidth() } .fold(0) { a, b -> maxOf(a, b) } }} #code(lang-kotlin){{ val max = group.map { it.getSuperPreferredWidth() }.fold(0, ::maxOf) }} #code(lang-kotlin){{ // この例だとIntなのでmax()とエルビス演算子`?:`が使用可能 val max = group.map { it.getSuperPreferredWidth() }.max() ?: 0 }} #twocolumn ** Collections.nCopies(...).joinToString(...) [#repeat] - `CharSequence#repeat(n)`で`n`回繰り返した文字列を生成可能 -- [https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/repeat.html repeat - Kotlin Programming Language] #twocolumn #code(lang-kotlin){{ val text = Collections.nCopies(2000, "aaaaaaaaaaaaa").joinToString("\n") }} #twocolumn #code(lang-kotlin){{ val text = "aaaaaaaaaaaaa\n".repeat(2000) }} #twocolumn ** joinToString(...) [#join] #twocolumn #code(lang-java){{ String msg = jlist.getSelectedValuesList().stream() .map(it -> it.title) .collect(Collectors.joining(", ")); }} #twocolumn #code(lang-kotlin){{ val msg = jlist.selectedValuesList .map { it.title } .joinToString(", ") }} #twocolumn ** maxOf(...) [#maxOf] #twocolumn #code(lang-java){{ // private List<AlignedLabel> group; int max = group.stream() .map(AlignedLabel::getSuperPreferredWidth) .reduce(0, Integer::max); }} #twocolumn #code(lang-kotlin){{ // private var group = mutableListOf<AlignedLabel>() // val max = group.map { it.getSuperPreferredWidth() }.fold(0, ::maxOf) // val max = group.map { it.getSuperPreferredWidth() }.maxOrNull() ?: 0 // group.isNotEmpty() val max = group.maxOf(AlignedLabel::getSuperPreferredWidth) }} #twocolumn ** 複数行文字列 [#StringLiterals] - 複数行文字列は三重引用符`"""`を使って生成可能 -- [https://kotlinlang.org/docs/reference/basic-types.html#string-literals String Literals - Basic Types: Numbers, Strings, Arrays - Kotlin Programming Language] #twocolumn #code(lang-kotlin){{ // private String makeTestHtml() { // return String.join("\n", strarray); // } // を自動変換すると以下のようなコードになる private fun makeTestHtml(): String { return arrayOf( "<html><body>", "<div>2222222222</div>", "</body></html>").joinToString("\n") } }} #twocolumn #code(lang-kotlin){{ // が、三重引用符で囲めば改行などをエスケープシーケンスで記入する必要はない private fun makeTestHtml() = """ <html> <body> <div>2222222222</div> </body> </html> """ }} #twocolumn ** Comparator.comparing(...).thenComparing(...) [#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>`が作成可能 -- [https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.comparisons/index.html kotlin.comparisons - Kotlin Programming Language] #twocolumn #code(lang-kotlin){{ // 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) }} #twocolumn #code(lang-kotlin){{ 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() })) }} #twocolumn ** filterIsInstance [#filterIsInstance] #twocolumn #code(lang-kotlin){{ // 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()) } }} #code(lang-kotlin){{ // Type mismatch. でエラーになる listOf(c0, c1, c2, c3) .mapNotNull { it.getModel() } .filterIsInstance(MutableComboBoxModel::class.java) .forEach { it.insertElementAt(str, it.getSize()) } }} #twocolumn #code(lang-kotlin){{ // reified type parameter を使用する listOf(c0, c1, c2, c3) .mapNotNull { it.getModel() } .filterIsInstance<MutableComboBoxModel<String>>() .forEach { it.insertElementAt(str, it.getSize()) } }} #code(lang-kotlin){{ listOf(c0, c1, c2, c3) .mapNotNull { it.getModel() as? MutableComboBoxModel<String> } .forEach { it.insertElementAt(str, it.getSize()) } }} #twocolumn ** Functional (SAM) interfaces [#FunctionalInterfaces] - `kotlin 1.4.0`から`Kotlin`インターフェイスでも`SAM`(`Single Abstract Method`)変換を利用可能になったが、まだ`IntelliJ`の自動変換では`Functional interfaces`ではなく普通の`interfaces`に変換されるのでこれを使用する場合は手動で置換する必要がある -- [https://kotlinlang.org/docs/reference/fun-interfaces.html Functional interfaces (SAM interfaces) - Kotlin Programming Language] #twocolumn #code(lang-kotlin){{ interface ExpansionListener : EventListener { fun expansionStateChanged(e: ExpansionEvent) } } // ... p.addExpansionListener(object : ExpansionListener { override fun expansionStateChanged(e: ExpansionEvent) { (e.source as? Component)?.also { // ... } } }) }} #twocolumn #code(lang-kotlin){{ fun interface ExpansionListener : EventListener { fun expansionStateChanged(e: ExpansionEvent) } } // ... p.addExpansionListener { e -> (e.source as? Component)?.also { // ... } } }} #twocolumn * Jetpack Compose for Desktop [#t9efb999] - [https://www.jetbrains.com/lp/compose/ Compose for Desktop UI Framework | JetBrains: Developer Tools for Professionals and Teams] -- via: [https://www.publickey1.jp/blog/20/kotlinuicompose_for_desktopwinmaclinuxjetbrains.html Kotlin用デスクトップUIライブラリ「Compose for Desktop」が登場、Win/Mac/Linuxアプリ開発対応。JetBrains - Publickey] -- あとで調べる * コメント [#comment] * Comment [#comment] #comment #comment