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);
}
}
Javaの設計者は、C++で機能する友人のアイデアを明示的に拒否しました。 「友人」を同じパッケージに入れます。プライベート、保護、およびパッケージ化されたセキュリティは、言語設計の一部として実施されます。
James Goslingは、Javaが間違いなくC++であることを望んでいました。彼は、友人がOOPの原則に違反しているので間違いだと感じたと思います。パッケージは、OOPについてあまり純粋になりすぎることなく、コンポーネントを整理する合理的な方法を提供します。
NRは、リフレクションを使用して不正行為を行うことができると指摘しましたが、それでもSecurityManagerを使用していない場合にのみ機能します。 Java標準セキュリティを有効にすると、セキュリティポリシーを作成して明示的に許可しない限り、リフレクションでごまかすことはできません。
すべてのクラスを同じパッケージに保持することを伴わない質問に対する2つの解決策があります。
最初の方法は、Friend Accessor/ Friend Package パターンを使用することです(Practical API Design、Tulach 2008)。
2番目は、OSGiを使用することです。 OSGiがこれを達成する方法を説明する記事 here があります。
私の知る限り、それは不可能です。
たぶん、あなたのデザインに関する詳細を教えてください。このような質問は、おそらく設計上の欠陥の結果です。
考えてみて
eirikmaの答えは簡単で優れています。もう1つ追加します:パブリックにアクセスできるメソッドgetFriend()を使用して使用できない友人を取得する代わりに、さらに一歩進んでトークンなしで友人を取得できないようにすることができます:getFriend(Service.FriendToken)。このFriendTokenは、プライベートコンストラクターを持つ内部パブリッククラスになるため、Serviceのみがインスタンス化できます。
再利用可能な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
クラスにはプライベートコンストラクターがあるため、これをより安全に行うことができます。
提供されたソリューションはおそらく最も簡単ではありませんでした。別のアプローチは、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;」より少し長いことを認めなければなりません。ただし、アノテーションを使用してコンパイル時のチェックを維持しながら短縮することも可能です。
C++のフレンドクラスは、Javaの内部クラスの概念に似ていると思います。内部クラスを使用すると、実際に囲みクラスと囲みクラスを定義できます。囲まれたクラスは、それを囲むクラスのパブリックおよびプライベートメンバーへのフルアクセスを持ちます。次のリンクを参照してください: http://docs.Oracle.com/javase/tutorial/Java/javaOO/nested.html
Java「パッケージ関連の友情」を持つことが可能です。これは単体テストに役立ちます。メソッドの前にprivate/public/protectedを指定しない場合、同じパッケージ内のクラスはそれにアクセスできますが、クラス外ではプライベートになります。
この規則は常に知られているわけではなく、C++の「友人」キーワードの適切な近似です。良い代替品だと思います。
フレンドアクセサパターンを使用するアプローチは、あまりにも複雑すぎると思います。私は同じ問題に直面しなければならなかったので、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で保護して定義します。アプリケーションの後半で、プライベートラッパークラスを記述し、プロテクトメソッドをパブリックとして公開します。それでおしまい。
キーワードなどを使用していません。
リフレクションなどを使用して「チート」することもできますが、「チート」はお勧めしません。
保護されたメソッドにアクセスする場合は、使用するクラスのサブクラスを作成して、使用するメソッドを公開(またはより安全にするために名前空間の内部)として公開し、クラスにそのクラスのインスタンスを含めることができます(プロキシとして使用します)。
私的方法に関する限り(私は思う)、あなたは運が悪い。
私はそれが誰にも役立つかどうかわかりませんが、私はそれを次のように扱いました:
インターフェイス(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;
}
}
ほとんどの場合、friendキーワードは不要であることに同意します。
最後に、本当に必要な場合は、他の回答で言及されているフレンドアクセサーパターンがあります。
この問題を解決するために私が見つけた方法は、次のようなアクセサオブジェクトを作成することです。
_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
から拡張参照を返すことさえできます。また、反射を必要としないため、あらゆる環境で使用できます。
委任、作曲、またはファクトリクラス(この問題が発生する問題に応じて)を好み、パブリッククラスにしないようにします。
「異なるパッケージのインターフェース/実装クラス」問題である場合、Implパッケージと同じパッケージ内にあるパブリックファクトリクラスを使用し、Implクラスの公開を防ぎます。
「この機能を別のパッケージの他のクラスに提供するためだけに、このクラス/メソッドを公開するのが嫌い」問題の場合、同じパッケージの公開デリゲートクラスを使用し、機能のその部分のみを公開します「外部」クラスに必要です。
これらの決定の一部は、ターゲットサーバーのクラスローディングアーキテクチャ(OSGiバンドル、WAR/EARなど)、展開、およびパッケージの命名規則によって決定されます。たとえば、上記の提案されたソリューション「Friend Accessor」パターンは、通常のJavaアプリケーションに適しています。クラスローディングスタイルの違いにより、OSGiで実装するのが難しいのではないかと思います。
Java 9の時点で、多くの場合、モジュールを使用してこれを非問題にすることができます。