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


196

他のクラスのサブクラスにすることなく、別のパッケージのクラスの非公開メソッドにアクセスできるJavaクラスを1つのパッケージで記述できるようにしたいと思います。これは可能ですか?

回答:


466

これは、C ++フレンドメカニズムを複製するためにJAVAで使用する小さなトリックです。

クラスRomeoと別のクラスがあるとしましょうJuliet。彼らは憎しみの理由で異なるパッケージ(家族)にあります。

Romeo彼女だけにしたいしcuddle Juliet、しJulietたいだけRomeo cuddleです。

C ++では、(恋人)としてJuliet宣言Romeofriendますが、Javaにはそのようなものはありません。

ここにクラスとトリックがあります:

最初の女性:

package capulet;

import montague.Romeo;

public class Juliet {

    public static void cuddle(Romeo.Love love) {
        Objects.requireNonNull(love);
        System.out.println("O Romeo, Romeo, wherefore art thou Romeo?");
    }

}

したがって、メソッドJuliet.cuddlepublicですが、Romeo.Loveそれを呼び出すにはが必要です。これはRomeo.Love、「署名セキュリティ」としてこれを使用して、Romeoこのメソッドのみを呼び出すことができ、愛が本物であることを確認することで、ランタイムがをスローするNullPointerExceptionようにしますnull

今男の子:

package montague;

import capulet.Juliet;

public class Romeo {
    public static final class Love { private Love() {} }
    private static final Love love = new Love();

    public static void cuddleJuliet() {
        Juliet.cuddle(love);
    }
}

クラスRomeo.Loveはパブリックですが、そのコンストラクタはprivateです。したがって、誰でもそれを見ることができますが、それRomeoを構築することしかできません。私は静的参照を使用しているため、Romeo.Love決して使用されないは一度だけ作成され、最適化に影響を与えません。

したがって、Romeoできるのcuddle Julietは彼だけです。なぜなら、彼女だけがRomeo.Loveインスタンスを構築してアクセスできるからです。JulietcuddleNullPointerException


107
「NullPointerExceptionで平手打ち」の+1。非常に印象的。
Nickolas 2014

2
@Steazyあります:NotNull、NonNull、およびCheckForNullアノテーションを探します。IDEのドキュメントを確認して、これらの注釈の使用方法と適用方法を確認してください。IntelliJはデフォルトでこれを埋め込み、Eclipseにはプラグイン(FindBugsなど)が必要であることを知っています。
Salomon BRYS 2014

27
あなたは作ることができるRomeoLoveためにJulia変更することで、永遠のloveようにフィールドをfinal;-)。
Matthias

5
@Matthiasラブフィールドは静的です...回答を編集して最終的なものにします;)
Salomon BRYS

12
ユーモアと素晴らしい例では、すべての回答がこのように(Y)+1である必要があります。
Zia Ul Rehman Mughal

54

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

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

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


11
私はペダントになるつもりはありませんが、アクセス修飾子はセキュリティメカニズムではありません。
Greg D

6
アクセス修飾子は、Javaセキュリティモデルの一部です。具体的には、反射についてjava.lang.RuntimePermissionを参照していました:accessDeclaredMembersおよびaccessClassInPackage。
デビッドG

54
ゴスリングfriendがOOP違反(特に、パッケージアクセスよりも違反)だと本当に思った場合、彼は本当にそれを理解していませんでした (完全に可能で、多くの人がそれを誤解しています)。
Konrad Rudolph、

8
クラスコンポーネントを分離する必要がある場合があります(例:実装とAPI、コアオブジェクトとアダプタ)。パッケージレベルの保護は、同時に、許容範囲が厳しく、制限が厳しすぎるため、これを適切に実行できません。
dhardy 14

2
@GregDこれら、開発者がクラスメンバーを誤って使用するのを防ぐのに役立つという意味で、セキュリティメカニズムと考えることできます。おそらく、安全機構と呼ばれる方がいいと思います。
2014

45

「フレンド」の概念は、たとえば、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);
    }
}

1
「公開」クラスの「静的」の意味がわからなかったため、静的ブロックは、クラスが最初にJVMに読み込まれたときに実行されるJavaクラス内のステートメントのブロックです
Guy L

興味深いパターンですが、ExposedクラスとAccessorクラスがパブリックである必要がありますが、APIを実装するクラス(つまり、パブリックJavaインターフェースのセットを実装するJavaクラスのセット)は「デフォルトで保護」されているため、クライアントからアクセスできません。実装から型を分離する。
ヤン・ガエル・Guéhéneuc

8
私はJavaにかなり錆びているので、無知を許してください。サロモンBRYSが投稿した「ロミオとジュリエット」ソリューションに対するこれの利点は何ですか?この実装は、コードベースでつまづいた場合(説明を添付せずに、つまり重いコメントを付けた場合)、パンツを怖がらせます。ロミオとジュリエットのアプローチは非常に簡単に理解できます。
Steazy、2014

1
このアプローチは実行時にのみ問題を可視化しますが、ロミオとジュリエットの誤用は、開発中のコンパイル時にそれらを可視化します。
ymajoros 2017年

1
@ymajorosロミオとジュリエットの例では、コンパイル時に誤用が表示されません。これは、引数が正しく渡され、例外がスローされることに依存しています。これらはどちらもランタイムアクションです。
Radiodef

10

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

1つは、(Practical API Design、Tulach 2008)で説明されているFriend Accessor / Friend Packageパターンを使用することです。

2つ目は、OSGiを使用することです。OSGiがこれを実現する方法を説明する記事がここにあります。

関連の質問:12、および3


7

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

たぶん、あなたはあなたにあなたのデザインについてもう少し詳しく教えてもらえますか?このような質問は、設計上の欠陥が原因である可能性があります。

考えてみて

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

3

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


3

これは、再利用可能な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コードを実行できないのは事実です。あなたのクラスがあればしかし、ない publicコンストラクタを持っている、内部クラスとしてフレンドクラスを複製することはおそらく賢明です。この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クラスにはプライベートコンストラクターがあるため、これをより安全な方法で行うことに注意してください。


2

提供されたソリューションはおそらく最も単純ではありませんでした。別のアプローチは、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;」より少し長いことを認めなければなりません。しかし、注釈を使用することにより、コンパイル時のチェックを維持しながら、短縮することが可能な場合があります。


これは、同じパッケージの通常のクラスがgetFriend()を実行し、プライベートメソッドをバイパスして保護されたメソッドを呼び出すだけなので、まったく機能しません。
user2219808 2017

1

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

このルールは常に知られているわけではなく、C ++の "friend"キーワードの良い近似です。良い代替品だと思います。


1
これは本当ですが、別のパッケージにあるコードについて本当に質問していました...
Matthew Murdoch

1

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


ええ、いいえ、そうではありません。現実の友情のようなものです:相互である必要はありませんが(AがBの友人であるということは、BがAの友人と見なされることを意味しません)、あなたとあなたの友人はまったく違う家族がいて、自分の、おそらく(必ずしもそうとは限りませんが)重複する友達の輪があります。(たくさんの友達と一緒にクラスを見たいと思っているわけではありません。これは便利な機能ですが、注意して使用する必要があります。)
Christopher Creutzig

1

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

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
しかしProtectedContainer、パッケージの外でサブクラス化できます!
ラファエル

0

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

プライベートメソッドに関する限り(私は思う)、あなたは運が悪いです。


0

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

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

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


0

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

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


3
私はこれをそんなに悪い考えだと思って、それが私には忌まわしいことを示唆することさえします。明らかにこれはせいぜい策略であり、どのデザインの一部であってはなりません。
shsteimer 2008年

0

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

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();
    }
}

多くのヘッドバンギング(良い種類ではありません)を行った後、これが私の最終的な解決策でした。柔軟で使いやすく、クラスアクセスを非常に適切に制御できます。(参照のみのアクセスは非常に便利です。)アクセサー/参照にプライベートではなく保護を使用すると、Fooのサブクラスはからの拡張参照を返すこともできgetReferenceます。また、リフレクションを必要としないため、あらゆる環境で使用できます。


0

Java 9以降、多くの場合、モジュールを使用してこれを問題のないものにすることができます。


0

パブリッククラスにすることを避けるために、委任、構成、またはファクトリクラス(この問題の原因となる問題に応じて)を好みます。

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

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

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


0

それが誰かに役立つかどうかはわかりませんが、次のように処理しました。

インターフェイス(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;
    }
}

-1

以前、リフレクションを使用して実行時に「フレンドチェック」を行い、メソッドを呼び出すクラスが許可されているかどうかを確認するためにコールスタックをチェックするリフレクションベースのソリューションを見たことがあります。ランタイムチェックであるため、明らかな欠点があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.