別のパッケージ内のクラスの非パブリック メソッドにアクセスできる Java クラスを、別のクラスのサブクラスにすることなく別のパッケージ内に記述できるようにしたいと考えています。これは可能ですか?
18 に答える
C++ のフレンド メカニズムを複製するために Java で使用する小さなトリックを次に示します。
classRomeo
と別の classがあるとしましょうJuliet
。それらは憎しみの理由で異なるパッケージ (ファミリー) に入っています。
Romeo
彼女だけにしたいcuddle
Juliet
と思っています。Juliet
Romeo
cuddle
C++ では(恋人) としてJuliet
宣言しますが、Java にはそのようなものはありません。Romeo
friend
クラスとトリックは次のとおりです。
レディースファースト:
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
は public ですが、そのコンストラクターはprivate
. Romeo
したがって、誰でもそれを見ることができますが、それを構築することしかできません。静的参照を使用するため、Romeo.Love
使用されない は一度だけ構築され、最適化には影響しません。
したがって、彼だけがインスタンスを構築してアクセスRomeo
できるcuddle
Juliet
ため、彼だけがそれを行うことができます。Romeo.Love
Juliet
cuddle
NullPointerException
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);
}
}
私の知る限り、それは不可能です。
たぶん、あなたは私たちにあなたのデザインについてもう少し詳細を与えることができます。このような質問は、設計上の欠陥の結果である可能性があります。
考えてみてください
- それらが非常に密接に関連しているのに、なぜそれらのクラスは異なるパッケージに含まれているのですか?
- AはBのプライベートメンバーにアクセスする必要がありますか、それとも操作をクラスBに移動して、Aによってトリガーする必要がありますか?
- これは本当に呼び出しですか、それともイベント処理の方が優れていますか?
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 コンストラクターがある場合は、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
このクラスにはプライベート コンストラクターがあるため、より安全な方法になっていることに注意してください。
eirikma の答えは簡単で優れています。もう 1 つ追加することがあります。公にアクセス可能なメソッド getFriend() を使用して使用できないフレンドを取得する代わりに、さらに一歩進んで、トークンなしでフレンドを取得できないようにすることができます: getFriend(Service.FriendToken)。この FriendToken は、プライベート コンストラクターを持つ内部パブリック クラスになるため、Service だけがインスタンス化できます。
提供されたソリューションは、おそらく最も単純ではありませんでした。もう 1 つのアプローチは、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());
}
}
次に、エンティティのパッケージ プライベート メンバーへのフレンド アクセスが必要なサービス:
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
キーワードなどを使用していません。
リフレクションなどを使用して「ごまかす」ことはできますが、「ごまかす」ことはお勧めしません。
保護されたメソッドにアクセスする場合は、使用するクラスのサブクラスを作成して、使用するメソッドをパブリック(またはより安全にするために名前空間の内部)として公開し、そのクラスのインスタンスをクラスに含めることができます。 (プロキシとして使用してください)。
私的な方法に関する限り(私は思う)、あなたは運が悪い。
ほとんどの場合、friend キーワードは不要であることに同意します。
- 密接に絡み合ったクラスのグループがあるほとんどの場合、Package-private (aka. default) で十分です。
- 内部へのアクセスが必要なデバッグ クラスの場合、通常はメソッドをプライベートにし、リフレクション経由でアクセスします。ここでは通常、速度は重要ではありません
- 場合によっては、「ハック」または変更される可能性のあるメソッドを実装することがあります。私はそれを公開しますが、 @Deprecated を使用して、このメソッドが存在することに依存してはならないことを示します。
最後に、本当に必要な場合は、他の回答で言及されているフレンド アクセサー パターンがあります。
public クラスにすることを避けるために、(この問題を引き起こす問題に応じて) 委譲、構成、またはファクトリ クラスを好みます。
「異なるパッケージのインターフェイス/実装クラス」の問題である場合は、impl パッケージと同じパッケージに含まれるパブリック ファクトリ クラスを使用して、impl クラスの公開を防ぎます。
「別のパッケージの他のクラスにこの機能を提供するためだけに、このクラス/メソッドを公開するのは嫌だ」という問題である場合は、同じパッケージでパブリック デリゲート クラスを使用し、機能のその部分のみを公開します。 「アウトサイダー」クラスに必要です。
これらの決定の一部は、ターゲット サーバーのクラスローディング アーキテクチャ (OSGi バンドル、WAR/EAR など)、展開、およびパッケージの命名規則によって決まります。たとえば、上記の提案された解決策である「フレンド アクセサー」パターンは、通常の Java アプリケーションには有効です。クラスローディングスタイルの違いでOSGiに実装するのはややこしいのではないでしょうか。
以前、リフレクションを使用して実行時に「フレンド チェック」を行い、コール スタックをチェックして、メソッドを呼び出すクラスが許可されているかどうかを確認するリフレクション ベースのソリューションを見たことがあります。実行時チェックであるため、明らかな欠点があります。