JavaプログラマのためのKotlin入門

  • 27
    いいね
  • 0
    コメント

kotlin.png

Kotlin が Android の公式言語になることが Goole I/O 2017 で発表されました。 Java プログラマが Kotlin を始めることがこれから多くなると思うので、 Kotlin をスムーズに始められるように次の 3 点についてまとめます。

  1. Javaとほぼ同じところ
  2. 新しい考え方が必要でつまづきがちなところ
  3. Kotlinならではの便利なこと

すべてを一つの投稿にすると長くなるので連載形式とし、本投稿では最初の「Javaと同じところ」について説明します。

Kotlinって何?

本題の前に、 Kotlin について簡単に説明します。

まずは↓の Android のコードを見て下さい。これは Android Studio が生成するテンプレートの Kotlin 版です。 Android アプリ開発者であれば、初見でも概ね何をしているのかわかると思います。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val toolbar = findViewById(R.id.toolbar) as Toolbar
        setSupportActionBar(toolbar)

        val fab = findViewById(R.id.fab) as FloatingActionButton
        fab.setOnClickListener { view ->
            Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                    .setAction("Action", null).show()
        }
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        menuInflater.inflate(R.menu.menu_main, menu)
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        val id = item.itemId


        if (id == R.id.action_settings) {
            return true
        }

        return super.onOptionsItemSelected(item)
    }
}

Kotlin は JetBrains 社が作っている JVM 上で動作する言語で、 "100% interoperable with Java (Java と完全に相互運用可能 )" を謳っています。あるクラス A を Java で書いて、それを継承した B を Kotlin で書いて、さらに B を継承した C を Java で書いたりできます。そのため、 これまで Java で書かれていたプロジェクトに、追加開発分だけ Kotlin を混ぜ込むようなことでき、新規採用もしやすいです

Kotlin は元々 Android で Java の代わりによく使われている言語です。 Android Studio は IntelliJ IDEA という IDE をベースに作られていますが、 JetBrains は IntelliJ IDEA の開発元です。当然 Kotlin は Android Studio でもうまく動くようにできており、 Google が公式に Kotlin を採用しているわけではないけど Android Studio (のベースである IntelliJ IDEA )では公式にサポートされているという、半公式言語のような状態でした。今回は、そんな Kotlin を Android の公式言語にしますよと Google が発表したという話です。

JetBrains は Java 用の IDE を作っているだけあって、 Java の良いところ・悪いところを熟知しています。 Kotlin は Better Java としてかゆいところに手が届くように作られており、一方で Java プログラマが抵抗感なくすんなりと受け入れられるようにできています。 特にパラダイムが大きく異なるわけでもないので、 Java プログラマならすぐに使えるようになると思います。僕の務める Qoncept では、 Java プログラマが新しく Kotlin のプロジェクトにアサインされる場合でも、 2, 3 日の勉強だけで実践投入されています1。個人的には、 Scala などの他の JVM 言語と比べると Java に近いため敷居が低いように思います。

僕の主観では、 Kotlin で書くと Java と比べて倍以上生産性が高いように感じます。かつて Java は 10 年ほどの長きにわたって僕のベスト言語でしたが、今では Java に戻ることはできません。↓のように、 Android に限らず Java の代わりに Kotlin を使っています。

Kotlinのインストール

  • とりあえずブラウザ上で試す: インストールしなくてもこちらからブラウザ上で実行可能
  • Android Studio 3.0: Kotlin がプリインされたプレビュー版をダウンロード
  • Android Studio 2.x: Plugins > Install JetBrains plugin… から Kotlin で検索してプラグインをインストール(詳細
  • IntelliJ IDEA: 同上(詳細
  • コマンドライン: Mac なら brew install kotlin 、その他環境も詳細はこちら

Javaとほぼ同じところ

Kotlin では多くの概念は Java と同じです。構文の見た目が違うだけなので、対応関係を覚えるだけでそれなりにプログラムが書けるようになります。本節では、そのような対応関係について説明します。

それぞれ、末尾に参考になる公式ドキュメントへのリンクをつけてありますので、詳細を知りたい場合はそちらを併せて御覧下さい。

Hello, world!

// Java
public class Main {
  public static void main(String[] args) {
      System.out.println("Hello, world!");
  }
}
// Kotlin
fun main(args: Array<String>) {
  println("Hello, world!")
}

Kotlin では main メソッドのためにわざわざクラスを作る必要がありません。また、 Kotlin では println がメソッドではなくクラスに属さない関数として実装されています。そのため System.out を書く必要がありません。関数は static メソッドのようなものなので、 static も書く必要がありません。デフォルトで public なので public も不要です。さらに、 Kotlin では文の末尾の ; も要りません。

funArray<String> などの書式については後で説明します。

演算子、リテラル、コメント

基本的に Java と同じです。 0x0F42L などのリテラルも使えます。また、他のいくつかの言語のように、桁が見やすいように 1_000_000 のようにアンダースコアを入れることもできます。

変数宣言

// Java
String s = "abc";
final String t = "xyz";
// Kotlin
var s: String = "abc"
val t: String = "xyz"

ご覧のように Kotlin では Java と違い、変数名の後ろに型を書きます。また、 final な変数を宣言するには val 、変更可能な変数を宣言するには var を使います。現代のプログラミングでは不変でよいものは不変にするのが望ましいと言われることが多いですが、 Java では final を付けるのが面倒でした。 valvar ならタイプ数も同じです。 Kotlin では var に優先して val 使い、どうしても再代入可能にしたい場合にだけ var を使います。2

varval を書くのが面倒と感じられるかもしれませんが、 Kotlin では 型推論( Type Inference ) によって変数宣言時に型を省略することができるため、変数宣言を Java よりも簡潔に書けることが多いです。

// Kotlin
var s = "abc"
val t = "xyz"

変数宣言時に型を省略した場合、右辺値(この場合は "abc""xyz" )から変数の型が推論されます。

プリミティブ型

Kotlin では Java でいうプリミティブ型の型名も Int, Double, Boolean のように大文字で始めます。

// Java
int a = 42;
final boolean b = true;
// Kotlin
var a: Int = 42
val b: Boolean = true

これらの型はクラスとして振る舞うのでメソッドを呼ぶことができます。 Java の発想では Integer などのクラスにボクシングされているのではないかと考えてしまいますが、そうではありません。内部的にはプリミティブとして扱われ、 Int のメソッドを呼ぶのは Java で static メソッドに int を渡すのと等価です。そのため、パフォーマンス上の心配はありません。ただし、 List<Int> などに渡された場合は Kotlin でもボクシングされます。

// Java
String s = Integer.toString(a);
// Kotlin
val s = a.toString()

制御構文

if

基本的に Java と同じです。しかし、 Kotlin の if は式として使えるので、次のように ifelse ブロックの中で最後に評価された式の値を直接代入することができます。

// Java
final String foo;
if (bar < 42) {
  foo = "abc";
} else {
  foo = "xyz";
}
// Kotlin
val foo = if (bar < 42) {
  "abc"
} else {
  "xyz"
}

まるで Java の三項演算子 x ? y : z のようです。そのため、 Kotlin には三項演算子がありません。

while, do - while

Java と同じです。

for

Kotlin には Java でいう拡張 for 文しかありません。構文はほぼ同じですが、 : の代わりに in を使います。 Java では↓の number の型を記述しなければならないですが、 Kotlin ではここでも型推論で省略できます。

// Java
for (int number : numbers) {
  System.out.println(number);
}
// Kotlin
for (number in numbers) {
  println(number)
}

↓のような for 文相当のことをするには次のように書きます。

// Java
for (int i = 0; i< 100; i++) {
  System.out.println(i);
}
// Kotlin
for (i in 0 until 100) {
  println(i)
}

この until は特殊な構文ではなくただのメソッドで、 0.until(100) と同じです。 Kotlin ではこのように、 +, * などの二項演算子と同じような中置記法のメソッドを作ることができます。 0 until 100 の戻り値は IntRange で、 IntRangeIterable なので for - in でループできるわけです。

この他にも、様々なメソッドを使って多様なループを表現できます。

// Kotlin
for (i in 99 downTo 0) println(i) // for (int i = 99; i >= 0; i--)
for (i in 0 until 100 step 2) println(i) // for (int i = 0; i < 100; i += 2)
for (i in 1..100) println(i) // for (int i = 1; i <= 100; i++)

switchwhen

switch 文は Kotlin では when になります。 when も式として使えるので、次のように簡単に書けます。また、 switch と違って次の case に進むことはないので break を忘れる心配もありません。

// Java
final String s;
switch (a) {
  case 0:
    s = "abc";
    break;
  case 1:
  case 2:
    s = "def";
    break;
  default:
    s = "xyz"
    break;
}
// Kotlin
val s = when (a) {
  0 -> "abc"
  1, 2 -> "def"
  else -> "xyz"
}

new

Kotlin では Java と違ってコンストラクタの呼び出しに new は不要です。

// Java
final Foo foo = new Foo();
// Kotlin
val foo = Foo()

クラス

Kotlin でもクラスの考え方は Java と同じです。しかし、 Kotlin ではずいぶんとすっきり書くことができます。

// Java
public class Person {
  private final String firstName;
  private final String lastName;
  private int age;

  public Person(String firstName, String lastName, int age) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }

  public String getFirstName() {
    return firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public int getAge() {
    return age;
  }

  public void setAge(int age)) {
    this.age = age;
  }

  public String getFullName() {
    return firstName + " " + lastName;
  }
}
// Kotlin
class Person {
  val firstName: String
  val lastName: String
  var age: Int

  constructor(firstName: String, lastName: String, age: Int) {
    this.firstName = firstName
    this.lastName = lastName
    this.age = age
  }

  fun getFullName(): String {
    return firstName + " " + lastName
  }
}

Kotlin ではデフォルトが public なので public を書く必要がありません。 Java のデフォルト(アクセス修飾子を省略した場合)と同じアクセスレベルを指定するには internal を使います。 privateprotected はそのままです。

次に、 firstName, lastName, age の getter, setter がないことに気付きます。 Java では firstName などに付けられていた private 修飾子もありません。実は、 Kotlin にはフィールドがありません。 firstName などはプロパティと呼ばれるもので Java のフィールドと getter, setter をまとめたようなものです。プロパティについては後で詳しく説明します。

getter や setter を書かなくて良いのでコードがずいぶんと短くなりました。 Java のコードは 31 行ですが、 Kotlin では 15 行です。しかし、↑のコードはわかりやすいようにあえて Java っぽく書いたものです。まだまだ短くなります。

プロパティ

プロパティ( Property ) はフィールドと getter, setter を合わせたようなものです。次のように使います。

// Java
final int age = person.getAge();
person.setBaz(age + 1);
// Kotlin
let age = person.age
person.age = age + 1

これだけだと Java でフィールドを public にしたのと同じに思えるかもしれません。しかし、プロパティはフィールドと違い、メソッドのように処理の結果を返すこともできます。 getFullName メソッドを fullName というプロパティに書き変えてみます。

// Java
public class Person {
  ...

  public String getFullName() {
    return firstName + " " + lastName;
  }
}
// Kotlin
class Person {
  ...

  val fullName: String
    get() {
      return firstName + " " + lastName
    }
}

こうして実装されたプロパティは普通のプロパティと同じように person.fullName で呼び出すことができます。なお、 val ではなく var とすると、 set を使って setter 相当の実装をすることもできます。

さらに、プロパティの実装が式一つで済む場合は、次のように省略して書けます。

// Kotlin
  val fullName: String
    get() = firstName + " " + lastName

Java でフィールドを public にせず getter や setter を作るのは、フィールドだとサブクラスでオーバーライドして挙動を変えたり、インターフェースで抽象化することができないからです。 Kotlin のプロパティはメソッドと等価なので、サブクラスでオーバーライドしたり、インターフェースにプロパティを宣言して、それをオーバーライドして実装することもできます。

コンストラクタ

Kotlin では次のようにして、プロパティの宣言とコンストラクタの宣言をまとめてやってしまうことができます。

// Kotlin
class Person(val firstName: String, val lastName: String, var age: Int) {
  ...
}

Java でフィールドの宣言とコンストラクタや getter, setter の実装は冗長なコードになりがちで IDE で自動生成することが多いですが、 Kotlin では↑だけで済んでしまします。 ... に入るのはさっきの fullName の実装( 2 行)だけなので、なんと Java で 31 行だったコードが 4 行になってしまいました。

このような形で書くコンストラクタを プリマリコンストラクタ( Primary Constructor ) と呼びます。プライマリコンストラクタは便利ですが、これではプロパティを初期化する以外の処理を書けません。プライマリコンストラクタにプロパティを初期化する以外の処理を書くには init を使います。

// Java
public class Person {
  private final String firstName;
  private final String lastName;
  private int age;

  public Person(String firstName, String lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
    age = 0;
  }

  ...
}
// Kotlin
class Person(val firstName: String, val lastName: String) {
  var age: Int

  init {
    age = 0
  }

  ...
}

一方、 constructor で作るコンストラクタを セカンダリコンストラクタ( Secondary Constructor ) と呼びます。プライマリコンストラクタは一つしか作れませんが、セカンダリコンストラクタはいくつでも作ることができます。

// Java
public class Person {
  private final String firstName;
  private final String lastName;
  private int age;

  public Person(String firstName, String lastName, int age) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }

  public Person(String firstName, String lastName) {
    this(firstName, lastName, 0);
  }

  ...
}
// Kotlin
class Person(val firstName: String, val lastName: String, var age: Int) {
  constructor(firstName: String, lastName: String) : this(firstName, lastName, 0) {
  }

  ...
}

↑でやりたいのは、 age を省略できるコンストラクタを作りたいだけです。そんなときはわざわざセカンダリコンストラクタを作らなくても、プライマリコンストラクタにデフォルト引数を設定することができます。

// Kotlin
class Person(val firstName: String, val lastName: String, var age: Int = 0) {
  ...
}

こうしておくと、 age を省略してコンストラクタを呼ぶと age0 を渡したものとして実行されます。

// Kotlin
val person = Person("Albert", "Einstein")
// Person("Albert", "Einstein", 0) と同じ

- Constructors: https://kotlinlang.org/docs/reference/classes.html#constructors

メソッド

// Java
public class Person {
  ...

  // 時間経過を表すメソッド
  public int elapse(int years) {
    age += years;
    return age;
  }
}
// Kotlin
class Person(val firstName: String, val lastName: String) {
  ...

  // 時間経過を表すメソッド
  fun elapse(years: Int): Int {
    age += years
    return age
  }
}

メソッドについては特に変わったところはありません。メソッドの宣言は fun で始めます。引数も変数宣言と同じように years: Int というスタイルで書き、戻り値の型も末尾で : Int のように指定します。戻り値がない場合は : Void ではなく : Unit と書くか、戻り値の型の記述を省略します。

なお、メソッドについても式一つで済む場合は、↓のように省略形で書くことができます。

// Kotlin
fun getFullName(): String = firstName + " " + lastName

また、コンストラクタ引数同様に、デフォルト引数を与えてオーバーロードの代わりとすることもできます。

// Java
  public int elapse(int years) {
    age += years;
    return age;
  }

  public int elapse() {
    return elapse(1);
  }
// Kotlin
  fun elapse(years: Int = 1): Int {
    age += years
    return age
  }
}

継承

Kotlin ではデフォルトでクラスやメソッドは final 扱いです。これは、現代のプログラミングにおいては実装の継承は良いことではなく、必要最小限にとどめるべきとされているからです。 final でなくすには open のキーワードを付けます。

// Java
public class Person {
  ...

  public String getFullName() {
    return firstName + " " + lastName;
  }
}

public final class EasternPerson extends Person {
  public EasternPerson(String firstName, String lastName, int age) {
    super(firstName, lastName, age);
  }

  @Override
  public String getFullName() {
    return lastName + " " + firstName; // 姓 名の順にする
  }
}
// Kotlin
// `open` で `final` でなくす
open class Person(val firstName: String, val lastName: String, var age: Int) {
  ...

  open val fullName: String // `final` でなくす
    get() = firstName + " " + lastName
}

class EasternPerson(firstName: String, lastName: String, age: Int) : Person(firstName, lastName, age) {
  override val fullName: String
    get() = lastName + " " + firstName // 姓 名の順にする
}

extends の代わりには上記のように : を使います。 EasternPerson のプライマリコンストラクタの引数には valvar も付いていないことに注目して下さい。 valvar を付けるとプロパティを宣言するという意味になってしまいますが、何も付けなければそれはただの引数であることを意味します。ここでは super クラスのコンストラクタに渡す引数にしたいだけなので varval も必要ありません。

: Person(firstName, lastName, age)(firstName, lastName, age) の部分は、 Java でいう super(firstName, lastName, age) に当たります。

また、 Java では @Override は強制ではないですが、 Kotlin では修飾子となり必須です。これによって、 @Override を付け忘れて typo するようなミスも確実に防げます。

インタフェース

インタフェースも書き方が異なるだけで考え方は Java と同じです。

// Java
public interface Foo {
  int getBar();
  void baz(String qux);
}
// Kotlin
interface Foo {
  val bar: Int
  fun baz(qux: String)
}

↑の bar のように、インターフェースでプロパティを宣言してクラスにそれを実装させることができます。このことからも、プロパティがメソッドのようなものであることがわかります。

// Kotlin
class ConcreteFoo(override val bar: Int): Foo {
  override fun baz(qux: String) {
    println(qux)
  }
}

ジェネリクス

Kotlin でも Java と同じようにジェネリクスが使えます。

// Java
public final class Box<T> {
  private T value;

  public Box(T value) {
    this.value = value;
  }

  public T getValue() {
    return value;
  }

  public void setValue(T value) {
    this.value = value;
  }
}
// Kotlin
class Box<T>(var value: T)
// {} の中に何も書かないなら↑の後の {} も不要

変性のコントロールは out , in を使って簡潔に書けます。

// Java
final Box<? extends Cat> cat = new Box(new Cat());
final Box<? extends Animal> animal = cat;

final Box<? super Animal> animal = new Box(new Animal());
final Box<? super Cat> cat = animal;
// Kotlin
val cat: Box<out Cat> = Box(Cat())
val animal: Box<out Animal> = cat

val animal: Box<in Animal> = Box(Animal())
val cat: Box<in Cat> = animal

なお、 Kotlin では利用時ではなく、 C# や Scala のように型パラメータの宣言時に変性を決定することもできますが、それについては次回の投稿で説明します。

コレクション

Kotlin では ListMap などのコレクションが、イミュータブルなものとミュータブルなもので分かれています。たとえば List はイミュータブル、 MutableList がミュータブルです3。 Java では型でコレクションのミュータビリティを制御することができなかったのでうれしいところです。

また、 ArrayListHashMap 等も使えますが、特に実装にこだわらないときには特定のクラスのコンストラクタでコレクションのインスタンスを生成するのではなく、 listOf 等の関数を使います。

// Java
final List<Integer> a = new ArrayList();
a.add(2);
a.add(3);
a.add(5);

final List<Integer> b = Collections.unmodifiableList(Arrays.asList(2, 3, 5));
// Kotlin
val a: MutableList<Int> = mutableListOf()
a.add(2)
a.add(3)
a.add(5)

val b: List<Int> = listOf(2, 3, 5)

配列

配列についても Array<String> のようなジェネリクスを使った表記をします。プリミティブ型の配列については Array<Int> などではなく IntArray などを使います。 Aray<Int> は Java でいう Integer[] に相当し効率が悪いので、 int[] 相当の IntArray などのクラスが別途用意されています。

// Java
final int[] a = new int[] { 2, 3, 5};
// Kotlin
let a = intArrayOf(2, 3, 5)

ラムダ式

Kotlin でも Java 8 以降同様にラムダ式を使えますが、必ずラムダ式全体を {} で囲む必要があります。

// Java
final Stream<Integer> squared = numbers.map(x -> x * x);
// Kotlin
val squared = numbers.map({ x -> x * x })

なお、 Kotlin のコレクションは mapfilter などが直接使えるように拡張されているので、↑のようなコードを List で直接使うことができます。

また、 Kotlin では最後のパラメータにラムダ式を渡す場合には、ラムダ式を () の外に出すことができます。さらに、引数がラムダ式しかないメソッドの場合には、 () そのものを省略することができます。加えて、ラムダ式の引数が一つしかない場合には、それを省略して it とすることができます。これらを合わせると↑のコードは次のように書けます。

// Kotlin
val squared = numbers.map { it * it }

try - catch - finally

try - catch - finally も Java と同じように使えます。一番大きな違いは、 Kotlin には検査例外がないことです。 Java で検査例外を throw するメソッドでもすべて非検査例外扱いになり、 catch しなくてもコンパイルエラーになりません。僕は、検査例外に関してはこちらのような考えなので、検査例外をなくしてしまったこと Kotlin の言語仕様の中で一番イケてないところだと考えています。

Java の try-with-resources も Kotlin にはないですが、 use メソッドで似たようなことができます。

// Java
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) {
  ...
} catch (IOException e) {
  ...
}
// Kotlin
try {
  file.bufferedReader().use { reader ->
    ...
  }
} catch (e: IOException) {
  ...
}

use については次々回に詳しく説明します。

packageimport

packageimport の書き方は末尾の ; がないだけで Java と同じです。 Kotlin から Java のパッケージを import し、それを使ったり Java のクラスを継承したりすることもできます。

Java ではクラス名が重複しているときに完全修飾名を使わなければならなかったので面倒でしたが、 Kotlin では as で別名を付けることができるので楽ちんです。

// Kotlin
import java.util.Date
import java.sql.Date as SqlDate

まとめ

Kotlin は、構文の見た目が異なるだけで、ほぼ Java に対応しているのがわかると思います。今回の内容だけでも大体のプログラムは Kotlin で書けそうと思えたのではないでしょうか。 Kotlin を使えば、今回紹介したプライマリコンストラクタやプロパティを使うだけでも、 Java と比べてずいぶんとコードがすっきりします。今回の内容だけでも Kotlin を使う価値があると思うので、興味がわいてきたら是非試してみて下さい。

次の投稿: (2) 新しい考え方が必要でつまづきがちなところ(後日投稿)



  1. もちろん、 Kotlin を使いこなすにはそれなりの学習が必要ですが、 Java と同じように( +α で)使うにはそれくらいの学習期間でも可能です。 

  2. Java でも至るところ final を付けるスタイルでコーディングしている人もいるでしょうが、多くのサンプルコードでは final が付いていませんし、 final を付けていない人も多いと思うのでこのような表現にしました。 

  3. ただし、これは厳密には正しくなくて、 MutableListList を継承しているがために、 MutableList のインスタンスを List 型の変数に代入できます。 Java より良くなったとはいえ、これは Kotlin のイケてないところです。