社内でKotlin勉強会を開催しているのですが、そこでKotlinの文法をSwift、Javaで書くとどうなるんだろうという疑問がよく出てくるので、調べて実装した結果を描いていこうかと思います。
言語的には別物なので、厳密なイコールにはなりにくいかと思いますが、ご容赦ください。
※まめに更新します。
参照渡しの検証
Kotlin
Kotlinで参照渡しを実装するにはどうするか社内の勉強会で話題になったので、ちょっとした検証をしてみました。
Kotlinは明示的にミュータブルであると宣言しないと参照渡し的な使い方はできない様子。
また、引数に代入しようとするとコンパイルエラーになります。
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変数の書き換えが可能。
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は変わらなかったです。
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と違うところかと思います。
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 インタフェースのような実装ができる様子。
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になるので、ただの定数扱いとなる。)
interface Lang {
public static final String name = ""; // インタフェースに定義したフィールドは強制的に public staic final になる。
public String getHelloWorld(String target);
}
class English implements Lang {
// public String name; // 宣言しなくてもOK
@Override
public String getHelloWorld(String target) {
return "Hello, %TARGET%!".replace("%TARGET%", target);
}
}
class Japanese implements Lang {
@Override
public String getHelloWorld(String target) {
return "こんにちわ, %TARGET%!".replace("%TARGET%", target);
}
}
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で実装することができる。
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として覚えるよりはこちらの方が理解しやすい。
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と同じように組める。
interface Lang {
public String getHelloWorld(String target);
}
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コンテキストから参照することはできません エラーが出る。
}
}
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インタフェースの実態クラスに移譲してみました。
(正直クラス設計が微妙な気もする。。。)
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プロパティを持たないようにしました。
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と比べると長くなってしまっているかなと思います。
interface Lang {
public String getHelloWorld(String target);
public String getPopulation(String countryName, int population);
}
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));
}
}
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;
}
}
public class England extends Country {
public static final String NAME = "England";
public static final int POPULATION = 53013000;
public England() {
super(NAME, new English(), POPULATION);
}
}
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 安全
これから書く。