はじめに
このページでは、主に良く利用する「基本的な型」「演算式」「制御構文」「関数定義」のJava/Swiftの記法について取り上げます。
本記事はJavaの場合は1.7、Swiftの場合はSwift3を基準に記載しておりますが、もし記法ミス等があったら申し訳ありません。
- JavaとSwift比較(1) ソース管理 / スコープ / 変数 編
- JavaとSwift比較(2) 基本的な型 / 演算式 / 制御構文 / 関数定義 編
- JavaとSwift比較(3) クラス実装 / クラスの継承 / クラス設計 編
基本的な型の紹介
Java
配列
Javaの配列は「型名[] 変数名;」と宣言します。
String[] hoge;
ただし、Javaの配列は初期化してヒープ領域にインスタンスが確保された後はその後のサイズの変更ができない性質を持ち、可変長配列ではありません。利用時には「型名[] 変数名 = new 型名[0];」と、あらかじめ格納できるサイズを指定して初期化を行う必要があります。
宣言と同時に初期値として「型名[] 変数名 = {値, 値, 値...};」と設定することも可能です。
配列の要素にアクセスするには「配列数数名[インデックス]」でアクセスします。
先頭のインデックスは0から開始され、たとえば、サイズが「3」で宣言された配列の最後のインデックスは「サイズ - 1」で「2」となります。
サイズ外の要素に対して操作を行おうとすると、例外が発生します。
String[] hoge = new String[3]; // サイズ3の配列を確保
hoge = new String[]{"a", "b", "c"}; // 初期値を代入して初期化することも可能
System,out.println(hoge[1]); // "b"と出力
// hoge[3] = 1; // 範囲外の操作しようとすると例外が発生する
配列内の要素を取得後、当該の値を変更した場合、配列自体の要素にも影響を与えます。
String[] hogeA= new String[]{"a", "b", "c"};
hogeA[0] = "x";
System.out.println(hogeA[0]); // "x"と出力
また、Javaの配列は参照型の性質をもつため、配列を別の配列の変数に代入し、代入先の配列変数を操作した場合、代入した配列にも影響されます。
String[] hogeA;
String[] hogeB = new String[]{"a", "b", "c"};
hogeA = hogeB;
hogeB[0] = "x";
System.out.println(hogeB[0]); // "x"と出力
System.out.println(hogeA[0]); // "x"と出力
List
Javaの「List」とはコレクションフレームワークの一つで、重複要素を許可し、順番を保持して要素を追加できる型です。
「List」は後述のインターフェースとなるため、インスタンス化されたデータ型には主にArrayList、LinkedListがあります。
List変数の宣言には、Listはインターフェースとなりますので、「List<要素型名> 変数名 = new Listインターフェース実装クラス<>();」と宣言します。
後述のジェネリクス「<>」により、格納したい要素の型名を指定します。
List<String> hoge = new ArrayList();
Listに要素を追加する場合、「List変数.add(値)」とすると自動的にサイズ拡張され、要素が追加されます。
ただし、注意点として、JavaのListに格納できる要素の型はオブジェクト型に限定され、プリミティブ型(int、char、longなどを示す)は要素として格納できません。
Listの要素にアクセスするには「List変数名.get(インデックス)」でアクセスできます。「変数名.set(インデックス, 値)」で変更をできます。
先頭のインデックスは0から開始され、たとえば、サイズが「3」のListの最後のインデックスは「サイズ - 1」で「2」となります。
サイズ外の要素に対して操作を行おうとすると、例外が発生します。
List<Integer> test = new ArrayList<>();
test.add(1);
test.add(2);
test.add(3);
test.set(2, 9);
// System.out.println(test.get(3)); // 範囲外の操作しようとすると例外が発生する
List内の要素を取得後、当該の値を変更した場合、List自体の要素にも影響を与えます。
// ※ Testクラスの実装は割愛
List<Test> test = new ArrayList<>();
Test a1 = new Test();
a1.a1 = "1";
test.add(a1);
Test a2 = new Test();
a2.a1 = "2";
test.add(a2);
System.out.println(test); // ["1", "2"]と出力される
Test q = test.get(1);
q.a1 = "A";
System.out.println(test); // ["1", "A"]と出力される
ただし、次の例では次のような結果になります。
// ※ Testクラスの実装は割愛
List<Test> test = new ArrayList<>();
Test a1 = new Test();
a1.a1 = "1";
test.add(a1);
Test a2 = new Test();
a2.a1 = "2";
test.add(a2);
System.out.println(test); // ["1", "2"]と出力される
Test q = test.get(1);
Test a3 = new Test();
a3.a1 = "3";
q = a3;
System.out.println(test); // ["1", "2"]と出力される
この例では、「String q = test.get(1);」で取得した「q」を、「q = a3;」と変更した時点で「q」の参照はa3になりますが、依然としてList「test」のインデックス1の要素の参照はa1を示しています。
つまり、参照が変わっているため、この例の場合、List「test」の要素には影響を与えません。
また、JavaのListは参照型の性質をもつため、List変数を別のListの変数に代入し、代入先のList変数を操作した場合、代入したList変数にも影響されます。
List<Test> hogeA;
List<Test> hogeB = new ArrayList<>();
Test a1 = new Test();
a1.a1 = "1";
hogeB.add(a1);
Test a2 = new Test();
a2.a1 = "2";
hogeB.add(a2);
Test a3 = new Test();
a3.a1 = "3";
hogeB.add(a3);
hogeA = hogeB;
hogeB.get(0).a1 = "x";
System.out.println(hogeA.get(0)); // "x"と出力
System.out.println(hogeB.get(0)); // "x"と出力
Map
Javaの「Map」とはコレクションフレームワークの一つで、キー・バリュー形式で値を格納できる型です。Swiftのディクショナリと同じような扱いができます。
「Map」は後述のインターフェースとなるため、インスタンス化されたデータ型には主にHashMap、TreeMap、LinkedHashMapがあります。
キー・バリュー形式で値を格納できる型として、類似したものにDictionaryがありますが、このデータ型のHashtableはMapより使い勝手が悪く(遅く、nullを格納できない等)、現在では「Map」を利用するのがほとんどです。
Map変数の宣言には、Mapはインターフェースとなりますので、「Map<要素キー型名, 要素値型名> 変数名 = new Mapインターフェース実装クラス<>();」と宣言します。
後述のジェネリクス「<>」により、格納したい要素の型名を指定します。
また、HashMap、TreeMap、LinkedHashMapはjava.utilパッケージに属しますので、利用時にはjava.utilパッケージからクラスインポートする必要があります。
Map<String, String> hoge = new HashMap();
Mapに要素を追加する場合、「Map変数.put(キー, 値)」とすると自動的にサイズ拡張され、指定のキーと値で要素が追加されます。
この時、既存のキーと重複するキー値を指定した場合、上書きされます。
ただし、注意点として、JavaのMapに格納できる要素のキー、値の型はオブジェクト型に限定され、プリミティブ型(int、char、longなどを示す)は要素として格納できません。
Mapの要素にアクセスするには「Map変数名.get(キー)」でアクセスできます。要素を除去は指定のキー値で「Map変数名.remove(キー)」で行います。
Map<Integer, Integer> test = new HashMap<>(); // Integerはオブジェクト型のため、要素のキーと値の型として指定可能
test.put(1, 11);
test.put(2, 12);
test.put(3, 13);
test.put(4, 14);
test.remove(3);
System.out.println(test); // {1=11, 2=12, 4=13}と出力される
Map内の要素を取得後、当該の値を変更した場合、Map自体の要素にも影響を与えます。
// ※ Testクラスの実装は割愛
Map<Integer, Test> test = new HashMap<>();
Test a1 = new Test();
a1.a1 = "1";
test.put(1, a1);
Test a2 = new Test();
a2.a1 = "2";
test.put(2, a2);
System.out.println(test); // {1=1, 2=2}と出力される
Test q = test.get(1);
q.a1 = "A";
System.out.println(test); // {1=A, 2=2}と出力される
ただし、次の例では次のような結果になります。
// ※ Testクラスの実装は割愛
Map<Integer, Test> test = new HashMap<>();
Test a1 = new Test();
a1.a1 = "1";
test.put(1, a1);
Test a2 = new Test();
a2.a1 = "2";
test.put(2, a2);
System.out.println(test); // ["1", "2"]と出力される
Test q = test.get(1);
Test a3 = new Test();
a3.a1 = "3";
q = a3;
System.out.println(test); // ["1", "2"]と出力される
この例は「Test q = test.get(1);」で取得した「q」を、「q = a3;」と変更した時点で「q」の参照はa3になりますが、依然としてMap「test」のキー「1」の要素の参照はa1を示しています。
つまり、参照が変わっているため、この例の場合、Map「test」の要素には影響を与えません。
また、あくまでキーとして利用されるのはキーの参照であるため、同じインスタンスを使いまわしてMap変数に追加しても同一のキーとして見なされます。
// ※ Testクラスの実装は割愛
java.util.Map<Test, String> test = new java.util.HashMap<>();
Test a1 = new Test();
a1.a1 = "1";
test.put(a1, "A"); // (1) インスタンスをキーとして追加
a1.a1 = "2";
test.put(a1, "B"); // (2) 同じインスタンスで追加
System.out.println(test); // (2)で追加した値として上書きされ、{2=B}と出力される
また、JavaのMapは参照型の性質をもつため、Map変数を別のMapの変数に代入し、代入先のMap変数を操作した場合、代入したMap変数にも影響されます。
Map<Integer, Test> hogeA;
Map<Integer, Test> hogeB = new HashMap();
Test a1 = new Test();
a1.a1 = "1";
hogeB.put(1, a1);
Test a2 = new Test();
a2.a1 = "2";
hogeB.put(2, a2);
Test a3 = new Test();
a3.a1 = "3";
hogeB.put(3, a3);
hogeA = hogeB;
hogeB.get(1).a1 = "x";
System.out.println(hogeA.get(1)); // "x"と出力
System.out.println(hogeB.get(1)); // "x"と出力
そのほかSwiftとの比較
JavaとSwiftのデータ型の比較を以下に記載します。
- Javaには構造体がありません。クラスで代替する必要があります。
- Javaにはタプルがありません。クラスで代替する必要があります。
Swift
配列
Swiftの配列は「var 変数名:[型名] = []」と宣言します。「var 変数名:Array = Array<型名>()」とも宣言できます。
宣言と同時に初期値として「var 変数名:型名[] = [値, 値, 値...]」と設定することも可能です。
Swiftの配列は可変長配列で「配列変数名.append(値)」とすると自動的にサイズ拡張され、要素が追加されます。
Swiftの場合、あらかじめサイズを指定する必要はなく、JavaのList型のように利用できます。
var test:[Int] = [] // Swiftの場合、あらかじめサイズ指定する必要はない
test.append(1) // 可変長配列のため、自動的に拡張される
配列の要素にアクセスするには「配列変数名[インデックス]」でアクセスします。
先頭のインデックスは0から開始され、たとえば、サイズが「3」で宣言された配列の最後のインデックスは「サイズ - 1」で「2」となります。
配列内の要素を取得後、当該の値を変更した場合、配列自体の要素にも影響を与えます。
var test:[Int] = [1, 2, 3]
test[0] = 9
print(test); // [9, 2, 3]と出力
ただし、次の例では次のような結果になります。
// ※ Testクラスの実装は割愛
var a1 = Test(a1: 1)
var a2 = Test(a1: 2)
var a3 = Test(a1: 3)
var test:[Test] = [a1, a2, a3]
print(test); // [Test(a1: 1), Test(a1: 2), Test(a1: 3)]と出力
var q = test[0]
var temp:Test = Test(a1: 9)
q = temp
print(test); // [Test(a1: 1), Test(a1: 2), Test(a1: 3)]と出力
この例は「var q = test[0]」で取得した「q」を、「q = temp」と変更した時点で「q」の参照はtempになりますが、依然として配列「test」のインデックス1の要素の参照はa1を示しています。
つまり、参照が変わっているため、この例の場合、Map「test」の要素には影響を与えません。
ただし、「test[0] = Test(a1: 9)」というように、直接書き換える場合はこの限りではありません。
// ※ Testクラスの実装は割愛
var a1 = Test(a1: 1)
var a2 = Test(a1: 2)
var a3 = Test(a1: 3)
var test:[Test] = [a1, a2, a3]
print(test); // [Test(a1: 1), Test(a1: 2), Test(a1: 3)]と出力
test[0] = Test(a1: 9)
print(test); // [Test(a1: 9), Test(a1: 2), Test(a1: 3)]と出力
print(a1); // [Test(a1: 1)と出力
また、Javaとは異なり、Swiftの配列は参照型ではなく値型になります。配列を別の配列の変数に代入し、代入先の配列変数を操作した場合は代入した配列には影響されません。
var test1:[Int] = [1, 2, 3]
var test2:[Int] = [4, 5, 6]
test2 = test1 // 別の配列変数に代入(※1)
test1[0] = 9
print(test1); // [9, 2, 3]と出力
print(test2); // (※1) 配列自体は値型のため、[1, 2, 3]と出力される
ディクショナリ
ディクショナリとは、キー・バリュー形式で値を格納できる型です。
「var 変数名:[要素キー型名, 要素値型名] = [:]」と宣言します。「var 変数名:Dictionary<要素キー型名, 要素値型名> = [:]」とも宣言できます。
宣言と同時に初期値として「var 変数名:Dictionary<要素キー型名, 要素値型名> = [要素キー:要素値, 要素キー:要素値 ...]」と設定することも可能です。
var test1:[Int:String] = [:]
var test2:Dictionary = Dictionary<Int, String>() // 宣言の仕方を左記のようにすることも可能
ディクショナリに要素を追加する場合、「ディクショナリ変数名[要素キー] = 要素値」とすると既存のディクショナリに要素が追加されます。この時、既存のキーと重複するキー値を指定した場合、上書きされます。
要素を除去は指定のキー値で「ディクショナリ変数名[キー] = nil」で「nil」値を指定することで除去されます。
ディクショナリの要素にアクセスするには「ディクショナリ変数名[キー]」でアクセスできます。
ディクショナリ内の要素を取得後、当該の値を変更した場合、ディクショナリ自体の要素にも影響を与えます。
// ※ Testクラスの実装は割愛
var items: Dictionary<Int, Test> = [:]
items[1] = Test(a1: 1)
print(items[1]!.a1) // 1と出力される
var q = items[1]!
q.a1 = 9
print(items[1]!.a1) // 9と出力される
ただし、次の例では次のような結果になります。
// ※ Testクラスの実装は割愛
var items: Dictionary<Int, Test> = [:]
var a1 = Test(a1: 1)
items[1] = a1
print(items[1]!.a1) // 1と出力される
var q:Test = items[1]!
var a3:Test = Test(a1: 9)
q = a3
print(items[1]!.a1) // 1と出力される
この例は「var q:Test = items[1]!」で取得した「q」を、「q = a3」と変更した時点で「q」の参照はa3になります依然としてディクショナリ「test」のキー「1」の要素の参照はa1を示しています。
つまり、参照が変わっているため、この例の場合、Map「test」の要素には影響を与えません。
ただし、「test[1] = Test(a1: 9)」というように、直接書き換える場合はこの限りではありません。
// ※ Testクラスの実装は割愛
var items: Dictionary<Int, Test> = [:]
var a1 = Test(a1: 1)
items[1] = a1
print(items[1]!.a1) // 1と出力される
items[1] = Test(a1: 9)
print(items[1]!.a1) // 9と出力される
print(a1.a1) // 1と出力される
また、要素値は任意のデータ型を指定可能ですが、キーとしてオブジェクトを指定したい場合、キーとなるオブジェクトがHashableプロトコルを実装している必要があります。
ディクショナリ Hashableプロトコルの実装例
/// TestClassをディクショナリのキーとして使用するにはHashableプロトコルに準拠しなくてはならない
public class TestClass : Hashable {
var a1:String = ""
init (a1:String) {
self.a1 = a1
}
/// ハッシュ値はEquatableの実装で使用する
public var hashValue: Int {
get {
/// 仮にこのハッシュ値が同じならば、ディクショナリ上では同じものとして取り扱われる
return a1.hashValue
}
}
}
/// TestClassのEquatable実装
func == (lhs:TestClass, rhs:TestClass) -> Bool {
return lhs.hashValue == rhs.hashValue
}
var items: Dictionary<TestClass, Int> = [:]
var a1:TestClass = TestClass(a1 : "A")
var a2:TestClass = TestClass(a1 : "B")
var a3:TestClass = TestClass(a1 : "C")
var a4:TestClass = TestClass(a1 : "D")
var a5:TestClass = TestClass(a1 : "A")
items[a1] = 1
items[a2] = 2
items[a3] = 3
items[a4] = 4
items[a5] = 5
for (k, v) in items { // A : 5、B : 2、C : 3、D : 4と出力される
print(k.a1 + " : " + v.description)
}
上記の例では、 「var a1:TestClass = TestClass(a1 : "A") 」と、?「var a5:TestClass = TestClass(a1 : "A")」で別のインスタンスを生成して、ディクショナリ「items」に追加しているにも関わらず、「a1」、「a5」は同じキーとして取り扱われました。
これは、前述のキーとして作成したTestクラスがキーの一致チェックに「TestClass.a1.hashValue」を使用しており、これが一致していた場合は同一キーと見做すように実装されているからです。
タプル
タプルとは、構造体より手軽に扱えるデータ型で複数の値を一組にしたものです。Javaには存在しない言語仕様です。
画面サイズの幅と高を返却するような関数の返り値のような、特に、一時的に使用する関連する値のグループを扱う場合に効果が期待できます。以下に特徴を羅列します。
- タプルは構造体のように事前に定義する必要がない。
- タプルは構造体のようにextensionで拡張できない。
- タプルはパターンマッチングによる代入や比較が可能
- タプルは匿名のメンバーを持つことや、外部名で管理することも可能
宣言には「var 変数名:(型名, 型名, 型名...)」と指定し、括弧「()」内にタプルとしてまとめて管理したい型を記述します。
「var 変数名:(型名, 型名) = (値, 値)」と宣言時に初期値を設定することも可能です。
また、まとめたいメンバーに対して外部名を付与することが可能で、「var 変数名:(外部名:型名, 外部名:型名) = (外部名:値, 外部名:値)」というように記述できます。
var test1:(name:String, age:Int) // 外部名を付与してわかりやすくできる
test1 = (name:"NYANKO", age:100)
print(test1.name) // 外部名を使用してアクセスも出来る
let test2:(String, Int) = ("WANKO", 2) // 外部名を付与せずに宣言も可能で、パターンマッチングによる代入ができる
print(test2.1) // アクセス時にはインデックスを使用できる
if (test2 == ("WANKO", 2)) { // パターンマッチングによる比較も可能
print("same.")
}
構造体
Swiftでは簡易にカプセル化を実現させる方法として構造体が用意されています。
単純なデータの管理に優れ、画面サイズの幅と高を返却するような関数の返り値などのデータ構造を表現するのに良く利用されます。
定義時には「struct 構造体名 {...}」と定義し、構造体のブロックにメンバーを定義します。宣言には「var 構造体変数名 = 構造体()」と宣言します。
構造体にはデフォルトコンストラクタをもち、構造体に定義されたメンバーの変数名を使用して、初期化処理時に外部名として初期化することも可能です。以下に特徴を羅列します。
- 構造体は利用する前に事前に定義する必要がある。
- 構造体はextensionで拡張できる。
- 構造体は継承できません。
- クラスと異なり、変数に代入された場合は値としてコピーされます。
構造体はクラスと使い勝手が似ていますが、クラスと大きく異なるのは構造体 ≒ 値 であるという点です。
クラスはインスタンスを生成して、「変数A」に代入し、また別の「変数B」に代入しても、その操作はインスタンスに対して行われます。
たとえば、「よしこ」というインスタンスは、ある時は『掃除のおばちゃん「変数A」』、ある時は『怪盗熟女フォーティーン「変数B」』ですが、「掃除のおばちゃん」としてたかの友梨ビューティークリニックで豊胸手術を受けた場合、「怪盗熟女フォーティーン」のバストも大きくなります。
これに反して、構造体は「変数A'」に代入し、また別の「変数B'」に代入した場合、これは別の値として取り扱われます。
public class TestClass {
var a1:String = ""
}
public class TestStruct {
var a1:String = ""
}
var testStruct:TestClass = TestClass() // 構造体を宣言
var testClass:TestClass = TestClass() // クラスのインスタンスを宣言
testStruct.a1 = "ABC"
testClass.a1 = "ABC"
var assignmentStruct = testStruct // 構造体を代入
var assignmentClass = testClass // クラスのインスタンスを代入
assignmentStruct.a1 = "DEF"
assignmentClass.a1 = "DEF"
print(testStruct.a1) // 構造体はコピーされるため、"ABC"と出力される
print(testClass.a1) // クラスは参照が渡されるため、代入先の影響を受けて"DEF"と出力される
struct TestStruct {
var a1:String = ""
}
class TestClass {
var a1:String = ""
}
そのほかJavaとの比較
タイプエイリアス
Swiftでは、既存の型に別名をつけて使用することができます。
public typealias hoge1 = Int
public typealias hoge2 = (String, Int, Int)
var a1:hoge1 = 1
var a2:hoge2 = ("ABC", 1, 2)
print(a1)
print(a2)
演算式
Java、Swiftも同じ四則演算の記法は同じく、比較演算子や、インクリメント / デクリメントの記法も同じのため、どちらか片方を習熟していれば問題なく理解できるかと思います。
制御構文
If 文
Java
int test = 1;
if (test == 1) {
System.out.println("1");
} else if (test == 1) {
System.out.println("2");
} else {?
System.out.println("3");
}
Swift
条件式を記述する際に、「(条件式)」と括弧を付与しても良いですが、付けないのが一般的な記法です。
var test = 1;
if test == 1 {
print("1")
} else if test == 1 {
print("2")
} else {
print("3")
}
Swich 文
Java
Javaはswitch文の評価はchar、byte、short、int、Character、Byte、Short、Integer、String(JDK 1.7以降)、またはenumしか利用することができません。
一致する評価結果のcaseステートメントのブロックの処理が行われた後、明示的なbreak文を配置することでswitchの条件分岐が抜け出すことができます。このため、複数の評価値が同処理を行う場合はbreakを記載せずにまとめます。
int test = 1;
switch(test) {
case 1:
case 2:
System.out.println("A");
break;
case 3:
System.out.println("C");
break;
default:
System.out.println("D");
}
Swift
Javaとは異なり、評価対象「switch (評価)」に式、値(変数)を設定することも、評価結果「case 評価値:」に整数の他に文字列、式などを設定することが可能です。
また、明示的なbreak文は不要で、一致する評価結果のcaseステートメント以下の処理が行われた場合、他のcaseステートメント以下の処理は行われません。このため、複数の評価値が同処理を行う場合は「case」に評価値をカンマ区切りで記述します。
var test:Int = 1
switch test {
case is Test:
// 型がTestの場合
print("A")
case is Int:
// 型がIntの場合
print("B")
case 1111, 2222:
// 値が1111、または2222の場合
// ただし、testの型がそもそもIntでない場合は「Expression pattern of type 'Int' cannot match values of type〜」、と出ます
print("C")
default:
print("D")
}
for 文
Java
基本形は以下のとおりに記載します。
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
1つずつ要素を検査する場合は拡張for文を利用できます。
Iterableインターフェースを実装しているクラスのインスタンス、および配列などで使用できます。
List<Integer> list = new ArrayList(); // ListはIterableを実装しているため、拡張for文が使用出来る
for (Integer item : list) {
System.out.println(i);
}
Swift
基本形は以下のとおりに記載します。
for i in 0 ..< 10 {
print(i)
}
1つずつ要素を検査する場合は拡張for文を利用できます。
SequenceTypeプロトコルの実装があるクラスのインスタンス、および配列などで使用できます。
for (item in 1..10) {
print(i)
}
while 文
Swiftの場合、「(条件式)」と括弧を割愛できる以外、Java、Swiftも記法は同じのため、どちらか片方を習熟していれば問題なく理解できるかと思います。
関数定義
Java
Javaにおける関数定義時の記法は以下になります。
// アクセス修飾子 返値型 関数名(引数型 引数変数名) {
// return 返値;
// }
private int test(int value) {
return value;
}
// 呼び出し方
test(1);
- 返値が存在しない場合は「返値型」に「void」を指定します。
- Javaの場合は返値にタプル、複数値を設定することができないため、返値はプリミティブ型(int、char、longなど)かオブジェクト型である必要があります。
- JavaにはSwiftにおける引数の「外部名」が存在せず、関数を呼び出す場合は定義された関数の引数の型、個数、順番を一致させることでパターンマッチさせる必要があります。
- Javaではデフォルト引数というものが存在しないため、引数の異なるメソッドを複数作成するテレスコーピングによる手法で実装する必要があります。
また、特記事項として、Javaでは関数に引き渡された引数は厳密には参照渡しではありませんが、引き渡された値を関数関数内部で操作できる理解で良いと思います。
たとえば、関数内部でインスタンスを生成しなおすというような使い方をすると、実際には参照渡しではないことがわかります。
Test test = new Test();
test.a1 = "1";
Test newTest = newTest(test);
System.out.print(test); // 1が出力
System.out.print(newTest); // nullが出力
private Test newTest(Test test) {
test = new Test(); // 新しく参照が作られる
test.a1 = "2";
return test;
}
また、イミュータブルな引数の場合は関数内で引数の値を操作しても呼び出し元の値は変化しません。
int value = 1;
test(value);
System.out.println(a); // 1が出力
private void test(int value) {
value = 2;
}
Swift
Swiftにおける関数定義時の記法は以下になります。
// func 関数名(外部名 引数変数名:引数型 = デフォルト値) -> 返値型 {
// return 返値;
// }
private func test(test value:Int = 9) -> Int {
return value;
}
// 呼び出し方
test(test:1);
- 返値が存在しない場合は「返値型」に「Void」を指定、または省略することで返値が存在しない関数を定義できます。
- Swiftの場合、タプルを利用することで返値に複数の値を設定することが可能です。
- Swiftには引数に「外部名」を付与することが可能のため、「外部名 引数変数名:引数型」と指定し、可読性を向上することができます。
- 外部名を明示的に「外部名 引数変数名:引数型」と指定するのではなく、「#引数変数名:引数型」というように変数名に「#」を指定することも可能です。
- Swift3からは関数ラベルのルールが変わり、外部名が引数変数名として利用されるようになりました。
- Swiftでは引数にデフォルト値を指定することが可能です。
Swiftでは関数に引き渡された引数は値渡しで引き渡されますが、参照渡しで引き渡すための方法が用意されています。
var a = 1
test(&a)
print(a) // 参照渡しで引き渡されたため、結果は「2」となる
func test(value:inout Int) {
a = 2
}
また、Swiftでは関数は値として取り扱うことができます。
例えば、文字列の引数を1つ持ち、文字列の返値を返却する関数の型情報は「(String) -> String」と表現することができます。
let funcTest:(String)->String = test // func test(name:String) -> String {...}をfuncTestに格納
let message = funcTest("Swift") // 関数を格納した変数からfunc test(name:String) -> String {...}を呼び出し
print(message)
// 関数
func test(name:String) -> String {
return "こんにちは" + name + "さん"
}
また、Swiftでは関数は値として取り扱うことができるため、関数に関数を引き渡すことも可能です。
let funcTest:(String)->String = test // func test(name:String) -> String {...}をfuncTestに格納
callTest(name: "Java", function: funcTest) // callTest(name:String, function:(String)->String) {...}を呼び出し
// 関数
func test(name:String) -> String {
return "こんにちは" + name + "さん"
}
// 引き渡されたtest(name:String) -> Stringを実行する関数
func callTest(name:String, function:(String)->String) {
let message = function(name) // 引き渡されたtest(name:String) -> Stringの呼び出し
print(message)
}
関数のなかに関数を定義して使用することも可能です。
// 実行メソッド
func exe() {
let value = test(value1: 5, value2:3) // 関数の呼び出し
print(value.0)
print(value.1)
}
// 関数を内部に持つ関数
func test(value1:Int, value2:Int) -> (Int, Int) {
// 内部関数(1)
func add(value1:Int, value2:Int) -> Int {
return value1 + value2
}
// 内部関数(2)
func subtract(value1:Int, value2:Int) -> Int {
return value1 - value2
}
let result1 = add(value1: value1, value2: value2) // 内部関数(1)の結果を取得
let result2 = subtract(value1: value1, value2: value2) // 内部関数(2)の結果を取得
return (result1, result2)
}
無名関数と高階関数
Swiftでは無名関数の実装が可能のため、変数に無名関数を設定することもできます。
無名関数とは、関数宣言をせずに(関数名を付与することなく)利用することができ、次のように扱えます。
// {(引数名:引数型名) -> (返値型名) in
// // 処理
// return 返値
// }
// (1) 引数なし、返値なしの場合
let closure1 = {() -> () in
print("test1")
}
closure1() // "test1"と出力
// (2) 引数なし、返値Stringの場合
let closure2 = {() -> String in
return "test2"
}
print(closure2()) // "test2"と出力
// (3) 引数String、Int、返値Stringの場合
let closure3 = {(param1:String, param2:Int) -> String in
return param1 + param2.description
}
print(closure3("test", 3)) // "test3"と出力
// (4) 引数の型を指定せず、呼び出し時の型推論を利用する場合返値Intの場合
let closures4:(String, Int) -> String
closures4 = {(param1, param2) -> String in
return param1 + param2.description
}
print(closures4("test", 4)) // "test4"と出力
関数リテラルを関数の引数として引き渡すことができます。
関数宣言された関数処理の実行後に、呼び出し元に応じたそれぞれの処理を行いたい場合など、別々の関数を宣言する必要がありません。
// 実行メソッド
func exe() {
// 三角形の面積計算(縦 × 横 ÷ 2)を無名関数として変数に格納
var calcTriangle = { (width:Int, height:Int) -> (Int) in
return width * height / 2
}
// 長方形の面積計算(縦 × 横)を無名関数として変数に格納
var calcRctangle = { (width:Int, height:Int) -> (Int) in
return width * height
}
print(calcAreaScreenRect(areaLogic: calcTriangle)) // 三角形の面積計算無名関数を引数として、スクリーン画面サイズ別面積計算関数を呼び出し
print(calcAreaScreenRect(areaLogic: calcRctangle)) // 長方形の面積計算無名関数を引数として、スクリーン画面サイズ別面積計算関数を呼び出し
}
/// スクリーン画面サイズ別面積計算
func calcAreaScreenRect(areaLogic:(Int, Int) -> (Int)) -> Int{
let rect = UIScreen.main.bounds;
return areaLogic(Int(rect.width), Int(rect.height))
}
また、無名関数はレキシカルスコープの特性により、呼び出し元の変数にアクセスできます。
無名関数を引数として利用する
var message = "Hello"
var printHello = {
print(message) // 無名関数はmessageにアクセスできる
}
printHello() // "Hello"と出力
なお、関数の引数には関数宣言で説明したとおり、無名関数でなくとも、間数宣言された関数を引数として利用することができます。このような関数を高階関数と言います。
クロージャ
クロージャ(関数閉包)とは、かいつまんで言うと、関数のなかに関数を定義することを指します。
具体的に言うと、「無名関数を以って実現する関数オブジェクト」のことを指します。
Swiftでは無名関数を利用することができるため、クロージャを利用することが可能です。
func getCounter()->()->Int {
var counter = 0
// 無名関数
let countUp = { ()->Int in
counter += 1
return counter
}
return countUp
}
上記の例では、内部に無名関数を定義して、その関数を返却するようにするように「getCounter」という関数が宣言されています。
この関数を呼び出すと、次のような結果になります。
let funcGetCounter:()-> Int = getCounter() // getCounterの返値の無名関数
print(funcGetCounter()) // "1"と出力される
print(funcGetCounter()) // "2"と出力される
print(funcGetCounter()) // "3"と出力される
「let funcGetCounter」で格納された無名関数は呼び出されるごとに、カウントアップされます。
このように、無名関数がレキシカルスコープで参照している「getCounter」という関数内の「var counter」は「let funcGetCounter」の参照がなくなるまで保持され、まるでクラスのインスタンスのように利用できます。