はじめに
このページでは、主に「クラス実装」「クラスの継承」「クラス設計」のJava/Swiftの記法について取り上げます。
本記事はJavaの場合は1.7、Swiftの場合はSwift3を基準に記載しておりますが、もし記法ミス等があったら申し訳ありません。
- JavaとSwift比較(1) ソース管理 / スコープ / 変数 編
- JavaとSwift比較(2) 基本的な型 / 演算式 / 制御構文 / 関数定義 編
- [JavaとSwift比較(3) クラス実装 / クラスの継承 / クラス設計 編]
クラス実装
以下のようなクラスの記法について記載します。
クラス設計
- 従業員クラス
- 以下のプロパティを持つ
- 名前:文字列型
- 性別:列挙型定数
- 年齢:数値
- 外部クラスから呼び出し可能な、自己紹介用のインスタンスメソッドを実装
- 以下のプロパティを持つ
Java
クラス定義
/**
* 従業員 クラス
*/
public class Employee {
/**
* 名前
*/
protected String name;
/**
* 性別
*/
protected Sex sex;
/**
* 年齢
*/
protected int age;
/**
* コンストラクタ
*
* @param name 名前
* @param sex 性別
* @param age 年齢
*/
public Employee(String name, Sex sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
/**
* コンストラクタ
*/
public Employee() {
this.name = "";
this.sex = Sex.Unknown;
this.age = 0;
}
/**
* 自己紹介
*
* @return 自己紹介テキスト
*/
public String selfIntroduction() {
String value = "";
value += "name:=" + this.name + ", ";
value += "age:=" + this.age + ", ";
value += "sex:=" + this.sex + ", ";
return value;
}
/**
* 性別enum
*/
public enum Sex {
Man("男"),
Woman("女"),
Unknown("不明");
private String value;
Sex(String value) {
this.value = value;
}
}
}
インスタンスの生成方法
Employee employee = new Employee(); // インスタンスの生成
String value = employee.selfIntroduction(); // インスタンスメソッドの呼び出し
System.out.print(value);
Swift
クラス定義
class Employee {
/// 性別enum
enum Sex:String {
case Man = "男"
case Woman = "女"
case Unknown = "不明"
}
/// 名前
var name:String
/// 性別
var sex:Sex
// 年齢
var age:Int
/// イニシャライザ
init(name:String = "", age:Int = 0, sex:Sex = Sex.Unknown) {
self.name = name
self.sex = sex
self.age = age
}
// 自己紹介
func selfIntroduction() -> String {
var value:String = ""
value += "name:=" + self.name + ", "
value += "age:=" + self.age.description + ", "
value += "sex:=" + self.sex.rawValue + ", "
return value
}
}
インスタンスの生成方法
let employee = Employee() // インスタンスの生成
let value = employee.selfIntroduction() // インスタンスメソッドの呼び出し
print(value)
クラスの継承
以下のようなクラスの記法について記載します。
クラス設計
- 従業員(Employee)クラスを継承したプログラマクラス
- 以下のプロパティを持つ
- 職位:列挙型定数
- 基底クラスである従業員(Employee)クラスの自己紹介用のインスタンスメソッドをオーバライドしたメソッドの実装
- 職位、年齢により給料を算出するメソッドの実装
- 以下のプロパティを持つ
Java
クラス定義
- JavaではSwiftと同様に多重継承は許可されておらず、基底クラスは一つのみです。
- 継承する際には「class クラス名 extends 基底クラス名 {...}」と記載します。
- Javaでは基底クラスと一致するメソッド名、引数型・引数個数のメソッドを継承クラスに実装することで、メソッドのオーバライドが行われます。
- メソッドがオーバライドされることを示す@Overrideアノテーションを付与することが推奨されます。
- JavaではComputedプロパティというものは存在せず、getter、setterなどはメソッドで表現されます。
/**
* プログラマ クラス
*/
public class Programmer extends Employee {
/**
* 職位
*/
private JobTitle jobTitle;
/**
* コンストラクタ
*
* @param jobTitle 職位
* @param name 名前
* @param sex 性別
* @param age 年齢
*/
public Programmer(JobTitle jobTitle, String name, Sex sex, int age) {
super(name, sex, age);
this.jobTitle = jobTitle;
}
/**
* 自己紹介
*
* @return 自己紹介テキスト
*/
@Override
public String selfIntroduction() {
String value = super.selfIntroduction();
value += "jobTitle:=" + this.jobTitle + ", ";
return value;
}
/**
* 給与計算
* @return 給与
*/
public int salary() {
int jobSalary;
switch (this.jobTitle) {
case A1:
jobSalary = 340000;
break;
case A2:
jobSalary = 300000;
break;
case B1:
case B2:
jobSalary = 260000;
break;
case C1:
case C2:
jobSalary = 220000;
break;
case D1:
case D2:
jobSalary = 180000;
break;
default:
jobSalary = 0;
}
int ageSalary;
// Javaではswitchの評価結果に式を記述することができないため、if文で計算する
if (super.age >= 0 && super.age < 22) {
ageSalary = 0;
} else if (super.age >= 22 && super.age < 28) {
ageSalary = 20000;
} else if (super.age >= 28 && super.age < 34) {
ageSalary = 40000;
} else if (super.age >= 34 && super.age < 40) {
ageSalary = 40000;
} else if (super.age >= 40 && super.age < 100) {
ageSalary = 20000;
} else {
ageSalary = 0;
}
return jobSalary + ageSalary;
}
/**
* 職位enum
*/
public enum JobTitle {
A1,
A2,
B1,
B2,
C1,
C2,
D1,
D2;
}
}
インスタンスの生成方法
Programmer programmer = new Programmer(Programmer.JobTitle.A1, "マリエ", Employee.Sex.Woman, 30); // インスタンスの生成
String value = programmer.selfIntroduction(); // インスタンスメソッドの呼び出し
int salary = programmer.salary(); // 給与計算メソッドの呼び出し
System.out.print(value);
Swift
クラス定義
- SwiftではJavaと同様に多重継承は許可されておらず、基底クラスは一つのみです。
- 継承する際には「class クラス名:基底クラス名 {...}」と記載します。
- Swiftでは基底クラスと一致するメソッド名に「override」修飾子を持つメソッドを継承クラスに実装することで、メソッドのオーバライドが行われます。
- Swiftではプロパティに対するgetter、setterを提供するComputedプロパティが存在します。
/// プログラマ
class Programmer:Employee {
/// 職位enum
enum JobTitle:Int {
case A1 = 1
case A2 = 2
case B1 = 3
case B2 = 4
case C1 = 5
case C2 = 6
case D1 = 7
case D2 = 8
case Unknown = 0
}
/// 職位
var jobTitle:JobTitle;
/// 名前
var salary:Int {
get {
var jobSalary = 0
switch self.jobTitle {
case JobTitle.A1:
jobSalary = 34-0000
case JobTitle.A2:
jobSalary = 30-0000
case JobTitle.B1, JobTitle.B2:
jobSalary = 26-0000
case JobTitle.C1, JobTitle.C2:
jobSalary = 22-0000
case JobTitle.D1, JobTitle.D2:
jobSalary = 18-0000
default:
jobSalary = 0
}
var ageSalary = 0
switch self.age {
case 0...22:
ageSalary = 0-0000
case 22...28:
ageSalary = 2-0000
case 28...34:
ageSalary = 4-0000
case 34...40:
ageSalary = 4-0000
case 40...100:
ageSalary = 2-0000
default:
ageSalary = 0
}
return jobSalary + ageSalary
}
}
/// イニシャライザ
init(jobTitle:JobTitle = JobTitle.Unknown) {
self.jobTitle = jobTitle
super.init()
}
// 自己紹介
override func selfIntroduction() -> String {
var value = super.selfIntroduction()
value += "jobTitle:=" + self.jobTitle.rawValue.description + ", "
return value
}
}
インスタンスの生成方法
let programmer = Programmer(jobTitle: Programmer.JobTitle.Unknown) // インスタンスの生成
let value = programmer.selfIntroduction() // インスタンスメソッドの呼び出し
let salary = programmer.salary // 給与計算Computedプロパティの呼び出し
print(value)
print(salary)
クラス設計
以下にはクラス設計をするうえで、JavaとSwift独自の仕様についての簡易な説明と、使いどころについて記載します。
Java
抽象クラス
Javaには抽象クラスという、オブジェクト指向の3大要素(カプセル化、継承、ポリモフィズム)を効果的に仕様定義する考え方があります。
抽象クラスはいわゆる普通のクラスとは異なり、その名の通り、抽象的であるために実体(インスタンス)化することはできません。スーパーヒーローは実在しますが、正義が実在できないことと同じように抽象的なものを表すクラスです。
抽象クラスを宣言するには「abstract class 抽象クラス名 {...}」というようにクラス名の前に「abstract 」という修飾子を付与します。
抽象クラスには処理の概要や共通のプロパティを定義し、抽象クラスを継承したサブクラスに具体的な処理を記述します。
/**
* 正義 抽象クラス
*/
public abstract class AbstractJustice {
/**
* 基本能力
*/
protected int mBasicAbility;
/**
* 潜在能力
*/
protected int mPotentialCapacity;
/**
* 救う処理
*/
public abstract void save();
/**
* 基本能力取得
*
* @return 基本能力
*/
public int getBasicAbility() {
return mBasicAbility;
}
/**
* 潜在能力取得
*
* @return 潜在能力
*/
public int getPotentialCapacity() {
return mPotentialCapacity;
}
/**
* 戦闘
*
* @param enemyAbility 敵能力
* @return 戦闘結果 true...勝利、false...敗北
*/
public boolean fight(int enemyAbility) {
int basicAbility = getBasicAbility();
if (basicAbility > enemyAbility) {
// 基本能力が上回っている場合は勝利
return true;
}
// 基本能力 + 潜在能力 * ランダム係数が上回っている場合は勝利、下回った場合は敗北
int potentialCapacity = getPotentialCapacity();
float coefficient = new java.util.Random().nextFloat();
return basicAbility + potentialCapacity * coefficient > enemyAbility;
}
}
/**
* アンパンマンクラス
*/
public class Anpanman extends AbstractJustice {
/**
* 初期基本能力
*/
private int INIT_BASIC_ABILITY = 10;
/**
* 初期潜在能力
*/
private int INIT_POTENTIAL_CAPACITY = 3;
/**
* コンストラクタ
*/
public Anpanman() {
mBasicAbility = INIT_BASIC_ABILITY;
mBasicAbility = INIT_POTENTIAL_CAPACITY;
}
@Override
public void save() {
System.out.println("顔をお食べ");
// 顔を食べさせると基本能力が下がるが、潜在能力は向上する
mBasicAbility--;
mPotentialCapacity++;
// 基本能力が3以下になるとバタ子さんが顔を変えてくれるので元に戻る
if (mBasicAbility <= 3) {
// \アンパンマン! 新しい顔よ!!/
mBasicAbility = INIT_BASIC_ABILITY; // <元気100倍 アンパンマン!!
}
}
@Override
public boolean fight(int enemyAbility) {
// アンパンマンは勝利補正が行われるため、敵能力に-10する
enemyAbility -= 10;
return super.fight(enemyAbility);
}
}
インターフェース
Java 8以前はインターフェースにはメソッドはインスタンスメソッドしか定義できませんでしたが、Java 8以降はstatic メソッドもインターフェースに実装できるようになりました。
また、デフォルトメソッドという考え方も追加され、インターフェースにメソッドの処理本体が実装できるようになりました。
Javaでのインターフェースとは、処理の概要を記述したもので、抽象クラスの抽象メソッドのみが集約した機能、と考えて戴いても問題ないかと思います。
Swiftでのプロトコルと言語仕様の違いから差異はあるにしろ、相似していると考えて良いでしょう。
ただし、機能としてのインターフェースの名が示す通り、処理の概要や規約をまとめたものになりますので、実体(インスタンス)化はできないし、プロパティとしてのメンバ変数を持つことはできません。「ガンダムの操作」自体に色、形などがなく、ガンダム自体に色、形があることと同じような意味です。(ガンダム好きの方がいらしたら、すみません……)
また、インターフェースはクラスの継承と異なり、クラスは複数のインターフェースを実装することができます。
インターフェースを宣言するには「interface インターフェース名 {...}」と記述します。
インターフェースには処理の概要のみの処理ブロックを記述していないメソッド、 及び静的定数のみを記述できます。
処理の概要と、期待される結果については定義しますが、その処理内容はインターフェースを実装したクラスが担い、指定のインターフェースを実装する場合はそのインターフェースの機能全てを実装する必要があります。
/**
* ガンダム操作
*/
public interface GundamOperation {
/**
* 速度アップ
*/
void speedUp();
/**
* 速度ダウン
*/
void speedDown();
/**
* ビームセイバー装備
*/
void equipmentSaber();
/**
* ビームセイバー解除
*/
void removeSaber();
/**
* 銃射
*/
void shooting();
}
/**
* ∀ガンダムクラス
*/
public class GundamTurnA implements GundamOperation {
@Override
public void speedUp() {
System.out.println("速度アップ");
}
@Override
public void speedDown() {
System.out.println("速度ダウン");
}
@Override
public void equipmentSaber() {
System.out.println("ビームセイバー装備");
}
@Override
public void removeSaber() {
System.out.println("ビームセイバー解除");
}
@Override
public void shooting() {
System.out.print("銃射");
}
}
/**
* ザククラス
*/
public class Zaku implements GundamOperation {
@Override
public void speedUp() {
System.out.println("速度アップ");
}
@Override
public void speedDown() {
System.out.println("速度ダウン");
}
@Override
public void equipmentSaber() {
System.out.println("ビームセイバー装備");
}
@Override
public void removeSaber() {
System.out.println("ビームセイバー解除");
}
@Override
public void shooting() {
System.out.print("銃射");
}
}
この時、インターフェースを利用することのメリットは対象のインターフェースが実装されている = インターフェースとして提供している操作が保証されている、という点にあります。
クラスがどのようなクラスであれ、インターフェースを提供していることで、柔軟な実装が可能になります。
GundamTurnA gundamTurnA = new GundamTurnA();
Zaku zaku = new Zaku();
shooting(gundamTurnA);
shooting(zaku);
private void shooting(GundamOperation gundamOperation) {
gundamOperation.shooting(); // インターフェースとして操作できる
}
Swift
エクエステンション
SwiftにはJavaにはない機能のうち、既存のクラスをエクステンションによる拡張することができます。なんとライブラリで提供されているクラスも可能です(!)
print(10.hex)
extension Int {
/// 16進数
var hex: String {
return String(self, radix: 16)
}
/// 8進数
var oct: String {
return String(self, radix: 8)
}
/// 2進数
var bin: String {
return String(self, radix: 2)
}
/// JP税率
static func jpTaxRate() -> Double {
return 0.8
}
/// JP税込金額
func amount() -> Double {
return Int.jpTaxRate() * Double(self)
}
}
ただし、以下のようなことはできません。
- Computedプロパティ以外のプロパティの定義
- 既に実装済みのメソッドのオーバーライド
プロトコル
Swiftでのプロトコルとは、処理の概要を記述したもので、Javaにおけるインタフェースと言語仕様の違いから差異はあるにしろ、相似していると考えて良いでしょう。
ただし、機能としてのプロトコルの名が示す通り、処理の概要や規約をまとめたものになりますので、実体(インスタンス)化はできないし、プロパティとしてのメンバ変数を持つことはできません。
また、Javaのインターフェースでは静的定数 (例:「static final String HOGE = "hoge"」)を持つことができますが、プロトコルではサポートされておらず、関数、及びComputedプロパティのみが提供できます。
プロトコルを宣言するには「protocol プロトコル名 {...}」と記述します。
プロトコルには処理の概要のみの処理ブロックを記述していないメソッド、 及び処理ブロックを記述していないゲッターメソッド / セッターメソッドのComputedプロパティのみを記述できます。
処理の概要と、期待される結果については定義しますが、その処理内容はプロトコルを実装したクラスが担います。
/// 読込デリゲートプロトコル
protocol ReadDelegate {
/// 読込処理
func read() -> Int
}
/// 進捗プロトコル
protocol Progressing {
/// 総量
var total: Int { get }
/// 完了量
var completed: Int { get set }
func read()
}
/// 文字列変換プロトコル
protocol StringTranslatable {
/// 文字列変換
var asString: String{ get }
}
class Comic : StringTranslatable, Progressing {
var author:String = ""
var title:String = ""
var text:String = ""
var readDelegate:ReadDelegate?
let rawTotal:Int = 300
var rawCompleted:Int = 0
var asString: String {
get {
return "author:=" + author + "\n"
+ "title:=" + title + "\n"
+ "text:=" + text + "\n"
}
}
var total: Int {
get {
return rawTotal
}
}
var completed: Int {
get {
return rawCompleted
}
set {
rawCompleted = newValue
}
}
func read() {
if (readDelegate != nil) {
let readPage = readDelegate?.read()
completed += readPage!
}
}
}
/// 本読込デリゲートプロトコル実装クラス
class BookReadDelegate:ReadDelegate {
func read() -> Int {
return 5
}
}
この時、プロトコルを利用することのメリットは対象のプロトコルが実装されている = プロトコルとして提供している操作が保証されている、という点にあります。
上記の「Book」クラスで既に記述済みのように「read()」関数の処理を「BookReadDelegate」に処理委譲しており、クラスがどのようなクラスであれ、プロトコルを提供していることで、柔軟な実装が可能になります。
また、関数などの引数において、当該引数が任意のプロトコルを実装している場合のみを示す場合は、「変数名:protocol<プロトコル名>」と指定します。
let comic = Comic()
comic.title = "El extranjero"
comic.author = "Albert Camus"
comic.text = "L'Etranger has the force and fascination of myth......"
comic.readDelegate = BookReadDelegate() // 処理を委譲するためにReadDelegateを実装したクラスを設定
readBook(comic)
func readBook(target:protocol<Progressing, StringTranslatable>) { // Progressing、StringTranslatableが実装している
print(target.asString)
target.read()
print(target.completed)
}
エクステンションによるプロトコルの適合
SwiftにはJavaにはない機能のうち、既存のクラスをエクステンションによるプロトコルの適合を行うことができます。
プロトコルで既存クラスをエクステンションするには「extension 既存クラス名: エクステンション対象プロトコル名 {...}」と記述します。
下の例では「Int」クラスに上記で作成した「StringTranslatable」でエクステンションし、「StringTranslatable」の実装要件である「asString」Computedプロパティの処理詳細を実装しています。
/// IntをStringTranslatableで拡張
extension Int: StringTranslatable {
var asString: String {
get {
return "この数値は[" + description + "]です。"
}
}
}
サブスクリプト
サブスクリプトとは、配列やディクショナリの要素にアクセスする時等に使う添字式のことです。
Swiftでは、構造体、列挙型、クラスにサブスクリプトを実装することができます。
var test = Test()
print(test["A"]) // "1"と出力
class Test {
let values = ["A":"1", "B":"2", "C":"3", "D":"4"]
subscript(param:String) -> String {
get {
return self.values[param]!
}
}
}
演算子オーバーロード
Swiftでは、既存の演算子についてオーバーロードすることができます。
演算子のオーバロードはグローバル関数として定義することで、独自の振る舞いをもたせることができます。
struct Test {
var count:Int = 0
}
struct SumTest {
var count:Int = 0
}
func + (left: Test, right: Test) -> SumTest {
let sum = left.count + right.count
return SumTest(count: sum)
}
let hoge1 = Test(count: 1)
let hoge2 = Test(count: 1)
let add = hoge1 + hoge2
print(add) // SumTest(count: 2)