オブジェクト指向プログラミングにおける重要な概念の一つに、カプセル化があります。 カプセル化 ( encapsulation ) とは、外部からデータの構造が見えないようにし、限られた方法でしかデータにアクセスできないようにすることです。 適切にカプセル化を行いますと、プログラムの作成・変更が容易になります。
カプセル化の本質は情報隠蔽です。 ここで、 情報隠蔽 ( information hiding ) とは、外部に対して必要最小限の情報だけを公開するという考え方です。 公開部分は、外部から利用されますので、簡単には変更できません。 一方、隠蔽部分は、外部から利用されませんので、いつでも変更できます。
関連する情報をひとまとめにしてからカプセル化を行ったプログラム単位は、 モジュール ( module ) と呼ばれます。 プログラムを作成するときに、モジュールを一つ一つ作成するようにしますと、プログラミングがやりやすくなります。
Java言語では、クラスがモジュールの役割を果たします。 つまり、関連する情報は一つのクラスにまとめ、クラスごとにプログラムを作成するということです。 また、情報隠蔽については、クラスのインスタンス変数やインスタンス・メソッドなどが対象となります。 公開するインスタンス変数と隠蔽するインスタンス変数、公開するインスタンス・メソッドと隠蔽するインスタンス・メソッドに分別します。 隠蔽されたインスタンス変数やインスタンス・メソッドを変更しても、他のプログラムは変更しなくてよいのです。
カプセル化の例として、何時何分という時刻のクラスを考えます。 クラス名を Time とし、何時を変数 hour , 何分を変数 minute で表すことにします。 メイン・プログラムでは、13時15分というデータを格納してから取り出します。
/* 1*/ class Time { /* 2*/ int hour; /* 3*/ int minute; /* 4*/ }
/* 1*/ class TimeMain { /* 2*/ public static void main (String[] args) { /* 3*/ Time t = new Time(); /* 4*/ t.hour = 13; /* 5*/ t.minute = 15; /* 6*/ System.out.println(t.hour + ":" + t.minute); /* 7*/ } /* 8*/ }
b04a001@AisaA1:~/comp3b% java TimeMain 13:15 b04a001@AisaA1:~/comp3b%
もし、変数名 hour と minute を、それぞれ ji と fun に変更しますと、メイン・プログラムも変えなければなりません。
/* 1*/ class Time { /* 2*/ int ji; /* 3*/ int fun; /* 4*/ }
/* 1*/ class TimeMain { /* 2*/ public static void main (String[] args) { /* 3*/ Time t = new Time(); /* 4*/ t.ji = 13; /* 5*/ t.fun = 15; /* 6*/ System.out.println(t.ji + ":" + t.fun); /* 7*/ } /* 8*/ }
一か所を変更するとすべてを変更しなければならないようでは、プログラムの変更は困難になってしまいます。
ここでカプセル化を行います。
まず、インスタンス変数は隠蔽します。
次に、インスタンス変数へアクセスするメソッドを用意し、これを公開します。
Java言語では、
private
と書きますと、その情報が隠蔽されます。
/* 1*/ class Time { /* 2*/ private int hour; /* 3*/ private int minute; /* 4*/ void setHour (int hour) { /* 5*/ this.hour = hour; /* 6*/ } /* 7*/ void setMinute (int minute) { /* 8*/ this.minute = minute; /* 9*/ } /* 10*/ int getHour () { /* 11*/ return this.hour; /* 12*/ } /* 13*/ int getMinute () { /* 14*/ return this.minute; /* 15*/ } /* 16*/ }
/* 1*/ class TimeMain { /* 2*/ public static void main (String[] args) { /* 3*/ Time t = new Time(); /* 4*/ t.setHour(13); /* 5*/ t.setMinute(15); /* 6*/ System.out.println(t.getHour() + ":" + t.getMinute()); /* 7*/ } /* 8*/ }
カプセル化を行ったことによって、隠蔽部分であるインスタンス変数を変更しても、メイン・プログラムはそのままでよくなります。
/* 1*/ class Time { /* 2*/ private int ji; /* 3*/ private int fun; /* 4*/ void setHour (int ji) { /* 5*/ this.ji = ji; /* 6*/ } /* 7*/ void setMinute (int fun) { /* 8*/ this.fun = fun; /* 9*/ } /* 10*/ int getHour () { /* 11*/ return this.ji; /* 12*/ } /* 13*/ int getMinute () { /* 14*/ return this.fun; /* 15*/ } /* 16*/ }
/* 1*/ class TimeMain { /* 2*/ public static void main (String[] args) { /* 3*/ Time t = new Time(); /* 4*/ t.setHour(13); /* 5*/ t.setMinute(15); /* 6*/ System.out.println(t.getHour() + ":" + t.getMinute()); /* 7*/ } /* 8*/ }
カプセル化を行うときは、どの情報を隠蔽し、どの情報を公開するかを適切に決定することが大事です。
特に、インスタンス変数はすべて
private
にするのが普通です。
今後の予定は次の通りです。
この授業の成績は、レポートの提出と試験の得点で決まります。
成績に関して次のような事情のある人はメールで連絡してください。 できる限り対処します。
問1. 以下のプログラムは、掛け算の九九を計算する(つもりの)ものです。 このプログラムの不具合を修正してください。
/* 1*/ class NineNine { /* 2*/ public static void main (String[] args) { /* 3*/ int i, j; /* 4*/ for (i = 1; i <= 9; i++) { /* 5*/ for (j = 1; j <= 9; j++) { /* 6*/ System.out.print(" " + (i * i)); /* 7*/ } /* 8*/ System.out.println(); /* 9*/ } /* 10*/ } /* 11*/ }
b04a001@AsiaA1:~/comp3b% java NineNine 1 1 1 1 1 1 1 1 1 // 正しくは 1 2 3 4 5 6 7 8 9 4 4 4 4 4 4 4 4 4 // 正しくは 2 4 6 8 10 12 14 16 18 9 9 9 9 9 9 9 9 9 // 正しくは 3 6 9 12 15 18 21 24 27 16 16 16 16 16 16 16 16 16 // 正しくは 4 8 12 16 20 24 28 32 36 25 25 25 25 25 25 25 25 25 // 正しくは 5 10 15 20 25 30 35 40 45 36 36 36 36 36 36 36 36 36 // 正しくは 6 12 18 24 30 36 42 48 54 49 49 49 49 49 49 49 49 49 // 正しくは 7 14 21 28 35 42 49 56 63 64 64 64 64 64 64 64 64 64 // 正しくは 8 16 24 32 40 48 56 64 72 81 81 81 81 81 81 81 81 81 // 正しくは 9 18 27 36 45 54 63 72 81 b04a001@AsiaA1:~/comp3b%
問2. 以下のプログラムは、 f (0) = 0, f ( n ) = 2 * f ( n - 1) + 1( n > 0)で定義される関数を計算する(つもりの)ものです。 この関数は、実際は f ( n ) = 2 ^ n - 1 を計算することが知られています。 このプログラムの不具合を修正してください。
/* 1*/ class Recursive { /* 2*/ public static void main (String[] args) { /* 3*/ System.out.println("f(2) = " + f(2)); /* 4*/ System.out.println("f(3) = " + f(3)); /* 5*/ System.out.println("f(4) = " + f(4)); /* 6*/ } /* 7*/ static int f (int n) { /* 8*/ if (n == 0) { /* 9*/ return 0; /* 10*/ } else { /* 11*/ return 2 * (n - 1) + 1; /* 12*/ } /* 13*/ } /* 14*/ }
b04a001@AsiaA1:~/comp3b% java Recursive f(2) = 3 // 正しい f(3) = 5 // 正しくは 7 f(4) = 7 // 正しくは 15 b04a001@AsiaA1:~/comp3b%
問3. 以下のプログラムは、[1, 2, 3, 4, 5] というリストを構成してから、各要素を2個ずつにする(つもりの)ものです。 このプログラムの不具合を修正してください。
/* 1*/ class ListNode { /* 2*/ int data; /* 3*/ ListNode next; /* 4*/ ListNode (int data, ListNode next) { /* 5*/ this.data = data; /* 6*/ this.next = next; /* 7*/ } /* 8*/ }
/* 1*/ class ListDouble { /* 2*/ public static void main (String[] args) { /* 3*/ int i, DUMMY = 0; /* 4*/ ListNode x, list = new ListNode(DUMMY, null); /* 5*/ x = list; /* 6*/ for (i = 1; i <= 5; i++) { /* 7*/ x.next = new ListNode(i, null); /* 8*/ } /* 9*/ x = list; /* 10*/ while (x.next != null) { /* 11*/ x.next = new ListNode(x.data, x.next); /* 12*/ x = x.next.next; /* 13*/ } /* 14*/ for (x = list.next; x != null; x = x.next) { /* 15*/ System.out.print(" " + x.data); /* 16*/ } /* 17*/ System.out.println(); /* 18*/ } /* 19*/ }
b04a001@AsiaA1:~/comp3b% java ListDouble 0 5 // 正しくは 1 1 2 2 3 3 4 4 5 5 b04a001@AsiaA1:~/comp3b%
問4. 次のプログラムは、以下の木の葉の個数を数える(つもりの)ものです。 このプログラムの不具合を修正してください。
10 | +-------+-------+ | | 20 30 | | +---+---+ +---+ | | | 40 50 60
/* 1*/ class TreeNode { /* 2*/ int data; /* 3*/ TreeNode left; /* 4*/ TreeNode right; /* 5*/ TreeNode (int data, TreeNode left, TreeNode right) { /* 6*/ this.data = data; /* 7*/ this.left = left; /* 8*/ this.right = right; /* 9*/ } /* 10*/ }
/* 1*/ class LeafCount { /* 2*/ public static void main (String[] args) { /* 3*/ int DUMMY = 0; /* 4*/ TreeNode tree = new TreeNode(DUMMY, null, null); /* 5*/ tree.right = new TreeNode(10, null, null); /* 6*/ tree.right.left = new TreeNode(20, null, null); /* 7*/ tree.right.right = new TreeNode(30, null, null); /* 8*/ tree.right.left.left = new TreeNode(40, null, null); /* 9*/ tree.right.left.right = new TreeNode(50, null, null); /* 10*/ tree.right.right.left = new TreeNode(60, null, null); /* 11*/ System.out.println(leafCount(tree.right)); /* 12*/ } /* 13*/ static int leafCount (TreeNode tree) { /* 14*/ if (tree == null) { /* 15*/ return 1; /* 16*/ } else if (tree.left == null && tree.right == null) { /* 17*/ return 0; /* 18*/ } else { /* 19*/ return leafCount(tree.left) + leafCount(tree.right); /* 20*/ } /* 21*/ } /* 22*/ }
b04a001@AsiaA1:~/comp3b% java LeafCount 1 // 正しくは 3 b04a001@AsiaA1:~/comp3b%
問5. 次のプログラムは、幅が4, 高さが3の長方形を生成してから、幅も高さも2倍にする(つもりの)ものです。 このプログラムの不具合を修正してください。
/* 1*/ class Rectangle { /* 2*/ int width = 0; /* 3*/ int height = 0; /* 4*/ void setWidth (int width) { /* 5*/ width = this.width; /* 6*/ } /* 7*/ void setHeight (int height) { /* 8*/ height = this.height; /* 9*/ } /* 10*/ int getWidth () { /* 11*/ return this.width; /* 12*/ } /* 13*/ int getHeight () { /* 14*/ return this.height; /* 15*/ } /* 16*/ void expand (int power) { /* 17*/ power = this.width * power; /* 18*/ power = this.height * power; /* 19*/ } /* 20*/ }
/* 1*/ class RectangleMain { /* 2*/ public static void main (String[] args) { /* 3*/ Rectangle rect = new Rectangle(); /* 4*/ rect.setWidth(4); /* 5*/ rect.setHeight(3); /* 6*/ rect.expand(2); /* 7*/ System.out.println(rect.getWidth()); /* 8*/ System.out.println(rect.getHeight()); /* 9*/ } /* 10*/ }
b04a001@AsiaA1:~/comp3b% java RectangleMain 0 // 正しくは 8 0 // 正しくは 6 b04a001@AsiaA1:~/comp3b%
今日の演習12の答案(Javaプログラム)をメールで提出してください。 差出人は学内のメール・アドレス(b04a001@cis.twcu.ac.jpなど)とし、宛先はkonishi@cis.twcu.ac.jpとします。 メールの本文には、学生番号、氏名、科目名、授業日(1月11日)を明記してください。