回答:
これは、C ++フレンドメカニズムを複製するためにJAVAで使用する小さなトリックです。
クラスRomeo
と別のクラスがあるとしましょうJuliet
。彼らは憎しみの理由で異なるパッケージ(家族)にあります。
Romeo
彼女だけにしたいしcuddle
Juliet
、しJuliet
たいだけRomeo
cuddle
です。
C ++では、(恋人)としてJuliet
宣言Romeo
しfriend
ますが、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.cuddle
はpublic
ですが、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
インスタンスを構築してアクセスできるからです。Juliet
cuddle
NullPointerException
Romeo
のLove
ためにJulia
変更することで、永遠のlove
ようにフィールドをfinal
;-)。
Javaの設計者は、C ++で機能するという友人の考えを明示的に拒否しました。「友達」を同じパッケージに入れます。言語設計の一部として、プライベート、保護、およびパッケージ化されたセキュリティが適用されます。
James Goslingは、Javaを間違いなくC ++にしたいと考えていました。私は彼がOOPの原則に違反しているので友人は間違いだと感じたと思います。パッケージは、OOPをあまり純粋にせずにコンポーネントを整理するための合理的な方法を提供します。
NRは、リフレクションを使用してカンニングすることができると指摘しましたが、それでもSecurityManagerを使用していない場合にのみ機能します。Java標準のセキュリティを有効にした場合、特にそれを許可するセキュリティポリシーを作成しない限り、リフレクションで不正行為を行うことはできません。
「フレンド」の概念は、たとえば、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);
}
}
すべてのクラスを同じパッケージに保持することを含まない、あなたの質問に対する2つの解決策があります。
1つは、(Practical API Design、Tulach 2008)で説明されているFriend Accessor / Friend Packageパターンを使用することです。
2つ目は、OSGiを使用することです。OSGiがこれを実現する方法を説明する記事がここにあります。
これは、再利用可能な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
クラスにはプライベートコンストラクターがあるため、これをより安全な方法で行うことに注意してください。
提供されたソリューションはおそらく最も単純ではありませんでした。別のアプローチは、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;」より少し長いことを認めなければなりません。しかし、注釈を使用することにより、コンパイル時のチェックを維持しながら、短縮することが可能な場合があります。
Javaでは、「パッケージ関連の友情」を持つことができます。これは、単体テストに役立ちます。メソッドの前にprivate / public / protectedを指定しない場合は、「パッケージ内のフレンド」になります。同じパッケージ内のクラスはそれにアクセスできますが、クラス外ではプライベートになります。
このルールは常に知られているわけではなく、C ++の "friend"キーワードの良い近似です。良い代替品だと思います。
C ++のフレンドクラスは、Javaの内部クラスの概念に似ていると思います。内部クラスを使用すると、実際に囲まれたクラスと囲まれたクラスを定義できます。囲まれたクラスは、その囲まれたクラスのパブリックおよびプライベートメンバーへのフルアクセスを持ちます。次のリンクを参照してください。http: //docs.oracle.com/javase/tutorial/java/javaOO/nested.html
フレンドアクセサーパターンを使用するアプローチは複雑すぎると思います。私は同じ問題に直面する必要があり、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で保護されたそれらを定義します。アプリケーションの後半で、プライベートラッパークラスを記述し、保護されたメソッドをパブリックとして公開します。それでおしまい。
ProtectedContainer
、パッケージの外でサブクラス化できます!
ほとんどの場合、フレンドキーワードは不要であることに同意します。
そして最後に、本当に必要な場合は、他の回答で言及されているフレンドアクセサーパターンがあります。
キーワードなどを使用していません。
リフレクションなどを利用して「チート」することもできますが、「チート」はお勧めしません。
この問題を解決するために私が見つけた方法は、次のようにアクセサーオブジェクトを作成することです。
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
ます。また、リフレクションを必要としないため、あらゆる環境で使用できます。
パブリッククラスにすることを避けるために、委任、構成、またはファクトリクラス(この問題の原因となる問題に応じて)を好みます。
それが「異なるパッケージのインターフェース/実装クラス」の問題である場合、私はimplパッケージと同じパッケージにあるパブリックファクトリクラスを使用して、implクラスの公開を防ぎます。
「別のパッケージにある他のクラスにこの機能を提供するためだけにこのクラス/メソッドを公開するのは嫌い」という問題の場合は、同じパッケージのパブリックデリゲートクラスを使用して、機能のその部分のみを公開します。 「アウトサイダー」クラスで必要です。
これらの決定の一部は、ターゲットサーバーのクラスローディングアーキテクチャ(OSGiバンドル、WAR / EARなど)、デプロイメント、およびパッケージの命名規則によって決定されます。たとえば、上記の提案されたソリューションである「Friend Accessor」パターンは、通常のJavaアプリケーションにとっては賢いものです。クラスローディングのスタイルの違いにより、OSGiで実装するのが難しいのではないでしょうか。
それが誰かに役立つかどうかはわかりませんが、次のように処理しました。
インターフェイス(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;
}
}