このドキュメントについて
本ドキュメントは Swift で書かれた iOS アプリを Android にポーティングする際のQoncept社内用ガイドライン を公開したものです。社内での利用を主目的としているため役に立つかわかりませんが、何かしら参考になれば幸いです。
※ 本ドキュメントは Swift から Java への移植を対象としていますが、 Swift から移植する場合、 Java ではなく Kotlin に移植すると幸せになれるかもしれません(特に Optional type とかクロージャとか)。 Java への移植を決定する前に一度 Kotlin を検討してみると良いと思います。
iOS と Android は異なるプラットフォームであり、それぞれに最適な設計は異なります。特に、 UI/UX についてその傾向は顕著です。しかし、現実には予算やスケジュール、クライアントの意向などの要因によって、 iOS アプリをそのまま Android にポーティングしなければならないことがあります。また、 Model ( MVC とか MVVM とかの Model です)に関してはまったく同じロジックになっている方がメンテナンス上望ましいことも多いです。本ドキュメントはそのような Swift のコードをそのまま Java にポーティングするケースを主な対象 としています。
Swiftがリリースされたことにより今後社内で iOS / Swift → Android / Java のポーティングが多くなることが見込まれたため、ひとまず ドラフト版として作成 しました。足りない点も多いですが、今後運用しながら改善していければと考えています。
iOS/Swift→Android/Javaの移植ガイドライン
Swift を Java にポーティングする際には、メンテナンス性を高めるためにこのドキュメントに基いてポーティング作業を行なって下さい。ただし、本ドキュメントは絶対的なルールではありません。 Java では大きく書き換えた方が良い箇所があれば、 Swift を書いた人に確認の上、作業を進めて下さい。
基本的な作業方針として、 Swift のコードをロジックや変数名をそのまま Java にポーティングして下さい。ただし、 Swift のコード中の viewController
を Java では activity
とするなど、明らかに読み替えた方が良いものは変更して下さい。識別子を変更する作業は、置換ではなく IDE ( Eclipse 、 Android Studio 等)の Refactor 機能を使って下さい。これは置換で変更するとコードを破壊する可能性があるためです。
例
let stringOrNil: String? = "ABC"
if let string = stringOrNil {
for character in string {
println(character)
}
} else {
println("No string")
}
final String stringOrNil = "ABC";
if (stringOrNull != null) {
final String string = stringOrNil; // 意味のないコードに見えるが、Swift側と変数名をそろえるために必要
for (char character : string.toCharArray()) {
System.out.println(character);
}
} else {
System.out.println("No string");
}
バージョン
- Swift 1.1
- Java 7
Java については、 Java 7 の新機能(特にダイアモンド演算子 \<\>
)を積極的に使うようにして下さい。
コメント
Swift のコード中のコメントはそのまま Java に移植して下さい。
例
/*
* 投票する。成人の場合にのみ有効。
*/
func vote() {
if age >= 20 { // 成年の場合
...
}
}
/*
* 投票する。成人の場合にのみ有効。
*/
public void vote() {
if (age >= 20) { // 成年の場合
...
}
}
変数
Swift で let
で宣言された変数はJavaでは final
で、 var
で宣言された変数は final
なしで宣言して下さい。
例
let a = 123
var b = "ABC"
final int a = 123;
String b = "ABC";
switch 文
Swift の switch
文の case
節の中は独立したスコープになっているため、次のように同名の変数を定義することができます。
switch foo {
case 0:
let a = 123
println(a)
case 1:
let a = 456
println(a)
default:
let a = 789
println(a)
}
Java で同じことを実現するために、 case
節を {}
で囲んでスコープを切るようにして下さい。
switch (foo) {
case 0: {
final int a = 123;
System.out.println(a);
break;
}
case 1: {
final int a = 456;
System.out.println(a);
break;
}
default: {
final int a = 789;
System.out.println(a);
break;
}
}
for 文
Swift には Java にはない便利な構文があるため、 Java で実装する際には等価なコードに変換して下さい。
例1
for i in 0..<100 {
...
}
for (int i = 0; i < 100; i++) {
...
}
例2
for (index, element) in enumerate([2, 3, 5, 7, 11, 13, 17]) {
println("The \(index + 1)th prime number is \(element)")
}
{ // 変数 index のスコープを限定するためにこの {} が必要
int index = 0;
for (int element : Arrays.asList(2, 3, 5, 7, 11, 13, 17)) {
System.out.println("The " + (index + 1) + "th prime number is " + element);
index++;
}
}
アクセス修飾子
アクセス修飾子については、次の表に示すような対応で変換して下さい。
Swift | Java |
---|---|
public | public |
(internal) (アクセス修飾子を省略) ※1 | public |
internal | (default) (アクセス修飾子を省略) |
private | private |
public, (internal) (protectedを意図している場合) ※2 | protected |
※1 Swift の internal
は Java の (default)
に近いですが、 Swift で書かれたアプリのコードでは public
を付けずに書くことが一般的なため、このようにポーティングルールを設定しています。
※2 protected
を意図している場合はSwiftのコード中に次のようにコメントを記します。
class Animal {
public /*protected*/ let numberOfLegs: Int
}
クラス
プレフィックスのないクラス(主に Swift で書かれたもの)
Swift でのクラス名をそのまま Java でのクラス名として下さい。
例
class Cat { ... }
public class Cat { ... }
プレフィックスのあるクラス(主に Objective-C で書かれたもの)
Swift でのクラス名からプレフィックスを取り除いて Java でのクラス名として下さい。既存アプリの改修の場合などに、 Objective-C のコードと Swift のコードが混在している可能性があります。
例
class XYZCat { ... }
public class Cat { ... }
抽象クラス
Swift に抽象クラスはありませんが、次のように実装されている場合は Java では抽象クラスとして実装して下さい。
class Animal {
private var numberOfLegs: Int
init(numberOfLegs: Int) {
self.numberOfLegs = numberOfLegs // dynamicTypeにアクセスする前にプロパティを初期化しなければならないのでここで初期化
if (self.dynamicType === Animal.self) { // 抽象クラスのインスタンス化を防ぐ
fatalError("Abstract")
}
}
func walk() {
fatalError("Abstract")
}
}
public abstract class Animal {
public Animal() {
}
public abstract void walk();
}
構造体( struct )
Java には構造体がないため、 Swift で定義された構造体は Java ではイミュータブルなクラスとして実装して下さい(値型はイミュータブルなクラスと等価なため)。
例
struct Vector {
var x: Float
var y: Float
}
var vector = Vector(x: 1, y: 2)
vector.x = 3
public class Vector {
private final float x;
private final float y;
public Vector(float x, float y) {
this.x = x;
this.y = y;
}
public float getX() {
return x;
}
public float getY() {
return y;
}
}
// 以下、どこかのメソッドの中
Vector vector = new Vector(1, 2);
vector = new Vector(3, vector.getY());
イニシャライザ
Swift のイニシャライザは、 Java ではコンストラクタとして実装して下さい。
例
class Animal {
private let name: String
init(name: String) {
self.name = name
}
}
public class Animal {
private final String name;
public Animal(String name) {
this.name = name;
}
}
プロトコル
Swift のプロトコルは、 Java ではインタフェースとして実装して下さい。命名についてはクラスと同様です。
例
protocol Animal { ... }
public interface Animal { ... }
列挙型( enum )
Swift の列挙型は、 Java でも列挙型として実装して下さい。
enum Direction {
case North, East, South, West
var reverse: Direction {
switch self {
case .North:
return .South
case .East:
return .West
case .South:
return .North
case .West:
return .East
}
}
}
public enum Direction {
NORTH, EAST, SOUTH, WEST; // Javaのenumの値は大文字のSNAKE_CASEで書くのが一般的
public Direction getReverse() {
switch (this) {
case NORTH:
return SOUTH;
case EAST:
return WEST;
case SOUTH:
return NORTH;
case WEST:
return EAST;
default:
throw new Error("Never reach here.");
}
}
}
プロパティ
Swift でのプロパティは、 Java において次のように実装して下さい。
- 通常のプロパティ( Stored Property )
-
public
またはinternal
の場合-
let
の場合: フィールドおよび getter -
var
の場合: フィールドおよび getter と setter
-
-
private
の場合- フィールド
-
-
Computed Property
- Read Only (
get
のみ)の場合: getter - Read/Write (
get
とset
)の場合: getter と setter
- Read Only (
Swift でのプロパティへのアクセスは、そのクラスのメソッド内でも getter / setter の呼び出しとして実装して下さい。ただし、 private
なプロパティへのアクセスはフィールドへのアクセスとして実装して下さい。また、イニシャライザの中でのプロパティへのアクセスについても、フィールドへのアクセスとして実装して下さい。
プロパティの型が Bool
の場合、対応する Java の getter 名は getXxx
ではなく isXxx
として下さい。
例
class Animal {
let name: String
var age: Int
private let numberOfLegs: Int
init(name: String, age: Int, numberOfLegs: Int) {
self.name = name
self.age = age
self.numberOfLegs = numberOfLegs
assert(self.age >= 0)
}
var detail: String {
return "\(name) (\(age)) with \(numberOfLegs) legs"
}
}
public class Animal {
private final String name;
private int age;
private final int numberOfLegs;
public Animal(String name, int age, int numberOfLegs) {
this.name = name; // イニシャライザの中でのアクセスはフィールドへのアクセスに置き換える。
this.age = age; // イニシャライザの中でのアクセスはフィールドへのアクセスに置き換える。
this.numberOfLegs = numberOfLegs;
assert this.age >= 0; // イニシャライザの中でのアクセスはフィールドへのアクセスに置き換える。
}
// nameプロパティはletなので対応するgetterを実装
public String getName() {
return name;
}
// ageプロパティはvarなので、対応するgetterとsetterを実装
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
// numberOfLegsプロパティはprivateなのでgetterを実装しない
// detailプロパティはRead Onlyなのでgetterを実装
public String getDetail() {
return getName() + " (" + getAge() + ") with " + numberOfLegs; // nameとageはgetterに、numberOfLegsは直接フィールドにアクセスしていることに注意
}
}
メソッド
Swift のメソッドを Java に移植するときは、基本的にはラベルを無視して下さい。
例
class Foo {
func bar(baz: Int, qux: String) {
...
}
}
let foo = Foo()
foo.bar(123, qux: "ABC") // 呼び出し時にqaxのラベルが必要
public class Foo {
public void bar(int baz, String qux) { // quxのラベルはメソッド名に現れない
...
}
}
// 以下、どこかのメソッドの中
final Foo foo = new Foo();
foo.bar(123, "ABC");
Optional, ImplicitlyUnwrappedOptional
Optional
( String?
や Optional<String>
など)および ImplicitlyUnwrappedOptional
( String!
や ImplicitlyUnwrappedOptional<String>
など)は、 Java では生の型として扱って下さい( Java 8 からは Optional
がありますが、 Android では現状で Java 7 までしか使えないので)。
ただし、 Swift で ImplicitlyUnwrappedOptional
から生の型に代入している箇所(引数に渡す場合も同じ)については暗黙的に Forced Unwrapping が発生するので、挙動を合わせる( null
だったら例外をスローする)ために後述の Swift2Java の x
メソッドを使って下さい。
例1
let nameOrNil: String? = ...
if let name = nameOrNil {
...
}
final String nameOrNil = ...;
if (nameOrNil != null) {
final String name = nameOrNil; // 意味のないコードに見えるが、Swift側と変数名をそろえるために必要
...
}
例2: ImplicitlyUnwrappedOptional
の代入で暗黙的に Forced Unwrapping が発生する例
let nameOrNil: String! = ...
let name: String = nameOrNil
final String nameOrNil = ...;
final String name = x(nameOrNil); // ここで暗黙的にForced Unwrappingが発生
例3: プリミティブ型に対応する型の Optional
Java でプリミティブ型に移植すべき型( Swift の Int
や Float
など)の Optional
については、プリミティブ型として移植すると null
を扱えないので、ラッパークラスの型として移植して下さい。
let fooOrNil: Int? = nil
if let foo = fooOrNil {
...
}
Integer fooOrNil = null;
if (fooOrNil != null) {
Integer foo = fooOrNil;
...
}
例4: Optional Binding で関数やメソッドの戻り値を使っているとき
if let foo = getFooOrNil() {
...
}
{ // 変数 foo のスコープを限定するためにこの {} が必要
final Foo foo = getFooOrNil();
if (foo != null) {
...
}
}
タプル
Swift にはタプルがありますが Java にはタプルがありません。そのため、移植の際にはこちらのライブラリを使って下さい。
上記ライブラリには Tuple0
, Tuple1<T1>
, Tuple2<T1, T2>
, Tuple3<T1, T2, T3>
, ... といったクラスが用意されています。リポジトリの gen ディレクトリには Tuple31
までが入っていますが、 src ディレクトリの TupleGenerator
を使って任意の要素数までのタプルクラスを生成することができます。
例1
let t: (Int, Double, String) = (123, 456.0, "789")
let integer = t.0
let string = t.2
final Tuple3<Integer, Double, String> t = new Tuple3<>(123, 456.0, "789");
final Integer integer = t.get0();
final String string = t.get2();
しかし、 Tuple
はイミュータブルであり、 Covariant であることを意図して設計されています。上記の Java のコードはワイルドカードを使って次のように書かれている方がより望ましいです。
final Tuple3<? extends Integer, ? extends Double, ? extends String> t = new Tuple3<>(123, 456.0, "789");
final Integer integer = t.get0();
final String string = t.get2();
ただし、 Swift のタプルは現時点では Invariant なようなので、 Swift からの移植という点では問題はないです。
※ Variance についてよくわからない場合はこちらを読んで下さい。
Function Type
Swift は第一級関数をサポートしているので関数を変数や引数に渡したり戻り値として受け取ったりすることができます。そのため、関数の型を表す構文( (Int) -> String
など)があります。
一方で Java にはそのような構文は存在しません。そのため、一つだけメソッドを持ったインタフェースを代わりに使います。 Java 8 にはそのための便利なインタフェース Function<T, R>
が用意されており、例えば引数で Integer
を受けて String
を返す関数は Function<Integer, String>
と書けます。
Java 7 にはそのようなインタフェースはないので、こちらのライブラリを利用して下さい。 Function
など、 Java 8 において関数の型を表すインタフェースを一式再現してあります。
Function
の他には、引数の型がない関数を表す Supplier<T>
(引数なく戻り値の型が T
)、戻り値ない( void
な)関数を表す Consumer<T>
(引数の型が T
で戻り値が void
)があります。戻り値が boolean
な関数は多いので、それに特化した Predicate<T>
(引数の型が T
で戻り値の型が boolean
)もあります。 また、引数が二つの場合に対応した、 BiFunction<T, U, R>
, BiConsumer<T, U>
および BiPredicate<T, U>
もあります。
しかし、それだけでは引数が三つ以上ある場合に対応することができません。 Java 8 にもそのようなインタフェースは用意されていません。そこで、引数が三つ以上の場合には前述のタプルを使います。
例1
let a: (Int) -> String = ...
let b: () -> String = ...
let c: (Int) -> Void = ...
let d: (Int) -> Bool = ...
let e: (Int, Double) -> String = ...
let f: (Int, Double, Char) -> String = ...
final Function<Integer, String> a = ...;
final Supplier<String> b = ...;
final Consumer<Integer> c = ...;
final Predicate<Integer> d = ...;
final BiFunction<Integer, Double, String> e = ...;
final Function<Tuple3<Integer, Double, Character>, String> f = ...;
ただし、タプルの項で説明したのと同様に、 Supplier
は Covariant, Consumer
と Predicate
は Contravariant, Function
は T
については Contravariant で R
については Covariant です。そのため、上記の Java のコードは次のように書かれている方がより望ましいです。
final Function<? super Integer, ? extends String> a = ...;
final Supplier<? extends String> b = ...;
final Consumer<? super Integer> c = ...;
final Predicate<? super Integer> d = ...;
final BiFunction<? super Integer, ? super Double, ? extends String> e = ...;
final Function<? super Tuple3<? extends Integer, ? extends Double, ? extends Character>, ? extends String> f = ...;
例2
ワイルドカードを使っておらず困る例に次のようなものが考えられます( Cat
は Animal
を継承したクラスとします)。
let getCat: () -> Cat = ...
let getAnimal: () -> Animal = getCat // 問題なし
final Supplier<Cat> getCat = ...;
final Supplier<Animal> getAnimal = getCat; // コンパイルエラー
次のようにワイルドカードを使えば問題ありません。
final Supplier<? extends Cat> getCat = ...;
final Supplier<? extends Animal> getAnimal = getCat; // 問題なし
※ Variance についてよくわからない場合はこちらを読んで下さい。
クロージャ
Java にはクロージャがないので、 Swift のクロージャは、前述の Function
などのインタフェースを実装した匿名内部クラスのインスタンスとして移植して下さい。
例1
let stringToInt: (String) -> Int = { $0.toInt()! }
final Function<String, Integer> stringToInt = new Function<String, Integer>() {
@Override
public Integer apply(String object) {
return Integer.parseInt(object);
}
}
こちらも、タプルの項や Function Type の項で述べたのと同じように Variance を考慮して次のように書く方が望ましいです。
final Function<? super String, ? extends Integer> stringToInt = new Function<String, Integer>() {
@Override
public Integer apply(String object) {
return Integer.parseInt(object);
}
}
※ Variance についてよくわからない場合はこちらを読んで下さい。
例2
次のようにクロージャが状態を持つ場合、Java の匿名内部クラスはそれを宣言するメソッド内の final
でないローカル変数にアクセスすることができないので、次のように要素数 1 の配列を作って Swift の挙動を再現して下さい。
var count: Int = 0
let getCount: () -> Int = { count++ }
println(getCount()) // 0
println(getCount()) // 1
println(getCount()) // 2
final int[] count = {0};
Supplier<? extends Integer> getCount = new Supplier<Integer>() {
@Override
public Integer get() {
return count[0]++;
}
};
System.out.println(getCount.get()); // 0
System.out.println(getCount.get()); // 1
System.out.println(getCount.get()); // 2
Void
Swift では Void
は ()
(空のタプルの型)のシンタックスシュガーです。稀なケースですが、 Swift で Void
型の値を扱っている箇所を移植しなければならない場合には Java の Void
クラスを使うのではなく空のタプルを表す Tuple0
クラスを使うようにして下さい。 Java の Void
クラスはインスタンスを生成できないため null
で代用すると、次のようなコードで想定外の誤動作を起こす可能性があります(次のコードは意味のない処理に見えますが、実際にジェネリックなプログラムを移植した際に意味のあるコードで Swift の Void
を Java の Void
に移植して問題になったことがあります)。
func foo<T>(tOrNil: T?) {
if let t = tOrNil {
println("X")
} else {
println("Y")
}
}
let a: Int = 123
foo(a) // X
let b: Int? = nil
foo(b) // Y
let c: Void = ()
foo(c) // X
public <T> void foo(T tOrNil) {
if (tOrNil != null) {
final T t = tOrNil;
System.out.println("X");
} else {
System.out.println("Y");
}
}
// 以下、どこかのメソッドの中
final Integer a = 123;
foo(123); // X
final Integer b = null;
foo(b); // Y
final Tuple0 c = new Tuple0();
foo(c); // X
// ダメな例
final Void d = null;
foo(d); // Y (cと同じことがやりたいけど結果が変わってしまう)
as, as?, is, ?, !, ??
Swift のコード中での as
, as?
, is
, ?
(Optional Chaining), !
(Forced Unwrapping), ??
はそれぞれ、 Swift2Java の Swift
クラスの static
メソッドである as
, asq
, is
, q
, x
, qq
に変換して下さい。
asq
や x
のメソッド名は、 ? = question, ! = exclamation によります。 Swift での ?
を q
に、 !
を x
に置き換えると考えるとわかりやすいです。
また、ここでの !
は ImplicitlyUnwrappedOptional
ではなく Forced Unwrapping であることに注意して下さい。 ImplicitlyUnwrappedOptional
は前述のように生の型として移植して下さい。
as
例1
let animal: Animal = Cat()
let cat = animal as Cat
Animal animal = new Cat();
Cat cat = as(animal, Cat.class); // the created Cat object
例2: 通常のキャストと違い null
をキャストしようとしてクラッシュする例
let animal: Animal? = nil
let cat = animal as Cat
Animal animal = null;
Cat cat = as(animal, Cat.class); // ClassCastException
as?
例1
let animal: Animal? = Cat()
let cat = animal as? Cat
Animal animal = new Cat();
Cat cat = asq(animal, Cat.class); // the created Cat object
例2
let animal: Animal? = nil
let cat = animal as? Cat
Animal animal = null;
Cat cat = asq(animal, Cat.class); // null
is
例1
let animal: Animal? = Cat()
let result = animal is Cat
Animal animal = new Cat();
boolean result = is(animal, Cat.class); // true
例2
let animal: Animal? = nil
let result = animal is Cat
Animal animal = new Cat();
boolean result = is(animal, Cat.class); // false
? (Optional Chaining)
例1
let s: String? = "abc"
let l = s?.length
String s = "abc";
Integer l = q(s, new Function<String, Integer>() {
@Override
public Integer apply(String object) {
return object.length();
}
}); // 3
例2
let s: String? = nil
let l = s?.utf16Count
String s = null;
Integer l = q(s, new Function<String, Integer>() {
@Override
public Integer apply(String object) {
return object.length();
}
}); // null
! (Forced Unwrapping)
例1
let s: String? = "abc"
let t = s!
String s = "abc";
String t = x(s);
例2
let s: String? = nil
let t = s!
String s = null;
String t = x(s); // NullPointerException
??
例1
let s = "abc"
let r = s ?? "xyz"
String s = "abc";
String r = qq(s, "xyz"); // "abc"
例2
let s: String? = nil
let r = s ?? "xyz"
String s = null;
String r = qq(s, "xyz"); // "xyz"
map, filter, reduce
map
, filter
, reduce
などの高階関数も Java 7 にはないので( Java 8 には Stream
があります)、 as
などと同じくSwift2Java の static
メソッドを使って移植して下さい。
なお下記の例で使っている Function
などのインタフェースは Function Type の項で説明したものです。
map
例: String の Array を Int の Array に変換
let result = ["123", "456", "789"].map { $0.toInt()! }
List<Integer> result = map(Arrays.asList("123", "456", "789"),
new Function<String, Integer>() {
@Override
public Integer apply(String t) {
return Integer.parseInt(t);
}
});
filter
例: 奇数の要素だけを残す
let result = [123, 456, 789].filter { $0 % 2 == 1 }
List<Integer> result = filter(Arrays.asList(123, 456, 789),
new Predicate<Integer>() {
@Override
public boolean test(Integer t) {
return t % 2 == 1;
}
});
reduce
例: 要素の合計を計算
let result = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].reduce(0) { $0 + $1 }
Integer result = reduce(
Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 0,
new BiFunction<Integer, Integer, Integer>() {
@Override
public Integer apply(Integer t, Integer u) {
return t + u;
}
});
Extension
Swift には Extension という構文があります。これは、既存の型に対して後からメソッドやプロパティ等を追加するものです。このような構文は Java にはないため、次のように移植して下さい。
ライブラリで宣言された型に対して Extension を実装する場合
Java で元の型に対してメソッドを付け足すことはできないので、 その型を第一引数としてとる static
メソッドとして実装して下さい。クラス名は、 Swift でのファイル名と同名にして下さい。
例
// ファイル名が ArrayExtension.swift の場合
extension Array {
func flatMap<U>(transform: (T) -> [U]) -> [U] {
return map(transform).reduce([]) { $0 + $1 }
}
}
import static jp.co.qoncept.swift.Swift.map;
import static jp.co.qoncept.swift.Swift.reduce;
...
public class ArrayExtension { // クラス名は Swift でのファイル名と同名とする
private ArrayExtension() { // インスタンス生成を防ぐためにコンストラクタを private にする
}
public static <T, U> List<U> flatMap(List<T> thiz,
Function<T, List<U>> transform) { // 第一引数の名前は thiz とする
return reduce(map(thiz, transform), new ArrayList<U>(),
new BiFunction<List<U>, List<U>, List<U>>() {
@Override
public List<U> apply(List<U> t, List<U> u) {
t.addAll(u);
return t;
}
});
}
}
これを利用する際には import static
するようにして下さい。
例
[[2], [3, 5], [7, 11, 13]].flatMap { $0 } // [2, 3, 5, 7, 11, 13]
import static foo.bar.ArrayExtension.flatMap;
import static java.util.Arrays.asList;
...
flatMap(asList(asList(2), asList(3, 5), asList(7, 11, 13)),
new Function<List<Integer>, List<Integer>>() {
@Override
public List<Integer> apply(List<Integer> t) {
return t;
}
}); // [2, 3, 5, 7, 11, 13]
アプリ内で宣言された独自の型に対して Extension を実装する場合
Swift では一つの型をあえて元の宣言と Extension に分けて実装することができます。そのアプリ内で宣言された独自の型に対してわざわざ Extension で分離して実装されている場合には、 Java ではすべて一つのクラス/インタフェース宣言の中に書くようにして下さい。
例
class Foo {
func bar() {
}
}
extension Foo {
func baz() {
}
}
public class Foo {
public void bar() {
}
public void baz() {
}
}
fatalError, assert
fatalError
は Error
の throw
に、 assert
は assert
に、次のように移植して下さい。
fatalError
fatalError("Some Error Message.");
throw new Error("Some Error Message.");
assert
assert(foo > 0, "Some Message.")
assert foo > 0 : "Some Message.";
標準ライブラリ
下記のように、それぞれの言語の標準ライブラリで対応するものが用意されている場合には対応するものを利用して下さい。
クラス、型
Swift | Objective-C | Java |
---|---|---|
Int | NSInteger | int, Integer ※1 |
UInt | NSUInteger | int, Integer ※1 |
Float | float | float, Float ※1 |
Double | double | double, Double ※1 |
CGFloat | CGFloat | double, Double ※1 |
NSTimeInterval | NSTimeInterval | double, Double ※1 |
String | NSString | String |
let Array<T> | NSArray | List<T> / Collections#unmodifiableList(List<T>) ※2 |
var Array<T> ※3 | NSMutableArray | List<T> / ArrayList<T> ※2 |
let Dictionary<T> | NSDictionary | Map<T> / Collections#unmodifiableMap(Map<T>) ※2 |
var Dictionary<T> ※3 | NSMutableDictionary | Map<T> / HashMap<T> ※2 |
T?, Optional<T> | T | T |
String (File path) | NSString (File path) | File |
NSURL | NSURL | URL |
UIViewController | UIViewController | Activity |
UIViewController (Child view controller) | UIViewController (Child view controller) | Fragment |
UIView | UIView | View |
UITableView | UITableView | ListView |
UIButton | UIButton | Button, ImageButton |
UIAlertView | UIAlertView | AlertDialog ※4 |
UIActionSheet | UIActionSheet | AlertDialog ※5 |
CGPoint | CGPoint | Vector2 ※6 |
CGSize | CGSize | Vector2 ※6 |
CGRect | CGRect | Rect2 ※6 |
※1 ラッパークラスは必要な場合(ジェネリクスの型パラメータなど)のみ使用して下さい。
※2 A / B の場合、変数の型としては A を、オブジェクトとしては B のインスタンスを使用して下さい。
※3 ただし、 Array
や Dictionary
自体の変更ではなく、変数を再代入可とするために var
を用いている場合は、オブジェクト生成のためには Collections#unmodifiableList(...)/unmodifiableMap(...)
を使用して下さい。
※4 AlertDialog.Builder#setPositiveButton(...)/setNegativeButton(...)
等を使用して下さい。
※5 AlertDialog.Builder#setItems()
を使用して下さい。
※6 Qoncept SDK のクラスです。
例1
let a = [1, 2, 3]
final List<Integer> a = Collections.unmodifiableList(Arrays.asList(1, 2, 3));
例2
var list = [1, 2, 3]
if flag {
list = []
}
// Swiftでvarで宣言されているが、ミュータブルであることを意図しているのではなく、変数への再代入を可能にすることを意図しているので、JavaではunmodifiableListとする。
List<Integer> list = Collections.unmodifiableList(Arrays.asList(1, 2, 3));
if (flag) {
list = Collections.emptyList();
}
例3
class SomeClass : UIAlertViewDelegate {
func someMethod() {
UIAlertView(title: "Title", message: "Message", delegate: self, cancelButtonTitle: "Cancel", otherButtonTitles: "OK")
}
func alertView(alertView: UIAlertView, clickedButtonAtIndex buttonIndex: Int) {
switch buttonIndex {
case 0:
println("Cancel was clicked.")
default:
println("OK was clicked")
}
}
}
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Title");
builder.setMessage("Message");
builder.setCancelable(false);
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
System.out.println("OK was clicked.");
}
});
builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
System.out.println("Cancel was clicked.");
}
});
builder.create().show();
プロパティ、メソッド、演算子
Swift | Java |
---|---|
Printable#description | toString() |
Equatable#== | equals(...) |
Hashable#hashValue | hashCode() |
Comparable#<, <=, >, >= | Comparable#compareTo(...) |
UIView#animateWithDuration... | Animationのサブクラス |
例1
class Foo : Hashable, Printable {
let name: String
init(name: String) {
self.name = name
}
var hashValue: Int {
return name.hashValue
}
var description: String {
return "Foo(\(name))"
}
}
func ==(lhs: Foo, rhs: Foo) -> Bool {
return lhs.name == rhs.name
}
public class Foo {
private final String name;
public Foo(String name) {
this.name = name;
}
public String getName() {
return name;
}
public int hashCode() {
return name.hashCode();
}
public boolean equals(Object object) {
if (!( object instanceof Foo)) {
return false;
}
Foo another = (Foo)object;
return name.equals(another.name);
}
public String toString() {
return "Foo(" + name + ")";
}
}
例2
someView.alpha = 0.0
UIView.animateWithDuration(0.5, animations: { () -> Void in
self.someView.alpha = 1.0
}) { (finished) -> Void in
// Do something
}
Animation animation = new AlphaAnimation(0.0f, 1.0f);
animation.setDuration(500L);
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(
Animation animation) {
}
@Override
public void onAnimationRepeat(
Animation animation) {
}
@Override
public void onAnimationEnd(
Animation animation) {
// Do something
}
});
someView.startAnimation(animation);
Array、Dictionary
Swiftの Array
と Dictionary
は値型なので、そのまま Java の List
や Map
に移植すると挙動が変わってしまう場合があります。
例1
var a = [1, 2, 3]
var b = a
b.append(4) // a = [1, 2, 3], b = [1, 2, 3, 4]
List<Integer> a = new ArrayList<>(Arrays.asList(1, 2, 3));
List<Integer> b = a;
b.add(4); // a = [1, 2, 3, 4], b = [1, 2, 3, 4]
このような場合は、 Swift のコードでの Array の代入に対応する箇所で、新しい List
のオブジェクトを生成するようにします。
すべての代入でコピーが必要なのではなく、必要に応じてコピーするようにして下さい
List<Integer> a = new ArrayList<>(Arrays.asList(1, 2, 3));
List<Integer> b = new ArrayList<>(a);
b.add(4); // a = [1, 2, 3], b = [1, 2, 3, 4]
例2
Swift でイニシャライザやメソッドに Array
等を渡しオブジェクト内に保持するようなケースでは、Javaでは List
のコピーを保持するようにして下さい。 getter 等で return
する際には、 return
した List
を書き換えられてしまわないように、 Collections#unmodifiableList(...)
でラップして return
するようにして下さい。
class Foo {
var list: [Int]
init(list: [Int]) {
self.list = list
}
}
public class Foo {
private List<Integer> list;
public Foo(List<Integer> list) {
this.list = new ArrayList<>(list);
}
public List<Integer> getList() {
return Collections.unmodifiableList(list);
}
public void setList(List<Integer> list) {
this.list = new ArrayList<>(list);
}
}
上記コードの Java の Foo#getList()
の実装では、 getList()
した後に Foo#list
が書き換えられると、 getList()
で得たオブジェクトが変更されてしまいます。そのため、厳密には Swift のコードと等価になりません。しかし、そのような挙動が問題になるケースはまれですし、コピーのコストがパフォーマンス上の問題を引き起こす可能性の方が大きいと判断し、 new ArrayList\<\>(list)
ではなく Collections.unmodifiableList(list)
をポーティングルールとして採用しました。
Delegate
Swift では View Controller 等を Delegate として実装することが多いですが、特に必要がない限り、 Java では匿名内部クラスとして実装して下さい。
class SomeViewController : UIViewController, FooDelegate {
func someMethod() {
let foo = Foo()
foo.delegate = self
foo.playBar(Bar())
}
func foo(foo: Foo, didPlayBar: Bar) {
...
}
}
public class SomeActivity extends Activity {
public void someMethod() {
final Foo foo = new Foo();
foo.setDelegate(new Foo.Delegate() {
public void didPlayBar(bar: Bar) {
...
}
});
foo.doBar(new Bar());
}
}
OpenGL ES
OpenGL ES の関数は、 Java では import static
を使い、関数のように書いて下さい。
glEnableClientState(GLenum(GL_VERTEX_ARRAY))
glVertexPointer(3, GLenum(GL_FLOAT), 0, vertices)
glEnableClientState(GLenum(GL_COLOR_ARRAY))
glColorPointer(4, GLenum(GL_UNSIGNED_BYTE), 0, colors)
glDrawArrays(GLenum(GL_TRIANGLE_STRIP), 0, 4)
glDisableClientState(GLenum(GL_COLOR_ARRAY))
glDisableClientState(GLenum(GL_VERTEX_ARRAY))
import static android.opengl.GLES10.GL_COLOR_ARRAY;
import static android.opengl.GLES10.GL_FLOAT;
import static android.opengl.GLES10.GL_TRIANGLE_STRIP;
import static android.opengl.GLES10.GL_UNSIGNED_BYTE;
import static android.opengl.GLES10.GL_VERTEX_ARRAY;
import static android.opengl.GLES10.glColorPointer;
import static android.opengl.GLES10.glDisableClientState;
import static android.opengl.GLES10.glDrawArrays;
import static android.opengl.GLES10.glEnableClientState;
import static android.opengl.GLES10.glVertexPointer;
...
// 以下、どこかのメソッドの中
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, vertices);
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer(4, GL_UNSIGNED_BYTE, 0, colors);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
一つずつ import static
を書くと大変なので、 import static android.opengl.GLES10.*;
で一度書いてから、最後に Ctrl ( Mac は Command ) + Shift + O で展開( Eclipse の場合)するのがオススメです。