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