Programming 3 / Java

トップに戻る

第9回 パッケージとアクセス制御

Javaの学習用の教材ページです。クラスをパッケージに分け、アクセス修飾子による公開範囲の違いを実際に確認します。

演習用フォルダ

ソースファイルは、次のフォルダ内にパッケージごとに保存します。コマンドプロンプトでこのフォルダに移動してから演習を進めてください。

cd /d C:\local-doc\github\pro3\09-a-1
以降のコマンド例にある C:\local-doc\github\pro3\09-a-1> は現在のフォルダを表すプロンプトです。入力するのは > より後ろのコマンドだけです。

1. 学習目標

参考:このスライドでは扱わない内容

以下の内容はこのスライドでは扱わない。目を通しておく。

参考文献:

『Java言語プログラミングレッスン 第3版(下) オブジェクト指向を始めよう』

パッケージ(package)とは?

packageA
└── PasswordChecker クラス

packageB
└── PasswordChecker クラス

パッケージが異なるので、同じ名前でもそれぞれ異なるクラスとして扱われる。

それぞれを識別するために、

パッケージ名.クラス名

で表現する。

これは完全限定名という。

packageA.PasswordChecker
packageB.PasswordChecker

以下の3つの要領を理解する。

1. パッケージの中にクラスを作るとき

そのクラスにおいて、

Java
package

宣言を記述する。

2. パッケージの中のクラスを使うとき

使う側のクラスにおいて、

Java
import

宣言を記述する。

3. import宣言の方法

import 宣言には、

がある。

パッケージの実体

パッケージの実体は「フォルダ」である。

これから実装するクラスとパッケージの関係

パッケージ「mytool」
└── MyPen クラス

パッケージ「myfood」
└── MyApple クラス

パッケージ「cooking」
└── Main クラス

第9回演習用フォルダ内に、以下のフォルダを作る。

mytool
myfood
cooking

MyPen クラスは mytool パッケージ内に作る。

そのため、mytool フォルダ内に以下のソースプログラムを作成する。

MyPen.java

コードの配色
Java
package mytool;

public class MyPen {
    public String getName() {
        return "Pen";
    }
}

ポイント

Java
package mytool;

によって、パッケージ名を指定する。

MyApple クラスは myfood パッケージ内に作る。

そのため、myfood フォルダ内に以下のソースプログラムを作成する。

MyApple.java

コードの配色
Java
package myfood;

public class MyApple {
    public String getName() {
        return "Apple";
    }
}

ポイント

Java
package myfood;

によって、パッケージ名を指定する。

Main クラスは cooking パッケージ内に作る。

そのため、cooking フォルダ内に以下のソースプログラムを作成する。

Main.java

コードの配色
Java
package cooking;

import mytool.*;
import myfood.*;

public class Main {
    public static void main(String[] args) {
        MyPen p = new MyPen();
        MyApple a = new MyApple();

        System.out.println("I have a " + p.getName());
        System.out.println("I have an " + a.getName());
    }
}

フォルダと各ソースファイルが以下のようになっているか確認する。

09-a-1
├─ mytool
│  └─ MyPen.java
│
├─ myfood
│  └─ MyApple.java
│
└─ cooking
   └─ Main.java
注意:
必ず第9回の演習用フォルダにカレントディレクトリを移動してから行う。

コンパイル

C:\local-doc\github\pro3\09-a-1>javac mytool\MyPen.java
C:\local-doc\github\pro3\09-a-1>javac myfood\MyApple.java
C:\local-doc\github\pro3\09-a-1>javac cooking\Main.java

実行

C:\local-doc\github\pro3\09-a-1>java cooking.Main

実行結果

I have a Pen
I have an Apple

各クラスをコンパイルしてから、cooking.Main を実行する。

解説1:package宣言

package 宣言で、各クラスが属するパッケージを記述した。

Mainクラス

コードの配色
Java
package cooking;

import mytool.*;
import myfood.*;

public class Main {
    public static void main(String[] args) {
        .....
    }
}

MyPenクラス

コードの配色
Java
package mytool;

public class MyPen {
    public String getName() {
        return "Pen";
    }
}

MyAppleクラス

コードの配色
Java
package myfood;

public class MyApple {
    public String getName() {
        return "Apple";
    }
}

Main クラスでは、別のパッケージにある MyPenMyApple クラスを使うために import 宣言した。

コードの配色
Java
package cooking;

import mytool.*;
import myfood.*;

public class Main {
    public static void main(String[] args) {
        MyPen p = new MyPen();
        MyApple a = new MyApple();

        System.out.println("I have a " + p.getName());
        System.out.println("I have a " + a.getName());
    }
}

* を使ったimport

Java
import mytool.*;

これは、mytool パッケージに属するすべてのクラスを表す。

MyPenクラスだけを使用する場合

完全限定名を用いて、

Java
import mytool.MyPen;

とする。

パッケージは階層化できる

具体例

mytool パッケージの中に colorpen パッケージを作り、その中に RedPen クラスを作る。

mytool
├── MyPen クラス
└── colorpen
    └── RedPen クラス

myfood
└── MyApple クラス

cooking
└── Main クラス

mytool フォルダ内に、

colorpen

フォルダを作る。

RedPen クラスは colorpen パッケージ内に作る。

そのため、colorpen フォルダ内に以下のソースプログラムを作成する。

RedPen.java

コードの配色
Java
package mytool.colorpen;

public class RedPen {
    public String getName() {
        return "Red Pen";
    }
}

パッケージ名

階層化されたパッケージ名は、

mytool.colorpen

となる。

これは、mytool パッケージ内の colorpen パッケージだからである。

パッケージ名は、

.

(ドット)で区切る。

RedPen クラスを使う処理を Main クラスに加える。

Main.java

コードの配色
Java
package cooking;

import mytool.*;
import myfood.*;
import mytool.colorpen.*;

public class Main {
    public static void main(String[] args) {
        MyPen p = new MyPen();
        MyApple a = new MyApple();
        RedPen r = new RedPen();

        System.out.println("I have a " + p.getName());
        System.out.println("I have an " + a.getName());
        System.out.println("I have a " + r.getName());
    }
}

フォルダと各ソースファイルが以下のようになっているか確認する。

09-a-1
├─ mytool
│  │  MyPen.java
│  │  MyPen.class
│  │
│  └─ colorpen
│     └─ RedPen.java
│
├─ myfood
│  ├─ MyApple.java
│  └─ MyApple.class
│
└─ cooking
   ├─ Main.java
   └─ Main.class
注意:
必ず第9回の演習用フォルダにカレントディレクトリを移動してから行う。

コンパイル

C:\local-doc\github\pro3\09-a-1>javac mytool\colorpen\RedPen.java
C:\local-doc\github\pro3\09-a-1>javac cooking\Main.java

実行

C:\local-doc\github\pro3\09-a-1>java cooking.Main

実行結果

I have a Pen
I have an Apple
I have a Red Pen

RedPenMain をコンパイルする。

ほかのクラスは変更していないので、コンパイル不要。

解説3

RedPen クラスの package 宣言時、階層化されたパッケージ名はドット「.」で区切って宣言した。

コードの配色
Java
package mytool.colorpen;

public class RedPen {
    public String getName() {
        return "Red Pen";
    }
}

また、

という関係になっている。

無名パッケージ

Javaには、様々な役割のクラス群がある。

例えば、

は、

java.lang

パッケージに含まれている。

java.langの特徴

java.lang パッケージに限り、import 宣言なしで利用可能。

つまり、java.lang パッケージはJava言語の最も基本的なパッケージとして、暗黙的に使用できる

プログラミング3では詳細は省略。

アクセス修飾子とは

復習:Javaのもっとも簡単なプログラム

コードの配色
Java
class Hello {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

ここで、

Java
public

と書いている部分について、

「なぜ public と書くのか?」

という疑問が生じる。

Javaには、

を必要に応じて隠蔽する機構がある。

これはProcessingでも可能。

この隠蔽する機構をアクセス制御という。

アクセス制御には、以下の3つのアクセス修飾子がある。

public

protected

private

指定なし

アクセス修飾子を指定しない場合。

注意

クラス、インタフェースに対しては、

protected
private

は指定不可。

アクセス修飾子自クラス同じパッケージ
サブクラス
同じパッケージ
他クラス
異なるパッケージ
サブクラス
異なるパッケージ
他クラス
public
protected×
指定なし××
private××××

覚え方

アクセス可能な範囲が広い順に並べると、

public
  ↓
protected
  ↓
指定なし
  ↓
private

となる。

ただし、protected異なるパッケージのサブクラスからアクセス可能という点に注意する。

条件

という場合を考える。

パッケージ構成

mytool
└── public指定の MyPen クラス

myfood
└── アクセス指定なしの MyApple クラス

cooking
└── Main クラス

結果

MyPen クラスは Main から呼び出せる。

一方、

MyApple クラスは Main から呼び出せない。

理由は、MyApplemyfood パッケージに属しているのに対して、Maincooking パッケージに属しており、MyApple にアクセス修飾子が指定されていないためである。

MyApple クラスのアクセス修飾子を指定なしにしてみる。

変更後の MyApple.java

コードの配色
Java
package myfood;

class MyApple {
    public String getName() {
        return "Apple";
    }
}

変更点は、

Java
public class MyApple

から

Java
class MyApple

にしたこと。

つまり、

Java
public

を削除する。

注意:
必ず第9回の演習用フォルダにカレントディレクトリを移動してから行う。

コンパイル

C:\local-doc\github\pro3\09-a-1>javac myfood\MyApple.java

実行

C:\local-doc\github\pro3\09-a-1>java cooking.Main

実行結果

Exception in thread "main" java.lang.IllegalAccessError:
tried to access class myfood.MyApple from class
cooking.Main
at cooking.Main.main(Main.java:9)

結果

MyApple クラスへのアクセスについてのエラーが表示される。

これは、MyApple がアクセス修飾子なしになったことで、異なるパッケージにある cooking.Main からアクセスできなくなったため。

条件

MyApplepublic クラスであっても、そのメソッドが private の場合は呼び出せない。

つまり、

MyAppleクラス自体 → public
getNameメソッド → private

という状態では、

パッケージ構成

mytool
└── public指定の MyPen クラス

myfood
├── public指定の MyApple クラス
└── private指定の getName メソッド

cooking
└── Main クラス

MyPen クラスは Main から呼び出せる。

MyApple クラスを呼び出せても、private 指定されたメソッドは呼び出しできない。

MyApple クラスを public に戻し、

Java
getName

メソッドを private にしてみる。

MyApple.java

コードの配色
Java
package myfood;

public class MyApple {
    private String getName() {
        return "Apple";
    }
}

変更内容

クラス:

Java
class MyApple

から

Java
public class MyApple

へ戻す。

メソッド:

Java
public String getName()

から

Java
private String getName()

へ変更する。

コンパイル

C:\local-doc\github\pro3\09-a-1>javac myfood\MyApple.java

実行

C:\local-doc\github\pro3\09-a-1>java cooking.Main

実行結果

I have a Pen
Exception in thread "main" java.lang.IllegalAccessError:
tried to access method
myfood.MyApple.getName()Ljava/lang/String; from class
cooking.Main
at cooking.Main.main(Main.java:12)

結果の確認

MyPen クラスのメソッド実行は成功しているので、

I have a Pen

と正しく表示される。

一方、MyApple クラスのメソッド実行では、アクセスエラーが発生した旨が表示される。

つまり、

MyAppleクラス → publicなのでアクセス可能
getNameメソッド → privateなのでアクセス不可

となる。

アクセス修飾子の種類

3つある。

ただし、アクセス修飾子の指定には「省略」を含めて4パターンある。

  1. public
  2. protected
  3. private
  4. 指定なし

アクセス制御により、カプセル化を実現できる。

カプセル化によって、

ことができる。

パッケージ

package

アクセス制御

Javaでは、

を必要に応じて隠蔽できる。

この仕組みをアクセス制御という。

アクセス修飾子は、

public
protected
private

の3種類。

さらに、アクセス修飾子を指定しない指定なしを含めると4パターン。

アクセス範囲

指定アクセス可能な範囲
publicどこからでも
protected同じパッケージ、またはサブクラス
指定なし同じパッケージ
private同じクラスのみ

※クラス・インタフェースには protectedprivate は指定できない。

カプセル化

アクセス制御によってカプセル化を実現できる。

その結果、

オブジェクト内部の実装を隠蔽して、より安全でバグの少ないプログラムを作れる。

という利点がある。