Kotlinに手を出してみる
Kotlinをはじめてみよう。
と思って、こんなことをした。
GitHubのほうに同じのをあげてるます。
インストールしてみる
sdkmanでいけるんじゃないかと思って、sdk install kotlin
を叩いてみる。
% sdk install kotlin Downloading: kotlin 1.0.1 % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 100 601 0 601 0 0 359 0 --:--:-- 0:00:01 --:--:-- 558 100 19.6M 100 19.6M 0 0 984k 0 0:00:20 0:00:20 --:--:-- 1586k Installing: kotlin 1.0.1 Done installing! Do you want kotlin 1.0.1 to be set as default? (Y/n): y Setting kotlin 1.0.1 as default.
入ったわ。
げってぃんぐすたーてっどを探す
文法もわからんからとりあえずググる。
一番上に公式が出てきてくれた時の安心感。
開いたら当然のように「IDEAでやろうぜ!」って言われる。 まあわかるけど、私はとりあえずコマンドラインとvimでやりたいんだーと思ったら、それっぽいのがある。
……ん。sdkmanで入れろって書いてる。 いいや。とりあえずはろーわーるど書いてみよう。
はろわ!
書くぜー。
ふむ。 そしてこんぱいr……ん?
$ kotlinc hello.kt -include-runtime -d hello.jar
なんかコマンド長いなーと思った。 まあいいか。実行したらhello.jar ができた。
サイズは80kbくらい、そこそこでかい。
-include-runtime
とかついてるし、実行可能jarなんだろと思って実行。
% java -jar hello.jar Hello, World!
できたできた。
zip展開してjavap
どうせ hello.class
とかあるんだろうと思ってunzipしたら、なんか HelloKt.class
ってのが入ってた。
% javap HelloKt Compiled from "hello.kt" public final class HelloKt { public static final void main(java.lang.String[]); }
こいつそのまま実行したらいけるんだろうなーと。
% java HelloKt Hello, World!
いけた。 んー、これだけならクラスだけでも動くのかな。 って思って、クラスだけ移動して実行。
% java HelloKt Exception in thread "main" java.lang.NoClassDefFoundError: kotlin/jvm/internal/Intrinsics at HelloKt.main(hello.kt) Caused by: java.lang.ClassNotFoundException: kotlin.jvm.internal.Intrinsics at java.net.URLClassLoader.findClass(URLClassLoader.java:381) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ... 1 more
だめか(当たり前だ
mainメソッドの中でクラスロードが入ってる。 なんだろう。
$ javap -v HelloKt ...略 #15 = Methodref #11.#14 // kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V ...略 public static final void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL Code: stack=2, locals=3, args_size=1 0: aload_0 1: ldc #9 // String args 3: invokestatic #15 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V 6: ldc #17 // String Hello, World! 8: astore_1 9: nop 10: getstatic #23 // Field java/lang/System.out:Ljava/io/PrintStream; 13: aload_1 14: invokevirtual #29 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 17: return ...略
パラメーターのnull
チェックをメソッドの先頭でやってるんだなーと。
javap見た感じ、そいつあったら動く気がする。 適当に作ってみる。
そして実行。
% java HelloKt [Ljava.lang.String;@7852e922 args Hello, World!
動いた。謎の自己満足。
そんなわけで
- 4/2(土) Kotlin 1.0リリース記念勉強会 in 京都
Kotlin勉強会の裏で書いてました。
この後は素直にIDEAで触ることにします。
EC2のAmazonLinuxにPayaraを放流してみた
前置き
EC2さん。便利ですよね。使ってますか?私はあまり使ってないです。別にほかのサービス使ってるとかでもないです。(ドヤ顔で目線を逸らしながら
たまに思い出したように触っては、ある程度使えるつもりになって。 しばらくしたら使い方を綺麗さっぱり忘れて、AWS Consoleにログインするたびにいろいろ変わってて戸惑うってのを繰り返しています。 いい加減にしたい。いや、これがいい加減なのか。
ふとしたことから雑なJavaEEアプリケーションを突っ込んだPayara microをEC2で動かしたいなーと思って、また思い出したようにEC2を触るわけで、また一からあれこれやってました。
理想と現実
理想
- EC2インスタンスを立ち上げて
- Payaraダウンロードしてきて
- war放り込んで
- 起動したら完了
現実
- EC2立ち上げるかー。microでいいか……ん、そいやnanoとかあったね。知ってる知ってる。聞いた事はある。
- jdkのインストールとか要ったね。あったあった。
- payaraさんダウンロード、短縮URLだけど……まあいけるか。
- コマンドわからん。wgetわからん。yumもわからん。 ← いつものこと
- さあ起動だ!……ん?……例外? ← new
Payaraさんを放り込んだら例外吐いて死んだ
jdkのインストール
まずはいつものアレ。
$ sudo yum install java-1.8.0-openjdk-devel $ sudo alternatives --config java (なんか出てくるので1.8.0を選択)
1.8.0_65だった。まあいいか。
参考: Amazon LinuxでJava8/Tomcat8の環境を構築する
payaraのインストール
次に http://www.payara.fish/downloads からリンクをコピーしてきてwgetする。
$ wget http://bit.ly/1W9d2Lb
ファイル名が 1W9d2Lb になるけど気にしない。
適当にmv
した。
payaraの起動
まだwarとか放り込んでないのでそのまま起動する。
$ java -jar payara-micro-4.1.1.154.jar
……ふつうはこれでいいのだけれど、こんなエラーになっちゃって。
$ java -jar payara-micro-4.1.1.154.jar 1 24, 2016 10:33:44 午前 com.sun.enterprise.v3.server.SystemTasksImpl setSystemPropertiesFromEnv 重大: Cannot determine host name, will use localhost exclusively java.net.UnknownHostException: ip-XXX-XXX-XXX-XXX: ip-XXX-XXX-XXX-XXX: unknown error at java.net.InetAddress.getLocalHost(InetAddress.java:1505) (中略) at fish.payara.micro.PayaraMicro.main(PayaraMicro.java:105) Caused by: java.net.UnknownHostException: ip-XXX-XXX-XXX-XXX: unknown error at java.net.Inet4AddressImpl.lookupAllHostAddr(Native Method) (中略) at java.net.InetAddress.getLocalHost(InetAddress.java:1500) ... 29 more (中略) [2016-01-24T10:33:55.956+0000] [Payara 4.1] [INFO] [NCLS-JMX-00006] [javax.enterprise.system.jmx] [tid: _ThreadID=87 _ThreadName=Thread-12] [timeMillis: 1453631635956] [levelValue: 800] JMXStartupService has disabled JMXConnector system Exception in thread "main" java.lang.NullPointerException at fish.payara.micro.services.PayaraMicroInstance.setInstanceName(PayaraMicroInstance.java:95) at fish.payara.micro.PayaraMicroRuntime.<init>(PayaraMicroRuntime.java:64) at fish.payara.micro.PayaraMicro.bootStrap(PayaraMicro.java:710) at fish.payara.micro.PayaraMicro.main(PayaraMicro.java:105)
とても悲しい。
起動はしてるのでサーバーのエラーページを表示することはできるのだけれど、--deploy
でwarを放り込んでも動いてくれない状態だった。
ホスト名が見つからないって言ってて、出てるのはローカルのホスト名。なので /etc/hosts
のlocalhostに足してあげる。
$ hostname ip-XXX-XXX-XXX-XXX $ sudo vi /etc/hosts 127.0.0.1 localhost localhost.localdomain ip-XXX-XXX-XXX-XXX
参考: EC2 インスタンス上に Cassandra クラスタを構成する〜 java.net.MalformedURLException の対処 〜
よくわからんけど 動くようになったからよし。
放流
- scpとかでwarを放り込んで
java -jar payara-micro-4.1.1.154.jar --deploy hoge.war
で起動
これで完了。やったね。
コンストラクタのメソッド利用で注意すること
Java入門ではさらっと以下のように書いた、コンストラクタでインスタンスメソッドを実行することについて掘り下げてみます。
コンストラクタからインスタンスメソッドを使用することは可能ですが、避けたほうが無難です。 コンストラクタの実行中はインスタンス自体が構築中のため、初期化が完了していない状態でメソッドが実行されることになります。
Javaエンジニア養成読本 [現場で役立つ最新知識、満載!] (Software Design plus)
- 作者: きしだなおき,のざきひろふみ,吉田真也,菊田洋一,渡辺修司,伊賀敏樹
- 出版社/メーカー: 技術評論社
- 発売日: 2014/11/11
- メディア: 大型本
- この商品を含むブログ (6件) を見る
文章だけで伝えるのはなかなか難しいものだとも思いますし、 本に書いたのに実際にこの問題を見た時に即解決できなくて悔しかった ので、 突っ込んでしっかり書くことにしました。くそう。。。
簡単なサンプルコード
コンストラクタでのインスタンスメソッド呼び出しが問題を起こすコードは、以下のようになります。
/* このコードは動作しません */ class Hoge { final String str; Hoge () { invoke(); this.str = "HOGERA"; } void invoke() { System.out.println(str.length()); } }
new Hoge()
とかするとNullPointerException
です。
Exception in thread "main" java.lang.NullPointerException at blog.constructor.Hoge.invoke at blog.constructor.Hoge.<init>
このくらい短いコードなら、流石にやらないでしょうが、
コンストラクタとinvoke
メソッドの間に数百行の隔たりがあればどうでしょう?
フィールドはfinal
だし、うっかり使ってしまうかもしれません。
作成時は問題がなかったとしても、不具合改修などでこの手の問題が起こしてしまったとしても、
その開発者を不注意だと責め立てることは、私には出来そうもありません。(自分もやっちゃうだろうし。)
少し複雑にしたサンプルコード
さらに問題を複雑にすると、冒頭のように1クラスに完結している形では問題なく動作しているにもかかわらず、 継承すると問題が起こるパターンも考えられます。擬似的なコードは次のようなものになります。
/* このコードは動作しません */ abstract class Parent { final String arg; Parent(String arg) { init(); this.arg = arg; } abstract void init(); } class Child extends Parent { Child(String arg) { super(arg); } @Override void init() { System.out.println(arg.toUpperCase(Locale.ROOT)); } }
new Child("xxx")
とかでNullPointerException
になります。
Exception in thread "main" java.lang.NullPointerException at blog.constructor.Child.init at blog.constructor.Parent.<init> at blog.constructor.Child.<init>
少し説明落ち着いてコードを追えば、コンストラクタ引数がフィールドに格納される前に、
そのフィールドを参照しているので、フィールド型のデフォルト値(参照型なのでnull
になる)となっているだけです。
しかし、このNullPointerException
に遭遇すると、おそらく「final
フィールドなのになぜ値が入っていないんだ?」となります。
コンストラクタの引数がnull
になっていないかや、どこかで受け渡し損ねてないかなどを確認するかもしれません。
デバッガでinit
メソッドにブレイクポイントを置いてみたりするかもしれません。いくら見ても、事実が示すようにフィールドはnull
です。
継承階層が深くなると、メソッドの呼び出しタイミングがわからなくなっていきます。 全てのコードの詳細を把握することは困難ですし、全ての人にそれを求めるべきではありません。 上記のようにinitメソッドが拡張ポイントのように扱われていると、フレームワークがコンストラクタの後に呼びだす初期化処理と想像しても、不思議ではないでしょう。
さらに複雑にした(略
もっと複雑に、もっとわかりにくくすることは可能です。もっともっと。 でも、書いても楽しくないので箇条書きで許してください。
- コンストラクタで呼ばれるメソッド(上記だと
init
)で例外が発生せず、そこから呼び出したメソッドで発生する。 - コンストラクタで呼ばれるメソッドで生成されたインスタンスにフィールドのデフォルト値が設定される。
- 対象がプリミティブ型
- 参照型であれば
NullPointerException
が発生することで助かります(そう、例外が発生すればまだ助かるのです。少なくとも不正な更新を行ったりしてデータを壊す可能性は下がります。ぬるぽはいい子です。)が、プリミティブ型ならおそらくデフォルト値でそのまますんなり動いてしまいます。
- 参照型であれば
- フィールド参照ではなく、そのフィールドを使用する別のメソッドを使用する。
- フィールドが
final
でなく、頻繁に書きかわる。
……こんなのデバッグしたくないです。
どうすれば?
Java入門ではこんな感じで書きました。
もしインスタンスメソッドを実行する必要があるのならば、privateメソッドやfinalメソッドのような 拡張されないメソッドにできないかを検討し、対象のメソッド内で他のメソッドを呼び出さないように気をつけましょう。
書いたことは書いたままなのですが、まだ他にも考えられます。
提供側として、どうすればよいか
もし実装クラスで拡張することを想定したインスタンスメソッドを、コンストラクタでどうしても実行したいのならば、どうすれば良いかを考えてみます。 まず、そのメソッドが何をするための拡張ポイントであり、どういうタイミングで実行されるかなど ドキュメントをしっかり書いてください 。 そして、一切のメンバを使用禁止にします。これは三階層以上の継承がある場合、二階層目のコンストラクタで初期化されるフィールドやそれを使用するメソッドが正常に動作しないタイミングで呼ばれてしまうためであり、そのことを具象化しようとしているクラスでの検知が困難だからです。 その上で、メソッドで使用する値は 全て メソッドの引数で渡しましょう。
これでようやく、気をつけて使っていれば大丈夫な可能性のある物体になるかもしれません。 しかし、間違った使い方のできるものは、間違った使い方をされるものです。 要約すると「やらせるな」ということです。考え直しましょう、真面目に。真剣に。悪いこと言わないから。
利用側として、どうすればよいか
さて、利用側の視点でどうすればいいかには触れてきませんでした。
これは いくら利用者が気をつけても、安全に設計できていないものを、安全に使用することはできない からです。 単純なケースであればテストコードなどで比較的容易に検出はできるのですが、「さらに複雑にした(略」あたりで書いているものは辛いです。
フレームワークやライブラリのコードを読むことは推奨したいのですが、必須ではありません。 特に仕事ではコストは確保されないでしょうし、そういうスキルも求められないでしょう。 なので、不具合に気づいたら修正依頼をするか、迂回するか、パッチを当てるくらいでしょうかね。
あとがき
Java入門 第2章「クラスを理解する」の最後の方に書いた コンストラクタでは注意してメソッドを使用する を掘り下げてみました。 コードある方がわかりやすいかな。どうだろう。どっちでもわかりにくいよ、と言われたらごめんなさいとしか言いようがない。 しかし全部このノリで書いたらページいくらあっても……難しい。
出版されてから1年経ちますが、内容はまだ古くなってません。 そのなかで、こんな感じの内容を入門と言い張ってます。入門だよ、うん。