- 概要
- 業務でハマった問題について発表します
- 実際のところ私もよく分ってないので、ぜひ分かる方はコメント下さい
- 前提
- Android Kotlin/JVMで、Java製のLibraryをKotlinから利用
実際にハマったコード
Desk.java
package library_package;
abstract class BaseDesk {
public enum Color { BLUE, ORANGE }
}
public class Desk extends BaseDesk {
private Color color;
public Desk(Color color) {
this.color = color;
}
}
こういうJava実装を、Javaから使うのは、当然うまくいく。
java
new Desk(Desk.Color.BLUE);
Kotlinから使うと、Compile Errorになる。
kotlin
Desk(Desk.Color.BLUE) // Unresolved reference: Color
kotlin
import library_package.BaseDesk // Cannot access 'BaseDesk': it is package-private in 'library_package'
Desk(BaseDesk.Color.BLUE) // Cannot access 'Color': it is public in 'BaseDesk'
どう回避したか
この問題を解決するために(だろうか?) getColorEnum()
という static method が用意されていた。
Desk.java
package library_package;
abstract class BaseDesk {
public enum Color { BLUE, ORANGE }
public static Color getColorEnum(String name) {
return Color.valueOf(name);
}
}
kotlin
@Suppress("INACCESSIBLE_TYPE") // suppress warning は必要だが
Desk(Desk.getColorEnum("BLUE")) // DeskコンストラクタにColorインスタンスを入れることが出来た
なぜエラーになったり/ならなかったり?
どういうケースでエラーとなるのか見てみると...
Foo.java
class BaseFoo {
public static int i = 0;
public static int inc() {
return ++i;
}
public static class FooFoo {
public static int j = 0;
}
}
public class Foo extends BaseFoo {}
java
int foo_i = Foo.i;
int foo_inc = Foo.inc();
int foo_foo_j = Foo.FooFoo.j;
kotlin
val foo_i = Foo.i
val foo_inc = Foo.inc()
val foo_foo_j: Int = Foo.FooFoo.j // Unresolved reference: FooFoo
Kotlinでは、super class の static inner class にアクセスできないようだ。
根拠となる言語仕様を軽く調べてみたが、わからなかった。
なぜメソッドの戻り値としては使えたのか?
Javaは、もともと不可視なクラスを戻り値に定義できるようだ。
public class Bar {
public static InternalBar getBar() {
return new InternalBar();
}
private static class InternalBar extends Bar {}
}
Bar bar = Bar.getBar(); // OK
Kotlinでは、こんな事はできない。
open class Baz {
companion object {
fun getInternalBaz(): InternalBaz { // 'public' function exposes its 'private' return type InternalBaz
return InternalBaz()
}
fun getBaz(): Baz { // OK
return InternalBaz()
}
}
private class InternalBaz : Baz()
}
INACCESSIBLE_TYPE warning とは?
参照できない場合にエラーとするKotlin Compilerの挙動が、前述のようなJavaの仕様と上手く合致せずにコンパイルできない問題があり、いまはCompile ErrorではなくWarningにしているが、今後適切に、本当にアクセス不可な場合だけコンパイルエラーとするように再変更する予定、ということらしい。
https://youtrack.jetbrains.com/issue/KT-11398 を参照。
もし Library 側に getColorEnum() が用意されてなかったら?
自分でJavaでUtility Methodを作ればよい。
DeskColorResolver.java
import library_package.Desk;
public class DeskColorResolver {
public static Desk.Color getColor(String name) {
return Desk.Color.valueOf(name);
}
}
kotlin
val desk = Desk(DeskColorResolver.getColorEnum("BLUE"))
と書ける。
本当にこれが正解だろうか...?