Kotlin のバックアップの現在との差分(No.3)
- keywords: [Kotlin, Swing, Java]
description: KotlinでのSwingの使用方法に関するメモなど
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
を使用する場合のサンプルの一覧です。Kotlin
でSwing
コンポーネントを使用するサンプルと、Java
からの変換に関するメモです。
実行環境
- SDKMAN! the Software Development Kit Managerで
kotlin
のインストールが可能
$ 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>
-
Object#getClass()
はjavaClass
- オーバーライドの方法、
@Override
はoverride
- 二次元配列
-
apply
-
-
Kotlin
-
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
#spanadd
import java.awt.*;
#spanend
#spanadd
import javax.swing.*;
#spanend
#spanadd
import javax.swing.table.*;
#spanend
#spanadd
#spanend
#spanadd
public class JTableExample {
#spanend
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);
#spanadd
#spanend
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);
});
}
#spanadd
}
#spanend
#spanadd
Kotlin
#spanend
import java.awt.*
import javax.swing.*
import javax.swing.table.*
fun makeUI(): JComponent {
val columnNames = arrayOf("String", "Integer", "Boolean")
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 model = object : DefaultTableModel(data, columnNames) {
override fun getColumnClass(column : Int) : Class<Any> {
return getValueAt(0, column).javaClass
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 table = JTable(model).apply {
// 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 {
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5))
add(JScrollPane(table))
}
}
#spandel
fun main(args : Array<String>) {
#spanend
#spanadd
fun main(args: Array<String>) {
#spanend
EventQueue.invokeLater {
JFrame("kotlin swing").apply {
defaultCloseOperation = WindowConstants.EXIT_ON_CLOSE
defaultCloseOperation = JFrame.EXIT_ON_CLOSE
add(makeUI())
size = Dimension(320, 240)
setLocationRelativeTo(null)
setVisible(true)
}
}
}
-
Java
JTree
Java
import java.awt.*;
#spanadd
import java.util.*;
#spanend
import javax.swing.*;
#spandel
import javax.swing.table.*;
#spanend
#spandel
public class JTableExample {
#spanend
#spanadd
public class JTreeExample {
#spanend
public JComponent makeUI() {
String[] columnNames = {"String", "Integer", "Boolean"};
Object[][] data = {
{"aaa", 12, true}, {"bbb", 5, false},
{"CCC", 92, true}, {"DDD", 0, false}
};
TableModel model = new DefaultTableModel(data, columnNames) {
@Override public Class<?> getColumnClass(int column) {
return getValueAt(0, column).getClass();
}
};
JTable table = new JTable(model);
table.setAutoCreateRowSorter(true);
JTree tree = new JTree();
JPanel p = new JPanel(new BorderLayout());
p.add(new JScrollPane(table));
JButton b1 = new JButton("expand");
b1.addActionListener(e -> expandAll(tree));
#spanadd
#spanend
JButton b2 = new JButton("collapse");
b2.addActionListener(e -> collapseAll(tree));
#spanadd
#spanend
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;
}
public static void main(String... args) {
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(WindowConstants.EXIT_ON_CLOSE);
f.getContentPane().add(new JTableExample().makeUI());
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new JTreeExample().makeUI());
f.setSize(320, 240);
f.setLocationRelativeTo(null);
f.setVisible(true);
});
}
}
JTree
-
Kotlin
#spanend #spanadd #twocolumn #spanend #spanadd `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) } addActionListener { expandAll(tree) } }) add(JButton("collapse").apply { addActionListener { collapseAll(tree) } addActionListener { collapseAll(tree) } }) } return JPanel(BorderLayout(5, 5)).apply { setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)) add(JScrollPane(tree)) add(p, BorderLayout.SOUTH) } } #spandel #spanend #spandel fun expandAll(tree : JTree) { #spanend #spanadd fun expandAll(tree: JTree) { #spanend var row = 0 while (row < tree.getRowCount()) { tree.expandRow(row) row++ } } #spandel #spanend fun collapseAll(tree : JTree) { var row = tree.getRowCount() - 1 while (row >= 0) { tree.collapseRow(row) row-- } } #spandel #spanend #spandel fun main(args : Array<String>) { #spanend #spanadd fun main(args: Array<String>) { #spanend EventQueue.invokeLater { JFrame("kotlin swing").apply { defaultCloseOperation = WindowConstants.EXIT_ON_CLOSE defaultCloseOperation = JFrame.EXIT_ON_CLOSE add(makeUI()) size = Dimension(320, 240) setLocationRelativeTo(null) setVisible(true) } } }
-
Java
JCheckBox
-
Smart Cast
、キャストでif
のネストが深くなるのを避けたい- イベント発生元が
AbstractButton
なのは自明なのでas
を使用する-
val b = e.getSource() as AbstractButton
-
- 以下のような場合は、
apply
でも回避可能
- イベント発生元が
Java
import java.awt.*;
#spandel
import java.util.*;
#spanend
import javax.swing.*;
#spandel
public class JTreeExample {
#spanend
#spanadd
public class JCheckBoxExample {
#spanend
public JComponent makeUI() {
JTree tree = new JTree();
JButton button1 = new JButton("expand A");
button1.addActionListener(e -> expandAll(tree));
JButton button2 = new JButton("collapse A");
button2.addActionListener(e -> collapseAll(tree));
JPanel p = new JPanel(new GridLayout(0, 1, 2, 2));
Arrays.asList(button1, button2).forEach(p::add);
JPanel panel = new JPanel(new BorderLayout());
panel.add(new JScrollPane(tree));
panel.add(p, BorderLayout.SOUTH);
return panel;
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;
}
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(WindowConstants.EXIT_ON_CLOSE);
f.getContentPane().add(new JTreeExample().makeUI());
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new JCheckBoxExample().makeUI());
f.setSize(320, 240);
f.setLocationRelativeTo(null);
f.setVisible(true);
});
}
}
JCheckBox
- メモ
-
Smart Cast
、キャストでif
のネストが深くなるのを避けたいが...
-
-
Kotlin
#spanend #spanadd #twocolumn #spanend #spanadd `Kotlin` #spanend #spanadd #code(lang-kotlin){{ #spanend import java.awt.* import javax.swing.* fun makeUI(): JComponent { val checkbox = JCheckBox("Always On Top", true).apply { addActionListener { val w = getTopLevelAncestor() if (w is Window) { w.setAlwaysOnTop(isSelected()) } } 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()) } } #spanadd // addActionListener { #spanend #spanadd // val w = getTopLevelAncestor() #spanend #spanadd // if (w is Window) { #spanend #spanadd // w.setAlwaysOnTop(isSelected()) #spanend #spanadd // } #spanend #spanadd // } #spanend } return JPanel(BorderLayout()).apply { add(checkbox, BorderLayout.NORTH) add(cb, BorderLayout.NORTH) } } #spandel fun main(args : Array<String>) { #spanend #spanadd fun main(args: Array<String>) { #spanend EventQueue.invokeLater { JFrame("kotlin swing").apply { defaultCloseOperation = WindowConstants.EXIT_ON_CLOSE defaultCloseOperation = JFrame.EXIT_ON_CLOSE add(makeUI()) size = Dimension(320, 240) setLocationRelativeTo(null) setVisible(true) } } }
-
Java
TableCellEditor
-
extends
,implements
-
IntelliJ
の自動変換では勝手にopen
は付けないようなので、以下のようなエラーが出る場合は、internal class CheckBoxesPanel : JPanel() {
をopen class CheckBoxesPanel : JPanel() {
に修正する#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
#spanend import java.awt.*; #spanadd import java.awt.event.*; #spanend #spanadd import java.util.*; #spanend import javax.swing.*; #spanadd import javax.swing.event.*; #spanend #spanadd import javax.swing.plaf.*; #spanend #spanadd import javax.swing.table.*; #spanend #spandel public class JCheckBoxExample { #spanend #spanadd public final class MainPanel { #spanend public JComponent makeUI() { JCheckBox checkbox = new JCheckBox("Always On Top", true); checkbox.addActionListener(e -> { AbstractButton b = (AbstractButton) e.getSource(); Container c = b.getTopLevelAncestor(); if (c instanceof Window) { ((Window) c).setAlwaysOnTop(b.isSelected()); 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(checkbox, BorderLayout.NORTH); 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); }); } #spanadd } #spanend #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.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; } 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 JCheckBoxExample().makeUI()); f.getContentPane().add(new LeafCheckBoxTreeTest().makeUI()); f.setSize(320, 240); f.setLocationRelativeTo(null); f.setVisible(true); }); } } #spanadd #spanend #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); } #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 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 #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 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 private fun makeUI(): JComponent { #spanend val tree = JTree() tree.setEditable(true) tree.setCellRenderer(CheckBoxNodeRenderer()) tree.setCellEditor(CheckBoxNodeEditor()) tree.setRowHeight(18) #spanadd #spanend 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) #spanadd } #spanend #spanadd #spanend #spanadd fun main(args: Array<String>) { #spanend 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) } } #spanadd } #spanend #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 #spanadd
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
#spanend #spanadd import java.awt.*; #spanend #spanadd import java.text.*; #spanend #spanadd import java.util.*; #spanend #spanadd import javax.swing.*; #spanend #spanadd import javax.swing.text.*; #spanend #spanadd #spanend #spanadd public class OptionalExample { #spanend private JComponent makeUI() { SimpleDateFormat format = new SimpleDateFormat( "mm:ss, SSS", Locale.getDefault()); DefaultFormatterFactory factory = new DefaultFormatterFactory( new DateFormatter(format)); #spanadd #spanend 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(); #spanadd #spanend JSpinner sp1 = new JSpinner(new SpinnerDateModel( d, null, null, Calendar.SECOND)); JSpinner.DefaultEditor ed1 = (JSpinner.DefaultEditor) sp1.getEditor(); ed1.getTextField().setFormatterFactory(factory); #spanadd #spanend 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); #spanadd #spanend 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); #spanadd #spanend 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); }); } #spanadd } #spanend #spanadd
Kotlin
#spanend #spanadd import java.awt.* #spanend #spanadd import java.text.* #spanend #spanadd import java.util.* #spanend #spanadd import javax.swing.* #spanend #spanadd import javax.swing.text.* #spanend #spanadd #spanend #spanadd fun makeUI(): JComponent { #spanend val format = SimpleDateFormat("mm:ss, SSS", Locale.getDefault()) val factory = DefaultFormatterFactory(DateFormatter(format)) #spanadd #spanend val calendar = Calendar.getInstance().apply { set(Calendar.HOUR_OF_DAY, 0) clear(Calendar.MINUTE) clear(Calendar.SECOND) clear(Calendar.MILLISECOND) } val d = calendar.getTime() #spanadd #spanend val sp1 = JSpinner(SpinnerDateModel(d, null, null, Calendar.SECOND)) val ed1 = sp1.getEditor() if (ed1 is JSpinner.DefaultEditor) { ed1.getTextField().setFormatterFactory(factory) } #spanadd #spanend val stepSizeMap: HashMap<Int, Int> = hashMapOf( Calendar.HOUR_OF_DAY to 1, Calendar.MINUTE to 1, Calendar.SECOND to 30, Calendar.MILLISECOND to 500) #spanadd #spanend 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) } #spanadd #spanend return JPanel().apply { add(sp1) add(sp2) } #spanadd } #spanend #spanadd #spanend #spanadd fun main(args: Array<String>) { #spanend EventQueue.invokeLater { JFrame("kotlin swing").apply { defaultCloseOperation = WindowConstants.EXIT_ON_CLOSE add(makeUI()) size = Dimension(320, 240) setLocationRelativeTo(null) setVisible(true) } } #spanadd } #spanend #spanadd
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
#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
#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
Jetpack Compose for Desktop
コメント
-