LoginSignup
195
198

More than 5 years have passed since last update.

iOS/Swift→Android/Javaの移植ガイドライン

Last updated at Posted at 2014-11-06

このドキュメントについて

本ドキュメントは 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 機能を使って下さい。これは置換で変更するとコードを破壊する可能性があるためです。

Swift
let stringOrNil: String? = "ABC"

if let string = stringOrNil {
    for character in string {
        println(character)
    }
} else {
    println("No string")
}
Java
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 に移植して下さい。

Swift
/*
 * 投票する。成人の場合にのみ有効。
 */
func vote() {
    if age >= 20 { // 成年の場合
        ...
    }
}
Java
/*
 * 投票する。成人の場合にのみ有効。
 */
public void vote() {
    if (age >= 20) { // 成年の場合
        ...
    }
}

変数

Swift で let で宣言された変数はJavaでは final で、 var で宣言された変数は final なしで宣言して下さい。

Swift
let a = 123
var b = "ABC"
Java
final int a = 123;
String b = "ABC";

switch 文

Swift の switch 文の case 節の中は独立したスコープになっているため、次のように同名の変数を定義することができます。

Swift
switch foo {
case 0:
    let a = 123
    println(a)
case 1:
    let a = 456
    println(a)
default:
    let a = 789
    println(a)
}

Java で同じことを実現するために、 case 節を {} で囲んでスコープを切るようにして下さい。

Java
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

Swift
for i in 0..<100 {
    ...
}
Java
for (int i = 0; i < 100; i++) {
    ...
}

例2

Swift
for (index, element) in enumerate([2, 3, 5, 7, 11, 13, 17]) {
    println("The \(index + 1)th prime number is \(element)")
}
Java
{ // 変数 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 でのクラス名として下さい。

Swift
class Cat { ... }
Java
public class Cat { ... }

プレフィックスのあるクラス(主に Objective-C で書かれたもの)

Swift でのクラス名からプレフィックスを取り除いて Java でのクラス名として下さい。既存アプリの改修の場合などに、 Objective-C のコードと Swift のコードが混在している可能性があります。

Swift
class XYZCat { ... }
Java
public class Cat { ... }

抽象クラス

Swift に抽象クラスはありませんが、次のように実装されている場合は Java では抽象クラスとして実装して下さい。

Swift
class Animal {
    private var numberOfLegs: Int

    init(numberOfLegs: Int) {
        self.numberOfLegs = numberOfLegs // dynamicTypeにアクセスする前にプロパティを初期化しなければならないのでここで初期化

        if (self.dynamicType === Animal.self) { // 抽象クラスのインスタンス化を防ぐ
            fatalError("Abstract")
        }
    }

    func walk() {
        fatalError("Abstract")
    }
}
Java
public abstract class Animal {
    public Animal() {
    }

    public abstract void walk();
}

構造体( struct )

Java には構造体がないため、 Swift で定義された構造体は Java ではイミュータブルなクラスとして実装して下さい(値型はイミュータブルなクラスと等価なため)。

Swift
struct Vector {
    var x: Float
    var y: Float
}

var vector = Vector(x: 1, y: 2)
vector.x = 3
Java
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 ではコンストラクタとして実装して下さい。

Swift
class Animal {
    private let name: String

    init(name: String) {
        self.name = name
    }
}
Java
public class Animal {
    private final String name;

    public Animal(String name) {
        this.name = name;
    }
}

プロトコル

Swift のプロトコルは、 Java ではインタフェースとして実装して下さい。命名についてはクラスと同様です。

Swift
protocol Animal { ... }
Java
public interface Animal { ... }

列挙型( enum )

Swift の列挙型は、 Java でも列挙型として実装して下さい。

Swift
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
        }
    }
}
Java
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 の場合: フィールドおよび gettersetter
    • private の場合
      • フィールド
  • Computed Property
    • Read Only ( get のみ)の場合: getter
    • Read/Write ( getset )の場合: gettersetter

Swift でのプロパティへのアクセスは、そのクラスのメソッド内でも getter / setter の呼び出しとして実装して下さい。ただし、 private なプロパティへのアクセスはフィールドへのアクセスとして実装して下さい。また、イニシャライザの中でのプロパティへのアクセスについても、フィールドへのアクセスとして実装して下さい。

プロパティの型が Bool の場合、対応する Java の getter 名は getXxx ではなく isXxx として下さい。

Swift
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"
    }
}
Java
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 に移植するときは、基本的にはラベルを無視して下さい。

Swift
class Foo {
    func bar(baz: Int, qux: String) {
        ...
    }
}

let foo = Foo()
foo.bar(123, qux: "ABC") // 呼び出し時にqaxのラベルが必要
Java
public class Foo {
    public void bar(int baz, String qux) { // quxのラベルはメソッド名に現れない
        ...
    }
}

// 以下、どこかのメソッドの中
final Foo foo = new Foo();
foo.bar(123, "ABC");

Optional, ImplicitlyUnwrappedOptional

OptionalString?Optional<String> など)および ImplicitlyUnwrappedOptionalString! や ImplicitlyUnwrappedOptional<String> など)は、 Java では生の型として扱って下さい( Java 8 からは Optional がありますが、 Android では現状で Java 7 までしか使えないので)。

ただし、 Swift で ImplicitlyUnwrappedOptional から生の型に代入している箇所(引数に渡す場合も同じ)については暗黙的に Forced Unwrapping が発生するので、挙動を合わせる( null だったら例外をスローする)ために後述の Swift2Javax メソッドを使って下さい。

例1

Swift
let nameOrNil: String? = ...

if let name = nameOrNil {
    ...
}
Java
final String nameOrNil = ...;

if (nameOrNil != null) {
    final String name = nameOrNil; // 意味のないコードに見えるが、Swift側と変数名をそろえるために必要

    ...
}

例2: ImplicitlyUnwrappedOptional の代入で暗黙的に Forced Unwrapping が発生する例

Swift
let nameOrNil: String! = ...
let name: String = nameOrNil
Java
final String nameOrNil = ...;
final String name = x(nameOrNil); // ここで暗黙的にForced Unwrappingが発生

例3: プリミティブ型に対応する型の Optional

Java でプリミティブ型に移植すべき型( Swift の IntFloat など)の Optional については、プリミティブ型として移植すると null を扱えないので、ラッパークラスの型として移植して下さい。

Swift
let fooOrNil: Int? = nil
if let foo = fooOrNil {
    ...
}
Java
Integer fooOrNil = null;
if (fooOrNil != null) {
    Integer foo = fooOrNil;
    ...
}

例4: Optional Binding で関数やメソッドの戻り値を使っているとき

Swift
if let foo = getFooOrNil() {
    ...
}
Java
{ // 変数 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

Swift
let t: (Int, Double, String) = (123, 456.0, "789")
let integer = t.0
let string = t.2
Java
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 のコードはワイルドカードを使って次のように書かれている方がより望ましいです。

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

Swift
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 = ...
Java
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 = ...;

ただし、タプルの項で説明したのと同様に、 SupplierCovariant, ConsumerPredicateContravariant, FunctionT については ContravariantR については Covariant です。そのため、上記の Java のコードは次のように書かれている方がより望ましいです。

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

ワイルドカードを使っておらず困る例に次のようなものが考えられます( CatAnimal を継承したクラスとします)。

Swift
let getCat: () -> Cat = ...
let getAnimal: () -> Animal = getCat // 問題なし
Java
final Supplier<Cat> getCat = ...;
final Supplier<Animal> getAnimal = getCat; // コンパイルエラー

次のようにワイルドカードを使えば問題ありません。

Java
final Supplier<? extends Cat> getCat = ...;
final Supplier<? extends Animal> getAnimal = getCat; // 問題なし

Variance についてよくわからない場合はこちらを読んで下さい。

クロージャ

Java にはクロージャがないので、 Swift のクロージャは、前述の Function などのインタフェースを実装した匿名内部クラスのインスタンスとして移植して下さい。

例1

Swift
let stringToInt: (String) -> Int = { $0.toInt()! }
Java
final Function<String, Integer> stringToInt = new Function<String, Integer>() {
    @Override
    public Integer apply(String object) {
        return Integer.parseInt(object);
    }
}

こちらも、タプルの項や Function Type の項で述べたのと同じように Variance を考慮して次のように書く方が望ましいです。

Java
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 の挙動を再現して下さい。

Swift
var count: Int = 0
let getCount: () -> Int = { count++ }

println(getCount()) // 0
println(getCount()) // 1
println(getCount()) // 2
Java
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 に移植して問題になったことがあります)。

Swift
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
Java
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), ?? はそれぞれ、 Swift2JavaSwift クラスの static メソッドである as, asq, is, q, x, qq に変換して下さい。

asqx のメソッド名は、 ? = question, ! = exclamation によります。 Swift での ?q に、 !x に置き換えると考えるとわかりやすいです。

また、ここでの !ImplicitlyUnwrappedOptional ではなく Forced Unwrapping であることに注意して下さい。 ImplicitlyUnwrappedOptional は前述のように生の型として移植して下さい。

as

例1

Swift
let animal: Animal = Cat()
let cat = animal as Cat
Java
Animal animal = new Cat();
Cat cat = as(animal, Cat.class); // the created Cat object

例2: 通常のキャストと違い null をキャストしようとしてクラッシュする例

Swift
let animal: Animal? = nil
let cat = animal as Cat
Java
Animal animal = null;
Cat cat = as(animal, Cat.class); // ClassCastException

as?

例1

Swift
let animal: Animal? = Cat()
let cat = animal as? Cat

Java
Animal animal = new Cat();
Cat cat = asq(animal, Cat.class); // the created Cat object

例2

Swift
let animal: Animal? = nil
let cat = animal as? Cat
Java
Animal animal = null;
Cat cat = asq(animal, Cat.class); // null

is

例1

Swift
let animal: Animal? = Cat()
let result = animal is Cat
Java
Animal animal = new Cat();
boolean result = is(animal, Cat.class); // true

例2

Swift
let animal: Animal? = nil
let result = animal is Cat
Java
Animal animal = new Cat();
boolean result = is(animal, Cat.class); // false

? (Optional Chaining)

例1

Swift
let s: String? = "abc"
let l = s?.length
Java
String s = "abc";
Integer l = q(s, new Function<String, Integer>() {
    @Override
    public Integer apply(String object) {
        return object.length();
    }
}); // 3

例2

Swift
let s: String? = nil
let l = s?.utf16Count
Java
String s = null;
Integer l = q(s, new Function<String, Integer>() {
    @Override
    public Integer apply(String object) {
        return object.length();
    }
}); // null

! (Forced Unwrapping)

例1

Swift
let s: String? = "abc"
let t = s!
Java
String s = "abc";
String t = x(s);

例2

Swift
let s: String? = nil
let t = s!
Java
String s = null;
String t = x(s); // NullPointerException

??

例1

Swift
let s = "abc"
let r = s ?? "xyz"

Java
String s = "abc";
String r = qq(s, "xyz"); // "abc"

例2

Swift
let s: String? = nil
let r = s ?? "xyz"
Java
String s = null;
String r = qq(s, "xyz"); // "xyz"

map, filter, reduce

map, filter, reduce などの高階関数も Java 7 にはないので( Java 8 には Stream があります)、 as などと同じくSwift2Javastatic メソッドを使って移植して下さい。

なお下記の例で使っている Function などのインタフェースは Function Type の項で説明したものです。

map

例: String の Array を Int の Array に変換

Swift
let result = ["123", "456", "789"].map { $0.toInt()! }
Java
List<Integer> result = map(Arrays.asList("123", "456", "789"),
    new Function<String, Integer>() {
        @Override
        public Integer apply(String t) {
            return Integer.parseInt(t);
        }
    });

filter

例: 奇数の要素だけを残す

Swift
let result = [123, 456, 789].filter { $0 % 2 == 1 }
Java
List<Integer> result = filter(Arrays.asList(123, 456, 789),
    new Predicate<Integer>() {
        @Override
        public boolean test(Integer t) {
            return t % 2 == 1;
        }
    });

reduce

例: 要素の合計を計算

Swift
let result = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].reduce(0) { $0 + $1 }
Java
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 でのファイル名と同名にして下さい。

Swift
// ファイル名が ArrayExtension.swift の場合
extension Array {
    func flatMap<U>(transform: (T) -> [U]) -> [U] {
        return map(transform).reduce([]) { $0 + $1 }
    }
}
Java
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 するようにして下さい。

Swift
[[2], [3, 5], [7, 11, 13]].flatMap { $0 } // [2, 3, 5, 7, 11, 13]
Java
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 ではすべて一つのクラス/インタフェース宣言の中に書くようにして下さい。

Swift
class Foo {
    func bar() {
    }
}

extension Foo {
    func baz() {
    }
}
Java
public class Foo {
    public void bar() {
    }

    public void baz() {
    }
}

fatalError, assert

fatalErrorErrorthrow に、 assertassert に、次のように移植して下さい。

fatalError

Swift
fatalError("Some Error Message.");
Java
throw new Error("Some Error Message.");

assert

Swift
assert(foo > 0, "Some Message.")
Java
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 ただし、 ArrayDictionary 自体の変更ではなく、変数を再代入可とするために var を用いている場合は、オブジェクト生成のためには Collections#unmodifiableList(...)/unmodifiableMap(...) を使用して下さい。
※4 AlertDialog.Builder#setPositiveButton(...)/setNegativeButton(...) 等を使用して下さい。
※5 AlertDialog.Builder#setItems() を使用して下さい。
※6 Qoncept SDK のクラスです。

例1

Swift
let a = [1, 2, 3]
Java
final List<Integer> a = Collections.unmodifiableList(Arrays.asList(1, 2, 3));

例2

Swift
var list = [1, 2, 3]
if flag {
    list = []
}
Java
// Swiftでvarで宣言されているが、ミュータブルであることを意図しているのではなく、変数への再代入を可能にすることを意図しているので、JavaではunmodifiableListとする。
List<Integer> list = Collections.unmodifiableList(Arrays.asList(1, 2, 3));
if (flag) {
    list = Collections.emptyList();
}

例3

Swift
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")
        }
    }
}
Java
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

Swift
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
}
Java
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

Swift
someView.alpha = 0.0
UIView.animateWithDuration(0.5, animations: { () -> Void in
    self.someView.alpha = 1.0
}) { (finished) -> Void in
    // Do something
}
Java
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の ArrayDictionary は値型なので、そのまま Java の ListMap に移植すると挙動が変わってしまう場合があります。

例1

Swift
var a = [1, 2, 3]
var b = a
b.append(4) // a = [1, 2, 3], b = [1, 2, 3, 4]
Java(良くない例)
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 のオブジェクトを生成するようにします。

すべての代入でコピーが必要なのではなく、必要に応じてコピーするようにして下さい

Java(良い例)
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 するようにして下さい。

Swift
class Foo {
    var list: [Int]

    init(list: [Int]) {
        self.list = list
    }
}
Java
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 では匿名内部クラスとして実装して下さい。

Swift
class SomeViewController : UIViewController, FooDelegate {
    func someMethod() {
        let foo = Foo()
        foo.delegate = self
        foo.playBar(Bar())
    }

    func foo(foo: Foo, didPlayBar: Bar) {
        ...
    }
}
Java
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 を使い、関数のように書いて下さい。

Swift
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))
Java
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 の場合)するのがオススメです。

195
198
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
195
198