LoginSignup
0
0

10.1 カプセル化とアクセス修飾子(public、protected、private)~Java Basic編

Last updated at Posted at 2023-02-04

はじめに

自己紹介

皆さん、こんにちは、Udemy講師の斉藤賢哉です。私はこれまで、25年以上に渡って企業システムの開発に携わってきました。特にアーキテクトとして、ミッションクリティカルなシステムの技術設計や、Javaフレームワーク開発などの豊富な経験を有しています。
様々なセミナーでの登壇や雑誌への技術記事寄稿の実績があり、また以下のような書籍も執筆しています。

いずれもJava EEJakarta EE)を中心にした企業システム開発のための書籍です。中でも 「アプリケーションアーキテクチャ設計パターン」は、(Javaに限定されない)比較的普遍的なテーマを扱っており、内容的にはまだまだ陳腐化していないため、興味のある方は是非手に取っていただけると幸いです(中級者向け)。

Udemy講座のご紹介

この記事の内容は、私が講師を務めるUdemy講座『Java Basic編』の一部の範囲をカバーしたものです。『Java Basic編』はこちらのリンクから購入できます(セールス対象外のためいつも同じ価格)。また定価の約30%OFFで購入可能なクーポンをQiita内で定期的に発行していますので、興味のある方は、ぜひ私の他の記事をチェックしてみてください。

この講座は、以下のような皆様にお薦めします。

  • Javaの言語仕様や文法を正しく理解すると同時に、現場での実践的なスキル習得を目指している方
  • 新卒でIT企業に入社、またはIT部門に配属になった、新米システムエンジニアの方
  • 長年IT部門で活躍されてきた中堅層の方で、学び直し(リスキル)に挑戦しようとしている方
  • 今後、フリーランスエンジニアとしてのキャリアを検討している方
  • Chat GPT」のエンジニアリングへの活用に興味のある方
  • Oracle認定Javaプログラマ」の資格取得を目指している方
  • IT企業やIT部門の教育研修部門において、新人研修やリスキルのためのオンライン教材をお探しの方

この記事を含むシリーズ全体像

この記事はJava SEの一部の機能・仕様を取り上げたものですが、一連のシリーズになっており、シリーズ全体でJava SEを網羅しています。また認定資格である「Oracle認定Javaプログラマ」(Silver、Gold)の範囲もカバーしています。シリーズの全体像および「Oracle認定Javaプログラマ」の範囲との対応関係については、以下を参照ください。

10.1 カプセル化とアクセス修飾子

チャプターの概要

このチャプターでは、アクセス修飾子と、それによって実現される「カプセル化」の概念について学びます。

10.1.1 アクセス修飾子

アクセス修飾子とは

アクセス修飾子とは、クラスやそのメンバーに対する「可視性」、言い方を替えると「公開範囲」を指定するための修飾子です。アクセス修飾子にはpublic、protected、privateの3種類がありますが、「アクセス修飾子を付与しない状態」も可視性の一種と考えられるため、4つに分類するものとします。
それぞれのアクセス修飾子ごとの公開範囲や、そのメンバーに対してアクセス可能な要素を以下の表に示します。

公開範囲 修飾子 アクセス可能な要素(from what?)
広い public すべてのクラスからアクセス可能
protected 自クラスおよび同一パッケージのクラスと、 自身の子クラスからアクセス可能
なし 自クラスおよび同一パッケージのクラスからアクセス可能
※換言すると、異なるパッケージからはアクセス不可
狭い private 自クラスのみからアクセス可能
※換言すると、他クラスからはアクセス不可

公開範囲は、publicが最も広く、protected、「アクセス修飾子なし」、privateの順に狭まります。「アクセス修飾子なし」の状態よりも、protectedを付与した方が(直感とは異なり)可視性の範囲は広くなりますので、注意してください。
まずpublicが付与された要素に対しては、すべてのクラスからアクセスが可能です(すべてのクラスに公開される)。
次にprotectedが付与された要素に対しては、自クラスおよび同一パッケージのクラスからしかアクセスができません。ただし自クラスを継承(チャプター11.1参照)した子クラスに限っては、異なるパッケージであってもアクセスが可能です。
「アクセス修飾子なし」の要素に対しては、自クラスおよび同一パッケージのクラスからしかアクセスができません。
privateが付与された要素に対しては、他クラスからは一切アクセスができません。

アクセス修飾子を付与可能な要素

ここでは、要素ごとに付与することが可能なアクセス修飾子を整理します。
まずクラスに対して付与可能なアクセス修飾子は、publicのみです。一方、クラスのメンバーであるフィールド、メソッド、コンストラクタには、すべての修飾子が付与可能です。

修飾子 付与可能な要素
クラス publicのみ
フィールド public、protected、private
メソッド public、protected、private
コンストラクタ public、protected、private

クラスに対するアクセス修飾子と、メンバーに対するアクセス修飾子が競合する場合は、可視性が狭い方が優先されます。つまり例えばクラスが「アクセス修飾子なし」の場合は、メンバーのアクセス修飾子がpublicであったとしても、「アクセス修飾子なし」の可視性が優先されます。(クラスにアクセスできない時点で、必然的にメンバーにもアクセスできない)
以上から、クラスにはpublicを付与し、メンバー毎の可視性の制御はメンバー単位に行う、というのが基本的な方針になります。

アクセス修飾子の具体例

ここでは、具体例をもとにアクセス修飾子の仕様を説明します。
以下のように、異なるパッケージに所属するFooクラスとBarクラスがあるものとします。

  • pro.kensait.pkg1.Foo
  • pro.kensait.pkg2.Bar

Fooクラスのコードを次に示します。

pro.kensait.pkg1.Foo
package pro.kensait.pkg1;
public class Foo {
    private int x;
    public Foo(int x) {
        this.x = x;
    }
    public int getX() {
        return x;
    }
    int pow() {
        return x * x;
    }
}

このようにFooクラスでは、まずクラスそのものにpublicを付与しています。メンバーについては、xフィールドにはprivateを、コンストラクタとgetX()メソッドにはpublicを付与しています。またpow()メソッドには、アクセス修飾子を付与していません。
このFooクラスに対して、パッケージが異なるBarクラス内からのアクセスは、以下のようになります。

snippet (pro.kensait.pkg2.Bar)
Foo foo = new Foo(10); //【1】OK
int val1 = foo.x; //【2】NG
int val2 = foo.getX(); //【3】OK
int val3 = foo.pow(); //【4】NG

まずFooクラスそのものにpublicを付与しているので、基本的にこのクラスはすべてのクラスに公開されます。またコンストラクタにもpublicを付与しているので、すべてのクラスで、このクラスのインスタンスを生成可能です。従って【1】のようにインスタンスを生成可能です。
xフィールドにはprivateを付与しているので、Barクラスからxフィールドにはアクセスできません(不可視)。従って【2】のようにBarクラス内でfoo.xと記述すると、コンパイルエラーになります。
getX()メソッドにはpublicを付与しているので、すべてのクラスからアクセス可能です。従って【3】のようにgetX()メソッドは呼び出し可能です。
pow()メソッドは「アクセス修飾子なし」なので、異なるパッケージからはアクセスできません(不可視)。従って【4】のようにBarクラス内でpow()メソッドを呼び出すと、コンパイルエラーになります。

10.1.2 カプセル化

カプセル化とは

Javaでは、アクセス修飾子によってクラスが保持する属性(フィールド)を外部から隠蔽したり、ローカル変数によってデータをメソッド内に閉じ込めたりすることができます。このようにオブジェクト指向型言語において、クラスに実装された処理の独立性を高めることを「カプセル化」と呼びます。従来の手続き型プログラミング言語では、グローバル変数が引き起こす「スパゲッティコード」が課題でしたが、Javaではカプセル化によって、処理の流れや構造が分かりやすいコードを記述することが可能になります。

アクセス修飾子とカプセル化の戦略

プログラムは、開発者自身のためだけに作成するとは限りません。クラスがリポジトリにコミットされ、プロジェクトチーム内で共有され始めると、そのクラスを「他の開発者にどのように公開するか」「どのように使われるのか」を意識しなければなりません。このような点を意識しないと、当該クラスを後で修正したとき、思わぬところで不具合が発生してしまう可能性があるためです。
そのためには各メンバーに対する公開範囲を必要以上に広げすぎないよう、適切にアクセス修飾子を付与したり、カプセル化によって内部情報を隠蔽したりすることが必要です。また詳細はチャプター11.1で触れますが、finalキーワードによって、クラスとメソッドの継承やオーバーライドを制御することも重要です。

カプセル化とアクセサメソッド

ここでは、カプセル化を実現するための一般的な方法について説明します。
まずフィールドにpublicを付与することは、原則として回避します。このようにする理由は、フィールドはあくまでもクラスが内部的に保持する属性なので、外部から自由に参照したり更新したりすることができると、カプセル化が崩れてしまうためです。よってフィールドにはprivateを付与し、外部からは不可視にします。
では外部からフィールド値を参照したり更新したりする場合は、どのようにしたらよいのでしょうか。それはアクセサメソッドと呼ばれるメソッドを経由して行います。アクセサメソッドとはフィールドにアクセスするためのメソッドで、ゲッターとセッターの2種類があります。ゲッターとはフィールド値を参照するためのメソッド、セッターとは更新するためのメソッドですが、外部からのアクセスを可能にするためにアクセス修飾子はpublicにします。
アクセサメソッドは、例えばnameフィールドが対象の場合、ゲッターは"getName"、セッターは"setName"というネーミング規約(get、set直後の文字は大文字)に従う必要があります。ここでゲッターおよびセッターから、get、setを取り除き、先頭を小文字化したもの(先の例では"name")を、プロパティと呼びます。基本的にはフィールド名とプロパティ名は一致させますが、nameフィールドを参照するためのゲッターをgetFullName()メソッドにする(この場合プロパティは"fullName")ことも技術的には可能です。

カプセル化の具体例

ここでは、「人物」を表すPersonクラス(レッスン7.1.2参照)を、カプセル化するケースを考えてみましょう。
以下のコードを見てください。

pro.kensait.java.lsn_10_1_1.Person
public class Person {
    // フィールド
    private String name; // 名前
    private int age; // 年齢
    // コンストラクタ
    public Person() {
    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // アクセサメソッド
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

このようにフィールドはprivateにし、フィールド値を参照・更新するためのアクセサメソッドはpublicにするのが、カプセル化の基本パターンです。
ここでこのPersonクラスのように、ゲッターとセッターが、単純にフィールド値を参照したり更新したりするだけであれば、フィールドをpublicにしても同じなのではないか、と感じるかもしれませんが、それはその通りです。
ただしアクセサメソッドがあることによって、フィールドに対する外部からのアクセスをコントロール可能になるため、以下のようなことを実現可能です。

  • 参照専用フィールドを作る。publicなゲッターのみを宣言(セッターなし)すれば、コンストラクタで初期値を代入後、フィールドを参照のみ可能(更新不可)にすることができる。
  • フィールドが後から仕様変更になった場合の影響を極小化する。例えばフルネームを表すnameフィールドが、後から苗字(lastNameフィールド)と名前(fisrtNameフィールド)に分割されたケースを考える。nameフィールドを不可視にさえしておけば、このような仕様変更があっても、公開されたgetName()メソッドの内容を「苗字と名前を文字列連結して返す」ように修正するだけで済むため、外部への影響は極小化できる。
  • フィールドをセッターを通して更新させることで、必要に応じて、設定される値の有効性を検証する。例えば「年齢(age)の有効範囲は0以上」という要件がある場合は、セッターの中で有効性を検証を行い、設定される値がマイナスの場合はエラーにする。なおこのようなチェックを「事前条件」と呼ぶ1

このチャプターで学んだこと

このチャプターでは、以下のことを学びました。

  1. アクセス修飾子とは公開範囲を指定するための修飾子であり、public、protected、指定なし、privateの4段階があること。
  2. アクセス修飾子は、クラス、フィールド、メソッド、コンストラクタに付与可能であること。
  3. カプセル化とは、クラスに実装された処理の独立性を高めること。
  4. アクセサメソッドにはゲッター、セッターがあり、カプセル化を実現するためのものであること。
  1. 事前条件では、if文でメソッド引数の有効性を検証し、エラーの場合はIllegalArgumentException例外を送出するのが一般的(チャプター19.2参照)。ただし実際のJavaアプリケーションでは、有効性の検証ロジックは比較的複雑になったり、ユーザーにエラーメッセージ返却が必要になったりするケースが多いため、セッターで値を設定するより前段階で、別の方法で検証が行われるケースが大半。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0