Java入門 STEP11|カプセル化を学ぼう

karin@taishi@felmia

カプセル化とは?

STEP10までに作ったStudentクラスでは、名前や点数をフィールドに保存していました。

現在のStudentクラスは、次のような形です。

public class Student {

    String name;
    int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    public void introduce() {
        System.out.println("名前:" + name);
        System.out.println("点数:" + score);
    }

}

この状態では、Studentクラスの外からフィールドの値を直接変更できます。

例えば、StudentSampleクラスから次のように書くことができます。

Student student = new Student("Lumy", 80);

student.score = 100;

これなら、点数を80点から100点に変更できます。

しかし、次のような変更もできてしまいます。

student.score = -100;

もしscoreがテストの点数を表しているのであれば、-100点という値はおかしいですね。

このように、外部からフィールドを自由に変更できる状態では、そのクラスにとって不適切な値が設定されてしまう可能性があります。

そこで使われる考え方のひとつが「カプセル化」です。

フィールドをprivateにしてみよう

フィールドの前にprivateを付けてみましょう。

Studentクラスを次のように変更してください。

public class Student {

    private String name;
    private int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    public void introduce() {
        System.out.println("名前:" + name);
        System.out.println("点数:" + score);
    }

}

変更したのは、

private String name;
private int score;

の部分です。

privateを付けたフィールドは、そのクラスの外から直接アクセスできなくなります。

そのため、StudentSampleクラスから、

student.score = -100;

と書こうとするとエラーになります。

一方、Studentクラス自身の中にある、

this.score = score;

や、

System.out.println("点数:" + score);

では、同じStudentクラスの中から自分のフィールドを使っているためアクセスできます。

privateを付けるとどうなる?

private String name;
private int score;

とすると、Studentクラスの外から、

student.name

student.score

のように直接アクセスできなくなります。

フィールドを外部から自由に操作させず、クラス自身が管理できるようにするために使います。

カプセル化は「隠すだけ」ではない

ここは大切なところです。

カプセル化というと、単純に「フィールドをprivateにして隠すこと」と覚えてしまいがちです。

しかし、目的はただ隠すことではありません。

外部からの操作方法を制限し、オブジェクトの状態を適切に管理しやすくすることが重要です。

今回なら、

点数を変更する場合は、0~100の値だけ受け付ける

というルールをStudentクラス側に持たせることもできます。

そのために、このあとgetter(ゲッター)とsetter(セッター)を使います。

ルミィ
ルミィ

privateは「絶対に触っちゃダメ!」というより、「直接変更せず、決めた方法で使ってね」というイメージだよ♪

getterを使って値を取得しよう

フィールドにprivateを付けると、クラスの外から直接アクセスできなくなります。

そのため、StudentSampleクラスから次のように書くことはできません。

System.out.println(student.score);

では、Studentクラスの外から点数を確認したい場合はどうすればよいのでしょうか。

このような場合は、フィールドの値を返すメソッドを用意します。

値を取得するためのメソッドを一般的に**getter(ゲッター)**と呼びます。

getScoreメソッドを作ってみよう

Studentクラスに、次のメソッドを追加します。

public int getScore() {
    return score;
}

getScore()は、Studentオブジェクトが持っているscoreの値をreturnで返しています。

STEP7で学んだ、戻り値のあるメソッドと同じ仕組みです。

それでは、StudentクラスにgetScore()を追加してみましょう。

public class Student {

    private String name;
    private int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    public void introduce() {
        System.out.println("名前:" + name);
        System.out.println("点数:" + score);
    }

    public int getScore() {
        return score;
    }

}

これで、Studentクラスの外からscoreを直接使うのではなく、getScore()を通して点数を取得できるようになりました。

getterを呼び出してみよう

StudentSampleクラスからgetScore()を使ってみます。

public class StudentSample {

    public static void main(String[] args) {
        Student student = new Student("Lumy", 80);

        int score = student.getScore();

        System.out.println("取得した点数:" + score);
    }

}

実行結果

取得した点数:80

今回の、

student.getScore()

によって、Studentオブジェクトが持っているscoreの値を取得しています。

そして、返された80を、

int score = student.getScore();

scoreという変数に代入しています。

直接フィールドを使うのではなく、メソッドを通して取得する

privateにする前

student.score

→ フィールドを直接使う

privateにしたあと

student.getScore()

getScore()を通して値を取得する

getterを用意することで、フィールドをprivateにしたまま、必要な値を外部から取得できます。

nameにもgetterを作る場合

nameprivateにしているため、外部から名前を取得したい場合は同じようにgetterを作ることができます。

public String getName() {
    return name;
}

nameString型なので、戻り値の型もStringになっています。

scoreなら、

public int getScore()

nameなら、

public String getName()

となります。

getterだから特別な方法で値を返しているわけではなく、STEP7で学んだメソッドとreturnを使ってフィールドの値を返しているということです。

ルミィ
ルミィ

getterも新しい仕組みに見えるけど、中身は今まで習ったメソッドとreturnなんだね♪

setterを使って値を変更しよう

getterを使うことで、privateにしたフィールドの値を外部から取得できるようになりました。

では、外部から点数を変更したい場合はどうすればよいでしょうか。

scoreprivateなので、StudentSampleクラスから、

student.score = 90;

と直接変更することはできません。

そこで、値を変更するためのメソッドを用意します。

値を設定するためのメソッドを一般的にsetter(セッター)と呼びます。

setScoreメソッドを作ってみよう

まずは、基本的なsetterを見てみましょう。

public void setScore(int score) {
    this.score = score;
}

setScore()は引数で新しい点数を受け取り、その値をフィールドのscoreへ代入しています。

ここでも、

this.score = score;

の左側にあるthis.scoreはフィールド、右側のscoreは引数です。

STEP10のコンストラクタで使った書き方と同じですね。

不正な点数が入らないようにしよう

ただし、先ほどのsetterでは、

student.setScore(-100);

とすれば、結局-100を設定できてしまいます。

そこで、setScore()の中で値を確認してから設定するようにしてみましょう。

今回は、点数を0~100の範囲にします。

Studentクラスを次のように変更してください。

public class Student {

    private String name;
    private int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    public void introduce() {
        System.out.println("名前:" + name);
        System.out.println("点数:" + score);
    }

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        if (score >= 0 && score <= 100) {
            this.score = score;
        }
    }

}

setScore()では、STEP4で学んだif文を使っています。

if (score >= 0 && score <= 100) {
    this.score = score;
}

これは、

「scoreが0以上、かつ100以下なら値を変更する」

という条件です。

setterを使ってみよう

StudentSampleクラスから点数を変更してみましょう。

public class StudentSample {

    public static void main(String[] args) {
        Student student = new Student("Lumy", 80);

        student.setScore(90);

        System.out.println("点数:" + student.getScore());
    }

}

実行結果

点数:90

setScore(90)では、90が0~100の範囲に入っているため、点数が変更されます。

では、次のようにしたらどうなるでしょうか。

student.setScore(-100);

は実行されません。

そのため、もともとの点数は変更されません。

privateとsetterを組み合わせる意味

最初のStudentクラスでは、

student.score = -100;

のように、外部から直接どんな値でも設定できました。

フィールドをprivateにすると、このような直接変更を禁止できます。

そして、点数を変更するときはsetScore()を使うようにすれば、Studentクラス側で値を確認してから変更できます。

直接変更させないことで、値をチェックできる

フィールドを直接変更する場合

student.score = -100;

→ 値をそのまま設定できてしまう

setterを使う場合

student.setScore(-100);

setScore()の中で値をチェック

不適切な値なら変更しない

このように、フィールドをprivateにして操作方法を用意することで、オブジェクトが不適切な状態になることを防ぎやすくなります。

getterやsetterは必ず両方必要?

getterとsetterは、必ずセットで作らなければならないわけではありません。

例えば、

「名前は外部から確認できるけれど、あとから変更はさせたくない」

のであれば、getName()だけを用意してsetName()を作らない、という設計もできます。

つまり、

getter → 外部から値を取得させたいとき

setter → 外部から値を変更させたいとき

に、必要なものを用意します。

privateにしたら、すべてのフィールドにgetter・setterを作るわけではない

どの値を外部から取得できるようにするのか、どの値を変更できるようにするのかは、そのクラスの役割に合わせて考えます。

ただフィールドをprivateにしてgetter・setterを全部作るのではなく、必要な操作だけを外部に公開するという考え方が大切です。

ルミィ
ルミィ

privateで直接変更できなくして、必要な操作だけメソッドからできるようにするんだね♪
setterなら、変更する前に値のチェックもできるよ!

コンストラクタからも不正な値が入らないようにしよう

先ほどsetScore()を作り、0~100以外の点数を設定できないようにしました。

public void setScore(int score) {
    if (score >= 0 && score <= 100) {
        this.score = score;
    }
}

これで、

student.setScore(-100);

としても、点数は変更されません。

しかし、現在のコンストラクタを見てみましょう。

public Student(String name, int score) {
    this.name = name;
    this.score = score;
}

コンストラクタでは、受け取ったscoreをそのままフィールドへ代入しています。

そのため、

Student student = new Student("Lumy", -100);

とすると、最初から-100という点数を持ったStudentオブジェクトが作られてしまいます。

setterではチェックしているのに、これでは少し困りますね。

コンストラクタからsetScoreを使おう

コンストラクタにも同じif文を書く方法はあります。

しかし、点数のルールが変わったときに、コンストラクタとsetterの両方を修正しなければなりません。

そこで、コンストラクタからもsetScore()を使うようにします。

Studentクラスを次のように変更してください。

public class Student {

    private String name;
    private int score;

    public Student(String name, int score) {
        this.name = name;
        setScore(score);
    }

    public void introduce() {
        System.out.println("名前:" + name);
        System.out.println("点数:" + score);
    }

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        if (score >= 0 && score <= 100) {
            this.score = score;
        }
    }

}

変更したのは、コンストラクタのこの部分です。

setScore(score);

これでコンストラクタから受け取った点数も、setScore()でチェックしてからフィールドへ設定されます。

なぜ同じif文を書かないの?

例えば、次のように書くこともできます。

public Student(String name, int score) {
    this.name = name;

    if (score >= 0 && score <= 100) {
        this.score = score;
    }
}

しかし、これでは同じチェック処理がsetScore()にもコンストラクタにも存在することになります。

もし将来、

「点数は0~100」から「点数は0~200」へ変更する

となったら、両方を修正しなければなりません。

setScore()にチェックをまとめておけば、基本的にはそこを修正すれば済みます。

これはSTEP7のメソッドで学んだ、同じ処理をまとめて修正しやすくするという考え方と同じです。

同じルールはできるだけ1か所で管理する

コンストラクタ

setScore(score)を呼び出す

setScore()で点数をチェックする

問題がなければフィールドへ設定する

こうしておけば、点数を設定するときのルールをsetScore()にまとめることができます。

不正な値を渡して確認してみよう

StudentSampleクラスで、実際に確認してみます。

public class StudentSample {

    public static void main(String[] args) {
        Student student = new Student("Lumy", -100);

        System.out.println("点数:" + student.getScore());
    }

}

実行結果

点数:0

-100setScore()の条件を満たさないため、フィールドのscoreには代入されません。

int型のフィールドは値を設定しなかった場合、初期値が0になるため、今回は0と表示されます。

今回は仕組みを分かりやすくするため、不正な値が渡された場合は点数を変更しないようにしています。

実際のシステムでは、不正な値が渡されたことを知らせるために例外を発生させるなど、別の方法を取ることもあります。

例外については、後のSTEPで詳しく学びます。

ルミィ
ルミィ

「点数を設定するときはここを通る!」って場所を決めておくと、ルールを管理しやすくなるね♪

よくある間違い

カプセル化では、フィールドをprivateにしたことで、これまでとはフィールドの扱い方が変わりました。

エラーになったときや思ったように値が変わらないときは、次の点を確認してみましょう。

privateのフィールドへ直接アクセスしている

Studentクラスでは、フィールドを次のようにprivateにしました。

private String name;
private int score;

この状態では、StudentSampleクラスから直接フィールドを使うことはできません。

そのため、次の書き方はエラーになります。

System.out.println(student.score);
student.score = 90;

値を取得したい場合はgetter、変更したい場合はsetterを使います。

int score = student.getScore();
student.setScore(90);

privateにしたフィールドはクラスの外から直接使えない

取得する
student.getScore()

変更する
student.setScore(90)

エラーになったら、.のあとにprivateのフィールドを直接指定していないか確認してみましょう。

getterでreturnを書き忘れる

今回作ったgetScore()は、点数を呼び出し元へ返すメソッドです。

public int getScore() {
    return score;
}

getScore()の戻り値の型はintなので、int型の値を返す必要があります。

次のようにreturnを書かないとエラーになります。

public int getScore() {
}

getterだけの特別なルールではなく、STEP7で学んだ戻り値のあるメソッドのルールと同じです。

setterにreturnを書こうとする

今回のsetScore()は、値を受け取ってフィールドを変更するためのメソッドです。

public void setScore(int score) {
    if (score >= 0 && score <= 100) {
        this.score = score;
    }
}

戻り値の型がvoidなので、今回は値を返す必要はありません。

getterとsetterの名前だけで判断するのではなく、そのメソッドが何を受け取り、何を返すのかを見るようにしましょう。

this.scoreとscoreを混同する

setterにも、STEP10で学んだthisが登場しました。

this.score = score;

この2つは役割が違います。

this.score
→ Studentオブジェクトが持っているフィールド

score
setScore(int score)で受け取った引数

右側の引数scoreを、左側のフィールドthis.scoreへ代入しています。

this.score = score;

で迷ったら、

「このStudentのscoreに、受け取ったscoreを入れる」

と読んでみましょう。

コンストラクタでもsetterでも考え方は同じです。

setterを作れば、どんな値でも設定できると思ってしまう

今回のsetScore()には、0~100という条件があります。

そのため、

student.setScore(90);

なら変更できますが、

student.setScore(-100);

では変更されません。

エラーが出ないのに値が変わらない場合は、setterの中に設定されている条件も確認してみましょう。

getterとsetterの名前を間違える

一般的には、フィールド名に合わせて次のような名前を付けます。

フィールドgettersetter
namegetName()setName()
scoregetScore()setScore()

例えばscoreを取得するメソッドなのにgetName()とすると、コードそのものが必ずエラーになるとは限りませんが、メソッド名と役割が一致せず分かりにくいコードになります。

名前から何をするメソッドなのか分かるようにしておきましょう。

ルミィ
ルミィ

getterとsetterで迷ったら、「値を取り出したい?」「値を変更したい?」から考えてみよう♪
それからprivateのフィールドを直接触っていないかもチェックだね!

STEP11のまとめ

このSTEPでは、オブジェクトが持つデータを適切に管理するための「カプセル化」について学びました。

最後に、今回のポイントを振り返っておきましょう。

  • privateを付けると、クラスの外からフィールドへ直接アクセスできなくなる
  • フィールドの値を取得したい場合は、getterを用意できる
  • フィールドの値を変更したい場合は、setterを用意できる
  • getterではreturnを使ってフィールドの値を返す
  • setterでは、受け取った値を確認してからフィールドへ設定することもできる
  • getterとsetterは、必ず両方作る必要があるわけではない
  • 外部から必要な操作だけができるようにすることで、オブジェクトの状態を管理しやすくなる
  • 同じチェック処理を1か所にまとめることで、修正もしやすくなる

カプセル化の流れを確認しよう

今回のStudentクラスでは、フィールドをprivateにしました。

private String name;
private int score;

これによって、Studentクラスの外から、

student.score = -100;

のように直接変更することはできなくなりました。

値を取得するときはgetterを使います。

student.getScore();

値を変更するときはsetterを使います。

student.setScore(90);

さらに、setScore()の中で値を確認することで、不適切な点数が設定されないようにしました。

public void setScore(int score) {
    if (score >= 0 && score <= 100) {
        this.score = score;
    }
}

カプセル化は、ただprivateにするだけではない

フィールドをprivateにする

外部から直接操作できなくする

必要に応じてgetterやsetterなどのメソッドを用意する

クラス側で値の取得方法や変更方法を管理する

大切なのは、オブジェクトが持つデータを、決められた方法で扱えるようにすることです。

ルミィ
ルミィ

privateで隠して終わりじゃないんだね!
「外からどう使ってもらうか」まで考えるのが大切なんだ♪

STEP1~11を終えて

ここまでお疲れさまでした!

STEP1では、Javaで最初のプログラムを動かすところからスタートしました。

そこから、変数やデータ型、演算子、条件分岐、キーボード入力、繰り返し処理、メソッド、配列と進み、STEP9からはクラスやオブジェクトについても学びました。

STEP9の最初には、「オブジェクト指向」という言葉も登場しました。

そのときは、

「関連するデータや処理を、1つのまとまりとして扱う」

というところからスタートしました。

実際にStudentクラスを作り、フィールドやメソッドを持たせ、そこからオブジェクトを作りました。

STEP10ではコンストラクタ、STEP11ではカプセル化についても学びました。

ここまで進めてきたことで、最初は言葉だけだったオブジェクト指向が、少しずつ実際のコードとつながってきたと思います。

ただし、オブジェクト指向についてはまだ途中です。

この先も、クラス同士の関係などを学びながら理解を深めていきます。

最初に書いたプログラムと比べると、扱えることはかなり増えています。

例えば、今なら「学生」をただの名前や点数として別々に扱うのではなく、Studentというクラスを作り、

Student student = new Student("Lumy", 80);

のように、1人の学生を1つのオブジェクトとして表現することもできます。

そして、そのオブジェクト自身にデータや処理を持たせることもできるようになりました。

ここまででJavaの学習が終わるわけではありません。

この先は、クラス同士の関係を作る継承や、複数のオブジェクトを扱うときにも便利なListなど、Javaでよく使われる機能をさらに学んでいきます。

まずは、ここまで学んだ内容を使って、自分で少しコードを書き換えてみるのもおすすめです。

名前や点数を変えるだけでも構いません。

「ここを変えたらどうなるだろう?」と試して、実行結果を確認してみてください。

ルミィ
ルミィ

STEP11までクリア、おつかれさま♪
最初のHello Worldから考えると、できることがずいぶん増えたね!
分からなくなったら前のSTEPに戻りながら、少しずつ進めば大丈夫だよ!

この先のSTEP

次のSTEPでは、あるクラスのフィールドやメソッドを別のクラスへ引き継ぐことができる「継承」について学びます。

例えば、学生を表すStudentとは別に「人」を表すPersonクラスを作り、

public class Student extends Person {
}

のように、クラス同士に関係を持たせることができます。

→ STEP12「継承を学ぼう」

記事URLをコピーしました