Java : アクセス修飾子の基本

カプセル化と情報隠蔽を実現するために、Javaではアクセス修飾子を使います。
アクセス修飾子(publicprivateなど)を使うと、クラスのメンバをどこまで公開するか?どこまで非公開にするか?を制御できます。

本記事では、そんなアクセス修飾子の基本をご紹介します。


概要

6.6. Access Control
The Java programming language provides mechanisms for access control, to prevent the users of a package or class from depending on unnecessary details of the implementation of that package or class. If access is permitted, then the accessed entity is said to be accessible.

Javaでは、自分で宣言したクラスやインタフェース、そしてそのメンバを

  • どこまで公開するか?
  • どこまで非公開にするか?

という制御が可能となっています。

これをアクセス制御といいます。
また、その制御にアクセス修飾子(publicやprivateなど)を使います。

カプセル化と情報隠蔽

カプセル化
プログラミングにおけるカプセル化(カプセルか、英: encapsulation)とは、データ(属性)とメソッド(手続き)を一つのオブジェクトにまとめ、その内容を隠蔽することを言う。カプセル化の概念は、デイビッド・パーナスの情報隠蔽(information hiding)の構成概念の一つとして見ることができる[1]。

Javaはオブジェクト指向をベースとしたプログラミング言語です。
そして、オブジェクト指向の大事な要素としてカプセル化と情報隠蔽があります。

必要最低限の情報のみを公開して、実装の詳細などは非公開(隠蔽)にするということですね。

カプセル化

こうすることにより、非公開の部分に修正が入っていたとしても、公開部分のみを使っている側には影響させない、という利点があります。
他の言い方をすると、クラス間の依存が小さくなります。(実装の詳細に依存しなくなります)

Javaでは情報隠蔽を実現するためにアクセス修飾子を使います。

アクセス修飾子の一覧と公開範囲

アクセス修飾子の一覧と、その公開範囲の表になります。

アクセス修飾子 同一クラス 別のクラス
(同一パッケージ)
サブクラス
(同一パッケージ)
別のクラス
(別パッケージ)
サブクラス
(別パッケージ)
public
private × × × ×
protected ×
なし (package access) × ×

この中で特に重要なのは、

  • public (公開)
  • private (非公開)

この2つになります。
最低限、この2つを理解できれば、Javaによるプログラミングは可能です。

まずは publicprivate の違いをしっかりと理解していきましょう。

public

public アクセス修飾子は、どこからでも、だれからでもアクセスが可能となります。
もっとも制限のゆるいアクセス修飾子です。

下記の図は、SampleA を中心とした公開範囲のイメージです。

public構成

private

private アクセス修飾子は、自分自身(同一クラス)からのみアクセス可能となります。
もっとも制限の厳しいアクセス修飾子です。

private構成

protected

protected アクセス修飾子は、サブクラスからもアクセスできるのが特徴です。
ただし、同一パッケージ内では public と同じようにアクセスできてしまうのでご注意ください。

protected構成

アクセス修飾子なし (package access)

アクセス修飾子なしは、同一パッケージからのアクセスのみを許可します。
別パッケージからのアクセスはできません。

package accessやpackage privateとも呼ばれています。

package構成

コード例

それぞれのアクセス修飾子の公開範囲は、なんとなくイメージできましたでしょうか。
それでは、実際にアクセス修飾子を使ったコード例を見ていきましょう。

クラス

トップレベルのクラスには、クラスそのものにアクセス修飾子をつけることができます。
ただし、publicアクセス修飾子なし(package access) だけが可能です。(private, protectedは指定できません)

public アクセス修飾子をつけたクラス例です。

package aaa;

public class PublicSample {
}

アクセス修飾子なしのクラス例です。

package aaa;

class PackageSample {
}

同一パッケージの別クラスからアクセスする例です。
どちらもアクセス可能です。

package aaa;

public class MainA {
    public void func() {
        final var publicSample = new PublicSample(); // OK
        final var PackageSample = new PackageSample(); // OK
    }
}

別パッケージの別クラスからアクセスする例です。
public のみアクセス可能です。

package xxx;

import aaa.PackageSample;
import aaa.PublicSample;

public class MainX {
    public void func() {
        final var publicSample = new PublicSample(); // OK
        final var PackageSample = new PackageSample(); // コンパイルエラー
    }
}

補足

  • トップレベルのクラスとは、例えば Sample.java ファイルであれば、Sampleクラスのことをいいます。
    javaファイルで宣言するもっとも外側にあるクラスですね。
  • トップレベル以外のクラスには、メンバクラス(内部クラス)やローカルクラス、匿名クラスがあります。

フィールド

同一クラスからのアクセス例です。
すべてのアクセス修飾子でアクセスが可能です。

package aaa;

public class SampleA {
    public int publicValue;
    private int privateValue;
    protected int protectedValue;
    int packageValue;

    public void func() {
        System.out.println(publicValue); // OK
        System.out.println(privateValue); // OK
        System.out.println(protectedValue); // OK
        System.out.println(packageValue); // OK
    }
}

同一パッケージの別クラスからのアクセス例です。
privateだけがアクセスできません。

package aaa;

public class SampleB {

    public void func() {
        final var sampleA = new SampleA();

        System.out.println(sampleA.publicValue); // OK
        System.out.println(sampleA.privateValue); // コンパイルエラー
        System.out.println(sampleA.protectedValue); // OK
        System.out.println(sampleA.packageValue); // OK
    }
}

同一パッケージのサブクラスからのアクセス例です。
privateだけがアクセスできません。

package aaa;

public class SubSampleA extends SampleA {

    public void func() {
        System.out.println(publicValue); // OK
        System.out.println(privateValue); // コンパイルエラー
        System.out.println(protectedValue); // OK
        System.out.println(packageValue); // OK
    }
}

別パッケージの別クラスからのアクセス例です。
publicのみがアクセス可能です。

package xxx;

import aaa.SampleA;

public class SampleX {

    public void func() {
        final var sampleA = new SampleA();

        System.out.println(sampleA.publicValue); // OK
        System.out.println(sampleA.privateValue); // コンパイルエラー
        System.out.println(sampleA.protectedValue); // コンパイルエラー
        System.out.println(sampleA.packageValue); // コンパイルエラー
    }
}

別パッケージのサブクラスからのアクセス例です。
publicとprotectedのみアクセス可能です。

package xxx;

import aaa.SampleA;

public class SubSampleX extends SampleA {

    public void func() {
        System.out.println(publicValue); // OK
        System.out.println(privateValue); // コンパイルエラー
        System.out.println(protectedValue); // OK
        System.out.println(packageValue); // コンパイルエラー
    }
}

メソッド

基本的な考え方はフィールドと同じです。

同一クラスからのアクセス例です。

package aaa;

public class SampleA {

    public void publicFunc() {
        System.out.println("public!");
    }

    private void privateFunc() {
        System.out.println("private!");
    }

    protected void protectedFunc() {
        System.out.println("protected!");
    }

    void packageFunc() {
        System.out.println("package access!");
    }

    public void func() {
        publicFunc(); // OK
        privateFunc(); // OK
        protectedFunc(); // OK
        packageFunc(); // OK
    }
}

同一パッケージの別クラスからのアクセス例です。

package aaa;

public class SampleB {

    public void func() {
        final var sampleA = new SampleA();

        sampleA.publicFunc(); // OK
        sampleA.privateFunc(); // コンパイルエラー
        sampleA.protectedFunc(); // OK
        sampleA.packageFunc(); // OK
    }
}

同一パッケージのサブクラスからのアクセス例です。

package aaa;

public class SubSampleA extends SampleA {

    public void func() {
        publicFunc(); // OK
        privateFunc(); // コンパイルエラー
        protectedFunc(); // OK
        packageFunc(); // OK
    }
}

別パッケージの別クラスからのアクセス例です。

package xxx;

import aaa.SampleA;

public class SampleX {

    public void func() {
        final var sampleA = new SampleA();

        sampleA.publicFunc(); // OK
        sampleA.privateFunc(); // コンパイルエラー
        sampleA.protectedFunc(); // コンパイルエラー
        sampleA.packageFunc(); // コンパイルエラー
    }
}

別パッケージのサブクラスからのアクセス例です。

package xxx;

import aaa.SampleA;

public class SubSampleX extends SampleA {

    public void func() {
        publicFunc(); // OK
        privateFunc(); // コンパイルエラー
        protectedFunc(); // OK
        packageFunc(); // コンパイルエラー
    }
}

インタフェース

トップレベルのインタフェースには、インタフェースそのものにアクセス修飾子をつけることができます。
ただし、publicアクセス修飾子なし(package access) だけが可能です。(private, protectedは指定できません)

public アクセス修飾子をつけたインタフェース例です。

package aaa;

public interface PublicSample {
}

アクセス修飾子なしのインタフェース例です。

package aaa;

interface PackageSample {
}

同一パッケージの別クラスからアクセスする例です。
どちらもアクセス可能です。

package aaa;

public class MainA {
    public void func() {
        final PublicSample publicSample = null; // OK
        final PackageSample PackageSample = null; // OK
    }
}

別パッケージの別クラスからアクセスする例です。
public のみアクセス可能です。

package xxx;

import aaa.PackageSample;
import aaa.PublicSample;

public class MainX {
    public void func() {
        final PublicSample publicSample = null; // OK
        final PackageSample PackageSample = null; // コンパイルエラー
    }
}

フィールド

クラス・フィールドとは違い

  • public
  • アクセス修飾子なし

のみ指定できます。

また、アクセス修飾子なしは public と同等となります。
package access の意味合いではないのでご注意ください。

package aaa;

public interface SampleA {

    // ※valueAとvalueBにアクセス制御の違いはありません。
    public int valueA = 1;
    int valueB = 2;
}

同一パッケージの別クラスからのアクセス例です。

package aaa;

public class SampleB {

    public void func() {
        System.out.println(SampleA.valueA); // OK
        System.out.println(SampleA.valueB); // OK
    }
}

同一パッケージの実装クラスからのアクセス例です。

package aaa;

public class SampleAImpl implements SampleA {

    public void func() {
        System.out.println(valueA); // OK
        System.out.println(valueB); // OK
    }
}

別パッケージの別クラスからのアクセス例です。

package xxx;

import aaa.SampleA;

public class SampleX {

    public void func() {
        System.out.println(SampleA.valueA); // OK
        System.out.println(SampleA.valueB); // OK
    }
}

別パッケージの実装クラスからのアクセス例です。

package xxx;

import aaa.SampleA;

public class SampleXImpl implements SampleA {

    public void func() {
        System.out.println(valueA); // OK
        System.out.println(valueB); // OK
    }
}

メソッド

インタフェース・メソッドも同様に、

  • public
  • アクセス修飾子なし

のみ指定できます。

アクセス修飾子なしは public と同等となります。

package aaa;

public interface SampleA {

    // ※funcAとfuncBにアクセス制御の違いはありません。
    public void funcA();

    void funcB();
}

同一パッケージで実装(アクセス)する例です。

package aaa;

public class SampleAImpl implements SampleA {

    // OK
    @Override
    public void funcA() {
        System.out.println("A!");
    }

    // OK
    @Override
    public void funcB() {
        System.out.println("B!");
    }
}

同一パッケージからのアクセス例です。

package aaa;

public class SampleB {

    public void func() {
        final SampleA sampleA = new SampleAImpl();

        sampleA.funcA(); // OK
        sampleA.funcB(); // OK
    }
}

別パッケージで実装(アクセス)する例です。

package xxx;

import aaa.SampleA;

public class SampleXImpl implements SampleA {

    // OK
    @Override
    public void funcA() {
        System.out.println("A!");
    }

    // OK
    @Override
    public void funcB() {
        System.out.println("B!");
    }
}

別パッケージの別クラスからのアクセス例です。

package xxx;

import aaa.SampleA;

public class SampleX {

    public void func() {
        final SampleA sampleA = new SampleXImpl();

        sampleA.funcA(); // OK
        sampleA.funcB(); // OK
    }
}

まとめ

アクセス修飾子、いかがでしたでしょうか。

プログラミングするさいは、公開範囲を小さく小さくするように意識していきましょう。
基本は private で、必要なところだけ public にしていくのがよいのかなと思います。


関連記事

ページの先頭へ