オブジェクト指向
オブジェクト指向とは、現実世界をモデル化する(現実の世界をそのままプログラムに置き換える)考え方のことです。
オブジェクトとは「もの」という意味で、現実世界に存在する様々な事柄を指しています。オブジェクトには椅子や机のような実際に目に見えるものも、注文や顧客みたいな抽象的な事柄も含まれます。
この考え方に従ってプログラムを書くのがオブジェクト指向プログラミングです。
オブジェクト指向プログラミングを支える代表的な言語としては、Java、Python、C++ などがあります。これらはいわゆる「クラスベース」のオブジェクト指向言語で、最初からオブジェクト指向を前提に設計されています。
実際にはオブジェクト指向の中にいくつかの概念があり、それらを知って使っていくと理解しやすいです。
オブジェクト指向の概念
オブジェクト指向における重要な概念は以下です。
- クラスとオブジェクト
- カプセル化とデータ隠蔽
- 継承
- ポリモーフィズム
※この先は「言葉での説明 → 図解 → コード例」の順で進めます。
最初の言葉での説明は、意味がすぐに分からなくても気にせず流してください。図解やコード例を見て理解できたら、改めて説明に戻ってみると簡単に入ると思います。
クラスとオブジェクト
クラスとオブジェクトはオブジェクト指向の中心となる概念です。
クラス
クラスとは、いくつものデータ型をまとめて扱える「集合体データ型」のことです。簡単に言うと、オブジェクト生成時の雛形になる定義で、設計図のようなものです。基本データ型だけでは表現しきれない複雑な情報を整理して扱うことができます。
クラスには大きく分けて属性と操作の二つを定義します。
-
属性
- そのクラスをもとに生成された実態(オブジェクト)の特性、状態を表す
- つまりデータにあたるもの
- オブジェクト指向言語では、変数として表現する
- クラス内で定義された変数はメンバ変数、フィールドなどと呼ぶ
-
操作
- そのクラスをもとに生成された実態(オブジェクト)の動作、振る舞いを表す。
- データの処理にあたるもの
- オブジェクト指向言語では、メソッドとして表現する
クラス内で定義された変数とメソッドのことをまとめて メンバ と呼びます。
クラスは、オブジェクトが持つべきデータ(変数)と操作(メソッド)をセットで定義する、精密で使いやすい設計図(雛形)と考えることができます。
オブジェクト
オブジェクトとは、クラスから作られた実態を表すものです。
クラスが設計図だとすると、オブジェクトはその設計図をもとに作られた建物のようなものです。
オブジェクトは、クラスで定義した属性(データ)と操作(処理)をそのまま持っています。
クラスから作られた個々のオブジェクトは特にインスタンスと呼ばれます。そして、クラスをもとにインスタンスを作ることをインスタンス化と言います。
オブジェクトを生成すると、そのオブジェクト専用にメモリが確保されます。
このおかげで、それぞれのオブジェクトは独立してデータを持ったり、操作を行ったりできるのです。
クラスとオブジェクトの例
クラスの例として社員クラスを考えてみましょう。
社員クラスには、そのプログラム内で社員として必要な 属性(名前などのデータ)と 操作(社員番号を設定するなどの処理)を定義します。そして、この社員クラスをもとに インスタンス化 することで、実際の社員オブジェクトを作り出すことができます。
生成された社員オブジェクトは、まるで社内のデスクに座った社員のように、名前やメールアドレスといった個人情報を持ちつつ、そのデータを使ってさまざまな操作ができるわけです。
たとえば、社員クラスから「Osada社員オブジェクト」と「Matsuo社員オブジェクト」を作ったとします。
これらのオブジェクトはそれぞれ独立していて、OsadaさんとMatsuoさんの情報や処理を個別に持っており、まさにプログラムの中の小さな社員たちといったイメージです。
クラスとオブジェクトをコードで確認
これまでの説明で使用した社員クラス、社員オブジェクトを書くとこんな感じです↓
//クラス
class Employee {
// フィールド
int empNo;
String empName;
// メソッド
void setData(int no, String name) {
empNo = no;
empName = name;
}
int getEmpNo() {
return empNo;
}
String getEmpName() {
return empName;
}
}
class CreateSample {
public static void main(String[] args) {
Employee emp1 = new Employee(); // Employeeオブジェクトの生成
emp1.setData(1, "Osada");
System.out.println("社員番号: " + emp1.getEmpNo());
System.out.println("社員名: " + emp1.getEmpName());
Employee emp2 = new Employee(); // Employeeオブジェクトの生成
emp2.setData(2, "Matsuo");
System.out.println("社員番号: " + emp2.getEmpNo());
System.out.println("社員名: " + emp2.getEmpName());
}
}
補足情報
- キーワード、修飾子
- クラスの修飾子は、
public,final,abstractが指定できる - フィールド・メソッドの修飾子は、
public,private,protectedが指定できる - クラスをもとにオブジェクトを生成するには、
newキーワードを使用する
- クラスの修飾子は、
※修飾子についての詳細はこの後扱います
- ブジェクトの初期化とメモリ
-
emp1とemp2はそれぞれ独立したメモリ領域を持ち、インスタンス変数empNoやempNameはオブジェクトごとに値が異なる - メソッド内で使うローカル変数は、自分で値を代入しないと使えない
-
カプセル化とデータ隠蔽
カプセル化とは、オブジェクト内に属性(変数)とそれに対する操作(メソッド)を一つにまとめて持たせることです。内部実装を隠して、利用者にとって一貫性あるインターフェースを提供することができます。
データ隠蔽とは、あるオブジェクト内の各種属性および操作に対してアクセスすることを制限することです。これにより、属性や操作に対して外部からの不正な値の代入や、利用者による誤用を防ぐことができます。
アクセスレベル
Javaでは、データ隠蔽を行うには、各変数やメソッドに対してアクセスレベルを指定します。
アクセスできるレベルの制御には、変数およびメソッド名の前にアクセス修飾子としてのキーワードpublic , private , protected を指定します。定数以外の変数に対してpublicを使用することは、データ隠蔽の観点から非推奨です。↓
| キーワード | アクセス制御の意味 |
|---|---|
| public | パッケージ内外を問わず、どこから(どのオブジェクト、どのメソッド)でもアクセス可能 |
| protected | 変数およびメソッドが定義されたクラス内のメソッド、このクラスを継承したサブクラス、同一パッケージ内のクラスからアクセス可能 |
| 何もつけない | 同一パッケージ内からのみアクセス可能 |
| private | 変数およびメソッドが定義されたクラス内のメソッドからのみアクセス可能 |
| ※継承とパッケージについての詳細はこの後扱います |
コードで確認↓
class Employee {
// フィールド
public int empNo;
private String empName;
// コンストラクタ(クラスと同じ名前で定義され、戻り値を持たない特別なメソッド。インスタンス生成時に自動的に呼び出され、初期化処理を行う)
public Employee(int no, String name) {
empNo = no;
empName = name;
}
public int getEmpNo() {
return empNo;
}
public String getEmpName() {
return empName;
}
}
class AccessSample {
public static void main(String[] args) {
Employee emp1 = new Employee(1, "Osada");
// 適切な取得方法(getterを経由)
System.out.println("社員番号: " + emp1.getEmpNo());
System.out.println("社員名: " + emp1.getEmpName());
// エラーにはならないがカプセル化とデータ隠蔽の観点から不適切な取得方法(データ隠蔽ができていない、そして副次的にカプセル化の恩恵も損なってしまっている)
System.out.pritnln('社員番号' + emp1.empNo);
// フィールドがprivateなので直接アクセスはできない
System.out.println("社員名: " + emp1.empName); // コンパイルエラー
}
}
補足情報
コンストラクタ
上記のコードで登場したコンストラクタとは、オブジェクトの初期化を行うための処理ブロックで、オブジェクト生成時に一度だけ呼び出されます。
主なルールは以下の通りです。
- クラスと同じ名前を持つ
- メソッドと異なり、戻り値を持たない
- メソッドと同様に、引数を受け取ることができる
- メソッドと同様に、オーバーロード(※) ができる
※オーバーロードとは、同一クラス内に、同じ名前で、引数の数または型が異なるメソッドを複数定義することです。呼び出す時に渡す引数をもとに、呼び出されるメソッドが決定します
オーバーロードの例
setData()メソッドをオーバーロードする
class Employee {
int empNo;
String empName;
//引数が一つのsetData()メソッド
void setData(int no){
empNo = no;
empName = "unknown";
}
//引数が二つのsetData()メソッド
void setData(int no, String name){
empNo = no;
empName = name;
}
void display(){
System.out.println("Employee No: " + empNo);
System.out.println("Employee Name: " + empName);
}
}
class OverloadSample{
public static void main(String args[]){
Employee emp1 = new Employee();
emp1.setData(1); //引数が一つのsetData()メソッドの呼び出し
emp1.display();
Employee emp2 = new Employee();
emp2.setData(2,"hayashi"); //引数が二つのsetData()メソッドの呼び出し
emp2.display();
}
}
出力:
Employee No: 1
Employee Name: unknown
Employee No: 2
Employee Name: hayashi
継承
継承とは、あるクラスを基にして、より具体的なクラスを定義する仕組みです。オブジェクト指向の用語では、基となる一般的なクラスをスーパークラス、それを発展させた具体的なクラスをサブクラスと呼びます。
サブクラスはスーパークラスの変数やメソッドを受け継ぐため、共通の機能を再利用できます。その上で、サブクラス独自の変数やメソッドを追加して、個別の特性を表現します。これにより、変更や拡張が必要な部分だけを記述する差分コーディングが可能になります。
継承関係を設計する方法には、大きく分けて汎化と特化の二つがあります。
• 汎化:複数のクラスに共通する属性や操作を抽出し、新たにスーパークラスを定義する方法
• 特化:一つのクラスを基にして、より具体的で詳細なサブクラスを定義する方法
サブクラスを定義する場合には、キーワードextendsを使用し、スーパークラスを指定します。
サブクラスはスーパークラスから全てのメソッドと変数を継承しますが、スーパークラスのコンストラクタは継承しません。
コードで確認
//Employee スーパークラス
class Employee {
private int empNo;
private String empName;
public void setData(int no, String name){
empNo = no;
empName = name;
}
public void display() {
System.out.println("Employee No: " + empNo);
System.out.println("Employee Name: " + empName);
}
}
// Employee スーパークラスを継承したEngineerサブクラス
class Engineer extends Employee {
private String projectName;
public void setProjectName(String project) {
projectName = project;
}
public void displayProjectName() {
System.out.println("Project: " + projectName);
}
}
class InheritanceSample {
public static void main(String args[]) {
Engineer eng1 = new Engineer();
// スーパークラスから継承したメソッドの呼び出し
eng1.setData(1, "Tanaka");
eng1.display();
// サブクラスで定義したメソッドの呼び出し
eng1.setProjectName("Duke Project");
eng1.displayProjectName();
}
}
出力:
Employee No: 1
Employee Name: Tanaka
Project: Duke Project
オーバーライド
オーバーライドは、スーパークラスで用意されたメソッドを、サブクラスで定義し直すことです。
ただし条件として、メソッド名、戻り値の型、引数の型と数 がスーパークラスのメソッドと同じであることが必要です。
オーバーライドされているメソッドが呼び出された時は、サブクラスのメソッドが使用されます。
メソッドをサブクラスからオーバーライドできないように保護したい場合は、メソッドを宣言する際にfinalというキーワードを指定します。(例:public final void display(){・・・})
先ほどのEmployeeクラスのdisplay()メソッドをEngineerクラスでオーバーライドしてみると以下のようになります。
//Employee スーパークラス
class Employee {
private int empNo;
private String empName;
public void setData(int no, String name){
empNo = no;
empName = name;
}
public void display() {
System.out.println("Employee No: " + empNo);
System.out.println("Employee Name: " + empName);
}
}
// Employee スーパークラスを継承したEngineerサブクラス
class Engineer extends Employee {
private String projectName;
public void setProjectName(String project) {
projectName = project;
}
public void display() { // スーパークラスのdisplay()メソッドをオーバーライド
System.out.println("Project: " + projectName);
}
}
class InheritanceSample {
public static void main(String args[]) {
Engineer eng1 = new Engineer();
eng1.setData(1, "Tanaka");
eng1.setProjectName("Duke Project");
eng1.display();
}
}
出力
Project: Duke Project
thisとsuper
インスタンス・メソッド内では、キーワードthisが使用できます。
thisは、現在のオブジェクトを意味します。
また、superは、現在のオブジェクトのスーパークラスを意味します。
現在のオブジェクトを使用する場合
this.変数
this.メソッド
現在のオブジェクトのスーパークラス部分を使用する場合
super.変数
super.メソッド
メソッド内で、オブジェクトを指定せずに変数名およびメソッド名を使用した場合は、thisオブジェクトが対象になります。
実際に使用してみましょう
class Employee {
private int empNo;
private String empName;
public void setData(int empNo, String empName){
this.empNo = empNo; // インスタンス変数と引数が同名であることに注意
this.empName = empName;
}
public void display() {
System.out.println("Employee No: " + empNo);
System.out.println("Employee Name: " + empName);
}
}
class Engineer extends Employee {
private String projectName;
public void setProjectName(String projectName) {
this.projectName = projectName;
}
public void display() {
super.display(); // スーパークラスのdisplayメソッドを呼び出し
System.out.println("Project: " + projectName);
}
}
class InheritanceSample {
public static void main(String args[]) {
Engineer eng1 = new Engineer();
eng1.setData(1, "Tanaka");
eng1.setProjectName("Duke Project");
eng1.display();
}
}
出力
Employee No: 1
Employee Name: Tanaka
Project: Duke Project
自クラスのコンストラクタ呼び出し
クラスに複数のコンストラクタがある場合、あるコンストラクタの効果を別のコンストラクタの内部に重複させたい場合があります。これは、メソッド呼び出しとしてキーワードthisを使用することにより実現できます。
class Employee{
private int empNo;
private String empName;
public Employee(){
this(0,"unknown"); //this(引数リスト)はオーバーロードされたコンストラクタを呼び出す
}
public Employee(int no, String name){
empNo = no;
empName = name;
}
}
スーパークラスのコンストラクタ呼び出し
スーパークラスを継承するオブジェクトは、サブクラスが実行される前に初期化されている必要があります。
通常、引数なしのコンストラクタを使用してスーパークラスのオブジェクトが初期化されます。
明示的にnスーパークラスのコンストラクタを呼び出す場合は、キーワードsuperを使用します。
class Employee{
private int empNo;
private String empName;
public Employee(int no, String name){
empNo = no;
empName = name;
}
class Engineer extends Employee{
public Engineer(int no, String name)
super(no,name); // super(引数リスト)はスーパークラスのコンストラクトを呼び出す
}
}
※this,superを使用したコンストラクタの呼び出しは、コンストラクタの先頭に記述しなければならない
class Employee {
private int empNo;
private String empName;
public Enployee(int no, String name){
empNo = empNo;
empName = empName;
}
public void display() {
System.out.println("Employee No: " + empNo);
System.out.println("Employee Name: " + empName);
}
}
class Engineer extends Employee {
private String projectName;
public Engineer(int no, String name) {
this(no,name,"unknown");
}
public Engineer(int no, String name, String project) {
super(no,name); // 自クラスのコンストラクタ呼び出し
projectName = project;
}
public void display() {
super.display();
System.out.println("Project: " + projectName);
}
}
class SuperConstructorSample {
public static void main(String args[]) {
Engineer eng1 = new Engineer(1, "Tanaka");
eng1.display();
}
}
出力
Employee No: 0
Employee Name: null
Project: unknown
ここまででpart①を終わります。
お気づきの点がありましたら、コメントいただけますと幸いです。
また、次回part②の内容に対するリクエストなどがもしあれば嬉しいです!
part②で取り扱う内容は以下の予定です。
- インターフェース
- クラス修飾子
- ポリモフィズム
- 参照型の型変換、キャスト
- パッケージ


