Java
Kotlin
メモ
Swift

JavaエンジニアがSwift、Kotlin、Javaを比べてみました。

社内でKotlin勉強会を開催しているのですが、そこでKotlinの文法をSwift、Javaで書くとどうなるんだろうという疑問がよく出てくるので、調べて実装した結果を描いていこうかと思います。
言語的には別物なので、厳密なイコールにはなりにくいかと思いますが、ご容赦ください。
※まめに更新します。

参照渡しの検証

Kotlin

Kotlinで参照渡しを実装するにはどうするか社内の勉強会で話題になったので、ちょっとした検証をしてみました。

Kotlinは明示的にミュータブルであると宣言しないと参照渡し的な使い方はできない様子。
また、引数に代入しようとするとコンパイルエラーになります。

ArrayTest.kt
fun addArray(array: MutableList<Int>) {
    array.add(9)
    println(array) // [1, 2, 3, 9]
}

fun changeArray(array: MutableList<Int>) {
    array = mutableListOf(1, 2, 3) // 引数に代入はできない。コンパイルエラーになる。
    println(array)
}

fun main(args: Array<String>) {
    val array = mutableListOf(1, 2, 3)
    println(array) // [1, 2, 3]
    addArray(array)
    println(array) // [1, 2, 3, 9]
}

Swift

Swiftはinoutで参照渡しであることを宣言すればできる(らしい)。
参照渡しなので関数内でarray変数の書き換えが可能。

ArrayTest.swift
func addArray(array : inout [Int])  {
    array.append(9)
    print(array)  // [1, 2, 3, 9]
}

func substituteArray(array : inout [Int])  {
    array = [100, 200, 300]
    print(array)  //  [100, 200, 300]
}

var array = [1, 2, 3]
print(array)    // [1, 2, 3]
addArray(array: &array)
print(array)    // [1, 2, 3, 9]
substituteArray(array: &array)
print(array)    // [100, 200, 300]

Java

Javaで同じように組んでみたが、arrayのメソッドを使って変更を加えることはできますが、
新しくインスタンス化して参照メソッド内で代入しても元の変数が示すarrayは変わらなかったです。

ArrayTest.java
import java.util.ArrayList;

class ArrayTest {
    private static void addArray(ArrayList<Integer> array) {
        array.add(9);
        System.out.println(array); // [1, 2, 3, 9]
    }

    private static void substituteArray(ArrayList<Integer> array) {
        array = new ArrayList<Integer>(); // ここで新しいArrayListを代入しても元のスコープに変更はない。
        array.add(100);
        array.add(200);
        array.add(300);
        System.out.println(array); // [100, 200, 300]
    }

    public static void main(String args[]) {
        ArrayList<Integer> array = new ArrayList<Integer>();
        array.add(1);
        array.add(2);
        array.add(3);
        System.out.println(array); // [1, 2, 3]
        addArray(array);
        System.out.println(array); // [1, 2, 3, 9]
        substituteArray(array);
        System.out.println(array); // [1, 2, 3, 9]([100, 200, 300]にはならない)
    }
}

インタフェース

Kotlin

Kotlin のインタフェースの使い方は特にJavaと差異はないかと思いましたが、フィールド変数にも制約を加えられるのがJavaと違うところかと思います。

InterfaceTest.kt
interface Lang {
    val name: String
    fun getHelloWorld(target: String) : String
}

class English : Lang {
    override val name = "English" // 宣言しないとコンパイルエラー
    override fun getHelloWorld(target: String) : String {
        return "Hello, $target!"
    }
}

class Japanese : Lang {
    override val name = "日本語"
    override fun getHelloWorld(target: String) : String {
        return "こんにちは、 $target!"
    }
}

fun main(args: Array<String>) {
        val Elang = English()
    println(Elang.getHelloWorld("world")) // Hello, world!
    val Jlang = Japanese()
    println(Jlang.getHelloWorld("world")) // こんにちは、 world!

}

Swift

Swiftの場合はProtocolで Kotlin インタフェースのような実装ができる様子。

InterfaceTest.swift
protocol Lang {
    var name : String {get}
    func getHelloWorld(target :String) -> String
}

class English: Lang {

    let name = "English"
    func getHelloWorld(target : String)  -> String {
        return "Hello, \(target)"
    }
}

class Japanese: Lang {

    let name = "Japanese"
    func getHelloWorld(target : String)  -> String {
        return "こんにちは, \(target)"
    }
}

let Elang = English()
print(Elang.getHelloWorld(target: "world")) // Hello, world!
let Jlang = Japanese()
print(Jlang.getHelloWorld(target: "world")) // こんにちは, world

Java

あんまりそういう使い方をしようと思わなかったので気づかなかったが、JavaはInterfaceにフィールドを定義したところで、実態クラスにフィールドを持たせるように強制することはできない。
(というかは強制的に public staic finalになるので、ただの定数扱いとなる。)

Lang.java
interface Lang {
    public static final String name = ""; // インタフェースに定義したフィールドは強制的に public staic final になる。
    public String getHelloWorld(String target);
}
English.java
class English implements Lang {
    // public String name; // 宣言しなくてもOK

    @Override
    public String getHelloWorld(String target) {
        return "Hello, %TARGET%!".replace("%TARGET%", target);
    }
}
Japanese.java
class Japanese implements Lang {

    @Override
    public String getHelloWorld(String target) {
        return "こんにちわ, %TARGET%!".replace("%TARGET%", target);
    }
}
InterfaceTest.java
class InterfaceTest {
    public static void main(String args[]) {
        English Elang = new English();
        System.out.println(Elang.getHelloWorld("world")); // Hello, world!
        Japanese Jlang = new Japanese();
        System.out.println(Jlang.getHelloWorld("world")); // こんにちは, world
    }
}

Companion オブジェクト

Kotlin

Java や他の言語でいう所の静的アクセス(スタティック、Static)をKotlinではCompanion Objectで実装することができる。

CompanionObjectTest.kt
interface Lang {
    val name: String
    fun getHelloWorld(target: String) : String
}

class English : Lang {
    override val name = "English"
    override fun getHelloWorld(target: String) : String {
        return "Hello, $target!"
    }
    companion object Factory { // Companion object
        fun create(): English = English()
    }
}

fun main(args: Array<String>) {
    val Egreeter = English.create() // CreateメソッドでEnglishの実体を生成
    println(Egreeter.getHelloWorld("world"))
}

色々試してみたのですが、CompanionObjectは以下のような動作をする。

  • 1クラス内に定義できるCompanionObjectは一つ
  • 静的アクセスなので、CompanionObject内から所属クラスのフィールド、メソッドにはアクセスできない。
  • const を付けて定数を定義できるし、companion object内に変数を宣言することもできる。
class English : Lang {
    override val name = "English"
    override fun getHelloWorld(target: String) : String {
        return "Hello, $target!"
    }
    companion object Factory { // Factoryはあってもなくてもいいけど、javaからcompanion object内にアクセスするのに必要。
        const val COMPANION_NAME = "Companion" // 定数の定義ができる。
        val companionName = "test" // constを付けなくても静的アクセスできるフィールド変数を宣言できる。
        fun create(): English = English()
        fun getNameCom(): String {
            // return name // Companion Object 内から Englishのフィールドにはアクセスできない。
            return companionName  // Companion Object内の変数ならアクセスできる
        }
    }
}

基本的には定数値か、createメソッドのようなFactoryパターンを実装するときに使用することを推奨しているように見える。

Swift

Swiftの場合はstatic宣言することで同様の動きを実装することができる。
どちらかというと、Companion Objectとして覚えるよりはこちらの方が理解しやすい。

StaticTest.swift
protocol Lang {
    var name : String {get}
    func getHelloWorld(target :String) -> String
}

class English: Lang {

    let name = "English"
    func getHelloWorld(target : String)  -> String {
        return "Hello, \(target)"
    }
    static var COMPANION_NAME = "Companion"
    static func create() -> English {
        return English()
    }
    static func getName() -> String {
        return name   // Staticな関数からはメンバ変数にはアクセスできない。
    }

}

let Egreeter = English.create()
print(Egreeter.getHelloWorld(target: "world"))
print(English.COMPANION_NAME) // 定数なのでアクセスできる

Java

Java もSwiftと同じように組める。

Lang.java
interface Lang {
    public String getHelloWorld(String target);
}
English.java
class English implements Lang {
    public String name = "English";

    @Override
    public String getHelloWorld(String target) {
        return "Hello, %TARGET%!".replace("%TARGET%", target);
    }

    public static final String COMPANION_NAME = "Companion";

    public static English create(){
        return new English();
    }
    public static String getName() {
        return name; // staticでない変数 nameをstaticコンテキストから参照することはできません エラーが出る。
    }
}
StaticTest.java
class StaticTest {
    public static void main(String args[]) {
        English Elang = English.create();
        System.out.println(Elang.getHelloWorld("world"));
        System.out.println(English.COMPANION_NAME);
    }
}

クラスの継承とコンストラクタ

Kotlin

クラスの継承とコンストラクタのやり方を調べるために、Englandクラスとその抽象クラスのCountryクラスを作成し、属性として、国名、言語、人口を持たせてみました。
また、国からは、挨拶の言葉と、人口が何人かを取得できるとし、実際の処理はLangインタフェースの実態クラスに移譲してみました。
(正直クラス設計が微妙な気もする。。。)

ConstructorTest.kt
interface Lang {
    val name: String
    fun getHelloWorld(target: String) : String
    fun getPopulation(countryName: String, population : Int) : String
}

class English : Lang {
    override val name = "English"
    override fun getHelloWorld(target: String) : String {
        return "Hello, $target!"
    }
    override fun getPopulation(countryName: String, population : Int) : String {
        return "$countryName has a population of $population people."
    }
}

// 基底クラスに国名、言語、人口の属性を持たせて、必須になるようにする
abstract class Country (_name : String, _lang: Lang, _population: Int) {
    val name : String = _name
    val lang : Lang = _lang
    val population = _population
    fun getPopulation() : String {
        return lang.getPopulation(name, population)
    }
}

class England() : Country("England", English(), 53013000)

fun main(args: Array<String>) {
    val country = England()

    println(country.name)
    println(country.lang.getHelloWorld("world"))
    println(country.getPopulation())

}

Swift

Swiftで書き直してみましたが、Swiftには抽象クラスのような考えがないので、あまりCountryクラスを実装する必要性はないと思いました。
Protocolを型として利用するのも推奨していない(と言うかできない?)ようなので、無理にスーパークラスにlangプロパティを持たないようにしました。

ConstructorTest.kt
import Foundation

protocol Lang {
    var name : String {get}
    func getHelloWorld(target :String) -> String
    func getPopulation(countryName: String, population : Int) -> String
}

class English: Lang {

    let name = "English"
    func getHelloWorld(target : String)  -> String {
        return "Hello, \(target)"
    }

    func getPopulation(countryName: String, population : Int) -> String {
        return "\(countryName) has a population of \(population) people."
    }
}
class Country {
    var name : String = ""
    var population : Int = 0
    init(_name : String, _population : Int) {
        name = _name
        population = _population
    }
}

class England : Country {
    var lang = English()

     init() {
        super.init(_name: "England", _population: 53013000)
    }

    func getPopulation() -> String {
        return self.lang.getPopulation(countryName: name, population: population)
    }

    func getHelloWorld(target: String) -> String {
        return self.lang.getHelloWorld(target: target)
    }
}

var country = England()

print(country.name)
print(country.getHelloWorld(target: "world"))
print(country.getPopulation())

Java

基本的にはKotlinと同じですが、Javaの作法に合わせてプロパティアクセスをgetterに変えています。
その分、コードとしてはやはりKotlinと比べると長くなってしまっているかなと思います。

Lang.java
interface Lang {
    public String getHelloWorld(String target);
    public String getPopulation(String countryName, int population);
}
English.java
class English implements Lang {

    private String name = "English";

    public String getName() {
         return name;
    }

    @Override
    public String getHelloWorld(String target) {
        return "Hello, %TARGET%!".replace("%TARGET%", target);
    }

    @Override
    public String getPopulation(String countryName, int population) {
    return  "%COUNTRY_NAME% has a population of %POPULATION% people."
        .replace("%COUNTRY_NAME%", countryName)
        .replace("%POPULATION%", Integer.toString(population));
    }

}
Country.java
public abstract class Country {
    private String name;
    private Lang lang;
    private int population;

    public Country(String _name, Lang _lang, int _population) {
        name = _name;
        lang = _lang;
        population = _population;
    }

    public String getPopulation() {
        return lang.getPopulation(name, population);
    }

    public String getName() {
        return name;
    }

    public Lang getLang() {
        return lang;
    }

}
England.java
public class England extends Country {

    public static final String NAME = "England";
    public static final int POPULATION = 53013000;

    public England() {
        super(NAME, new English(), POPULATION);
    }
}

ConstructorTest.java
class ConstructorTest {

    public static void main(String args[]) {
        England country = new England();

        System.out.println(country.getName());
        System.out.println(country.getLang().getHelloWorld("world"));
        System.out.println(country.getPopulation());
    }
}

Null 安全

これから書く。