Help us understand the problem. What is going on with this article?

SwiftとKotlinの文法を比較してみた(基礎パート)

More than 1 year has passed since last update.

はじめに

@ToraDadyさんが発表を担当されたKotlinスタートブック読書勉強会#2にオーディエンスとして参加してきました。

普段、Swiftを書いているiOSデベロッパーが、Kotlinとの文法の違いをどう意識したか浅めの感想ですが、メモとして書きたいと思いました。

基礎文法がテーマでしたので、その範囲で、KotlinとSwiftの異同について感想を併せて記したいと思います。

※なお、Swiftのコードは完全に独自に自分で書いたものです。Swift3系だとどういう表現に対応するか比較するために添えて、Playgroundで試しつつ書きました。(もしKotlinとの対応などに誤解があれば、教えていだたけると助かります。)

定数リテラル

いきなり定数リテラルの説明ですが、会場での話の流れに沿ってメモした通りに書いていますね。

まず、Kotlinでの定数リテラルについて。

kotlin
1_234 // == 1234

かつてKotlinでは定数を表現するときに、区切り記号として _ を使えなかったそうです。実際、ガイドにしているKotlinスタートブックでは、「現時点では」使えないと説明されています。

しかし、現在では使えます。例えば、会計で使うカンマのように使えるので、金額の定数を間違えずに記述するときに使えますね。

Swiftでも全く同じ記法ですね。区切りはどういう単位でも構わないようです。

swift
1_234 // == 1234

厳格な型チェック

ObjcからSwiftに移行したときに型チェックの厳しさに驚きました。Kotlinも同じように厳格です。

大は小を兼ねるの関係から、Long型の変数にIntの数値を代入できそうですが、型が違うのでエラーが発生します。

kotlin
val foo = 123 //Int
val bar:Long = foo //エラー

Long型はSwiftにそもそもありませんが、Int16などが整数型として用意されています。同様の操作をするとエラーが発生します。

swift
let foo = 123 //Int
let bar:Int16 = foo //エラー
//let bar:Int16 = Int(foo) //キャストすれば代入可能。

変数の文字列展開

変数を文字列の中で展開したい場合、kotlinでは次のような書式を取ります。

kotlin
val name = "Jack"
"Hello, ${name}!"
//または、型推論が機能する場合は{}を省略しても記述できる。
//"Hello, $name!"
swift
let name = "Jack"
"Hello, \(name)"

トリプルクオート

文字列を扱う " " だと、改行コードを表現する場合に改行コードを明示的に記述する必要があり、また一行で記述するのでレイアウトが崩れて見づらくなります。

"""でくくると、改行コードなしで改行した文字列を指定できます。

全く同じ機能がSwiftにも、Swift4から導入されましたね。

会場の声: CUIのヘルプのテキストをソースコードに書くときに'''は便利に使える。

ヌルの配列

Int型でヌルになっている3要素の配列を作るには?

kotlin
val ints = arrayOfNulls<Int>(3)

Swiftでは、次のような基本になるので、kotlinのほうが簡潔な印象ですね。

swift
let strs = Array<Int?>(repeatElement(nil, count: 3))

しかし、現在Kotlinには配列リテラルがないので、Swiftでは次のように書けるものがKotlinでは上のようにしか書けません。

swift
let strs: Array<Int?> = [nil,nil,nil]

会場でのやりとり: Pythonのようにstrs[-1]と記述できるか? →できない。範囲外指定エラーが発生します。Swiftと同じですね。(ちなみに、Pythonでは末尾の要素への参照を意味するそうです。)

配列リテラルがない

Swiftでは[]で書けますが、Kotlinでは現時点ではarrayOf()とするしかありません。

swift
let strs = ["Abc","Def"]
kotlin
var strs = arrayOf("Abc","Def")

会場でのやりとり: なぜKotlinでは配列リテラルを用意していないのか?

型を混合した配列の扱い

kotlin
var arr = arrayOf[1,"2"] //arrはAny型として推論される

arr[0] + arr[1] を実行すると、Any型に+の演算子が定義されていないので、エラーが発生します。

Swiftでも同様のコードをビルドすると、エラーが発生するという点では同じです。しかし、Swiftの場合、型推論の時点でエラーが生じます。(Heterogeneous collection literal could only be inferred to '[Any]') KotlinがAnyの配列と理解した上で、演算子の未定義でエラーを生じることと違いますね。

swift
let arr:Array<Any> = [1,"2"] //これならok

ArrayとListの違い

Arrayと違って、Listはインターフェースである。また、Listには複数実装がある。例えば、LinkedListなど。

会場の声: どういう場合に使い分けるのか? →Listのバリエーションを便利に使えるときはList系が便利。

ここにいうインターフェースは、Swiftのプロトコルの概念に近い?

Map

Swiftのmap関数のようなものではありません。むしろDictionaryに対応するものです。

kotlin
val themap = mutableMapOf("one" to 1, "two" to 2)

toで対応させるのが新鮮ですよね。MapOf()とmutableMapOf()の違いは、可変性の有無です。

会場でプレゼンターの@ToraDadyさんが実演してくださった要素の追加の記法が新鮮でした。

kotlin
themap += "three" to 3 //themap == ("one" to 1, "two" to 2, "three" to 3)

機能的にはSwiftのDictionaryと同じですね。

swift
let dict = ["one":1, "two":2, "three":3]

レンジ

Kotlinでは、範囲を扱うためのRange系のオブジェクトが用意されています。IntRange、LongRange、CharRangeです。

関連: Ranges

1から10までの範囲を記述するには、

kotlin
1..10

と書けます。

Swiftだと閉区間は、もう一つピリオドが多いですよね。for inでよく見るやつです。

swift
1...10

Kotlinでは、1から10未満の場合は次のように書きます。

kotlin
1 until 10

Swiftのほうが分かりやすいと思うのは慣れのせいでしょうか。

swift
1..<10

会場の声: Kotlin方式だと英文のように読みやすい面がある。kotlinでは 10 in 1 until 10で式として評価できるが、Swiftではできない。

関連: Swift 3のRange徹底解説

リストと反転

kotlinではリスト化とその反転のための関数があります。
次のようにすると、(5,4,3,2,1)というリストを取得できます。

kotlin
(1..5).reversed().toList() //(1)
(1..5).toList().reversed() //(2)

(1)と(2)では最終の値としては同じ結果が得られます。しかし、(2)はリストを2度作ることになるので、比較的にパフォーマンスが悪いです。そういう会場での議論がありました。

条件分岐: if

kotlinのifは式として使えるのが新鮮です。どういう意味かというと、例えば、次のように記述できるのです。

kotlin
val message = if(flag == true) "yes" else "no"

同じことをSwiftでしようとしたら、三項演算子を使うことになります。ほぼ同じですし、簡潔に記述できるのでこちらのほうが良いのではないかという感想があります。ただ、Kotlinのif文のほうが一見して分かりやすいですよね。

swift
let message = flag ? "yes" : "no"

when

kotlinのwhenというのは見慣れないキーワードで驚きました。実質的には、Swiftのswitchのように使えるものだと理解しました。

Swiftで次のようなswitch文を書けますが、同様のものがwhenだと以下のようになります。

swift
switch x {
case 1:
    "one"
case 2:
    "two"
default:
    "unknown"
}
kotlin
when (x){
 1 -> "one"
 2 -> "two"
else -> { "unknown" }
}

caseという文字列がないので見た目が簡潔ですね。
elseはdefaultに対応するものです。原則として必須です。

会場の声: elseを省略できるときはどんな場合か?

結論的には、sealedClassと列挙子Enumの場合は、全件を挙げておくと、例外的にelseを省略できる、ということのようです。反対に言えば、それ以外の場合に論理的にelseがありえない場合も、ビルドするためにはelseが不可欠ということですね。

ところで、型を評価することも可能です。

kotlin
when (x){
 is Int -> "Int"
else -> { "unknown" }
}

同様のことをSwiftで書くと、

swift
switch x {
case is Int:
    "Int"
default:
    "unknown"
}

挙動での違いとしては、Kotlinでは、型で分岐すると、その型でキャストされる機能が働くということでした。こういうコードが動きます。

kotlin
var x:Any = "123"
when (x){
 is String -> Int(x) //この中に入ると、xはAnyではなくStrint型にキャストされる
else -> { "unknown" }
}

Swiftで真似しようとすると、キャストされないのでエラーが発生しますね。

swift
let x:Any = "123"

switch x {
case is String:
    Int(x) //xはAnyなのでエラーになる。
  //Int(String(x)) //これは可。
default:
    "unknown"
}

追記: case let asを使えば、分岐しつつキャストする表現ができるという件、@sora0077@githubさんにご指摘いただきました。次のように書けば、kotlinの例のように動きますね。

swift
let x:Any = "123"

switch x {
case let x as String: //分岐してキャストできる
 Int(x)
default:
    "unknown"
}

この他にkotlinでもwhileがあります。

for

kotlinでは、iteratableなクラスを自前で実装できます。SwiftでもIteratableプロトコルを採用すれば、同様の実装ができます。

会場の声: kotlinでは、Iteratorからの継承関係を記述しなくても、operator fun next:(): Tと operator fun hasNext()] Boolean を簡潔に定義するだけで、インテレートできるクラスを実装できる。このことに驚いた、とのお話がありました。

Swiftにはないbreak loopという機能があります。(追記: @lovee さんのご指摘で、同等の機能がSwiftにもあることを知りました。外側のループにラベルをつけて、break ラベル、とすると、外側にブレイクできるというものです。コードはコメント欄をご覧ください。)

kotlin
loop@ for (x in 1..2){
 for (y in 1..10){
  if (y==5)
  break@loop
 }
}
//次の処理

break@loopとすると、loop@へ抜ける。上の例では、xが1で、yが1ないし5と来て、次の処理に降りていく流れになります。2回目は回りません。

コードをあっさり記述できる面もありますが、ジャンプすると処理を追いにくくなる面も出てくると思うので、使い方が大事ですね。

おわりに

別の言語との比較をすると、いつも使っている言語の機能を確認するきっかけを得られたり、将来の仕様変更への想像力が少しつく気がしました。

私は第一回も参加させてもらいましたが、いずれもKotlinのコードを丁寧に、頭からおさらいしてくださるので大変勉強になります。実際に実務でkotlinのコードを書かれているエンジニアの方がお話をインタラクティブに展開してくださると、本の記述の重み付けができてよいですね。

最後に、今回スピーカーを担当された@ToraDadyさん、主催者の皆さま、今回会場提供してくださったOrigamiさん、会場で議論を聞かせていただいた参加者のみなさまありがとうございました。

関連

読書勉強会という形で、テキストとしてこの本が採用されています。
Kotlinスタートブック

hsylife
iOSエンジニア。フリーランス、株式会社ジーニー 代表取締役(2009年〜)自社/受託開発。「メモ電卓(ge-calc)」が88万ダウンロード。GitHubでSwiftyPickerPopover公開中。案件ご相談お気軽に連絡ください。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away