web-dev-qa-db-ja.com

JavaでC ++の「フレンド」の概念をシミュレートする方法はありますか?

Javaクラスを別のパッケージのクラスの非パブリックメソッドにアクセスできるパッケージを他のクラスのサブクラスにせずに記述できるようにしたいと思います。これは可能?

172
Matthew Murdoch

「友人」の概念は、Javaで、たとえばAPIをその実装から分離するのに役立ちます。実装クラスがAPIクラス内部へのアクセスを必要とすることは一般的ですが、これらはAPIクライアントに公開されるべきではありません。これは、以下に詳述する「Friend Accessor」パターンを使用して実現できます。

APIを介して公開されるクラス:

package api;

public final class Exposed {
    static {
        // Declare classes in the implementation package as 'friends'
        Accessor.setInstance(new AccessorImpl());
    }

    // Only accessible by 'friend' classes.
    Exposed() {

    }

    // Only accessible by 'friend' classes.
    void sayHello() {
        System.out.println("Hello");
    }

    static final class AccessorImpl extends Accessor {
        protected Exposed createExposed() {
            return new Exposed();
        }

        protected void sayHello(Exposed exposed) {
            exposed.sayHello();
        }
    }
}

「フレンド」機能を提供するクラス:

package impl;

public abstract class Accessor {

    private static Accessor instance;

    static Accessor getInstance() {
        Accessor a = instance;
        if (a != null) {
            return a;
        }

        return createInstance();
    }

    private static Accessor createInstance() {
        try {
            Class.forName(Exposed.class.getName(), true, 
                Exposed.class.getClassLoader());
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e);
        }

        return instance;
    }

    public static void setInstance(Accessor accessor) {
        if (instance != null) {
            throw new IllegalStateException(
                "Accessor instance already set");
        }

        instance = accessor;
    }

    protected abstract Exposed createExposed();

    protected abstract void sayHello(Exposed exposed);
}

「friend」実装パッケージのクラスからのアクセス例:

package impl;

public final class FriendlyAccessExample {
    public static void main(String[] args) {
        Accessor accessor = Accessor.getInstance();
        Exposed exposed = accessor.createExposed();
        accessor.sayHello(exposed);
    }
}
44
Matthew Murdoch

Javaの設計者は、C++で機能する友人のアイデアを明示的に拒否しました。 「友人」を同じパッケージに入れます。プライベート、保護、およびパッケージ化されたセキュリティは、言語設計の一部として実施されます。

James Goslingは、Javaが間違いなくC++であることを望んでいました。彼は、友人がOOPの原則に違反しているので間違いだと感じたと思います。パッケージは、OOPについてあまり純粋になりすぎることなく、コンポーネントを整理する合理的な方法を提供します。

NRは、リフレクションを使用して不正行為を行うことができると指摘しましたが、それでもSecurityManagerを使用していない場合にのみ機能します。 Java標準セキュリティを有効にすると、セキュリティポリシーを作成して明示的に許可しない限り、リフレクションでごまかすことはできません。

52
David G

すべてのクラスを同じパッケージに保持することを伴わない質問に対する2つの解決策があります。

最初の方法は、Friend Accessor/ Friend Package パターンを使用することです(Practical API Design、Tulach 2008)。

2番目は、OSGiを使用することです。 OSGiがこれを達成する方法を説明する記事 here があります。

関連質問: 12 、および。

10
Jeff Axelrod

私の知る限り、それは不可能です。

たぶん、あなたのデザインに関する詳細を教えてください。このような質問は、おそらく設計上の欠陥の結果です。

考えてみて

  • それらが非常に密接に関連しているのに、なぜそれらのクラスは異なるパッケージにあるのですか?
  • AはBのプライベートメンバーにアクセスできますか、または操作をクラスBに移動し、Aによってトリガーされる必要がありますか?
  • これは本当に呼び出しですか、イベント処理が優れていますか?
8
Black

eirikmaの答えは簡単で優れています。もう1つ追加します:パブリックにアクセスできるメソッドgetFriend()を使用して使用できない友人を取得する代わりに、さらに一歩進んでトークンなしで友人を取得できないようにすることができます:getFriend(Service.FriendToken)。このFriendTokenは、プライベートコンストラクターを持つ内部パブリッククラスになるため、Serviceのみがインスタンス化できます。

3
AriG

再利用可能なFriendクラスを使用した明確な使用例です。このメカニズムの利点は、使いやすさです。単体テストクラスに、他のアプリケーションよりも多くのアクセス権を付与するのに適している場合があります。

最初に、Friendクラスの使用方法の例を示します。

public class Owner {
    private final String member = "value";

    public String getMember(final Friend friend) {
        // Make sure only a friend is accepted.
        friend.is(Other.class);
        return member;
    }
}

次に、別のパッケージでこれを実行できます。

public class Other {
    private final Friend friend = new Friend(this);

    public void test() {
        String s = new Owner().getMember(friend);
        System.out.println(s);
    }
}

Friendクラスは次のとおりです。

public final class Friend {
    private final Class as;

    public Friend(final Object is) {
        as = is.getClass();
    }

    public void is(final Class c) {
        if (c == as)
            return;
        throw new ClassCastException(String.format("%s is not an expected friend.", as.getName()));
    }

    public void is(final Class... classes) {
        for (final Class c : classes)
            if (c == as)
                return;
        is((Class)null);
    }
}

ただし、問題は次のように悪用される可能性があることです。

public class Abuser {
    public void doBadThings() {
        Friend badFriend = new Friend(new Other());
        String s = new Owner().getMember(badFriend);
        System.out.println(s);
    }
}

これで、Otherクラスにパブリックコンストラクターがないため、上記のAbuserコードが不可能になる可能性があります。ただし、クラス する パブリックコンストラクターがある場合は、Friendクラスを内部クラスとして複製することをお勧めします。これを取る Other2クラスの例:

public class Other2 {
    private final Friend friend = new Friend();

    public final class Friend {
        private Friend() {}
        public void check() {}
    }

    public void test() {
        String s = new Owner2().getMember(friend);
        System.out.println(s);
    }
}

そして、Owner2クラスは次のようになります。

public class Owner2 {
    private final String member = "value";

    public String getMember(final Other2.Friend friend) {
        friend.check();
        return member;
    }
}

Other2.Friendクラスにはプライベートコンストラクターがあるため、これをより安全に行うことができます。

3
intrepidis

提供されたソリューションはおそらく最も簡単ではありませんでした。別のアプローチは、C++と同じ考え方に基づいています。プライベートメンバーは、所有者が自分自身の友人を作る特定のクラスを除いて、パッケージ/プライベートスコープの外部からアクセスできません。

メンバーへのフレンドアクセスを必要とするクラスは、アクセス実装メソッドを実装するサブクラスを返すことにより、隠しプロパティを所有するクラスがアクセスをエクスポートできる内部パブリック抽象「フレンドクラス」を作成する必要があります。フレンドクラスの「API」メソッドはプライベートにすることができるため、フレンドアクセスが必要なクラスの外部からはアクセスできません。その唯一のステートメントは、エクスポートクラスが実装する抽象保護メンバーへの呼び出しです。

コードは次のとおりです。

まず、これが実際に機能することを確認するテスト:

package application;

import application.entity.Entity;
import application.service.Service;
import junit.framework.TestCase;

public class EntityFriendTest extends TestCase {
    public void testFriendsAreOkay() {
        Entity entity = new Entity();
        Service service = new Service();
        assertNull("entity should not be processed yet", entity.getPublicData());
        service.processEntity(entity);
        assertNotNull("entity should be processed now", entity.getPublicData());
    }
}

次に、Entityのパッケージプライベートメンバーへの友人アクセスが必要なサービス:

package application.service;

import application.entity.Entity;

public class Service {

    public void processEntity(Entity entity) {
        String value = entity.getFriend().getEntityPackagePrivateData();
        entity.setPublicData(value);
    }

    /**
     * Class that Entity explicitly can expose private aspects to subclasses of.
     * Public, so the class itself is visible in Entity's package.
     */
    public static abstract class EntityFriend {
        /**
         * Access method: private not visible (a.k.a 'friendly') outside enclosing class.
         */
        private String getEntityPackagePrivateData() {
            return getEntityPackagePrivateDataImpl();
        }

        /** contribute access to private member by implementing this */
        protected abstract String getEntityPackagePrivateDataImpl();
    }
}

最後に、クラスapplication.service.Serviceのみにパッケージプライベートメンバーへのフレンドリアクセスを提供するEntityクラス。

package application.entity;

import application.service.Service;

public class Entity {

    private String publicData;
    private String packagePrivateData = "secret";   

    public String getPublicData() {
        return publicData;
    }

    public void setPublicData(String publicData) {
        this.publicData = publicData;
    }

    String getPackagePrivateData() {
        return packagePrivateData;
    }

    /** provide access to proteced method for Service'e helper class */
    public Service.EntityFriend getFriend() {
        return new Service.EntityFriend() {
            protected String getEntityPackagePrivateDataImpl() {
                return getPackagePrivateData();
            }
        };
    }
}

さて、「friend service :: Service;」より少し長いことを認めなければなりません。ただし、アノテーションを使用してコンパイル時のチェックを維持しながら短縮することも可能です。

2
eirikma

C++のフレンドクラスは、Javaの内部クラスの概念に似ていると思います。内部クラスを使用すると、実際に囲みクラスと囲みクラスを定義できます。囲まれたクラスは、それを囲むクラスのパブリックおよびプライベートメンバーへのフルアクセスを持ちます。次のリンクを参照してください: http://docs.Oracle.com/javase/tutorial/Java/javaOO/nested.html

1
Sephiroth

Java「パッケージ関連の友情」を持つことが可能です。これは単体テストに役立ちます。メソッドの前にprivate/public/protectedを指定しない場合、同じパッケージ内のクラスはそれにアクセスできますが、クラス外ではプライベートになります。

この規則は常に知られているわけではなく、C++の「友人」キーワードの適切な近似です。良い代替品だと思います。

1
daitangio

フレンドアクセサパターンを使用するアプローチは、あまりにも複雑すぎると思います。私は同じ問題に直面しなければならなかったので、C++で知られるJavaの古くて良いコピーコンストラクタを使用して解決しました。

public class ProtectedContainer {
    protected String iwantAccess;

    protected ProtectedContainer() {
        super();
        iwantAccess = "Default string";
    }

    protected ProtectedContainer(ProtectedContainer other) {
        super();
        this.iwantAccess = other.iwantAccess;
    }

    public int calcSquare(int x) {
        iwantAccess = "calculated square";
        return x * x;
    }
}

アプリケーションでは、次のコードを記述できます。

public class MyApp {

    private static class ProtectedAccessor extends ProtectedContainer {

        protected ProtectedAccessor() {
            super();
        }

        protected PrivateAccessor(ProtectedContainer prot) {
            super(prot);
        }

        public String exposeProtected() {
            return iwantAccess;
        }
    }
}

この方法の利点は、アプリケーションのみが保護されたデータにアクセスできることです。 friendキーワードの正確な代替ではありません。しかし、カスタムライブラリを作成し、保護されたデータにアクセスする必要がある場合は、非常に適していると思います。

ProtectedContainerのインスタンスを処理する必要があるときはいつでも、ProtectedAccessorをラップしてアクセスできます。

また、保護されたメソッドでも機能します。それらをAPIで保護して定義します。アプリケーションの後半で、プライベートラッパークラスを記述し、プロテクトメソッドをパブリックとして公開します。それでおしまい。

1
Chris

キーワードなどを使用していません。

リフレクションなどを使用して「チート」することもできますが、「チート」はお勧めしません。

0
NR.

保護されたメソッドにアクセスする場合は、使用するクラスのサブクラスを作成して、使用するメソッドを公開(またはより安全にするために名前空間の内部)として公開し、クラスにそのクラスのインスタンスを含めることができます(プロキシとして使用します)。

私的方法に関する限り(私は思う)、あなたは運が悪い。

0
Omar Kooheji

私はそれが誰にも役立つかどうかわかりませんが、私はそれを次のように扱いました:

インターフェイス(AdminRights)を作成しました。

上記の関数を呼び出すことができるはずのすべてのクラスは、AdminRightsを実装する必要があります。

次に、次のように関数HasAdminRightsを作成しました。

private static final boolean HasAdminRights()
{
    // Gets the current hierarchy of callers
    StackTraceElement[] Callers = new Throwable().getStackTrace();

    // Should never occur with me but if there are less then three StackTraceElements we can't check
    if (Callers.length < 3)
    {
        EE.InvalidCode("Couldn't check for administrator rights");
        return false;

    } else try
    {

        // Now we check the third element as this function is the first and the function wanting to check for the rights the second. We try to use it as a subclass of AdminRights.
        Class.forName(Callers[2].getClassName()).asSubclass(AdminRights.class);

        // If everything worked up to now, it has admin rights!
        return true;

    } catch (Java.lang.ClassCastException | ClassNotFoundException e)
    {
        // In the catch, something went wrong and we can deduce that the caller has no admin rights

        EE.InvalidCode(Callers[1].getClassName() + " doesn't have administrator rights");
        return false;
    }
}
0
mdre

ほとんどの場合、friendキーワードは不要であることに同意します。

  • 密接に絡み合ったクラスのグループがあるほとんどの場合、パッケージプライベート(別名、デフォルト)で十分です。
  • 内部へのアクセスを必要とするデバッグクラスの場合、通常、メソッドをプライベートにし、リフレクションを介してアクセスします。ここでは通常、速度は重要ではありません
  • 場合によっては、「ハック」または変更される可能性のあるメソッドを実装します。私はそれを公開しますが、@ Deprecatedを使用して、このメソッドが存在することに依存してはならないことを示します。

最後に、本当に必要な場合は、他の回答で言及されているフレンドアクセサーパターンがあります。

0
Casebash

この問題を解決するために私が見つけた方法は、次のようなアクセサオブジェクトを作成することです。

_class Foo {
    private String locked;

    /* Anyone can get locked. */
    public String getLocked() { return locked; }

    /* This is the accessor. Anyone with a reference to this has special access. */
    public class FooAccessor {
        private FooAccessor (){};
        public void setLocked(String locked) { Foo.this.locked = locked; }
    }
    private FooAccessor accessor;

    /** You get an accessor by calling this method. This method can only
     * be called once, so calling is like claiming ownership of the accessor. */
    public FooAccessor getAccessor() {
        if (accessor != null)
            throw new IllegalStateException("Cannot return accessor more than once!");
        return accessor = new FooAccessor();
    }
}
_

getAccessor()を呼び出す最初のコードは、アクセサーの「所有権を主張します」。通常、これはオブジェクトを作成するコードです。

_Foo bar = new Foo(); //This object is safe to share.
FooAccessor barAccessor = bar.getAccessor(); //This one is not.
_

これには、C++のフレンドメカニズムよりも利点があります。これは、クラスごとレベルではなく、インスタンスごとレベルでアクセスを制限できるためです。アクセサー参照を制御することにより、オブジェクトへのアクセスを制御します。また、複数のアクセサーを作成し、それぞれに異なるアクセスを許可することで、どのコードが何にアクセスできるかをきめ細かく制御できます。

_class Foo {
    private String secret;
    private String locked;

    /* Anyone can get locked. */
    public String getLocked() { return locked; }

    /* Normal accessor. Can write to locked, but not read secret. */
    public class FooAccessor {
        private FooAccessor (){};
        public void setLocked(String locked) { Foo.this.locked = locked; }
    }
    private FooAccessor accessor;

    public FooAccessor getAccessor() {
        if (accessor != null)
            throw new IllegalStateException("Cannot return accessor more than once!");
        return accessor = new FooAccessor();
    }

    /* Super accessor. Allows access to secret. */
    public class FooSuperAccessor {
        private FooSuperAccessor (){};
        public String getSecret() { return Foo.this.secret; }
    }
    private FooSuperAccessor superAccessor;

    public FooSuperAccessor getAccessor() {
        if (superAccessor != null)
            throw new IllegalStateException("Cannot return accessor more than once!");
        return superAccessor = new FooSuperAccessor();
    }
}
_

最後に、物事をもう少し整理したい場合は、すべてをまとめた参照オブジェクトを作成できます。これにより、すべてのアクセサーを1つのメソッド呼び出しで要求し、リンクされたインスタンスと一緒に保つことができます。参照を取得したら、それを必要とするコードにアクセサーを渡すことができます。

_class Foo {
    private String secret;
    private String locked;

    public String getLocked() { return locked; }

    public class FooAccessor {
        private FooAccessor (){};
        public void setLocked(String locked) { Foo.this.locked = locked; }
    }
    public class FooSuperAccessor {
        private FooSuperAccessor (){};
        public String getSecret() { return Foo.this.secret; }
    }
    public class FooReference {
        public final Foo foo;
        public final FooAccessor accessor;
        public final FooSuperAccessor superAccessor;

        private FooReference() {
            this.foo = Foo.this;
            this.accessor = new FooAccessor();
            this.superAccessor = new FooSuperAccessor();
        }
    }

    private FooReference reference;

    /* Beware, anyone with this object has *all* the accessors! */
    public FooReference getReference() {
        if (reference != null)
            throw new IllegalStateException("Cannot return reference more than once!");
        return reference = new FooReference();
    }
}
_

たくさんのヘッドバンギング(良い種類ではありません)の後、これが私の最終的な解決策であり、とても気に入っています。柔軟で使いやすく、クラスアクセスを非常に適切に制御できます。 (参照のみアクセスは非常に便利です。)アクセサ/参照にprivateではなくprotectedを使用すると、FooのサブクラスはgetReferenceから拡張参照を返すことさえできます。また、反射を必要としないため、あらゆる環境で使用できます。

0
jpfx1342

委任、作曲、またはファクトリクラス(この問題が発生する問題に応じて)を好み、パブリッククラスにしないようにします。

「異なるパッケージのインターフェース/実装クラス」問題である場合、Implパッケージと同じパッケージ内にあるパブリックファクトリクラスを使用し、Implクラスの公開を防ぎます。

「この機能を別のパッケージの他のクラスに提供するためだけに、このクラス/メソッドを公開するのが嫌い」問題の場合、同じパッケージの公開デリゲートクラスを使用し、機能のその部分のみを公開します「外部」クラスに必要です。

これらの決定の一部は、ターゲットサーバーのクラスローディングアーキテクチャ(OSGiバンドル、WAR/EARなど)、展開、およびパッケージの命名規則によって決定されます。たとえば、上記の提案されたソリューション「Friend Accessor」パターンは、通常のJavaアプリケーションに適しています。クラスローディングスタイルの違いにより、OSGiで実装するのが難しいのではないかと思います。

0
Shashi Velur

Java 9の時点で、多くの場合、モジュールを使用してこれを非問題にすることができます。

0
Raphael