Javaプログラミング入門 11章 [アクセス修飾子とカプセル化]

Java

こんにちは。ECF Techブログ
担当 Michiharu.Tです。

Javaプログラミング入門記事の第11章をお送りいたします。
第11章のテーマは「アクセス修飾子とカプセル化」です。

本連載の初回および章立ての一覧については下記のリンクから確認できます。

Javaプログラミング入門 0章
0章 Javaプログラミングを始めよう こんにちは。ECF Techブログ 担当 Michiharu.Tです。 この記事はプログラミング言語のJavaを、実際に動かしながら学びたい人のための学習教材となっています。手を多く動かすこと、感覚を...

解説動画

2024/02/16 解説動画を掲載しました。

11-1 プログラムの安全性

オブジェクト指向プログラミングの目指す重要なテーマのひとつに、プログラムの安全性があります。ここではプログラムの安全性について、9章で登場したPointCardクラスを使って考察したいと思います。

プログラム例(PointCard.java 再掲)

public class PointCard {
    int id;     //カード番号
    int point;  //残ポイント

    //コンストラクタ
    PointCard(int id, int point) {
        this.id = id;
        this.point = point;
    }

    //支払処理
    void payment( int price ){
        //金額の1%をポイントに加算
        point += price*0.01;
    }
}

一見すると問題ないプログラムに見えますが、ポイントカードの仕様が次のようになっている場合、このプログラムが問題になる場合があります。

ポイントカードの仕様

  • IDは1度割り振られたあとは変更されない。
  • ポイントが0未満の値になることはない。
仕様とは、システムやプログラムがどのような情報を持ち、どのような動作をするべきかをあらかじめ取り決めたものです。

お店アプリを製作する中で、PointCardクラスが様々な別クラスから利用される場合、次のような誤った使い方をされてしまう場合があります。

プログラム例(Main.java)

public class Main{
    public static void main(String[] args){
        PointCard card = new PointCard(1,100);
        card.point = -200;
        card.id = 5;
    }
}

PointCardクラスからインスタンスを生成し、利用するプログラムですが次のような問題があります。

  • pointフィールドに負の数を代入してしまっている(card.point = -200)。
  • idフィールドが後から変更されてしまっている(card.id = 5)。

このように、フィールドが本来あってはならない値になってしまう可能性がある。という点では、PointCardクラスは安全なプログラムとは言えません。本章では、このPointCardクラスの修正をとおして、クラスの安全性を高めるプログラミングについて見ていきます。

11-2 アクセス修飾子

PointCardクラスを修正する最初の一歩として、まず始めにフィールドを他のクラスからアクセスできないようにしたいと思います。アクセス修飾子を使うことで、これを実現できます。

11-2-1 アクセス修飾子の基本

アクセス修飾子は修飾子のひとつで、クラス、フィールド、メソッドなどに付け加えることができます。アクセス修飾子を使用することで、フィールドやメソッドの公開範囲を定義することができます。

アクセス修飾子にはprivateprotectedpublicの3種類があり、アクセス修飾子そのものを付けない方法も加え、4種類の公開範囲が定義できるようになっています。

下表にアクセス修飾子とその公開範囲を示します。下に行くほど公開可能な範囲が広くなります。

アクセス修飾子 公開範囲
private 自身のクラス内のみでアクセス可能
※指定なし 自身が属するパッケージ内のクラスからはアクセス可能
protected 自身の属するパッケージ内のクラスと自身のサブクラスからアクセス可能
public すべてのクラスからアクセス可能

※表内のパッケージサブクラスの概念は、後の章の学習内容になります。現時点ではpublicprivateについてご確認頂ければOKです。

11-2-2 アクセス修飾子の動作

では実際に、アクセス修飾子を付け加えたプログラムがどのように動作するかを確認してみましょう。

プログラム例(PointCard.java)

public class PointCard {
    public int id;      //カード番号
    private int point;  //残ポイント

    //コンストラクタ
    PointCard(int id, int point) {
        this.id = id;
        this.point = point;
    }

    //支払処理
    void payment( int price ){
        //金額の1%をポイントに加算
        point += price*0.01;
    }
}

元のPointCard.javaとの変更点は、2,3行目だけです。publicprivateをそれぞれのフィールドにつけ加えています。つづけて、メインクラスのプログラムです。

プログラム例(Main.java)

public class Main{
    public static void main(String[] args){
        PointCard card = new PointCard(1,100);
        System.out.println(card.id);
        System.out.println(card.point);
    }
}

コンパイル結果

Main.java:5: エラー: pointはPointCardでprivateアクセスされます
        System.out.println(card.point);
                               ^
エラー1個

PointCardクラスにおいて、idフィールドの宣言部分にはpublicが付けられています。publicが付けられているフィールドはどのクラスからでもアクセスできるため、Main.javaの4行目の記述はエラーになりません。

一方で、pointフィールドは宣言部分にprivateが付けられているため、別のクラス(ここではMainクラス)からのアクセスができません。したがって5行目でエラーが発生しています。

2つのクラス間のアクセス可否の状態を示すと下図のようになります。

2クラス間のアクセス

11-3 アクセサメソッド

アクセス修飾子を使って、フィールドを他のクラスからアクセスさせない状態を作ることができましたが、一方で、フィールドに対する一切のアクセスができない状態は大変不便です。次はアクセサ(Accessor)メソッドを使って、この点を解消するプログラムを作成しましょう。

11-3-1 アクセサメソッドの定義

アクセサメソッドはフィールドへのアクセス手段を提供するメソッドです。といっても特別な文法があるわけではなく普通のメソッドです。

アクセサメソッドには次の2種類が存在し、書き方には一定のルールがあります。

  • ゲッターメソッド ※ゲッターとも言います
  • セッターメソッド ※セッターとも言います

(1)ゲッター(getter)メソッド

フィールドの値を返すメソッドです。メソッド定義は次のように記述します。

public 戻り値型 getXxxx(){
    return フィールド名;
}
  • 戻り値型は、フィールドの型と同じになります。
  • Xxxxの部分にはフィールド名の先頭1文字を大文字にした名前を設定します。
  • アクセス修飾子はpublicとします。

PointCardクラスのpointフィールドに値を設定するゲッターは次のように定義します。

public int getPoint(){
    return point;
}

(2)セッター(setter)メソッド

フィールドに値をセットするメソッドです。メソッド定義は次のように記述します。

public void setXxxx(型名 引数名){
    this.フィールド名 = 引数名;
}
  • 型名はフィールドの型と同じになります。
  • 引数名とフィールド名は同じになります。
  • Xxxxの部分にはフィールド名の先頭1文字を大文字にした名前を設定します。
  • アクセス修飾子はpublicとします。

PointCardクラスのpointフィールドに値を設定するセッターは次のように定義します。

public void setPoint(int point){
    this.point = point;
}

11-3-2 アクセサメソッドの使用例

アクセサメソッドを使って、PointCardクラスを改善したプログラム例を示します。

プログラム例(PointCard.java)

public class PointCard {
    private int id;     //カード番号
    private int point;  //残ポイント

    PointCard( int id, int point ){
        this.id = id;
        this.point = point;
    }
    //idフィールドのゲッター
    public int getId(){
        return this.id;
    }
    //pointフィールドのゲッター
    public int getPoint(){
        return this.point;
    }
    //pointフィールドのセッター
    public void setPoint( int point ){
        if( point >= 0 ){
            this.point = point;
        }
    }
    //支払処理
    void payment( int price ){
        //金額の1%をポイントに加算
        point += price*0.01;
    }
}
  • フィールドはすべてprivateとしています。
  • どちらのフィールドも値を取得する可能性があることを前提に、ゲッターを定義しています。
  • idフィールドはインスタンス生成(コンストラクタ呼び出し)時に設定されたら、以降変更されない仕様なので、セッターは定義していません。
  • pointフィールドは他クラスから値を設定する場合があるためセッターを定義しています。ただし、負の値はフィールドに代入されないようになっています。

次に、修正したPointCardクラスを利用するメインクラスの例を示します。

プログラム例(Main.java)

public class Main {
    public static void main(String[] args) {
        PointCard card = new PointCard(1,100);
        //pointを-50に変更
        card.setPoint(-50);
        System.out.println(card.getPoint());
        //pointを200に変更
        card.setPoint(200);
        //idとpointを表示
        System.out.println(card.getId());
        System.out.println(card.getPoint());
    }    
}

実行結果

100
1
200

解説

3行目PointCard card = new PointCard(1,100);
PointCardインスタンスを生成しています。コンストラクタ経由でidフィールドに1、pointフィールドに100が代入されます。

5行目card.setPoint(-50);
セッターを使って、pointフィールドに-50の代入を試みています。setPointメソッドはpoint >= 0の場合のみ、値がフィールドに設定されるようになっているため、-50は設定されません。

6行目System.out.println(card.getPoint());
PointCardインスタンスのpointフィールドの値を取得し、表示しています。フィールドはprivateになっているため、このようにゲッターを使用する必要があります。実行結果から、pointフィールドは100のままであることがわかります。

8行目card.setPoint(200);
セッターを使って、pointフィールドに200を設定しています。こちらは正の数なのでフィールドに設定されます。

10,11行目
PointCardインスタンスの2つのフィールドを表示しています。実行結果からpointフィールドが200になっていることがわかります。

これで、PointCardクラスが誤用されにくい、安全性の高いクラスとなりました。

11-4 カプセル化

本章ではここまで、PointCardクラスの安全性向上のための改善を進めてきました。この一連の修正は、オブジェクト指向プログラミングにおけるカプセル化という概念の実現を目指したものです。

カプセル化とは、簡単に言えばデータデータに対する操作をひとまとめにすることです。Javaのプログラムにおいては、インスタンスがカプセルにあたると言えます。

さらに、データを外部から見えなくしてしまう(隠ぺい)ことで、保守性や安全性を高められるとする考え方です。下図はカプセル化のイメージです。

今回のPointCardクラスのように、原則としてデータ(フィールド)をprivateとし、外部のプログラム(他のクラスなど)からアクセスをする場合はメソッドを利用してもらう設計は、カプセル化を実現する基本的なパターンとなります。

タイトルとURLをコピーしました