なんの記事?
Javaで書くとこうなるものってKotlinではどう書くの?
的なコード比較の覚え書き。
2018/1/10頂いたコメントを元に@JvmFieldとconstの違いを加筆・修正 ありがとうございます!
最近、趣味でKotlinに触れてます。
Kotlinの文法をまとめた記事はたくさんありますが、
Javaとのコード比較してるものが見つからなかったのでメモしていきます。
業務ではまだまだJavaが大半なので混乱しないようにすることが目的。
超基礎的なものも書いてく。
変数宣言
操作 | Kotlin | Java |
---|---|---|
定数 | 修飾子 val 変数名 : 型 | 修飾子 final 型 変数名 |
変数 | 修飾子 var 変数名 : 型 | 修飾子 型 変数名 |
メソッド・関数 | fun 関数名(変数名: 型...): 戻り値 | 戻り値 関数名(型 変数名...) |
型推論
Kotlinでは型推論が可能なので、型を省略して変数を定義することもできる。
String str = "文字列";
final int value = 0;
var str = "文字列"
val value = 0
文字列
文字列の結合
Kotlinでは文字列リテラルの中に式を入れることができる。
String name = "Benjamin Franklin";
int age = 20
String text = "Your name is " + name + ". You'll be " + (age + 1) + " years old next year.";
var name = "Benjamin Franklin"
var age = 20
var text = "Your name is $name. You'll be ${age + 1} years old next year."
変数名だけ入れる場合は波括弧は省略可能。
生文字リテラル
ダブルクォテーション3つで囲まれた文字列は生文字リテラルと呼ばれ、
円マークや改行などがエスケープされずそのまま反映される。
String add = "IJKL";
String text = "ABCD\n" +
"EFGH\n" +
add + "\n" +
"MNOP";
var add = "IJKL"
var text = """
ABCD
EFGH
$add
MNOP
"""
インデントなどもそのまま反映されてしまうので、
インデントを反映させたくない場合は行頭にパイプを置いて、trimMargin()を呼ぶ。
var add = "IJKL"
var text = """
|ABCD
|EFGH
|$add
|MNOP
""".trimMargin()
オプショナル
これが数あるKotlinの中でも最も強力な機能の一つ。
KotlinはNull安全になるよう設計されていて、デフォルトではNullが許容されていない。
例えば、
val a: String = null
これはコンパイルエラーになる。
なぜなら、Kotlinではデフォルトでnullを許容していないからだ。
nullを許容したい場合は型の後ろに?を付ける。
val a: String? = null // これはOK
オプショナル付きで定義された変数にアクセスする場合は必ずNullチェックが強要される。
Javaと比較してみよう。
String a = null;
a.contains("hoge"); // 当然nullなのでヌルポで落ちる
var a: String? = null
a.contains("hoge") // そもそもコンパイルが通らない
Kotlinのこのソースはコンパイル時点で弾かれる。
NotNullが保障されていないからだ。
Nullableな型にアクセスするためにはこうする。
まずはJavaでお馴染みのNullチェック。
String a = null;
if (a != null) {
a.contains("hoge"); // nullならここは通らない
}
Java8なら一応Optionalという同等の機能は使える
Optional<String> a = Optional.of(null);
a.ifPresent(notNull -> notNull.contains("hoge"))
肝心のKotlin
val a: String? = null
a?.contains("hoge")
Nullableな型の関数呼び出しの場合は、?.のようにするだけで良い。
aがNullならcontainsは実行されず何もしない。
Javaと比較しても非常に簡潔に書けることが分かるし、
Javaはあくまで実行時チェックでしかないのでJava8のOptionalを使ったとしても、
そもそもOptionalを使い忘れた場合の根本的解決にはなっていない。
Kotlinはコンパイル時にチェックが入るので、Nullチェックが漏れることもない。
KotlinのOptionalについてはこれだけで一つ記事が書けるぐらいのボリュームなので
別記事にまとめました。
【Null安全】Kotlin Java比較メモ 正しいNull安全の使い方
関数(メソッド)
ぶっちゃけ関数とメソッドの違いをよくわかってない。
ここでは便宜上、関数で統一します。
基本構文
アクセス修飾子 戻り値型 関数名(引数型 引数名) {
処理
}
// 例
public int add(int a, int b) {
return a + b;
}
// 呼び出し側
add(2, 3);
アクセス修飾子 fun 関数名(引数名:引数型): 戻り値型 {
処理
}
// 例
fun add(a:Int, b:Int): Int {
return a + b
}
// 呼び出し側
add(2, 3)
Kotlinの場合、アクセス修飾子が省略された場合はpublic扱い。
可変長引数
public int sum(int... args) {
int sum = 0;
for (int value: args) {
sum++ value;
}
return sum;
}
Kotlinはピリオド3つではなく、varargという修飾子を使う
fun sum(vararg args:Int) {
var sum:Int = 0
for (value:Int in args) {
sum++ value;
}
return sum
}
名前付き引数
Kotlinでは関数を呼ぶ時に引数名を指定できる。
例えば、引数が多いときなどに有効。
public String chaosArguments(String who, String where, String when, String what, String why) {
return when + "に" + where + "で" + who + "が" + what + "を" + why + "だからした";
}
chaosArguments("ウメハラリュウ", "ガイルステージ", "制限時間残り10秒", "昇竜拳", "小足見てから余裕");
// 制限時間残り10秒にガイルステージでウメハラリュウが昇竜拳を小足見てから余裕だからした
分かりづらい!!引数順も!例題も!
fun chaosArguments(who:String, where:String, when:String, what:String, why:String): String {
return "$whenに$whereで$whoが$whatを$whyだからした"
}
chaosArguments(
who = "ウメハラリュウ",
where = "ガイルステージ",
when = "制限時間残り10秒",
what = "昇竜拳",
why = "小足見てから余裕"
)
こんな風に
引数名 = 値
とすることができる。
どの引数に何を渡しているかがわかりやすくなる。
ちなみに引数名は関数の引数定義順である必要はないので、
chaosArguments(
where = "ガイルステージ",
when = "制限時間残り10秒",
who = "ウメハラリュウ",
what = "昇竜拳",
why = "小足見てから余裕"
)
とかでも良い。
クラス宣言
基本構文
public class ClassName {
// フィールド
private int param1;
private String param2;
// コンストラクタ
public ClassName(int param1, String param2) {
this.param1 = param1;
this.param2 = param2;
}
// メソッド
private void myMethod() {
// 何かの処理
return;
}
}
class ClassName(private val param1: Int, private val param2: String) {
// メソッド
private fun myMethod() {
// 何かの処理
return
}
}
Kotlinではjavaと異なり、コンストラクタをクラス名の直後に書く。
Kotlinではプライマリコンストラクタと呼ぶらしい。
引数部分は修飾子やval、varのプロパティの宣言もできる。
ちなみにプライマリコンストラクタに引数を取らない場合は以下のように省略可能。
class ClassName{
// hogehoge
}
コンストラクタの色々
コンストラクタ内で何か処理をする場合
public class ClassName {
private int param1;
final private String param2;
// コンストラクタ
public ClassName(int param1, String param2) {
this.param1 = param1 + 2;
this.param2 = param2 + "add String";
}
}
class ClassName(param1: Int, param2: String) {
private var param1: Int
private val param2: String
init {
this.param1 = param1 + 2
this.param2 = param2 + "add String"
}
}
コンストラクタの中で引数を直接代入せず、何かしらの演算をする場合は
initブロックを使って記述する。
この場合はJavaと同じようにトップレベルに変数の宣言をする。
複数のコンストラクタを記述する場合
public class ClassName {
private int param1;
private String param2;
public ClassName(int param1){
this.param1 = param1;
this.param2 = "initial";
}
public ClassName(int param1, String param2) {
this.param1 = param1;
this.param2 = param2;
}
public ClassName(int param1, String param2, String param3) {
this.param1 = param1;
this.param2 = param2;
String addParam = param3;
}
}
class ClassName {
private var param1: Int
private var param2: String
constructor(param1: Int) {
this.param1 = param1
this.param2 = "initial"
}
constructor(param1: Int, param2: String) {
this.param1 = param1
this.param2 = param2
}
constructor(param1: Int, param2: String, param3: String) {
this.param1 = param1
this.param2 = param2
val addParam = param3
}
}
constructorブロックを使えばいくらでも追加可能。
プライマリ以外のコンストラクタをセカンダリコンストラクタと呼ぶ。
上記のようにセカンダリオンリーでもOK。
セカンダリコンストラクタからプライマリコンストラクタを呼ぶ
public class ClassName {
private int param1;
public ClassName(int param1){
this.param1 = param1;
}
public ClassName(int param1, String param2) {
this(param1);
}
}
class ClassName(private val param1: Int) {
constructor(param1: Int, param2: String) : this(param1) {
}
}
constructor(引数) : this(引数)
のようにして呼ぶ。
引数に初期値設定する
例えば以下のようにコンストラクタを複数用意して、
param1とparam2の両方に引数を渡すか、
param1だけ引数を渡してparam2はデフォルト値を使うといったこと実装をすることが多い。
(Androidでも良く見かけますよね。)
Kotlinではとてもシンプルな記述でこれを実装できる。
public class ClassName {
private int param1;
private String param2;
public ClassName(int param1){
this(param1, "initialize");
}
public ClassName(int param1, String param2) {
this.param1 = param1;
this.param2 = param2;
}
}
class ClassName @JvmOverloads constructor(private val param1: Int, private val param2: String = "initialize")
すごい!たった一行で記述することができる!
引数を 変数: 型 = 初期値
にすることで、引数が与えられなかった場合の初期値を指定することができる。
Javaではこのような機能がないので、@JvmOverloadsというアノテーションを使って解決する。
例えばAndroidでCustomViewを作ろうと思ったら
public class CustomView extends View {
public CustomView(Context context) {
super(context);
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
class CustomView : View {
@JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, style: Int = 0) : super(context, attrs, style) {
}
}
こんな超シンプルな記述でイケてしまうのです。最高かよ!!
なお、プライマリコンストラクタは
class ClassName (val param1: Int){...}
のようにすると書いたけどこれは略記法で、アノテーションとかアクセス修飾子などを付ける場合はちゃんと
class ClassName public @annotation constructor(val param1: Int){...}
と書かないといけない。
クラスの継承
基本構文
// スーパークラス
public class Human {
private String sex;
public Human(String sex) {
this.sex = sex;
}
}
// サブクラス
public class Hero extends Human {
public Hero(String sex) {
super(sex);
}
}
// スーパークラス
open class Human(private var sex: String)
// サブクラス
class Hero(sex: String) : Human(sex)
クラス名の後にコロンとスーパークラス名を指定する。
スーパークラスにプライマリコンストラクタがある場合は継承時に初期化しなければならない。
open修飾子
Javaの場合、デフォルトでは特に何もしなくてもクラスの継承ができるが、
Kotlinはopen修飾子が付いていないクラスは継承できない。
つまり、KotlinのクラスはデフォルトではJavaで言うとfinal classになっている。
安易な継承はすんなってことらしい。
インナークラス
基本構文
public class Hero {
private String name = "まさお";
private void action() {
//インナークラスのフィールドやメソッドはインスタンス化すればアクセスできる。
Equipment eq = new Equipment();
eq.specialAction();
}
// インナークラス
public class Equipment {
private String sword = "木の棒";
private void specialAction() {
// アウタークラスはインスタンス化しなくてもアクセス可能。
String owner = name;
action();
}
}
}
class Hero {
private val name: String? = "まさお"
private fun action() {
// ここはJavaと同じ
val eq = Equipment()
eq.specialAction()
}
inner class Equipment {
private val sword: String = "木の棒"
// アウタークラスからアクセスできるようにするにはアクセス修飾子がinternalかpublicじゃないといけない
internal fun specialAction() {
val owner = name
action()
}
}
}
インナークラスは
inner class InnterClassName{}
で定義できる。
Javaと異なるのは、Javaはアウタークラスからインナークラスのメソッドはprivateでもアクセスできるが、
Kotlinの場合はprivateだとアクセスできない。
internalというアクセス修飾子にしないとダメ。
thisのインスタンス指定
例えばインナークラスからアウタークラスのインスタンスを指定したい場合Javaと記述が異なる
public class Hero {
public class Equipment {
Hero hero = Hero.this;
Equipment eq = this;
}
}
class Hero {
inner class Equipment {
val hero = this@Hero
val eq1 = this@Equipment
val eq2= this
}
}
Javaの場合はアウタークラス名.thisでアウタークラスのインスタンスを取得できる。
Kotlinはthis@クラス名で指定したクラスのインスタンスを取得できる。
上記の例では、this@Equipmentとthisは同じ。
プロパティ
Javaで最も退屈なコードと言えばgetter/setter。
これもKotlinならクールに解決してくれる。
基本構文
public class ClassName {
// これはフィールド
private int param = 0;
// これはアクセサ
public int getParam() {
return param;
}
// これもアクセサ
public void setParam(int mParam) {
param = mParam;
}
}
class Hero {
// これはフィールドではなくプロパティ 内部的にアクセサが生成される
var param = 0
}
これだけ。
実はKotlinではフィールドというものは存在せず、一見フィールドに見えるそれはプロパティと呼ばれる。
Kotlinではプロパティを宣言すると内部的にアクセサを生成する。
自前でgetter/setterを作らなくてもアクセサ経由で参照することができる。
valで変数定義した場合は当然、setterは使えない。
カスタムgetter/setter
自前でgetter/setterを定義することも可能。
public class ClassName {
private int param = 0;
// 絶対値で返す
public int getParam() {
return Math.abs(param);
}
// 絶対値を設定する
public void setParam(int mParam) {
param = Math.abs(mParam);
}
}
class ClassName {
var param = 0
get(){
return Math.abs(param)
}
//set()の引数名は慣例的にvalueにするようだ
set(value) {
field = Math.abs(value)
}
}
この場合は内部的にアクセサは作られない。
カスタムsetterの中で出てくるfieldというものはバッキングフィールドと呼ばれ、
プロパティの値を参照するときのために自動的に定義される。
例えば
var param = 0
//バッキングフィールドを使わない例。循環参照になって落ちる。
set(value) {
param = Math.abs(value)
}
このようなカスタムセッターを作ってしまうと、
カスタムセッターの中で再度paramのsetterを呼ぶことになり循環参照してしまう。
param = Math.abs(value)
はparamにMath.abs(value)の値を代入するのではなく、paramのセッターの引数にMath.abs(value)を渡して呼び出すことと同じ意味であることに注意。
オーバーライド
プロパティもオーバーライドが可能。
オーバーライドされるプロパティにはクラスと同様にopen修飾子が必要。
Javaのフィールドはオーバーライドではなく隠蔽となる。
参考:http://log.nissuk.info/2012/03/java.html
class SubClass : SuperClass() {
override var param = "sub"
get() = "override"
}
open class SuperClass {
open var param = "super"
}
値だけでなくgetter/setterのオーバーライドも可能。
当然SubClassからparamにアクセスすれば文字列"override"を返す。
staticフィールド
Kotlinにはstaticという概念はなく、
Javaでいうstaticフィールドのようなものを扱いたい場合はcompanion objectというシングルトンオブジェクトを使う
public class ClassName {
public static final int STATIC_VALUE = 1;
public static final
}
class ClassName {
companion object {
@JvmField val STATIC_VALUE = 1
}
}
@JvmFieldというアノテーションはJava側から呼び出す際にcompanion objectを介さずに呼び出すために必要。
@JvmFieldがない場合のKotlinのSTATIC_VALUEへのアクセス方法は
int value = ClassName.Companion.getSTATIC_VALUE();
となるが、@JvmFieldのアノテーションが付いているプロパティについては
int value = ClassName.STATIC_VALUE;
と、Javaのstaticフィールドと同じ要領でアクセスすることができる。
また、Javaのプリミティブ型とString型は
const
という修飾子もある
static final int HOGE = 0;
static final List<String> FUGA = new ArrayList<>();
companion object {
const val HOGE = 0
const val FUGA = ArrayList<String>() // これはダメ。
@JvmField val FUGA = ArrayList<String>() // これはOK。
}
constと@JvmFieldの違いは正直分からないけど、
この記事を見る感じでは、プリミティブ型においては違いがないように見える。
constと@JvmFieldの違いはコンパイル後の振る舞いのようだ。
constはフィールド領域で初期値が定義されるのに対し、@JvmFieldはstaticコンストラクタで初期値が定義される。
試しに以下のコードをバイトコードで見てみる
class A {
companion object {
const val a = 12
@JvmField val b = 12
@JvmField val c = Date() // 非プリミティブ
}
}
// access flags 0x19
public final static I a = 12
// access flags 0x19
public final static I b = 12
@Lkotlin/jvm/JvmField;() // invisible
// access flags 0x19
public final static Ljava/util/Date; c
@Lkotlin/jvm/JvmField;() // invisible
@Lorg/jetbrains/annotations/NotNull;() // invisible
// access flags 0x8
static <clinit>()V
NEW com/hoge/A$Companion
DUP
ACONST_NULL
INVOKESPECIAL com/hoge/A$Companion.<init> (Lkotlin/jvm/internal/DefaultConstructorMarker;)V
PUTSTATIC com/hoge/A.Companion : Lcom/hoge/A$Companion;
L0
LINENUMBER 12 L0
BIPUSH 12
PUTSTATIC com/hoge/A.b : I
L1
LINENUMBER 13 L1
NEW java/util/Date
DUP
INVOKESPECIAL java/util/Date.<init> ()V
PUTSTATIC com/hoge/A.c : Ljava/util/Date;
RETURN
MAXSTACK = 3
MAXLOCALS = 0
これをまとめるとこんな感じ。
初期化のタイミング | const | @JvmField(プリミティブ) | @JvmField(非プリミティブ) |
---|---|---|---|
フィールド定義 | ○ | ○ | × |
staticコンストラクタ | × | ○ | ○ |
プリミティブ型を@JvmFieldで定義してしまうと二回初期値を代入してしまうことになるので、
プリミティブ型はconst、非プリミティブ型は@JvmFieldとするのが良さそう。
配列
変数のみ宣言してあとから実体を代入する
String[] a;
a = new String[5];
var a: Array<String?>
a = arrayOfNulls<String>(5)
Kotlinでは代入時に型のあとに?を明示的につけない場合Nullが許容されないため、
Array<String?>とする必要がある。
arrayOfNulls<型>()で中身がnullで埋められた配列を作成できる。
変数と実体を同時に定義する
String[] a = new String[5];
var a = arrayOfNulls<String>(5)
・宣言と同時にNullを代入する場合、?は不要
初期値を与える
String[] a = {"a","b","c"};
var a = arrayOf("a","b","c")
ファクトリ関数を使った初期化
String[] a = {"0","2","4","6","8"};
var a = Array(5,{ i -> (i * 2).toString() })
・Array()の第一引数に要素数、第二引数にはインデックス値と計算式を取る
配列要素の操作
####代入
a[0]= "test";
a[0] = "test"
a.set(0, "test")
####取得
final String element = a[0];
val element = a[0]
val element = a.get(0)
####要素数の取得
final int size = a.length;
val size = a.size
####全要素の取得
// 添え字付きのループ
for(int i = 0; i < c.length; i++){
final String element = c[i];
}
// 要素を直接取得するループ
for(String elm : c){
final String element = elm;
}
// 添え字付きのループ
for (i in c.indices) {
val element = c[i]
}
// 要素を直接取得するループ
for (elm in c) {
val element = elm
}
// forEachを使った要素を直接取得するループ
c.forEach{elm -> val element = elm}
Kotlin独自のループ
// 添え字と要素の中身を同時に取り出す
for ((index, value) in c.withIndex()) {
println("the element at $index is $value.")
}
コレクション
KotlinにはJavaの配列にあたるArrayの他にもちろんコレクションに相当するものもある。
List
Listの生成
List<Integer> list = Arrays.asList(0,1,2);
val list = listOf(0,1,2)
要素の取得
final int elm = list.get(0);
val elm = list[0]
val elm = list.get(0)
immutable(不変)とmutable(可変)
Listの要素に代入したり、追加したりするにはどうしたらいいか。
後述するMap、Setも含めてKotlinのコレクションはimmutable(不変)で、
生成後には中身が変えられない。
Javaのコレクションはmutableなので、意識しなくても代入したり追加したりができる。
もちろん、Kotlinにもmutableなコレクションも用意してあるので、代入や追加をしたい場合はこちらを使う。
要素の追加
List<Integer> list = new ArrayList<>();
list.add(0);
val list: MutableList<Int> = mutableListOf()
list.add(0)
要素の代入
List<String> list = Arrays.asList("one","two","three");
list.set(0,"new-one");
val list = mutableListOf("one","two","three")
list[0] = "new-one"
要素の削除
List<String> list = Arrays.asList("one","two","three");
list.remove(0);
val list = mutableListOf("one","two","three")
list.removeAt(0)
Map
Mapも基本的にはListと同じ。
Mapの生成
Map<String, Integer> map = new HashMap<String, Integer>() {
{
put("one", 1);
put("two", 2);
put("three", 3);
}
};
val map = mapOf("one" to 1, "two" to 2, "three" to 3)
値の取得
final int value = map.get("one");
val value = map["one"]
val value = map.get("one")
要素の追加
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("one", 1);
val map: MutableMap<String, Int> = mutableMapOf()
map.put("one", 1)
要素の代入
Map<String, Integer> map = new HashMap<String, Integer>() {
{
put("one", 1);
put("two", 2);
put("three", 3);
}
};
map.put("one", 100);
val map = mutableMapOf("one" to 1, "two" to 2, "three" to 3)
map.put("one", 100)
要素の削除
Map<String, Integer> map = new HashMap<String, Integer>() {
{
put("one", 1);
put("two", 2);
put("three", 3);
}
};
map.remove("one");
val map = mutableMapOf("one" to 1, "two" to 2, "three" to 3)
map.remove("one")
Set
List、Mapとほとんど同じなので割愛。
インターフェース
基本構文
public interface MyInterface {
// 初期値ありのフィールド
String str = "interface";
// 実装なしのメソッド
// Java8前でも利用可能
void emptyMethod();
// デフォルト実装ありのメソッド
// Java8以降のみ利用可能
void defaultMethod(){
// do something
}
}
interface MyInterface {
// 実装なしのメソッド
fun emptyMethod()
// デフォルト実装ありのメソッド
fun defaultMethod() {
// do something
}
companion object {
val str= "interface"
}
}
抽象プロパティ(フィールド)
Javaではフィールドをabstractすることはできないが、Kotlinではabstractなプロパティを定義することができる。
interface MyInterface {
// 暗黙的にabstractになってる
val abstractStr: String
// getter/setterは定義できる
var strHasAccessor: String
get() = strHasAccessor
set(value) {
strHasAccessor = value
}
// 以下のような実装は不可。初期値を与えることはできない
val str = "init"
}
インターフェース内のプロパティは初期値を持つことはできない。
インターフェースの実装
Kotlinで書かれた以下のインターフェースを実装するクラスを書いてみる。
Javaでは継承を表すextendsとインターフェース実装を表すimplementsが区分されていたが、
Kotlinではインターフェース実装も
クラス名 :(コロン) インターフェース名
となる。
もちろんカンマ区切りで複数のインターフェースを実装することができる。
interface MyInterface {
// abstractプロパティ
val abstractStr: String
// 実装なしメソッド
fun abstractMethod()
// 実装ありメソッド
fun defaultMethod(){
// do something
}
}
public class MyClass implements MyInterface {
@NonNull
@Override
public String getAbstractStr() {
// return値に設定値を入れる
return "init";
}
@Override
public void abstractMethod() {
// do something
}
// Java8以前ではデフォルト実装ありのメソッドも必ず@Overrideしなくてはいけないようだ
@Override
public void defaultMethod() {
// do something
}
}
class MyClass : MyInterface{
override val abstractStr: String = "init"
override fun abstractMethod() {
}
}
ここで注目すべきなのは、
抽象プロパティをアクセサの定義なしに直接初期値を入れることができる点と
デフォルト実装されているdefaultMethod()はオーバーライドするかどうかをユーザが決められる点。