Kotlin のバックアップの現在との差分(No.14)
- keywords: [Kotlin, Swing, Java]
description: KotlinでSwingコンポーネントを使用するサンプルと、Javaからの変換に関するメモなど
author: aterai
pubdate: 2017-05-31
pubdate: 2017-05-31T18:35:21+09:00
- 概要
- 実行環境
- IntelliJの自動変換
- 手動修正が不要なIntelliJの自動変換
- Swing + Kotlin サンプル
- 手動変換
- Jetpack Compose for Desktop
- コメント
概要
Kotlin
でSwing
コンポーネントを使用するサンプルと、Javaからの変換に関するメモです。
Kotlin
でSwing
コンポーネントを使用するサンプルと、Java
からの変換に関するメモです。
実行環境
- SDKMAN! the Software Development Kit Managerで
kotlin
をインストール可能 - SDKMAN! the Software Development Kit Managerで
kotlin
のインストールが可能
$ curl -s "https://get.sdkman.io" | bash $ curl -s "https://get.sdkman.io" | bash $ sdk install kotlin $ kotlinc -version info: Kotlin Compiler version 1.1.2-2 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
- Using Gradle - Kotlin Programming Language
- kotlin-examples/gradle/hello-world at master ・ Kotlin/kotlin-examples
- 上記のサンプル
build.gradle
が参考になる
- 上記のサンプル
- Kotlin Basics: Create Executable Kotlin JARs, using Gradle
-
jar
作成は同じくこちらの方法が使用可能 - Configuring Dependencies - Using Gradle - Kotlin Programming Language
-
kotlin-stdlib
の場合Java 6
以上が対象になるので、Java 7
で導入されたAutoCloseable
でuse
を使用すると@InlineOnly public inline fun <T : Closeable?, R> ???.use(block: (???) -> ???): ??? defined in kotlin.io
とコンパイルエラーになる -
kotlin-stdlib-jdk7
かkotlin-stdlib-jdk8
を使用すれば回避可能
-
-
#spanend
#spanadd
buildscript {
#spanend
ext.kotlin_version = 'latest.release'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
apply plugin: 'kotlin'
#spanend
#spanadd
apply plugin: 'application'
#spanend
#spanadd
#spanend
#spanadd
// group 'KotlinSwingTips'
#spanend
#spanadd
// version '1.0-SNAPSHOT'
#spanend
#spanadd
#spanend
#spanadd
mainClassName = 'example.AppKt'
#spanend
#spanadd
#spanend
#spanadd
defaultTasks 'run'
#spanend
#spanadd
#spanend
#spanadd
repositories {
#spanend
mavenCentral()
jcenter()
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
dependencies {
#spanend
// implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
testImplementation 'junit:junit:4.11'
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
sourceCompatibility = 1.8
#spanend
#spanadd
#spanend
#spanadd
compileKotlin {
#spanend
kotlinOptions.jvmTarget = "$sourceCompatibility"
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
compileTestKotlin {
#spanend
kotlinOptions.jvmTarget = "$sourceCompatibility"
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
jar {
#spanend
manifest { attributes 'Main-Class': "$mainClassName" }
from { configurations.compileClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
#spanadd
}
#spanend
#spanadd
IntelliJ
の自動変換
- メニューの
Code
,Convert Java File to Kotlin File
(Ctrl+Alt+Shift+K)で、Java
のソースコードをKotlin
のソースコードに変換可能-
Hoge.java
を変換するとHoge.kt
が生成されて、Hoge.java
は削除されてしまうので注意が必要-
Undo
(Ctrl+Z)で変換は取り消されて削除されたJava
ファイルは復活する
-
-
-
File
メニュー、Project Structure
パネルのProject SDK
が<No SDK>
になっていると、@Override
がoverride
に変換されない場合がある -
Ktolin
プロジェクトを作成して適当なApp.kt
を作成し、そこにJava
のソースコードをコピー・ペーストすると自動変換可能- Ctrl+Alt+Shift+Kでファイル変換するより高機能?
Improved Java to Kotlin converter
- Kotlin 1.3.50 released | Kotlin Blog
In the future, the new converter is going to become the default one. In this release, it’s available for preview. To turn it on, specify the
Use New J2K (experimental)
flag in settings.
-
Kotlin 1.3.50
でIntelliJ IDEA
のConvert Java File to Kotlin File
(Ctrl+Alt+Shift+K)が実験的に改良されている-
Use New J2K(experimental)
フラグで試用可能になるが、設定をJ2K
で検索しても見つからない -
Settings
ダイアログ→Languages & Frameworks
→Use New Java to Kotlin Converter
にチェックを入れる必要がある -
Kotlin Plugin
を1.4.31-release-IJ2019.2-1
から1.3.50-release-IJ2019.2-1
にするとUse New Java to Kotlin Converter
にチェックをしているかどうかに関わらず、KotlinNullPointerException
で変換に失敗する場合がある?- KotlinNullPointerException on J2K convertion of constructor reference as Function type argument : KT-33431
#spanend #spanadd kotlin.KotlinNullPointerException at org.jetbrains.kotlin.idea.actions.JavaToKotlinAction.actionPerformed(JavaToKotlinAction.kt:221) #spanend #spanadd ... #spanend #spanadd
- KotlinNullPointerException on J2K convertion of constructor reference as Function type argument : KT-33431
-
- クリップボード経由の変換は
1.3.50-release-IJ2019.2-1
でもエラーにならず実行可能 - 以下のほとんどが
Use New J2K(experimental)
をオンにすると修正されている
演算子の優先順位
- 以下のようなソースコードを
IntelliJ
で自動変換した場合、論理演算子の左シフト(<<
)はshl
に変換され、またand
の優先順位が異なるので括弧が減る
Java
#spanend
#spanadd
btn.setSelected((i & (1 << 2)) != 0);
#spanend
#spanadd
Kotlin
(自動変換)
#spanend
#spanadd
btn.setSelected(i and (1 shl 2) != 0)
#spanend
#spanadd
// 以下のように誤変換される場合がある???
#spanend
#spanadd
// 再現しないので勘違いかもしれない
#spanend
#spanadd
// btn.setSelected(i and (1 shl 2 != 0))
#spanend
#spanadd
-
bitwise AND
(and
の優先順位)- Bitwise operators are not in the precedence table - Support - Kotlin Discussions
- Kotlinの演算子優先順位表
- Basic Types: Numbers, Strings, Arrays - Kotlin Programming Language
-
Kotlin
のbitwise and
はinfix function
のため、Equality
の!=
より優先順位が高い
-
Java
の演算子優先順位表: Operators (The Java™ Tutorials > Learning the Java Language > Language Basics)-
Java
は逆で、equality
(==
,!=
)のほうがbitwise AND
(&
)より優先順位が高い
-
型パラメータ(修正済)
- 修正済
IntelliJ
の自動変換ではArrays.asList(...)
の型パラメータを自動的に変換してくれない-
Arrays.<Component>asList(...)
と型パラメータを明示しても省略される -
for (c in Arrays.asList(...)) {
に変換されてerror: none of the following functions can be called with the arguments supplied:
になる -
for (c: Component in Arrays.asList<Component>(...)) {
、またはfor (c in listOf<Component>(...)) {
などに手動で修正(for (c: Component in Arrays<Component>.asList(...)) {
も可能?)
-
- 型引数
-
Java
とKotlin
では型引数を付ける位置が異なるので注意
-
-
Java
:Arrays.<Component>asList(...)
Generic Methods (The Java™ Tutorials > Learning the Java Language > Generics (Updated))
For static generic methods, the type parameter section must appear before the method's return type.
-
Kotlin
:Arrays.asList<Component>(...)
Generics: in, out, where - Kotlin Programming Language
To call a generic function, specify the type arguments at the call site after the name of the function:
-
Kotlin
でJava
風にfor (c in Arrays.<Component>asList(...)) {
を使用すると、error: expecting an element
とエラーになる
Java
#spanend
#spanadd
Box box = Box.createVerticalBox();
#spanend
#spanadd
for (Component c: Arrays.<Component>asList(textField1, textField2, combo3, combo4)) {
#spanend
box.add(c);
box.add(Box.createVerticalStrut(5));
#spanadd
}
#spanend
#spanadd
// for (Component c: Arrays.asList(...)) { でも同じ変換結果になる
#spanend
#spanadd
Kotlin
(自動変換)
#spanend
#spanadd
val box = Box.createVerticalBox()
#spanend
#spanadd
for (c in Arrays.asList(textField1, textField2, combo3, combo4)) {
#spanend
box.add(c)
box.add(Box.createVerticalStrut(5))
#spanadd
}
#spanend
#spanadd
// error: none of the following functions can be called with the arguments supplied:
#spanend
#spanadd
// public open fun add(p0: Component!): Component! defined in javax.swing.Box
#spanend
#spanadd
// public open fun add(p0: PopupMenu!): Unit defined in javax.swing.Box
#spanend
#spanadd
// box.add(c)
#spanend
#spanadd
// ^
#spanend
#spanadd
Kotlin
(手動修正)
#spanend
#spanadd
val box = Box.createVerticalBox()
#spanend
#spanadd
for (c in Arrays.asList<Component>(textField1, textField2, combo3, combo4)) {
#spanend
box.add(c);
box.add(Box.createVerticalStrut(5));
#spanadd
}
#spanend
#spanadd
// or:
#spanend
#spanadd
// for (c in listOf<Component>(textField1, textField2, combo3, combo4)) {
#spanend
#spanadd
// for (c in Arrays<Component>.asList(textField1, textField2, combo3, combo4)) {
#spanend
#spanadd
境界型パラメータ
-
IntelliJ
の自動変換で以下のような境界型パラメータをもつJava
クラスを変換し、kotlinc
でコンパイルすると無駄な境界型パラメータが存在すると警告してくれる-
warning: 'LocalDate' is a final type, and thus a value of the type parameter is predetermined
-
LocalDate
クラスはfinal
で継承不可のため、境界型パラメータは無意味 -
ECJ
(Eclipse Compiler for Java
)もThe type parameter T should not be bounded by the final type LocalDate. Final types cannot be further extended
と警告してくれる -
Java
側をclass CalendarViewTableModel<T extends TemporalAdjuster> extends DefaultTableModel {
か、class CalendarViewTableModel extends DefaultTableModel {
に修正する
-
Java
#spanend
#spanadd
class CalendarViewTableModel<T extends LocalDate> extends DefaultTableModel {
#spanend
public CalendarViewTableModel(T date) {
#spanadd
Kotlin
(自動変換)
#spanend
#spanadd
class CalendarViewTableModel<T : LocalDate> public constructor(date: T) : DefaultTableModel() {
#spanend
#spanadd
クラスリテラル(修正済)
- 修正済
総称型を持つクラスのクラスリテラル(Class Literals
)をIntelliJ
で自動変換すると失敗する場合がある- 例えば
JComboBox.class
はIntelliJ
の自動変換でJComboBox<*>::class.java
に変換されるが、JComboBox::class.java
に修正しないとコンパイルエラーになる -
Only classes are allowed on the left hand side of a class literal
- J2K converts class literals including redundant generic <*> : KT-15791
- 例えば
- 逆に
Java
のc instanceof JComboBox
をKotlin
に変換するときは、c is JComboBox<*>
にしないとエラーになる- こちらは
IntelliJ
の自動変換で正常に変換可能 -
One type argument expected. Use 'JComboBox<*>' if you don't want to pass type arguments
- こちらは
Java
#spanend
#spanadd
enabledCheck.addActionListener(e -> {
#spanend
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());
}
#spanadd
});
#spanend
#spanadd
Kotlin
(自動変換)
#spanend
#spanadd
enabledCheck.addActionListener({ e ->
#spanend
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)
}
#spanadd
})
#spanend
#spanadd
Kotlin
(手動修正)
#spanend
#spanadd
enabledCheck.addActionListener {
#spanend
val c = SwingUtilities.getAncestorOfClass(JComboBox::class.java, this)
if (c is JComboBox<*>) {
val item = c.getItemAt(data.index) as ComboItem
item.isEnabled = (e.getSource() as JCheckBox).isSelected()
editableCheck.setEnabled(item.isEnabled)
textField.setEnabled(item.isEnabled)
}
#spanadd
}
#spanend
#spanadd
型引数の省略(修正済)
- 修正済
Java 7
以降で可能なジェネリッククラスのコンストラクタ呼び出しに必要な型引数を省略する記法(<>, diamond operator
)(Generic Types (The Java™ Tutorials > Learning the Java Language > Generics (Updated)))は、IntelliJ
の自動変換では正常に変換されない場合がある - 例えば、以下の
Java
コードのrenderer = new CheckBoxCellRenderer<>();
部分は、renderer = CheckBoxCellRenderer<example.CheckBoxNode>()
に変換され、Type mismatch: inferred type is CheckBoxCellRenderer<CheckBoxNode> but CheckBoxCellRenderer<E>? was expected
とエラーになる- これを
renderer = CheckBoxCellRenderer<>()
と空にすると、今度はType expected
とエラーになる -
Kotlin
には<>, diamond operator
は存在せず?、型引数を省略したい場合は何も書く必要がない(Generics: in, out, where - Kotlin Programming Language) -
renderer = CheckBoxCellRenderer<E>()
、またはrenderer = CheckBoxCellRenderer()
にすれば正常に動作する - 変換元の
Java
コードがrenderer = new CheckBoxCellRenderer<E>();
なら、IntelliJ
の自動変換ではrenderer = CheckBoxCellRenderer()
になり正常に動作する
- これを
-
JList<CheckBoxNode> list2 = new CheckBoxList<>(model);
が、なぜかval list2 = CheckBoxList<E>(model)
に変換されるバグも存在する?- 正しく
val list2 = CheckBoxList<CheckBoxNode>(model)
に変換される場合もあるが、条件が不明 - どちらにしても、
val list2 = CheckBoxList(model)
には変換してくれない
- 正しく
Java
#spanend
#spanadd
class CheckBoxList<E extends CheckBoxNode> extends JList<E> {
#spanend
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);
}
// ...
#spanadd
}
#spanend
#spanadd
Kotlin
(自動変換)
#spanend
#spanadd
internal class CheckBoxList<E : CheckBoxNode> protected constructor(model: ListModel<E>) : JList<E>(model) {
#spanend
@Transient
private var renderer: CheckBoxCellRenderer<E>? = null
#spanadd
#spanend
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)
}
// ...
#spanadd
}
#spanend
#spanadd
Kotlin
(手動修正)
#spanend
#spanadd
internal class CheckBoxList<E : CheckBoxNode> protected constructor(model: ListModel<E>) : JList<E>(model) {
#spanend
@Transient
private var renderer: CheckBoxCellRenderer<E>? = null
#spanadd
#spanend
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)
}
// ...
#spanadd
}
#spanend
#spanadd
匿名内部クラス
-
IntelliJ
の自動変換ではerror: this class does not have a constructor tree.addTreeWillExpandListener(object : TreeWillExpandListener() {
とエラーになる-
IntelliJ 2018.2.5
の自動変換では正しく変換されるようになっている
-
- インターフェイスから匿名内部クラスのインスタンスを生成する場合は
()
は不要- SAM Conversions - Calling Java from Kotlin - Kotlin Programming Language
-
tree.addTreeWillExpandListener(object : TreeWillExpandListener() {
をtree.addTreeWillExpandListener(object : TreeWillExpandListener {
に修正
-
kotlin 1.4.0
からkotlin
のインターフェイスでもラムダでの記述も可能になった
Java
#spanend
#spanadd
tree.addTreeWillExpandListener(new TreeWillExpandListener() {
#spanend
@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");
}
#spanadd
});
#spanend
#spanadd
Kotlin
(自動変換)
#spanend
#spanadd
tree.addTreeWillExpandListener(object : TreeWillExpandListener() {
#spanend
@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")
}
#spanadd
})
#spanend
#spanadd
Kotlin
(手動修正)
#spanend
#spanadd
tree.addTreeWillExpandListener(object : TreeWillExpandListener {
#spanend
@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")
}
#spanadd
})
#spanend
#spanadd
ラベル付きReturn
-
匿名内部クラスからreturn
で抜けるJava
コードをIntelliJ
の自動変換で変換すると、unresolved reference: @decode
とエラーになるKotlin
コードに変換される場合がある-
IntelliJ 2018.2.5
の自動変換では正しく変換されるようになっている - 以下のような変換例の場合、
return@decode.addActionListener
をreturn@addActionListener
のようにメソッド名のみのラベルに変更する必要がある - Returns and Jumps: break and continue - Kotlin Programming Language
-
Java
#spanend
#spanadd
JButton decode = new JButton("decode");
#spanend
#spanadd
decode.addActionListener(e -> {
#spanend
String b64 = textArea.getText();
if (b64.isEmpty()) {
return;
}
try (InputStream is = ...) {
label.setIcon(new ImageIcon(ImageIO.read(is)));
} catch (IOException ex) {
ex.printStackTrace();
}
#spanadd
});
#spanend
#spanadd
Kotlin
(自動変換)
#spanend
#spanadd
val decode = JButton("decode")
#spanend
#spanadd
decode.addActionListener({ e ->
#spanend
val b64 = textArea.getText()
if (b64.isEmpty()) {
return@decode.addActionListener
}
try {
// ...
} catch (ex: IOException) {
ex.printStackTrace()
}
#spanadd
})
#spanend
#spanadd
Kotlin
(手動修正)
#spanend
#spanadd
val decode = JButton("decode")
#spanend
#spanadd
decode.addActionListener {
#spanend
val b64 = textArea.getText()
if (b64.isEmpty()) {
return@addActionListener
}
try {
// ...
} catch (ex: IOException) {
ex.printStackTrace()
}
#spanadd
}
#spanend
#spanadd
tryの戻り値
-
Optional.map(...)
などからreturn
で戻り値を返すJava
コードをIntelliJ
の自動変換で変換すると、Return
のラベルがエラーになるKotlin
コードに変換される場合がある-
@Optional.ofNullable(getClass().getResource("unkaku_w.png")).map
がラベルになっている?
-
-
Kotlin
ではtry
は式で戻り値を持つことが可能なので、ラベル付きReturn
は不要 - Try is an expression - Exceptions: try, catch, finally, throw, Nothing - Kotlin Programming Language
Java
#spanend
#spanadd
BufferedImage bi = Optional.ofNullable(getClass().getResource("unkaku_w.png"))
#spanend
.map(url -> {
try {
return ImageIO.read(url);
} catch (IOException ex) {
return makeMissingImage();
}
}).orElseGet(() -> makeMissingImage());
#spanadd
Kotlin
(自動変換)
#spanend
#spanadd
val bi = Optional.ofNullable(javaClass.getResource("unkaku_w.png")).map({ url ->
#spanend
try {
return@Optional.ofNullable(getClass().getResource("unkaku_w.png")).map ImageIO . read url
} catch (ex: IOException) {
return@Optional.ofNullable(getClass().getResource("unkaku_w.png")).map makeMissingImage ()
}
#spanadd
}).orElseGet({ makeMissingImage() })
#spanend
#spanadd
Kotlin
(手動修正1)
#spanend
#spanadd
val bi = Optional.ofNullable(javaClass.getResource("unkaku_w.png")).map { url ->
#spanend
try {
ImageIO.read(url)
} catch (ex: IOException) {
makeMissingImage()
}
#spanadd
}.orElseGet { makeMissingImage() }
#spanend
#spanadd
Kotlin
(手動修正2)
#spanend
#spanadd
val bi = javaClass.getResource("unkaku_w.png")?.let {
#spanend
try {
ImageIO.read(it)
} catch (ex: IOException) {
makeMissingImage()
}
#spanadd
} ?: makeMissingImage()
#spanend
#spanadd
Kotlin
(手動修正3)
#spanend
#spanadd
// runCatching関数でResultを取得するようtry-catchを置き換える
#spanend
#spanadd
val bi = runCatching {
#spanend
ImageIO.read(javaClass.getResource("unkaku_w.png"))
#spanadd
}.getOrNull() ?: makeMissingImage()
#spanend
#spanadd
メソッド参照
-
IntelliJ
の自動変換ではメソッド参照の変換が苦手?いくらか修正されている?-
IntelliJ 2018.2.5
の自動変換では、???
ではなくPredicate<Component>
などに変換されるようになった
-
Java
#spanend
#spanadd
public static Stream<TreeNode> descendants(TreeNode node) {
#spanend
Class<TreeNode> clz = TreeNode.class;
return Collections.list((Enumeration<?>) node.children())
.stream().filter(clz::isInstance).map(clz::cast);
#spanadd
}
#spanend
#spanadd
Kotlin
(自動変換)
#spanend
#spanadd
fun descendants(node: TreeNode): Stream<TreeNode> {
#spanend
val clz = TreeNode::class.java
return Collections.list(node.children() as Enumeration<*>)
.stream().filter(???({ clz!!.isInstance() })).map(clz!!.cast)
#spanadd
}
#spanend
#spanadd
Kotlin
(手動修正)
#spanend
#spanadd
fun descendants(node: TreeNode): Stream<TreeNode> {
#spanend
val clz = TreeNode::class.java
return Collections.list(node.children() as Enumeration<*>)
.stream().filter(clz::isInstance).map(clz::cast)
// .filterIsInstance(clz).stream()
#spanadd
}
#spanend
#spanadd
Kotlin
(手動修正): Iterable<*>.filterIsInstance()
が便利
#spanend
#spanadd
fun descendants(node: TreeNode): List<TreeNode> {
#spanend
return (node.children() as Enumeration<*>)
.toList().filterIsInstance(TreeNode::class.java)
#spanadd
}
#spanend
#spanadd
- 例えば
.filter(Container.class::isInstance)
のようなメソッド参照を、IntelliJ 2018.2.5
の自動変換では.filter(Predicate<Component> { Container::class.java!!.isInstance(it) })
のように変換可能になった?が、import
の追加は対応していない
Java
#spanend
#spanadd
public static Stream<Component> stream(Container parent) {
#spanend
return Stream.of(parent.getComponents())
.filter(Container.class::isInstance)
.map(c -> stream(Container.class.cast(c)))
.reduce(Stream.of(parent), Stream::concat);
}
#spanadd
Kotlin
(自動変換)
#spanend
#spanadd
// 自動的に以下のimportは生成されないのでunresolved referenceになる
#spanend
#spanadd
// import java.util.function.BinaryOperator
#spanend
#spanadd
// import java.util.function.Predicate
#spanend
#spanadd
fun stream(parent: Container): Stream<Component> {
#spanend
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) })
#spanadd
}
#spanend
#spanadd
Kotlin
(手動修正)
#spanend
#spanadd
import java.util.function.BinaryOperator
#spanend
#spanadd
import java.util.function.Predicate
#spanend
#spanadd
fun stream(parent: Container): Stream<Component> {
#spanend
// 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
#spanadd
}
#spanend
#spanadd
Kotlin
(手動修正)
#spanend
#spanadd
// Stream を Iterable に変換してしまう方法もある
#spanend
#spanadd
fun descendants(parent: Container): List<Component> {
#spanend
return parent.getComponents().toList()
.filterIsInstance(Container::class.java)
.map { descendants(it) }
.fold(listOf<Component>(parent)) { a, b -> a + b }
#spanadd
}
#spanend
#spanadd
// ...
#spanend
#spanadd
descendants(fileChooser1)
#spanend
.filterIsInstance(JTable::class.java)
// .firstOrNull()?.apply(JTable::removeEditor)
.firstOrNull()?.let { table ->
println(table)
table.removeEditor()
}
#spanadd
メソッド参照(終端処理の戻り値)
- Container#add(Component)メソッドは戻り値として
Component
を返す-
Java
の場合、終端処理のforEach(p::add)
では戻り値を無視してContainer#add(Component)
のメソッド参照が可能 -
Kotlin
の場合、forEach(p::add)
とすると、add
メソッドで引数がComponent!
、戻り値がUnit
になるメソッドを探すためメソッド参照で以下のようなエラーになる- Function reference: ignoring the returned value - Support - Kotlin Discussions
-
map(p::add)
にすればエラーは回避できる
-
hello.kt:38:66: error: none of the following functions can be called with the arguments supplied: public open fun add(p0: Component!): Component! defined in javax.swing.JPanel public open fun add(p0: Component!, p1: Any!): Unit defined in javax.swing.JPanel public open fun add(p0: Component!, p1: Any!, p2: Int): Unit defined in javax.swing.JPanel public open fun add(p0: Component!, p1: Int): Component! defined in javax.swing.JPanel public open fun add(p0: PopupMenu!): Unit defined in javax.swing.JPanel public open fun add(p0: String!, p1: Component!): Component! defined in javax.swing.JPanel listOf(button1, button2, button3, button4).forEach(p::add) ^
Java
#spanend
#spanadd
JPanel p = new JPanel(new GridLayout(0, 1, 2, 2));
#spanend
#spanadd
Arrays.asList(button1, button2).forEach(p::add);
#spanend
#spanadd
Kotlin
(自動変換)
#spanend
#spanadd
val p = JPanel(GridLayout(0, 1, 2, 2))
#spanend
#spanadd
Arrays.asList(button1, button2).forEach( ?? ? ({ p.add() }))
#spanend
#spanadd
Kotlin
(手動修正)
#spanend
#spanadd
val p = JPanel(GridLayout(0, 1, 2, 2))
#spanend
#spanadd
listOf(button1, button2).forEach { b -> p.add(b) }
#spanend
#spanadd
//or listOf(button1, button2).map(p::add)
#spanend
#spanadd
コンストラクタ参照
-
IntelliJ
の自動変換ではコンストラクタ参照がうまく変換できない場合がある- 例えば
Stream.of("A", "B", "C").map(JToggleButton::new)
がStream.of("A", "B", "C").map(Function<String, JToggleButton> { JToggleButton(it) })
に変換されて、以下のようなエラーになる
- 例えば
e: App.kt: (13, 34): Interface Function does not have constructors e: App.kt: (13, 82): Unresolved reference: it e: App.kt: (13, 89): Cannot choose among the following candidates without completing type inference: @HidesMembers public inline fun <T> Iterable<???>.forEach(action: (???) -> Unit): Unit defined in kotlin.collections @HidesMembers public inline fun <K, V> Map<out ???, ???>.forEach(action: (Map.Entry<???, ???>) -> Unit): Unit defined in kotlin.collections e: App.kt: (13, 99): Cannot infer a type for this parameter. Please specify it explicitly.
-
::JToggleButton
に修正すればエラーを回避可能
Java
#spanend
#spanadd
Stream.of("A", "B", "C").map(JToggleButton::new).forEach(r -> {
#spanend
p.add(r);
bg.add(r);
#spanadd
});
#spanend
#spanadd
Kotlin
(自動変換)
#spanend
#spanadd
Stream.of("A", "B", "C").map(Function<String, JToggleButton> { JToggleButton(it) }).forEach({ r ->
#spanend
p.add(r)
bg.add(r)
#spanadd
})
#spanend
#spanadd
Kotlin
(手動修正)
#spanend
#spanadd
Stream.of("A", "B", "C").map(::JToggleButton).forEach { r ->
#spanend
p.add(r)
bg.add(r)
#spanadd
}
#spanend
#spanadd
Number#intValue()
-
Number#intValue()
をIntelliJ
で自動変換するとintValue()
がそのまま使用されるが、コンパイルするとerror: unresolved reference: intValue
とエラーになるので手動でtoInt()
などを使用するよう修正する必要がある-
1.4.31-release-IJ2019.2-1
の自動変換ではtoInt()
になる
-
Java
#spanend
#spanadd
double d = delta.y * GRAVITY;
#spanend
#spanadd
int ia = (int) d;
#spanend
#spanadd
int ib = (int) Math.floor(d);
#spanend
#spanadd
int ic = new BigDecimal(d).setScale(0, RoundingMode.DOWN).intValue();
#spanend
#spanadd
System.out.format("%d %d %d%n", ia, ib, ic);
#spanend
#spanadd
Kotlin
(自動変換)
#spanend
#spanadd
val d = delta.y * GRAVITY
#spanend
#spanadd
val ia = d.toInt()
#spanend
#spanadd
val ib = Math.floor(d) as Int
#spanend
#spanadd
val ic = BigDecimal(d).setScale(0, RoundingMode.DOWN).intValue()
#spanend
#spanadd
System.out.format("%d %d %d%n", ia, ib, ic)
#spanend
#spanadd
Kotlin
(手動修正)
#spanend
#spanadd
val d = delta.y * GRAVITY
#spanend
#spanadd
val ia = d.toInt()
#spanend
#spanadd
val ib = Math.floor(d).toInt()
#spanend
#spanadd
val ic = BigDecimal(d).setScale(0, RoundingMode.DOWN).toInt()
#spanend
#spanadd
println("${ia} ${ib} ${ic}")
#spanend
#spanadd
Comparator
-
IntelliJ
の自動変換ではerror: calls to static methods in Java interfaces are prohibited in JVM target 1.6. Recompile with '-jvm-target 1.8'
とエラーになる-
kotlinc hello.kt -jvm-target 1.8 -include-runtime -d hello.jar
とkotlinc
にオプションを追加
-
-
IntelliJ
の自動変換ではerror: using 'sort(kotlin.Comparator<in T> /* = java.util.Comparator<in T> */): Unit' is an error. Use sortWith(comparator) instead.
とエラーになる-
list.sort(...)
をlist.sortWith(...)
に修正
-
-
IntelliJ
の自動変換ではerror: type inference failed: Not enough information to infer parameter T in fun <T : Any!> comparingInt(p0: ((T!) -> Int)!): Comparator<T!>!
とエラーになる-
Comparator.comparingInt({ l -> l.get(0) })
をComparator.comparingInt<List<Int>>({ l -> l.get(0) })
に修正
-
Java
#spanend
#spanadd
import java.util.*;
#spanend
#spanadd
public class SortTest {
#spanend
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);
}
#spanadd
}
#spanend
#spanadd
Kotlin
(自動変換)
#spanend
#spanadd
fun main(args: Array<String>) {
#spanend
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)
#spanadd
}
#spanend
#spanadd
Kotlin
(手動修正)
#spanend
#spanadd
fun main(args: Array<String>) {
#spanend
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)
#spanadd
}
#spanend
#spanadd
Kotlin
(手動修正)
#spanend
#spanadd
fun main(args: Array<String>) {
#spanend
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)
#spanadd
}
#spanend
#spanadd
16進数数値リテラル内のアンダースコア
-
IntelliJ
の自動変換では、頭に0x
を付けて16
進数表記した数値リテラルにアンダースコアを付けると、IDE Fatal Errors
になり変換できない-
IntelliJ 2018.2.5
の自動変換では、前処理でアンダースコアを除去?して正しく変換されるようになっている -
IntelliJ
で自動変換できないだけで、Kotlin
で16
進数表記の数値にアンダースコアを付けても問題なく動作可能 - アンダースコアがなければ、
16
進数表記でも問題なくIntelliJ
で自動変換可能 - 例1:
new Color(0xEE_EE_EE)
は、IntelliJ
の自動変換ではjava.lang.NumberFormatException: For input string: "E_EE_EE"
とエラーになる - 例2:
new Color(0x64_88_AA_AA, true)
は、IntelliJ
の自動変換ではjava.lang.NumberFormatException: For input string: "64_8"
とエラーになる
-
Java
#spanend
#spanadd
editor1.setSelectionColor(new Color(0x64_88_AA_AA, true)); // IntelliJで自動変換できない
#spanend
#spanadd
editor2.setSelectionColor(new Color(0x6488AAAA, true)); // IntelliJで自動変換可能
#spanend
#spanadd
Kotlin
(手動修正)
#spanend
#spanadd
editor1.setSelectionColor(Color(0x64_88_AA_AA, true)) // 問題なし
#spanend
#spanadd
editor2.setSelectionColor(Color(0x6488AAAA, true)) // 問題なし
#spanend
#spanadd
プライマリコンストラクタでのプロパティ定義
-
Java
のクラスで変数宣言にコメントが付いている場合、IntelliJ
の自動変換ではそのコメントがプライマリコンストラクタの引数でのプロパティ定義にそのままコピーされてしまう場合がある-
たとえば以下の例では、// = table.getTableHeader();
がclass RowHeaderRenderer<E>(private val header: JTableHeader // = table.getTableHeader();) : JLabel(), ...
とプライマリコンストラクタのプロパティ定義に入り込んでそれ以降がコメントアウトされてしまうため、コンパイルエラーになる - コメントの末尾で改行が入るよう修正された
-
Java
#spanend
#spanadd
class RowHeaderRenderer<E> extends JLabel implements ListCellRenderer<E> {
#spanend
private final JTableHeader header; // = table.getTableHeader();
#spanadd
#spanend
public RowHeaderRenderer(JTableHeader header) {
super();
this.header = header;
this.setOpaque(true);
// ...
#spanadd
Kotlin
(旧自動変換)
#spanend
#spanadd
inner class RowHeaderRenderer<E>(private val header: JTableHeader // = table.getTableHeader();) : JLabel(), ListCellRenderer<E> {
#spanend
#spanadd
#spanend
init {
this.setOpaque(true)
// ...
#spanadd
Kotlin
(新自動変換)
#spanend
#spanadd
inner class RowHeaderRenderer<E>(private val header: JTableHeader // = table.getTableHeader();
#spanend
#spanadd
) : JLabel(), ListCellRenderer<E> {
#spanend
#spanadd
#spanend
init {
this.setOpaque(true)
// ...
#spanadd
int、Int
-
Java
では例えば-1
を0xFFFFFFFF
のような16
進数表記でも表現可能だが、Kotlin
ではerror: the integer literal does not conform to the expected type Int
とエラーになるため、IntelliJ
の自動変換ではマイナス付の表記に自動変換される - Java Integer.MAX_VALUE vs Kotlin Int.MAX_VALUE - Stack Overflow
Java
#spanend
#spanadd
Color argb = new Color(0xffaabbcc, true);
#spanend
#spanadd
Color rgb = new Color(0xaabbcc);
#spanend
#spanadd
Kotlin
(自動変換)
#spanend
#spanadd
val argb = Color(-0x554434, true)
#spanend
#spanadd
val rgb = Color(0xaabbcc)
#spanend
#spanadd
Kotlin
(手動修正)
#spanend
#spanadd
// LongからIntに変換する方法もある
#spanend
#spanadd
val argb = Color(0xff_aa_bb_cc.toInt(), true)
#spanend
#spanadd
println(argb == Color(-0x554434, true)) // true
#spanend
#spanadd
@SuppressWarnings
-
IntelliJ
の自動変換では、@SuppressWarnings("unchecked")
などは削除されるので、Unchecked cast: WatchEvent<*>! to WatchEvent<Path>
と警告される- 手動で
@Suppress("UNCHECKED_CAST")
などを付ける必要がある
- 手動で
Java
#spanend
#spanadd
@SuppressWarnings("unchecked")
#spanend
#spanadd
WatchEvent<Path> ev = (WatchEvent<Path>) event;
#spanend
#spanadd
Kotlin
(手動修正)
#spanend
#spanadd
@Suppress("UNCHECKED_CAST")
#spanend
#spanadd
val ev = event as WatchEvent<Path>
#spanend
#spanadd
二次元配列
-
IntelliJ
の自動変換で二次元配列をクローンするコードを変換した場合、Type mismatch: inferred type is Array<Int?> but Array<Int> was expected
のようなエラーになる場合がある- Array - Kotlin Programming Language
-
Array
のコンストラクタで第二引数のinit
関数を使用して初期値を指定するなどの修正が必要
Java
#spanend
#spanadd
private final Integer[][] mask;
#spanend
#spanadd
@SuppressWarnings("PMD.UseVarargs")
#spanend
#spanadd
protected SudokuCellRenderer(Integer[][] src) {
#spanend
super();
Integer[][] dest = new Integer[src.length][src[0].length];
for (int i = 0; i < src.length; i++) {
System.arraycopy(src[i], 0, dest[i], 0, src[0].length);
}
this.mask = dest;
#spanadd
}
#spanend
#spanadd
Kotlin
(自動変換)
#spanend
#spanadd
private val mask: Array<Array<Int>>
#spanend
#spanadd
init {
#spanend
val dest = Array<Array<Int>>(src.size) { arrayOfNulls(src[0].size) }
for (i in src.indices) {
System.arraycopy(src[i], 0, dest[i], 0, src[0].size)
}
this.mask = dest
#spanadd
}
#spanend
#spanadd
Kotlin
(手動修正1)
#spanend
#spanadd
private val mask: Array<Array<Int?>>
#spanend
#spanadd
init {
#spanend
val dest = Array<Array<Int?>>(src.size) { arrayOfNulls(src[0].size) }
// もしくはval mask: Array<Array<Int>>のままで、適当なInt値で初期化
// val dest = Array(src.size, { Array(src[0].size, { 0 }) }) // 0で初期化
for (i in src.indices) {
System.arraycopy(src[i], 0, dest[i], 0, src[0].size)
}
this.mask = dest
#spanadd
}
#spanend
#spanadd
Kotlin
(手動修正2)
#spanend
#spanadd
private val mask: Array<Array<Int>>
#spanend
#spanadd
init {
#spanend
// System.arraycopyなどは使用せず、コピー元の二次元配列を参照して初期化
this.mask = Array(src.size, { i -> Array(src[i].size, { j -> src[i][j] }) })
#spanadd
}
#spanend
#spanadd
Ellipse2D.Double
-
IntelliJ
の自動変換でEllipse2D.Double
とkotlin.Double
競合する場合がある?- 再現できなくなった
Java
#spanend
#spanadd
import java.awt.geom.Ellipse2D;
#spanend
#spanadd
#spanend
#spanadd
enum ButtonLocation {
#spanend
CENTER(0d),
NORTH(45d),
EAST(135d),
SOUTH(225d),
WEST(-45d);
private final double degree;
ButtonLocation(double degree) {
this.degree = degree;
}
#spanadd
#spanend
public double getStartAngle() {
return degree;
}
#spanadd
}
#spanend
#spanadd
// ...
#spanend
#spanadd
Shape inner = new Ellipse2D.Double(xx, xx, ww, ww);
#spanend
#spanadd
Kotlin
(自動変換)
#spanend
#spanadd
import java.awt.geom.Ellipse2D.Double
#spanend
#spanadd
#spanend
#spanadd
enum class ButtonLocation(val startAngle: kotlin.Double) {
#spanend
CENTER(0.0), NORTH(45.0), EAST(135.0), SOUTH(225.0), WEST(-45.0);
#spanadd
}
#spanend
#spanadd
// ...
#spanend
#spanadd
val inner: Shape = Double(xx, xx, ww, ww)
#spanend
#spanadd
Kotlin
(手動修正)
#spanend
#spanadd
import java.awt.geom.Ellipse2D
#spanend
#spanadd
#spanend
#spanadd
enum class ButtonLocation(val startAngle: Double) {
#spanend
CENTER(0.0), NORTH(45.0), EAST(135.0), SOUTH(225.0), WEST(-45.0);
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
val inner: Shape = Ellipse2D.Double(xx, xx, ww, ww)
#spanend
#spanadd
手動修正が不要なIntelliJ
の自動変換
-
IntelliJ
の自動変換で変換後の手動修正が不要なケースのメモ
セーフキャスト
-
IntelliJ
の自動変換では、instanceof
と三項演算子を使ったJava
コードはセーフキャスト演算子as?
(safe cast operator
)を使ったコードになる
Java
#spanend
#spanadd
Set<?> f = v instanceof Set ? (Set<?>) v : EnumSet.noneOf(Permissions.class);
#spanend
#spanadd
Kotlin
#spanend
#spanadd
val f = v as? Set<*> ?: EnumSet.noneOf(Permissions::class.java)
#spanend
#spanadd
明示的に外側のクラス名で修飾されたthis
- 明示的に
this
を外側のクラス名で修飾したJava
コードはIntelliJ
の自動変換で@label
で修飾されたコードになる
Java
#spanend
#spanadd
class FileListTable extends JTable {
#spanend
private class RubberBandingListener extends MouseAdapter {
@Override public void mousePressed(MouseEvent e) {
FileListTable table = FileListTable.this
// ...
#spanadd
Kotlin
#spanend
#spanadd
class FileListTable(model: TableModel) : JTable(model) {
#spanend
private inner class RubberBandingListener : MouseAdapter() {
override fun mousePressed(e: MouseEvent) {
val table = this@FileListTable
// ...
#spanadd
enumを参照等価性で比較する
-
最近?のIntelliJ
の自動変換では、列挙子(enum
)の比較を===
演算子に変換する-
以前は==
演算子に変換していた??? - Equality - Kotlin Programming Language
-
Java
#spanend
#spanadd
checkBox.setSelected(node.getStatus() == Status.SELECTED);
#spanend
#spanadd
Kotlin
#spanend
#spanadd
checkBox.setSelected(it.status === Status.SELECTED)
#spanend
#spanadd
Swing + Kotlin サンプル
SwingUtilities.getAncestorOfClass(...)
-
SwingUtilities.getAncestorOfClass(...)
メソッドでよく使用する以下のようなJava
コードが、Kotlin
ではセーフキャスト演算子as?
(safe cast operator
)とエルビス演算子?:
を使って一行で記述可能
Java
#spanend
#spanadd
Container p = SwingUtilities.getAncestorOfClass(JViewport.class, this);
#spanend
#spanadd
if (!(p instanceof JViewport)) {
#spanend
return;
#spanadd
}
#spanend
#spanadd
Kotlin
#spanend
#spanadd
val p = SwingUtilities.getAncestorOfClass(JViewport::class.java, this) as? JViewport ?: return
#spanend
#spanadd
JTable
- メモ
-
Class<?>
は、Class<Any>
かClass<Object>
?Class<*>
かClass<out Any>
に変換した方が良いかもしれない-
IntelliJ
の自動変換ではClass<*>
-
-
Object#getClass()
はo.javaClass
- getClass() - Calling Java from Kotlin - Kotlin Programming Language
-
Integer::class.java
はClass<Integer>
になるので、以下のサンプルで使用すると、error: type inference failed. Expected type mismatch: inferred type is Class<Integer> but Class<Any> was expected
とエラーになる - Star-projections - Generics - Kotlin Programming Language
- オーバーライドの方法、
@Override
はoverride
- 配列、二次元配列
- Java Arrays - Calling Java from Kotlin - Kotlin Programming Language
-
IntelliJ
の自動変換ではarrayOf(arrayOf<Any>("aaa", 12, true), ...
-
apply
-
switch
はwhen
-
-
Class<?>
は、Class<Any>
かClass<Object>
?Class<*>
かClass<out Any>
に変換した方が良いかもしれない-
IntelliJ
の自動変換ではClass<*>
-
-
Object#getClass()
はo.javaClass
- getClass() - Calling Java from Kotlin - Kotlin Programming Language
-
Integer::class.java
はClass<Integer>
になるので、以下のサンプルで使用すると、error: type inference failed. Expected type mismatch: inferred type is Class<Integer> but Class<Any> was expected
とエラーになる - Star-projections - Generics - Kotlin Programming Language
- オーバーライドの方法、
@Override
はoverride
- 配列、二次元配列
- Java Arrays - Calling Java from Kotlin - Kotlin Programming Language
-
IntelliJ
の自動変換ではarrayOf(arrayOf<Any>("aaa", 12, true), ...
-
apply
-
switch
はwhen
-
DefaultTableModel#getColumnClass(...)
メソッドをオーバーライドして、Boolean::class.java
を返してもセルレンダラーにJCheckBox
は適用されない-
IntelliJ
の自動変換ではBoolean::class.java
になるが、java.lang.Boolean::class.java
に修正する必要がある
-
Java
import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;
public class JTableExample {
public JComponent makeUI() {
String[] cn = {"String", "Integer", "Boolean"};
Object[][] data = {
{"aaa", 12, true}, {"bbb", 5, false},
{"CCC", 92, true}, {"DDD", 0, false}
};
TableModel m = new DefaultTableModel(data, cn) {
@Override public Class<?> getColumnClass(int col) {
return getValueAt(0, col).getClass();
}
};
JTable table = new JTable(m);
table.setAutoCreateRowSorter(true);
JPanel p = new JPanel(new BorderLayout());
p.add(new JScrollPane(table));
return p;
}
public static void main(String... args) {
EventQueue.invokeLater(() -> {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new JTableExample().makeUI());
f.setSize(320, 240);
f.setLocationRelativeTo(null);
f.setVisible(true);
});
}
}
Kotlin
#spanend
#spanadd
#code(lang-kotlin){{
#spanend
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
// 2 -> Boolean::class.java
// // XXX: 2 -> Boolean::class.java
// 2 -> java.lang.Boolean::class.java
// else -> Object::class.java
// }
// }
// }
val table = JTable(m).apply {
autoCreateRowSorter = true
}
return JPanel(BorderLayout(5, 5)).apply {
add(JScrollPane(table))
}
}
fun main(args: Array<String>) {
EventQueue.invokeLater {
JFrame("kotlin swing").apply {
defaultCloseOperation = JFrame.EXIT_ON_CLOSE
add(makeUI())
size = Dimension(320, 240)
setLocationRelativeTo(null)
setVisible(true)
}
}
}
JTree
Java
import java.awt.*;
import java.util.*;
import javax.swing.*;
public class JTreeExample {
public JComponent makeUI() {
JTree tree = new JTree();
JButton b1 = new JButton("expand");
b1.addActionListener(e -> expandAll(tree));
JButton b2 = new JButton("collapse");
b2.addActionListener(e -> collapseAll(tree));
JPanel pp = new JPanel(new GridLayout(1, 0, 5, 5));
Arrays.asList(b1, b2).forEach(pp::add);
JPanel p = new JPanel(new BorderLayout(5, 5));
p.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
p.add(new JScrollPane(tree));
p.add(pp, BorderLayout.SOUTH);
return p;
}
protected static void expandAll(JTree tree) {
int row = 0;
while (row < tree.getRowCount()) {
tree.expandRow(row);
row++;
}
}
protected static void collapseAll(JTree tree) {
int row = tree.getRowCount() - 1;
while (row >= 0) {
tree.collapseRow(row);
row--;
}
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new JTreeExample().makeUI());
f.setSize(320, 240);
f.setLocationRelativeTo(null);
f.setVisible(true);
});
}
}
Kotlin
#spanend
#spanadd
#code(lang-kotlin){{
#spanend
import java.awt.*
import javax.swing.*
fun makeUI(): JComponent {
val tree = JTree()
val p = JPanel(GridLayout(1, 0, 5, 5)).apply {
add(JButton("expand").apply {
addActionListener { expandAll(tree) }
})
add(JButton("collapse").apply {
addActionListener { collapseAll(tree) }
})
}
return JPanel(BorderLayout(5, 5)).apply {
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5))
add(JScrollPane(tree))
add(p, BorderLayout.SOUTH)
}
}
fun expandAll(tree: JTree) {
var row = 0
while (row < tree.getRowCount()) {
tree.expandRow(row)
row++
}
}
fun collapseAll(tree : JTree) {
var row = tree.getRowCount() - 1
while (row >= 0) {
tree.collapseRow(row)
row--
}
}
fun main(args: Array<String>) {
EventQueue.invokeLater {
JFrame("kotlin swing").apply {
defaultCloseOperation = JFrame.EXIT_ON_CLOSE
add(makeUI())
size = Dimension(320, 240)
setLocationRelativeTo(null)
setVisible(true)
}
}
}
JCheckBox
Smart Cast
、キャストでif
のネストが深くなるのを避けたい-
as
を使用する:val b = e.getSource() as AbstractButton
- イベント発生元が
AbstractButton
なのは自明なのでas
を使用する-
val b = e.getSource() as AbstractButton
-
- 以下のような場合は、
apply
でも回避可能
-
Java
import java.awt.*;
import javax.swing.*;
public class JCheckBoxExample {
public JComponent makeUI() {
JCheckBox cb = new JCheckBox("Always On Top", true);
cb.addActionListener(e -> {
AbstractButton b = (AbstractButton) e.getSource();
Container c = b.getTopLevelAncestor();
if (c instanceof Window) {
((Window) c).setAlwaysOnTop(b.isSelected());
}
});
JPanel p = new JPanel(new BorderLayout());
p.add(cb, BorderLayout.NORTH);
return p;
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new JCheckBoxExample().makeUI());
f.setSize(320, 240);
f.setLocationRelativeTo(null);
f.setVisible(true);
});
}
}
Kotlin
#spanend
#spanadd
#code(lang-kotlin){{
#spanend
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)
}
}
}
インターフェイス、匿名内部クラス、throws
-
addTreeWillExpandListener(object: TreeWillExpandListener() { ...
と記述するとerror: this class does not have a constructor
とエラーになる - インターフェイスから匿名内部クラスのインスタンスを生成する場合は
()
は不要で、addTreeWillExpandListener(object: TreeWillExpandListener { ...
と記述する
TableCellEditor
-
extends
,implements
-
IntelliJ
の自動変換では勝手にopen
は付けないようなので、以下のようなエラーが出る場合は、internal class CheckBoxesPanel : JPanel() {
をopen class CheckBoxesPanel : JPanel() {
に修正する#spanend #spanadd MainPanel.kt:81:37: error: this type is final, so it cannot be inherited from #spanend #spanadd internal class CheckBoxesRenderer : CheckBoxesPanel(), TableCellRenderer { #spanend #spanadd
-
-
IntelliJ
の自動変換ではstatic
変数がうまく変換できない?- 手動で
enum class
にして回避#spanend #spanadd companion object { #spanend protected val TITLES = arrayOf("r", "w", "x") #spanadd } #spanend #spanadd // ... #spanend #spanadd MainPanel.kt:45:5: error: property must be initialized or be abstract #spanend var buttons: Array<JCheckBox> ^ #spanadd MainPanel.kt:55:19: error: type mismatch: inferred type is Array<JCheckBox?> but Array<JCheckBox> was expected #spanend buttons = arrayOfNulls<JCheckBox>(TITLES.size) #spanadd
- 手動で
Java
import java.awt.*;
#spanadd
import java.awt.event.*;
#spanend
#spanadd
import java.util.*;
#spanend
import javax.swing.*;
import javax.swing.event.*;
#spandel
import javax.swing.plaf.IconUIResource;
#spanend
#spandel
import javax.swing.tree.*;
#spanend
#spanadd
import javax.swing.plaf.*;
#spanend
#spanadd
import javax.swing.table.*;
#spanend
#spandel
public class ThrowsExample {
#spanend
#spanadd
public final class MainPanel {
#spanend
public JComponent makeUI() {
JPanel p = new JPanel(new GridLayout(1, 0, 5, 5));
p.add(new JScrollPane(new JTree()));
p.add(new JScrollPane(makeTree()));
p.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
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);
#spanadd
#spanend
JPanel p = new JPanel(new BorderLayout());
p.add(new JScrollPane(table));
return p;
}
private static JTree makeTree() {
Icon icon = new EmptyIcon();
UIManager.put("Tree.expandedIcon", new IconUIResource(icon));
UIManager.put("Tree.collapsedIcon", new IconUIResource(icon));
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);
});
}
#spanadd
}
#spanend
#spanadd
class CheckBoxesPanel extends JPanel {
#spanend
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);
}
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
class CheckBoxesRenderer extends CheckBoxesPanel implements TableCellRenderer {
#spanend
@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;
}
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
class CheckBoxesEditor extends AbstractCellEditor implements TableCellEditor {
#spanend
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;
}
#spanadd
}
#spanend
#spanadd
Kotlin
#spanend
#spanadd
import java.awt.*
#spanend
#spanadd
import java.awt.event.*
#spanend
#spanadd
import java.util.*
#spanend
#spanadd
import javax.swing.*
#spanend
#spanadd
import javax.swing.event.*
#spanend
#spanadd
import javax.swing.plaf.*
#spanend
#spanadd
import javax.swing.table.*
#spanend
#spanadd
#spanend
#spanadd
fun makeUI(): JComponent {
#spanend
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))
}
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
fun main(args: Array<String>) {
#spanend
EventQueue.invokeLater {
JFrame("kotlin swing").apply {
defaultCloseOperation = JFrame.EXIT_ON_CLOSE
add(makeUI())
size = Dimension(320, 240)
setLocationRelativeTo(null)
setVisible(true)
}
}
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
open class CheckBoxesPanel() : JPanel() {
#spanend
public val buttons = arrayOf(JCheckBox(Permission.READ.toString()), JCheckBox(Permission.WRITE.toString()), JCheckBox(Permission.EXECUTE.toString()))
#spanadd
#spanend
override fun updateUI() {
super.updateUI()
setOpaque(false)
setBackground(Color(0x0, true))
setLayout(BoxLayout(this, BoxLayout.X_AXIS))
EventQueue.invokeLater({ initButtons() })
}
#spanadd
#spanend
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))
}
}
#spanadd
#spanend
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)
}
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
enum class Permission(var str: String) {
#spanend
READ("r"), WRITE("w"), EXECUTE("x");
override fun toString() = str
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
internal class CheckBoxesRenderer : CheckBoxesPanel(), TableCellRenderer {
#spanend
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
}
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
internal class CheckBoxesEditor : AbstractCellEditor(), TableCellEditor {
#spanend
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())
})
}
}
#spanadd
#spanend
override fun getTableCellEditorComponent(table: JTable, value: Any, isSelected: Boolean, row: Int, column: Int): Component {
panel.updateButtons(value)
return panel
}
#spanadd
#spanend
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
}
#spanadd
}
#spanend
#spanadd
ActionListener
-
IntelliJ
の自動変換ではActionListener
などの関数型インターフェースのラムダ式をうまく変換できない場合がある-
AbstractCellEditor#stopCellEditing()
はboolean
を返すので、error: type mismatch: inferred type is (???) -> Boolean but ActionListener? was expected
とエラーになる
-
- SAM Conversions - Calling Java from Kotlin - Kotlin Programming Language
Java
#spanend
#spanadd
protected ActionListener handler;
#spanend
#spanadd
// ...
#spanend
#spanadd
handler = e -> stopCellEditing();
#spanend
#spanadd
Kotlin
(自動変換)
#spanend
#spanadd
protected var handler: ActionListener? = null
#spanend
#spanadd
// ...
#spanend
#spanadd
handler = { e -> stopCellEditing() }
#spanend
#spanadd
// error: type mismatch: inferred type is (???) -> Boolean but ActionListener? was expected
#spanend
#spanadd
// handler = { e -> stopCellEditing() }
#spanend
#spanadd
// error: cannot infer a type for this parameter. Please specify it explicitly.
#spanend
#spanadd
// handler = { e -> stopCellEditing() }
#spanend
#spanadd
// error: modifier 'override' is not applicable to 'local function'
#spanend
#spanadd
// override fun actionPerformed(e: ActionEvent) : Unit {
#spanend
#spanadd
Kotlin
(手動修正)
#spanend
#spanadd
protected var handler: ActionListener? = null
#spanend
#spanadd
// ...
#spanend
#spanadd
handler = ActionListener { stopCellEditing() }
#spanend
#spanadd
// or:
#spanend
#spanadd
handler = object: ActionListener {
#spanend
override fun actionPerformed(e: ActionEvent) : Unit { // Unitは無くても可
stopCellEditing()
}
#spanadd
}
#spanend
#spanadd
-
IntelliJ
の自動変換で、以下のようなエラーになる場合は、Object
をAny
に手動変換するerror: class 'CheckBoxNodeRenderer' is not abstract and does not implement abstract member public abstract fun getTreeCellRendererComponent(p0: JTree!, p1: Any!, p2: Boolean, p3: Boolean, p4: Boolean, p5: Int, p6: Boolean): Component! defined in javax.swing.tree.TreeCellRenderer internal class CheckBoxNodeRenderer : TreeCellRenderer {
- getterのみオーバーライド
Java
#spanend
#spanadd
class CheckBoxNodeEditor extends AbstractCellEditor implements TreeCellEditor {
#spanend
@Override public Object getCellEditorValue() {
return new CheckBoxNode(checkBox.getText(), checkBox.isSelected());
}
// ...
#spanadd
Kotlin
(自動変換)
#spanend
#spanadd
val cellEditorValue: Any
#spanend
override get() = CheckBoxNode(checkBox.getText(), checkBox.isSelected())
#spanadd
//error: modifier 'override' is not applicable to 'getter'
#spanend
#spanadd
// override get() = CheckBoxNode(checkBox.getText(), checkBox.isSelected())
#spanend
#spanadd
Kotlin
(手動修正)
#spanend
override fun getCellEditorValue(): Any {
return CheckBoxNode(checkBox.getText(), checkBox.isSelected())
}
#spanadd
Java
#spanend
#spanadd
import java.awt.*;
#spanend
#spanadd
import java.awt.event.*;
#spanend
#spanadd
import java.util.*;
#spanend
#spanadd
import javax.swing.*;
#spanend
#spanadd
import javax.swing.event.*;
#spanend
#spanadd
import javax.swing.tree.*;
#spanend
#spanadd
#spanend
#spanadd
public class LeafCheckBoxTreeTest {
#spanend
private JComponent makeUI() {
JTree tree = new JTree();
tree.setEditable(true);
tree.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
int row = 0;
while (row < tree.getRowCount()) {
tree.expandRow(row++);
tree.setCellRenderer(new CheckBoxNodeRenderer());
tree.setCellEditor(new CheckBoxNodeEditor());
tree.setRowHeight(18);
#spanadd
#spanend
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;
}
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");
}
});
return tree;
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 ThrowsExample().makeUI());
f.getContentPane().add(new LeafCheckBoxTreeTest().makeUI());
f.setSize(320, 240);
f.setLocationRelativeTo(null);
f.setVisible(true);
});
}
}
#spandel
class EmptyIcon implements Icon {
#spanend
@Override public void paintIcon(Component c, Graphics g, int x, int y) {
/* Empty icon */
#spanadd
class CheckBoxNodeRenderer implements TreeCellRenderer {
#spanend
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);
}
@Override public int getIconWidth() {
return 0;
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
class CheckBoxNodeEditor extends AbstractCellEditor implements TreeCellEditor {
#spanend
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 int getIconHeight() {
return 0;
@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;
}
}
#spanadd
#spanend
#spanadd
class CheckBoxNode {
#spanend
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;
}
#spanadd
}
#spanend
Kotlin
#spanend
#spanadd
#code(lang-kotlin){{
#spanend
import java.awt.*
#spanadd
import java.awt.event.*
#spanend
#spanadd
import java.util.*
#spanend
import javax.swing.*
import javax.swing.event.*
#spandel
import javax.swing.plaf.IconUIResource
#spanend
import javax.swing.tree.*
#spandel
fun makeUI(): JComponent {
#spanend
val emptyIcon = EmptyIcon()
UIManager.put("Tree.expandedIcon", IconUIResource(emptyIcon))
UIManager.put("Tree.collapsedIcon", IconUIResource(emptyIcon))
#spanadd
private fun makeUI(): JComponent {
#spanend
val tree = JTree()
tree.setEditable(true)
tree.setCellRenderer(CheckBoxNodeRenderer())
tree.setCellEditor(CheckBoxNodeEditor())
tree.setRowHeight(18)
val tree = JTree().apply {
setEditable(true)
border = BorderFactory.createEmptyBorder(4, 4, 4, 4)
addTreeWillExpandListener(object: TreeWillExpandListener {
@Throws(ExpandVetoException::class)
override fun treeWillExpand(e: TreeExpansionEvent) {
//throw ExpandVetoException(e, "Tree expansion cancelled")
}
@Throws(ExpandVetoException::class)
override fun treeWillCollapse(e: TreeExpansionEvent) {
throw ExpandVetoException(e, "Tree collapse cancelled")
}
})
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
}
#spandel
#spanend
var row = 0;
while (row < tree.getRowCount()) {
tree.expandRow(row++);
}
#spandel
#spanend
return JPanel(GridLayout(1, 0)).apply {
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5))
add(JScrollPane(JTree()))
add(JScrollPane(tree))
}
return JScrollPane(tree)
}
#spandel
class EmptyIcon: Icon {
#spanend
override fun paintIcon(c: Component, g: Graphics , x: Int, y: Int) {
/* Empty icon */
}
override fun getIconWidth(): Int = 0
override fun getIconHeight(): Int = 0
#spandel
}
#spanend
#spandel
#spanend
fun main(args: Array<String>) {
EventQueue.invokeLater {
UIManager.put("swing.boldMetal", false)
JFrame("kotlin swing").apply {
defaultCloseOperation = WindowConstants.EXIT_ON_CLOSE
defaultCloseOperation = JFrame.EXIT_ON_CLOSE
add(makeUI())
size = Dimension(320, 240)
setLocationRelativeTo(null)
setVisible(true)
}
}
}
#spanadd
#spanend
#spanadd
internal class CheckBoxNodeRenderer : TreeCellRenderer {
#spanend
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)
}
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
internal class CheckBoxNodeEditor : AbstractCellEditor(), TreeCellEditor {
#spanend
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)
}
}
#spanadd
#spanend
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
}
#spanadd
#spanend
override fun getCellEditorValue(): Any {
return CheckBoxNode(checkBox.getText(), checkBox.isSelected())
}
#spanadd
#spanend
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
}
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
internal class CheckBoxNode public constructor(val text: String, val selected: Boolean) {
#spanend
override fun toString(): String {
return text
}
#spanadd
}
#spanend
Optional
-
Optional#orElse(...)
は、let {...} ?: ...
に変換-
Integer stepSize = Optional.ofNullable(stepSizeMap.get(calendarField)).orElse(1);
-
val stepSize = stepSizeMap.get(calendarField).let { it } ?: 1
-
Null safety and platform types
- 例えば
SpinnerDateModel#getNextValue(...)
メソッドをoverride fun getNextValue(): Object = Calendar.getInstance().apply { ...
のように変換すると、error: type mismatch: inferred type is Date! but Object was expected
とエラーになる override fun getNextValue(): Any = Calendar.getInstance().apply { ...
とAny
を使用するようオーバーライドする必要があるDate!
は、Date
もしくはDate?
の意味- Calling Java from Kotlin - Kotlin Programming Language
T! means "T or T?"
Java
import java.awt.*;
import java.text.*;
import java.util.*;
import javax.swing.*;
import javax.swing.text.*;
public class OptionalExample {
private JComponent makeUI() {
SimpleDateFormat format = new SimpleDateFormat(
"mm:ss, SSS", Locale.getDefault());
DefaultFormatterFactory factory = new DefaultFormatterFactory(
new DateFormatter(format));
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.clear(Calendar.MINUTE);
calendar.clear(Calendar.SECOND);
calendar.clear(Calendar.MILLISECOND);
Date d = calendar.getTime();
JSpinner sp1 = new JSpinner(new SpinnerDateModel(
d, null, null, Calendar.SECOND));
JSpinner.DefaultEditor ed1 = (JSpinner.DefaultEditor) sp1.getEditor();
ed1.getTextField().setFormatterFactory(factory);
HashMap<Integer, Integer> stepSizeMap = new HashMap<>();
stepSizeMap.put(Calendar.HOUR_OF_DAY, 1);
stepSizeMap.put(Calendar.MINUTE, 1);
stepSizeMap.put(Calendar.SECOND, 30);
stepSizeMap.put(Calendar.MILLISECOND, 500);
JSpinner sp2 = new JSpinner(new SpinnerDateModel(
d, null, null, Calendar.SECOND) {
@Override public Object getPreviousValue() {
Calendar cal = Calendar.getInstance();
cal.setTime(getDate());
Integer calendarField = getCalendarField();
Integer stepSize = Optional.ofNullable(
stepSizeMap.get(calendarField)).orElse(1);
cal.add(calendarField, -stepSize);
return cal.getTime();
}
@Override public Object getNextValue() {
Calendar cal = Calendar.getInstance();
cal.setTime(getDate());
Integer calendarField = getCalendarField();
Integer stepSize = Optional.ofNullable(
stepSizeMap.get(calendarField)).orElse(1);
cal.add(calendarField, stepSize);
return cal.getTime();
}
});
JSpinner.DefaultEditor ed2 = (JSpinner.DefaultEditor) sp2.getEditor();
ed2.getTextField().setFormatterFactory(factory);
JPanel p = new JPanel();
p.add(sp1);
p.add(sp2);
return p;
}
public static void main(String... args) {
EventQueue.invokeLater(() -> {
JFrame f = new JFrame();
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.getContentPane().add(new OptionalExample().makeUI());
f.setSize(320, 240);
f.setLocationRelativeTo(null);
f.setVisible(true);
});
}
}
Kotlin
#spanend
#spanadd
#code(lang-kotlin){{
#spanend
import java.awt.*
import java.text.*
import java.util.*
import javax.swing.*
import javax.swing.text.*
fun makeUI(): JComponent {
val format = SimpleDateFormat("mm:ss, SSS", Locale.getDefault())
val factory = DefaultFormatterFactory(DateFormatter(format))
val calendar = Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, 0)
clear(Calendar.MINUTE)
clear(Calendar.SECOND)
clear(Calendar.MILLISECOND)
}
val d = calendar.getTime()
val sp1 = JSpinner(SpinnerDateModel(d, null, null, Calendar.SECOND))
val ed1 = sp1.getEditor()
if (ed1 is JSpinner.DefaultEditor) {
ed1.getTextField().setFormatterFactory(factory)
}
val stepSizeMap: HashMap<Int, Int> = hashMapOf(
Calendar.HOUR_OF_DAY to 1,
Calendar.MINUTE to 1,
Calendar.SECOND to 30,
Calendar.MILLISECOND to 500)
val sp2 = JSpinner(object: SpinnerDateModel(d, null, null, Calendar.SECOND) {
override fun getPreviousValue(): Any = Calendar.getInstance().apply {
time = getDate()
val stepSize = stepSizeMap.get(calendarField).let { it } ?: 1
add(calendarField, -stepSize)
}.getTime()
override fun getNextValue(): Any = Calendar.getInstance().apply {
setTime(getDate())
val stepSize = stepSizeMap.get(calendarField).let { it } ?: 1
add(calendarField, stepSize)
}.getTime()
})
val ed2 = sp2.getEditor()
if (ed2 is JSpinner.DefaultEditor) {
ed2.getTextField().setFormatterFactory(factory)
}
return JPanel().apply {
add(sp1)
add(sp2)
}
}
fun main(args: Array<String>) {
EventQueue.invokeLater {
JFrame("kotlin swing").apply {
defaultCloseOperation = WindowConstants.EXIT_ON_CLOSE
add(makeUI())
size = Dimension(320, 240)
setLocationRelativeTo(null)
setVisible(true)
}
}
}
SwingWorker
- Replacing SwingWorker with Kotlin coroutines ・ Pushing Pixelsを参考に、
SwingWorker
をkotlin-coroutines
に置き換えるテストをしているが、unresolved reference
になるkotlin 1.3.0
、kotlinx.coroutines 1.0.0
で動作確認 - JavaのSwingWorkerをKotlinのCoroutinesに置き換えるに移動
JComponent#updateUI()
-
Swing
コンポーネントのUI
プロパティを現在のLookAndFeel
で設定されている値にリセットするupdateUI()
メソッドは、コンストラクタから呼び出されるのでインスタンス変数が初期化される前に実行されることがある - このため、
Kotlin
のコードに変換したupdateUI()
メソッド内でnull
チェックを行うと不要なnull
チェックをしているとか、!= null
が常にtrue
であると警告されるが、これに従ってnull
チェックを外すとNullPointerException
が発生する -
Optional
やObjects.nonNull(...)
で、警告をごまかす方法もある
Java
#spanend
#spanadd
public final class MainPanel extends JPanel {
#spanend
private final JSpinner spinner = new JSpinner();
@Override public void updateUI() {
super.updateUI();
Long lv = Optional.ofNullable(spinner)
.map(s -> (Long) s.getModel().getValue())
.orElse(1000L);
// Long lv = spinner != null ? (Long) spinner.getModel().getValue() : 1000L;
// ...
}
#spanadd
Kotlin
#spanend
#spanadd
class MainPanel : JPanel(BorderLayout()) {
#spanend
private val spinner = JSpinner()
private val spinner: JSpinner? = JSpinner()
override fun updateUI() {
super.updateUI()
val lv = Optional.ofNullable(spinner)
.map { it.getModel().getValue() }
.orElse(1000L)
#spanadd
#spanend
// Unnecessary safe call on a non-null receiver of type JSpinner
// val lv = spinner
// ?.let { it.getModel().getValue() }
// ?: 1000L
#spanadd
#spanend
// NullPointerException
// val lv = spinner.getModel().getValue()
#spanadd
#spanend
// Condition 'spinner != null' is always 'true'
// val lv = if (spinner != null) spinner.getModel().getValue() else 1000L
#spanadd
#spanend
// private val spinner: JSpinner? = JSpinner() なら(他の個所もspinner?にすれば)以下でOK
// val lv = spinner?.getModel()?.getValue() ?: 1000L
}
#spanadd
JComponent#createUI(...)
-
Kotlin 1.3.21
では@JvmStatic
を付けてstatic
なメソッドを上書き不可 - "Accidental override" reported when a @JvmStatic method in a Kotlin class has the same signature as a static method in a Java base class : KT-12993
- たとえば
BasicComboBoxUI
を継承する独自UI
を作成するために、static
なcreateUI(...)
メソッドを上書きする必要があるが、@JvmStatic fun createUI(c: JComponent): ComponentUI {
は、以下のようにエラーになる
#spanend
#spanadd
e: C:\kst\SearchBarLayoutComboBox\src\main\kotlin\example\BasicSearchBarComboBoxUI.kt: (189, 5): Accidental override: The following declarations have the same JVM signature (createUI(Ljavax/swing/JComponent;)Ljavax/swing/plaf/ComponentUI;):
#spanend
fun createUI(p0: JComponent!): ComponentUI! defined in javax.swing.plaf.basic.BasicComboBoxUI
fun createUI(c: JComponent?): ComponentUI defined in example.BasicSearchBarComboBoxUI
#spanadd
- 現状では
createUI(...)
を使用せずupdateUI()
をオーバーライドして直接独自UI
を設定するなどで回避するしかない?
DefaultTreeCellEditor
-
JTree.startEditingAtPath(...)
メソッドを実行すると以下のようなIllegalArgumentException
が発生する場合がある- このメソッドを辿ると
BasicTreeUI
内でイベントをnull
でTreeCellEditor#isCellEditable(event)
を呼び出している箇所がある - その為、
DefaultTreeCellEditor#isCellEditable(e: EventObject)
のオーバーライドはDefaultTreeCellEditor#isCellEditable(e: EventObject?)
に変更する必要がある
- このメソッドを辿ると
#spanend
#spanadd
Exception in thread "AWT-EventQueue-0" java.lang.IllegalArgumentException: Parameter specified as non-null is null: method example.MainPanel$1.isCellEditable, parameter e
#spanend
#spanadd
at example.MainPanel$1.isCellEditable(App.kt)
#spanend
#spanadd
at javax.swing.plaf.basic.BasicTreeUI.startEditing(BasicTreeUI.java:2129)
#spanend
#spanadd
at javax.swing.plaf.basic.BasicTreeUI.startEditingAtPath(BasicTreeUI.java:620)
#spanend
#spanadd
at javax.swing.JTree.startEditingAtPath(JTree.java:2405)
#spanend
#spanadd
at example.TreePopupMenu$1.actionPerformed(App.kt:49)
#spanend
#spanadd
Kotlin
自動変換
#spanend
#spanadd
import java.awt.* // ktlint-disable no-wildcard-imports
#spanend
#spanadd
import java.awt.event.MouseEvent
#spanend
#spanadd
import java.util.EventObject
#spanend
#spanadd
import javax.swing.* // ktlint-disable no-wildcard-imports
#spanend
#spanadd
import javax.swing.event.AncestorEvent
#spanend
#spanadd
import javax.swing.event.AncestorListener
#spanend
#spanadd
import javax.swing.tree.DefaultMutableTreeNode
#spanend
#spanadd
import javax.swing.tree.DefaultTreeCellEditor
#spanend
#spanadd
import javax.swing.tree.DefaultTreeCellRenderer
#spanend
#spanadd
import javax.swing.tree.TreePath
#spanend
#spanadd
#spanend
#spanadd
class MainPanel : JPanel(BorderLayout()) {
#spanend
init {
val tree = JTree()
tree.cellEditor = object : DefaultTreeCellEditor(tree, tree.cellRenderer as? DefaultTreeCellRenderer) {
override fun isCellEditable(e: EventObject) = e !is MouseEvent && super.isCellEditable(e)
}
tree.isEditable = true
tree.componentPopupMenu = TreePopupMenu()
add(JScrollPane(tree))
preferredSize = Dimension(320, 240)
}
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
class TreePopupMenu : JPopupMenu() {
#spanend
private var path: TreePath? = null
private val editItem: JMenuItem
private val editDialogItem: JMenuItem
#spanadd
#spanend
override fun show(c: Component, x: Int, y: Int) {
(c as? JTree)?.also { tree ->
val tsp = tree.selectionPaths
path = tree.getPathForLocation(x, y)
val isEditable = tsp != null && tsp.size == 1 && tsp[0] == path
editItem.isEnabled = isEditable
editDialogItem.isEnabled = isEditable
super.show(c, x, y)
}
}
#spanadd
#spanend
init {
val field = JTextField()
field.addAncestorListener(FocusAncestorListener())
editItem = add("Edit")
editItem.addActionListener {
path?.also {
(invoker as? JTree)?.startEditingAtPath(it)
}
}
editDialogItem = add("Edit Dialog")
editDialogItem.addActionListener {
(path?.lastPathComponent as? DefaultMutableTreeNode)?.also { node ->
field.text = node.userObject.toString()
(invoker as? JTree)?.also { tree ->
val ret = JOptionPane.showConfirmDialog(
tree, field, "Rename", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE
)
if (ret == JOptionPane.OK_OPTION) {
tree.model.valueForPathChanged(path, field.text)
}
}
}
}
add("dummy")
}
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
class FocusAncestorListener : AncestorListener {
#spanend
override fun ancestorAdded(e: AncestorEvent) {
e.component.requestFocusInWindow()
}
#spanadd
#spanend
override fun ancestorMoved(e: AncestorEvent) {
/* not needed */
}
#spanadd
#spanend
override fun ancestorRemoved(e: AncestorEvent) {
/* not needed */
}
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
fun main() {
#spanend
EventQueue.invokeLater {
runCatching {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())
}.onFailure {
it.printStackTrace()
Toolkit.getDefaultToolkit().beep()
}
JFrame().apply {
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
getContentPane().add(MainPanel())
pack()
setLocationRelativeTo(null)
setVisible(true)
}
}
#spanadd
}
#spanend
#spanadd
Kotlin
手動修正
#spanend
#spanadd
import java.awt.* // ktlint-disable no-wildcard-imports
#spanend
#spanadd
import java.awt.event.MouseEvent
#spanend
#spanadd
import java.util.EventObject
#spanend
#spanadd
import javax.swing.* // ktlint-disable no-wildcard-imports
#spanend
#spanadd
import javax.swing.event.AncestorEvent
#spanend
#spanadd
import javax.swing.event.AncestorListener
#spanend
#spanadd
import javax.swing.tree.DefaultMutableTreeNode
#spanend
#spanadd
import javax.swing.tree.DefaultTreeCellEditor
#spanend
#spanadd
import javax.swing.tree.DefaultTreeCellRenderer
#spanend
#spanadd
import javax.swing.tree.TreePath
#spanend
#spanadd
#spanend
#spanadd
class MainPanel : JPanel(BorderLayout()) {
#spanend
init {
val tree = JTree()
tree.cellEditor = object : DefaultTreeCellEditor(tree, tree.cellRenderer as? DefaultTreeCellRenderer) {
override fun isCellEditable(e: EventObject) = e !is MouseEvent && super.isCellEditable(e)
}
tree.isEditable = true
tree.componentPopupMenu = TreePopupMenu()
add(JScrollPane(tree))
preferredSize = Dimension(320, 240)
}
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
class TreePopupMenu : JPopupMenu() {
#spanend
private var path: TreePath? = null
private val editItem: JMenuItem
private val editDialogItem: JMenuItem
#spanadd
#spanend
override fun show(c: Component, x: Int, y: Int) {
(c as? JTree)?.also { tree ->
val tsp = tree.selectionPaths
path = tree.getPathForLocation(x, y)
val isEditable = tsp != null && tsp.size == 1 && tsp[0] == path
editItem.isEnabled = isEditable
editDialogItem.isEnabled = isEditable
super.show(c, x, y)
}
}
#spanadd
#spanend
init {
val field = JTextField()
field.addAncestorListener(FocusAncestorListener())
editItem = add("Edit")
editItem.addActionListener {
path?.also {
(invoker as? JTree)?.startEditingAtPath(it)
}
}
editDialogItem = add("Edit Dialog")
editDialogItem.addActionListener {
(path?.lastPathComponent as? DefaultMutableTreeNode)?.also { node ->
field.text = node.userObject.toString()
(invoker as? JTree)?.also { tree ->
val ret = JOptionPane.showConfirmDialog(
tree, field, "Rename", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE
)
if (ret == JOptionPane.OK_OPTION) {
tree.model.valueForPathChanged(path, field.text)
}
}
}
}
add("JMenuItem")
}
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
class FocusAncestorListener : AncestorListener {
#spanend
override fun ancestorAdded(e: AncestorEvent) {
e.component.requestFocusInWindow()
}
#spanadd
#spanend
override fun ancestorMoved(e: AncestorEvent) {
/* not needed */
}
#spanadd
#spanend
override fun ancestorRemoved(e: AncestorEvent) {
/* not needed */
}
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
fun main() {
#spanend
EventQueue.invokeLater {
runCatching {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())
}.onFailure {
it.printStackTrace()
Toolkit.getDefaultToolkit().beep()
}
JFrame().apply {
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
getContentPane().add(MainPanel())
pack()
setLocationRelativeTo(null)
setVisible(true)
}
}
#spanadd
}
#spanend
#spanadd
手動変換
-
IntelliJ
の自動変換で正常に変換されるが、手動修正でもっと短いKotlin
コードに変換が可能なケースのメモ-
java.util.*
のクラスを使用している箇所は変換可能な場合が多い
-
IntStream
-
java.util.stream.IntStream
はIntRange
に置換可能
#spanend
#spanadd
val indices = IntStream.range(0, l.getModel().getSize())
#spanend
.filter { rb.intersects(l.getCellBounds(it, it)) }.toArray()
#spanadd
#spanend
#spanadd
val indices = (0 until l.getModel().getSize())
#spanend
.filter { rb.intersects(l.getCellBounds(it, it)) }.toIntArray()
#spanadd
Random
-
java.util.Random
は1.3
からIntRange.ramdom()
などに置換可能
#spanend
#spanadd
val lengthOfTask = 10 + Random().nextInt(50)
#spanend
#spanadd
#spanend
#spanadd
val lengthOfTask = (10..60).random()
#spanend
#spanadd
List#get(0)
-
java.util.List#.get(0)
はList#firstOrNull()
などに置換可能
#spanend
#spanadd
var flag = 1
#spanend
#spanadd
val keys = table.getRowSorter().getSortKeys()
#spanend
#spanadd
if (!keys.isEmpty()) {
#spanend
val sortKey = keys.get(0)
if (sortKey.getColumn() == column && sortKey.getSortOrder() == SortOrder.DESCENDING) {
flag = -1
}
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
val flag = table.getRowSorter().getSortKeys().firstOrNull()
#spanend
?.takeIf { it.getColumn() == column && it.getSortOrder() == SortOrder.DESCENDING }
?.let { -1 } ?: 1
#spanadd
配列の最終要素
- 配列の最終要素の取得は、
Array#last()
などに置換可能
#spanend
#spanadd
p.setComponentZOrder(p.getComponent(p.getComponentCount() - 1), 0)
#spanend
#spanadd
#spanend
#spanadd
p.setComponentZOrder(p.getComponents().last(), 0)
#spanend
#spanadd
Optional
Optional.ofNullable(...)
-
Optional.ofNullable(...).filter(...)
は、?.takeIf {...}
などに置換可能
#spanend
#spanadd
val clz = JTable::class.java
#spanend
#spanadd
Optional.ofNullable(SwingUtilities.getAncestorOfClass(clz, e.getComponent()))
#spanend
.filter(clz::isInstance)
.map(clz::cast)
.filter(JTable::isEditing)
.ifPresent(JTable::removeEditor)
#spanadd
#spanend
#spanadd
SwingUtilities.getAncestorOfClass(JTable::class.java, e.getComponent())
#spanend
?.takeIf { it is JTable }
?.let { it as JTable }
?.takeIf { it.isEditing() }
?.also{ it.removeEditor() }
#spanadd
#spanend
#spanadd
// takeIf {...}内のitにはスマートキャストが効くが、それを越えて作用はしない
#spanend
#spanadd
SwingUtilities.getAncestorOfClass(JTable::class.java, e.getComponent())
#spanend
?.takeIf { it is JTable && it.isEditing() }
?.also { (it as JTable).removeEditor() }
// ?.also { JTable::removeEditor } // コンパイルエラーにはならないが、removeEditor()メソッドは実行されない?
#spanadd
Optional#orElse(...)
-
Optional#orElse(...)
はエルビス演算子?:
に置換可能
#spanend
#spanadd
val lv = Optional.ofNullable(spinner).map { it.getModel().getValue() }.orElse(1000L)
#spanend
#spanadd
#spanend
#spanadd
val lv = spinner?.getModel()?.getValue() ?: 1000L
#spanend
#spanadd
Objects
Objects.nonNull(...)
-
Objects.nonNull(o)
などは、セーフコール演算子?.
に置換可能
#spanend
#spanadd
if (Objects.nonNull(colHead) && colHead.isVisible()) {
#spanend
val colHeadHeight = Math.min(availR.height, colHead.getPreferredSize().height)
// ...
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
colHead?.takeIf { it.isVisible() }?.let {
#spanend
val colHeadHeight = Math.min(availR.height, it.getPreferredSize().height)
// ...
#spanadd
}
#spanend
#spanadd
Objects.requireNonNull(...)
-
Objects.requireNonNull(o)
はrequireNotNullに置換可能 -
Non
ではなくNot
になることに注意
#spanend
#spanadd
Objects.requireNonNull(url)
#spanend
#spanadd
#spanend
#spanadd
requireNotNull(url)
#spanend
#spanadd
Stream
-
Stream
は、List
(Iterable
)に置換可能な場合が多い - 配列も
Iterable
なのでStream
にする必要はあまりない
#spanend
#spanadd
fun stream(parent: Container): Stream<Component> = Arrays.stream(parent.getComponents())
#spanend
.filter(Container::class.java::isInstance)
.map { c -> stream(Container::class.java.cast(c)) }
.reduce(Stream.of<Component>(parent), { a, b -> Stream.concat<Component>(a, b) })
#spanadd
#spanend
#spanadd
fun descendants(parent: Container): List<Component> = parent.getComponents()
#spanend
.filterIsInstance(Container::class.java)
.map { descendants(it) }
.fold(listOf<Component>(parent), { a, b -> a + b })
#spanadd
Stream.flatMap(...)
-
Stream#flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
はそのままIterable<T>.flatMap(transform: (T) -> Iterable<R>)
に置換可能
#spanend
#spanadd
// // Java
#spanend
#spanadd
// private Stream<MenuElement> descendants(MenuElement me) {
#spanend
#spanadd
// return Stream.of(me.getSubElements())
#spanend
#spanadd
// .flatMap(m -> Stream.concat(Stream.of(m), descendants(m)));
#spanend
#spanadd
// }
#spanend
#spanadd
fun descendants(me: MenuElement): Stream<MenuElement> {
#spanend
return Stream.of(*me.subElements)
.flatMap { Stream.concat(Stream.of(it), descendants(it)) }
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
// StreamではなくIterableが使用可能
#spanend
#spanadd
private fun descendants(me: MenuElement): List<MenuElement> =
#spanend
me.getSubElements().flatMap { listOf(it) + descendants(it) }
#spanadd
Stream.reduce(...)
- 初期値有りの
Stream#reduce(T identity, BinaryOperator<T> accumulator)
は、Iterable<T>.fold(initial: R, operation: (acc: R, T) -> R)
に置換可能
#spanend
#spanadd
// // Java
#spanend
#spanadd
// int max = group.stream()
#spanend
#spanadd
// .map(AlignedLabel::getSuperPreferredWidth)
#spanend
#spanadd
// .reduce(0, Integer::max);
#spanend
#spanadd
val max = group.stream()
#spanend
.map(Function<AlignedLabel, Int> { it.getSuperPreferredWidth() })
.reduce(0, BinaryOperator<Int> { a, b -> Integer.max(a, b) })
#spanadd
#spanend
#spanadd
val max = group.stream()
#spanend
.map { it.getSuperPreferredWidth() }
.reduce(0, Integer::max)
#spanadd
#spanend
#spanadd
val max = group
#spanend
.map { it.getSuperPreferredWidth() }
.fold(0) { a, b -> maxOf(a, b) }
#spanadd
#spanend
#spanadd
val max = group.map { it.getSuperPreferredWidth() }.fold(0, ::maxOf)
#spanend
#spanadd
#spanend
#spanadd
// この例だとIntなのでmax()とエルビス演算子`?:`が使用可能
#spanend
#spanadd
val max = group.map { it.getSuperPreferredWidth() }.max() ?: 0
#spanend
#spanadd
Collections.nCopies(...).joinToString(...)
-
CharSequence#repeat(n)
でn
回繰り返した文字列を生成可能
#spanend
#spanadd
val text = Collections.nCopies(2000, "aaaaaaaaaaaaa").joinToString("\n")
#spanend
#spanadd
#spanend
#spanadd
val text = "aaaaaaaaaaaaa\n".repeat(2000)
#spanend
#spanadd
joinToString(...)
#spanend
#spanadd
String msg = jlist.getSelectedValuesList().stream()
#spanend
.map(it -> it.title)
.collect(Collectors.joining(", "));
#spanadd
#spanend
#spanadd
val msg = jlist.selectedValuesList
#spanend
.map { it.title }
.joinToString(", ")
#spanadd
maxOf(...)
#spanend
#spanadd
// private List<AlignedLabel> group;
#spanend
#spanadd
int max = group.stream()
#spanend
.map(AlignedLabel::getSuperPreferredWidth)
.reduce(0, Integer::max);
#spanadd
#spanend
#spanadd
// private var group = mutableListOf<AlignedLabel>()
#spanend
#spanadd
// val max = group.map { it.getSuperPreferredWidth() }.fold(0, ::maxOf)
#spanend
#spanadd
// val max = group.map { it.getSuperPreferredWidth() }.maxOrNull() ?: 0
#spanend
#spanadd
// group.isNotEmpty()
#spanend
#spanadd
val max = group.maxOf(AlignedLabel::getSuperPreferredWidth)
#spanend
#spanadd
複数行文字列
- 複数行文字列は三重引用符
"""
を使って生成可能
#spanend
#spanadd
// private String makeTestHtml() {
#spanend
#spanadd
// return String.join("\n", strarray);
#spanend
#spanadd
// }
#spanend
#spanadd
// を自動変換すると以下のようなコードになる
#spanend
#spanadd
private fun makeTestHtml(): String {
#spanend
return arrayOf(
"<html><body>",
"<div>2222222222</div>",
"</body></html>").joinToString("\n")
#spanadd
}
#spanend
#spanadd
#spanend
#spanadd
// が、三重引用符で囲めば改行などをエスケープシーケンスで記入する必要はない
#spanend
#spanadd
private fun makeTestHtml() = """
#spanend
<html>
<body>
<div>2222222222</div>
</body>
</html>
#spanadd
"""
#spanend
#spanadd
Comparator.comparing(...).thenComparing(...)
- 複数キーでソートする
Comparator<T>
は、Java
ではComparator.comparing(Function<? super T,? extends U> keyExtractor).thenComparing(Function<? super T,? extends U> keyExtractor)
で作成可能 -
Kotlin
ではcompareBy(vararg selectors: (T) -> Comparable<*>?)
やComparator<T>.thenBy(...)
で同等のComparator<T>
が作成可能
#spanend
#spanadd
// val tnc = Comparator.comparing(Function<DefaultMutableTreeNode, Boolean> { it.isLeaf() })
#spanend
#spanadd
val tnc = Comparator.comparing<DefaultMutableTreeNode, Boolean> { it.isLeaf() }
#spanend
.thenComparing { n -> n.getUserObject().toString() }
#spanadd
val children = parent.children().toList()
#spanend
.filterIsInstance(DefaultMutableTreeNode::class.java)
.sortedWith(tnc)
#spanadd
#spanend
#spanadd
val tnc = compareBy<DefaultMutableTreeNode> { it.isLeaf() }
#spanend
.thenBy { it.getUserObject().toString() }
#spanadd
val children = parent.children().toList()
#spanend
.filterIsInstance(DefaultMutableTreeNode::class.java)
.sortedWith(tnc)
// .sortedWith(compareBy(DefaultMutableTreeNode::isLeaf, { it.getUserObject().toString() }))
#spanadd
filterIsInstance
#spanend
#spanadd
// Only classes are allowed on the left hand side of a class literal
#spanend
#spanadd
// とエラーになる
#spanend
#spanadd
listOf(c0, c1, c2, c3)
#spanend
.mapNotNull { it.getModel() }
.filterIsInstance(MutableComboBoxModel<String>::class.java)
.forEach { it.insertElementAt(str, it.getSize()) }
#spanadd
#spanend
#spanadd
// Type mismatch. でエラーになる
#spanend
#spanadd
listOf(c0, c1, c2, c3)
#spanend
.mapNotNull { it.getModel() }
.filterIsInstance(MutableComboBoxModel::class.java)
.forEach { it.insertElementAt(str, it.getSize()) }
#spanadd
#spanend
#spanadd
// reified type parameter を使用する
#spanend
#spanadd
listOf(c0, c1, c2, c3)
#spanend
.mapNotNull { it.getModel() }
.filterIsInstance<MutableComboBoxModel<String>>()
.forEach { it.insertElementAt(str, it.getSize()) }
#spanadd
#spanend
#spanadd
listOf(c0, c1, c2, c3)
#spanend
.mapNotNull { it.getModel() as? MutableComboBoxModel<String> }
.forEach { it.insertElementAt(str, it.getSize()) }
#spanadd
Functional (SAM) interfaces
-
kotlin 1.4.0
からKotlin
インターフェイスでもSAM
(Single Abstract Method
)変換を利用可能になったが、まだIntelliJ
の自動変換ではFunctional interfaces
ではなく普通のinterfaces
に変換されるのでこれを使用する場合は手動で置換する必要がある
#spanend
#spanadd
interface ExpansionListener : EventListener {
#spanend
fun expansionStateChanged(e: ExpansionEvent)
}
#spanadd
}
#spanend
#spanadd
// ...
#spanend
#spanadd
p.addExpansionListener(object : ExpansionListener {
#spanend
override fun expansionStateChanged(e: ExpansionEvent) {
(e.source as? Component)?.also {
// ...
}
}
#spanadd
})
#spanend
#spanadd
#spanend
#spanadd
fun interface ExpansionListener : EventListener {
#spanend
fun expansionStateChanged(e: ExpansionEvent)
}
#spanadd
}
#spanend
#spanadd
// ...
#spanend
#spanadd
p.addExpansionListener { e ->
#spanend
(e.source as? Component)?.also {
// ...
}
#spanadd
}
#spanend
#spanadd