<概要>パッケージとアクセス制御について学びます。
パッケージは、名前の衝突を防ぐために用意されたグループ分けの仕組みです。
関連しているクラスを一つの小包(パッケージ)として扱い、
名前の衝突する可能性低下、検索性の向上、カプセル化などの役割を果たします。
また、クラスの名前やメンバーの名前の集まりのことを名前空間と呼びます。
●例題17-1 最も基本的なパッケージの利用方法。
○プログラム
//p1\Class1.java
package p1;
public class Class1{
public Class1(){
System.out.println("Class1");
}
}
//Class2.java
import p1.*;
public class Class2{
public static void main(String[] args){
Class1 c1=new Class1();
System.out.println("Class2");
}
}
○実行結果
D:\atsushi\Java\pp197>java Class2
Class1
Class2
-- Press any key to exit (Input "c" to continue) --
○解説
パッケージを宣言するには予約語 package を使います。
package p1; で、p1 という名前のパッケージを宣言したことになります。
パッケージを利用するには予約語 import を使います。
import p1.*; で、p1 という名前のパッケージに含まれる全てのクラスとインタフェースを取り込んだことになります。
* は、不特定文字列の代わりとなる記号です。
もし、p1 パッケージに含まれる Class1 クラスだけ利用したいのであれば、
import p1.Class1; と書くことも出来ます。
このように、パッケージの名前とクラスやインタフェースの名前を
合わせて書いたものを完全限定名と言います。
ただしこの場合は、p1 パッケージに含まれる Class1 クラス以外は利用できません。
何か特別な事情がない限り、* を使えば良いでしょう。
ちなみに、パッケージの名前だけを書くのは誤りです。
誤り:import p1;
パッケージを指定していない Class2 クラスは、無名パッケージのメンバーになります。
★ソースファイルの保存場所
ソースファイルの保存場所はパッケージの名前によりある程度拘束されます。
pp197
├p1
│└Class1.java //p1 パッケージ
└Class2.java //無名パッケージ
プロジェクトフォルダ(自由に名前を決められる) pp197 の下に
パッケージの名前と同じ名前のフォルダを作り、その中にソースファイルを入れなければなりません。
無名パッケージの場合は、プロジェクトフォルダの中に直接入れましょう。
他の場所に保存するとコンパイルエラーです。
もし他の誰かが作成した同じ名前のパッケージに同じ名前のクラスがあったらどうなるでしょうか?
プロジェクトフォルダが異なれば、ファイルが上書きされる心配はありません。
しかし、コンパイルエラー(重複定義)は免れません。
同じ名前のパッケージに同じ名前のクラスがあるという事態を避けるためにも、
パッケージにはユニークな(唯一の)名前を付けましょう。
★ java.lang パッケージ
java.lang パッケージ内の public クラスとインタフェースは自動的に取り込まれます。
すなわち、以下の宣言が自動的に行われます。
import java.lang.*;
●例題17-2 import されるものとは?
○プログラム
//p1\Class1.java
package p1;
public class Class1{
public Class1(){
System.out.println("Class1");
}
}
//p1\include\Class2.java
package p1.include;
import p1.*;
public class Class2{
public Class2(){
System.out.println("Class2");
}
}
//Class3.java
import p1.*;
import p1.include.*;
public class Class3{
public static void main(String[] args){
Class1 c1=new Class1();
Class2 c2=new Class2();
System.out.println("Class3");
}
}
○実行結果
D:\atsushi\Java\pp198>java Class3
Class1
Class2
Class3
-- Press any key to exit (Input "c" to continue) --
○解説
パッケージの名前には階層を持たせることが出来ます。
package p1.include; としたとき、p1.include パッケージは p1 パッケージのサブパッケージと呼ばれますが、
二つのパッケージに名前空間上の関連性はありません。
ただパッケージを分類して利用者に分かり易くしただけです。
pp198
├p1
│├Class1.java //p1 パッケージ
│└include
│ └Class2.java //p1.include パッケージ
└Class3.java //無名パッケージ
ところで、import 宣言で取り込まれるものは何でしょうか?
そのパッケージのプログラム全て?
C++言語ではそうでしたが、Java言語ではクラスとインタフェースの宣言のみを取り込みます。
例えば、import p1.include.*; と書いても、
p1.include パッケージのプログラム import p1.*; は取り込まれません。
package は、プログラムの先頭、
import は、package の次、クラス宣言の前に書かなければなりません。
package は、一度しか書けませんが、
import は、何度でも書けます。
誤りではない:import p1.*; import p1.*;
定義まで取り込んでいたら重複定義でエラーですが、
宣言のみを上手く取り込んでいるみたいですね。
*=*=*=*=*=*=*=*=*=*= アクセス制御 *=*=*=*=*=*=*=*=*=*=
C++言語のアクセス制御はクラスに適用するものでした。
ファイルの外部に公開するか否かはヘッダーファイルが管理していましたね。
しかし、Java言語のアクセス制御はクラスとパッケージの両方で使われます。
分かり易くなったような、相変わらずややこしいような……。
★クラスとインタフェースに対するアクセス制御
なし | 同じパッケージからのみ使える |
public | 他のパッケージからも使える |
クラスとインタフェースに適用できるのは、この二つだけです。
「なし」というのも何かの省略と同義ではなく、独立した一つのアクセス制御なのですね。
java.lang パッケージから自動的に取り込まれるのが
public なクラスとインタフェースに限定されていた意味は分かりましたか?
「なし」を取り込んでも使えないからですね。
★クラスのメソッドとフィールドに対するアクセス制御
publicが付いているクラス のメソッド(*1)とフィールド |
publicが付いていないクラス のメソッドとフィールド |
|
private | クラス内でのみ使える | 同左 |
なし | 同じパッケージからのみ使える | 同左 |
protected | 同じパッケージとサブクラスからのみ使える(*2) | 同左 |
public | 他のパッケージからも使える | 同じパッケージからのみ使える |
*1:コンストラクタはメソッドと同様に扱います。
*2:他のパッケージであってもサブクラスからなら使える。
★インタフェースのメソッドとフィールドに対するアクセス制御
publicが付いているインタフェース のメソッドとフィールド |
publicが付いていないインタフェース のメソッドとフィールド |
|
private | (指定できない) | (指定できない) |
なし | 他のパッケージからも使える | 同じパッケージからのみ使える |
protected | (指定できない) | (指定できない) |
public | 他のパッケージからも使える | 同じパッケージからのみ使える |
●例題17-3 同じパッケージにアクセスするには?
○プログラム
//p2\ClassA.java
package p2;
class ClassA{
public ClassA(){
System.out.println("ClassA");
}
}
//p2.ClassB.java
package p2;
class ClassB{
public static void main(String[] args){
ClassA ca=new ClassA();
System.out.println("ClassB");
}
}
○実行結果
D:\atsushi\Java\pp204\build>java p2.ClassB
ClassA
ClassB
-- Press any key to exit (Input "c" to continue) --
○解説
同じパッケージにアクセスするのに import 宣言は必要ありません。
実は今までも無名パッケージから無名パッケージにアクセスしていましたよね。
●例題17-4 異なるパッケージ、同じ名前のクラスの優先順位は?
○プログラム
//p3\Class1.java
package p3;
public class Class1{
public Class1(){
System.out.println("p3\\Class1");
}
}
//Class1.java
import p3.*;
public class Class1{
public Class1(){
System.out.println("Class1");
}
public static void main(String[] args){
Class1 ca=new Class1();
p3.Class1 cb=new p3.Class1();
}
}
○実行結果
D:\atsushi\Java\pp210>java Class1
Class1
p3\Class1
-- Press any key to exit (Input "c" to continue) --
○解説
パッケージが異なれば、同じ名前のクラスであっても、異なるクラスとして扱われます。
したがって、このプログラムのように同じ名前のクラスを取り込んでも全く問題ありません。
それでは、どちらが優先的に呼び出されるのでしょうか?
同じパッケージならば、
ファイル内のクラス>ファイル外のクラス
という結果が得られました。
完全限定名を使えば、あやふやさがなくなり明示的に特定のパッケージのクラスを呼び出すことが出来ます。
★ファイル外のクラス同士の優先順位は?
import 宣言により同じ名前のクラスを複数、異なるパッケージから取り込んだ場合は、
それらの優先順位は全て同じです。
どちらも取り込まれたパッケージの場合は …… パッケージ1のクラス=パッケージ2のクラス
このとき、ファイル内に同じ名前のクラスがない場合は、
優先順位が全て同じである為に、参照があやふやであるとのエラーが出ます。
完全限定名を使えば、あやふやさは解消され、エラーも出なくなります。
●例題17-5 private なコンストラクタの意味するところは?
○プログラム
class Math{
static double PI=3.14;
private Math(){
}
static void sine(){
System.out.println("Math.sine()");
}
}
public class ClassM{
private ClassM(){
System.out.println("ClassM");
}
public static void main(String[] args){
System.out.println("Math.PI="+Math.PI);
Math.sine();
ClassM cm=new ClassM();
}
}
○実行結果
D:\atsushi\Java\pp208>java ClassM
Math.PI=3.14
Math.sine()
ClassM
-- Press any key to exit (Input "c" to continue) --
○解説
C++言語では、コンストラクタは public でなければならないと決められていましたが、
Java言語では、private なコンストラクタも許されており、特別な働きをします。
private コンストラクタしか持たないクラスは、そのクラス外部でインスタンスが作れなくなるのです。
何故ならば、クラス外部からはコンストラクタを呼び出せないのですから。
private というアクセス制御が働いているのですね。
しかし、絶対にインスタンスを作れないということではありません。
private は、クラス内部からならアクセスできます。
つまり、同じクラスの内部からなら自分のインスタンスを作ることが出来ます。
でも、普通は作りません。
private なコンストラクタの役割は「入れ物としてのクラスであることを強調すること」なのですから。
忘れちゃならないのは、
・自分でコンストラクタを書かなければ、public なデフォルトコンストラクタが生成される。
・継承しているのであれば、サブクラスのコンストラクタの中で必ずスーパークラスのコンストラクタも呼び出される。
ということです。
注意しなければならないのは後者ですね。
例えば、private コンストラクタしか持たないクラスを継承しても問題ありませんが、
継承したサブクラスのインスタンスを作ろうとすると、
スーパークラスのコンストラクタも呼び出されることになり、エラーです。
ちょっとややこしいですが、頭の中を良く整理して考えてみて下さい。
それでは、private なコンストラクタと public なコンストラクタ、
両方あった場合(オーバーロード)はどうでしょう?
クラス外部からもインスタンスは作成できます。
活用法としては、複数の public なコンストラクタの共通処理を
private なコンストラクタにまとめて書けば、プログラムがすっきりします。
private Math(){
…… //共通の処理
}
public Math(int m){
this();
……
}
public Math(int m,String str){
this();
……
}
自分のコンストラクタを呼び出す場合は this() を使うんでしたね。
this() の代わりに Math() なんて書いちゃ駄目ですよ。
protected コンストラクタしか持たないクラスも同様の働きをします。
この場合は、同じパッケージとサブクラス以外でのインスタンス作成を禁じます。
そして「なし」コンストラクタしか持たないクラスの場合は、
同じパッケージ以外でのインスタンス作成を禁じます。
大丈夫ですよね?
ポイントは「コンストラクタにアクセスできるか否か」です。