Ich möchte in der Lage sein, eine Java-Klasse in ein Paket zu schreiben, die auf nicht öffentliche Methoden einer Klasse in einem anderen Paket zugreifen kann, ohne sie zu einer Unterklasse der anderen Klasse machen zu müssen. Ist das möglich?
Ich möchte in der Lage sein, eine Java-Klasse in ein Paket zu schreiben, die auf nicht öffentliche Methoden einer Klasse in einem anderen Paket zugreifen kann, ohne sie zu einer Unterklasse der anderen Klasse machen zu müssen. Ist das möglich?
Antworten:
Hier ist ein kleiner Trick, den ich in JAVA verwende, um den C ++ - Freundmechanismus zu replizieren.
Nehmen wir an, ich habe eine Klasse Romeo
und eine andere Klasse Juliet
. Sie sind aus Hassgründen in verschiedenen Paketen (Familien).
Romeo
will cuddle
Juliet
und Juliet
will sie nur lassen Romeo
cuddle
.
In C ++ Juliet
würde Romeo
als (Liebhaber) deklarieren, friend
aber es gibt keine solchen Dinge in Java.
Hier sind die Klassen und der Trick:
Frauen zuerst :
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?");
}
}
Die Methode Juliet.cuddle
ist also, public
aber Sie brauchen eine Romeo.Love
, um sie aufzurufen. Es verwendet dies Romeo.Love
als "Signatursicherheit", um sicherzustellen, dass nur Romeo
diese Methode aufgerufen werden kann, und überprüft, ob die Liebe echt ist, so dass die Laufzeit eine auslöst, NullPointerException
wenn dies der Fall ist null
.
Jetzt Jungs:
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);
}
}
Die Klasse Romeo.Love
ist öffentlich, ihr Konstruktor jedoch private
. Daher kann es jeder sehen, aber nur Romeo
konstruieren. Ich verwende eine statische Referenz, sodass die Romeo.Love
nie verwendete nur einmal erstellt wird und keine Auswirkungen auf die Optimierung hat.
Daher Romeo
kann cuddle
Juliet
und nur er kann , denn nur so kann er bauen und Zugang eine Romeo.Love
Instanz, die durch erforderlich ist , Juliet
um cuddle
sie (sonst werden sie dich mit einem Schlag NullPointerException
).
Romeo
s‘ Love
für Julia
ewige durch die sich verändernde love
Feld sein final
;-).
Die Designer von Java lehnten die Idee eines Freundes ausdrücklich ab, da es in C ++ funktioniert. Sie legen Ihre "Freunde" in das gleiche Paket. Private, geschützte und verpackte Sicherheit wird als Teil des Sprachdesigns erzwungen.
James Gosling wollte, dass Java ohne Fehler C ++ ist. Ich glaube, er hatte das Gefühl, dass ein Freund ein Fehler war, weil er gegen die OOP-Prinzipien verstößt. Pakete bieten eine vernünftige Möglichkeit, Komponenten zu organisieren, ohne OOP zu puristisch zu sehen.
NR wies darauf hin, dass Sie mit Reflection schummeln könnten, aber selbst das funktioniert nur, wenn Sie den SecurityManager nicht verwenden. Wenn Sie die Java-Standardsicherheit aktivieren, können Sie nur dann mit Reflexion betrügen, wenn Sie eine Sicherheitsrichtlinie schreiben, die dies ausdrücklich zulässt.
friend
OOP verletzt wurde (insbesondere mehr als der Paketzugriff), dann verstand er es wirklich nicht (durchaus möglich, viele Leute verstehen es falsch).
Das "Freund" -Konzept ist in Java beispielsweise nützlich, um eine API von ihrer Implementierung zu trennen. Es ist üblich, dass Implementierungsklassen Zugriff auf API-Klasseninternale benötigen, diese sollten jedoch nicht für API-Clients verfügbar gemacht werden. Dies kann mithilfe des unten beschriebenen Musters "Friend Accessor" erreicht werden:
Die Klasse, die über die API verfügbar gemacht wird:
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();
}
}
}
Die Klasse, die die 'Freund'-Funktionalität bereitstellt:
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);
}
Beispielzugriff von einer Klasse im Implementierungspaket 'friend':
package impl;
public final class FriendlyAccessExample {
public static void main(String[] args) {
Accessor accessor = Accessor.getInstance();
Exposed exposed = accessor.createExposed();
accessor.sayHello(exposed);
}
}
Es gibt zwei Lösungen für Ihre Frage, bei denen nicht alle Klassen im selben Paket bleiben.
Das erste ist die Verwendung des in (Practical API Design, Tulach 2008) beschriebenen Friend Accessor / Friend Package- Musters.
Die zweite ist die Verwendung von OSGi. Es gibt einen Artikel hier erklärt , wie OSGi dies leistet.
Soweit ich weiß, ist das nicht möglich.
Vielleicht könnten Sie uns weitere Details zu Ihrem Design geben. Fragen wie diese sind wahrscheinlich das Ergebnis von Designfehlern.
Überlegen Sie einfach
Die Antwort von eirikma ist einfach und ausgezeichnet. Ich könnte noch etwas hinzufügen: Anstatt eine öffentlich zugängliche Methode zu haben, getFriend (), um einen Freund zu erhalten, der nicht verwendet werden kann, könnten Sie einen Schritt weiter gehen und es nicht zulassen, den Freund ohne Token zu erhalten: getFriend (Service.FriendToken). Dieses FriendToken wäre eine innere öffentliche Klasse mit einem privaten Konstruktor, sodass nur der Dienst eine instanziieren könnte.
Hier ist ein klares Anwendungsbeispiel mit einer wiederverwendbaren Friend
Klasse. Der Vorteil dieses Mechanismus ist die einfache Verwendung. Vielleicht gut, um Unit-Test-Klassen mehr Zugriff zu gewähren als dem Rest der Anwendung.
Hier ist zunächst ein Beispiel für die Verwendung der Friend
Klasse.
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;
}
}
Dann können Sie dies in einem anderen Paket tun:
public class Other {
private final Friend friend = new Friend(this);
public void test() {
String s = new Owner().getMember(friend);
System.out.println(s);
}
}
Die Friend
Klasse ist wie folgt.
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);
}
}
Das Problem ist jedoch, dass es wie folgt missbraucht werden kann:
public class Abuser {
public void doBadThings() {
Friend badFriend = new Friend(new Other());
String s = new Owner().getMember(badFriend);
System.out.println(s);
}
}
Nun kann es wahr sein, dass die Other
Klasse keine öffentlichen Konstruktoren hat, was den obigen Abuser
Code unmöglich macht. Wenn Ihre Klasse jedoch über einen öffentlichen Konstruktor verfügt, ist es wahrscheinlich ratsam, die Friend-Klasse als innere Klasse zu duplizieren. Nehmen Sie diese Other2
Klasse als Beispiel:
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);
}
}
Und dann wäre die Owner2
Klasse so:
public class Owner2 {
private final String member = "value";
public String getMember(final Other2.Friend friend) {
friend.check();
return member;
}
}
Beachten Sie, dass die Other2.Friend
Klasse einen privaten Konstruktor hat, was dies zu einer viel sichereren Methode macht.
Die bereitgestellte Lösung war vielleicht nicht die einfachste. Ein anderer Ansatz basiert auf der gleichen Idee wie in C ++: Auf private Mitglieder kann außerhalb des Pakets / privaten Bereichs nicht zugegriffen werden, mit Ausnahme einer bestimmten Klasse, die der Eigentümer zu einem Freund von sich selbst macht.
Die Klasse, die einen Freundzugriff auf ein Mitglied benötigt, sollte eine innere öffentliche abstrakte "Freundklasse" erstellen, auf die die Klasse, die die verborgenen Eigenschaften besitzt, den Zugriff exportieren kann, indem sie eine Unterklasse zurückgibt, die die Methoden zur Implementierung des Zugriffs implementiert. Die "API" -Methode der Friend-Klasse kann privat sein, sodass außerhalb der Klasse, die einen Friend-Zugriff benötigt, nicht auf sie zugegriffen werden kann. Die einzige Anweisung ist ein Aufruf eines abstrakten geschützten Elements, das die exportierende Klasse implementiert.
Hier ist der Code:
Zuerst der Test, der bestätigt, dass dies tatsächlich funktioniert:
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());
}
}
Dann der Dienst, der Freund Zugriff auf ein privates Paketmitglied von Entity benötigt:
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();
}
}
Schließlich: die Entity-Klasse, die einen benutzerfreundlichen Zugriff auf ein privates Paketmitglied nur für die Klasse application.service.Service bietet.
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();
}
};
}
}
Okay, ich muss zugeben, dass es etwas länger ist als "friend service :: Service;" Möglicherweise kann es jedoch verkürzt werden, während die Überprüfung der Kompilierungszeit mithilfe von Anmerkungen beibehalten wird.
In Java ist es möglich, eine "paketbezogene Freundlichkeit" zu haben. Dies kann für Unit-Tests nützlich sein. Wenn Sie nicht privat / öffentlich / geschützt vor einer Methode angeben, ist dies "Freund im Paket". Eine Klasse im selben Paket kann darauf zugreifen, ist jedoch außerhalb der Klasse privat.
Diese Regel ist nicht immer bekannt und eine gute Annäherung an ein C ++ - Schlüsselwort "friend". Ich finde es ein guter Ersatz.
Ich denke, dass Freundklassen in C ++ wie ein Konzept der inneren Klasse in Java sind. Mit inneren Klassen können Sie tatsächlich eine einschließende und eine eingeschlossene Klasse definieren. Die geschlossene Klasse hat vollen Zugriff auf die öffentlichen und privaten Mitglieder der einschließenden Klasse. Siehe den folgenden Link: http://docs.oracle.com/javase/tutorial/java/javaOO/nested.html
Ich denke, der Ansatz, das Friend-Accessor-Muster zu verwenden, ist viel zu kompliziert. Ich musste mich dem gleichen Problem stellen und löste es mit dem guten, alten Kopierkonstruktor, der aus C ++ bekannt ist, in 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;
}
}
In Ihrer Anwendung können Sie den folgenden Code schreiben:
public class MyApp {
private static class ProtectedAccessor extends ProtectedContainer {
protected ProtectedAccessor() {
super();
}
protected PrivateAccessor(ProtectedContainer prot) {
super(prot);
}
public String exposeProtected() {
return iwantAccess;
}
}
}
Der Vorteil dieser Methode ist, dass nur Ihre Anwendung Zugriff auf die geschützten Daten hat. Es ist nicht gerade eine Ersetzung des Schlüsselworts friend. Aber ich denke, es ist ziemlich gut geeignet, wenn Sie benutzerdefinierte Bibliotheken schreiben und auf geschützte Daten zugreifen müssen.
Wann immer Sie sich mit Instanzen von ProtectedContainer befassen müssen, können Sie Ihren ProtectedAccessor darum wickeln und erhalten Zugriff.
Es funktioniert auch mit geschützten Methoden. Sie definieren sie geschützt in Ihrer API. Später in Ihrer Anwendung schreiben Sie eine private Wrapper-Klasse und machen die geschützte Methode als öffentlich verfügbar. Das ist es.
ProtectedContainer
können Unterklasse außerhalb des Pakets sein!
Wenn Sie auf geschützte Methoden zugreifen möchten, können Sie eine Unterklasse der Klasse erstellen, die Sie verwenden möchten, die die Methoden verfügbar macht, die Sie als öffentlich (oder aus Sicherheitsgründen intern im Namespace) verwenden möchten, und eine Instanz dieser Klasse in Ihrer Klasse haben (Verwenden Sie es als Proxy).
Was private Methoden betrifft (glaube ich), haben Sie kein Glück.
Ich bin damit einverstanden, dass das Schlüsselwort friend in den meisten Fällen nicht erforderlich ist.
Und schließlich, wenn es wirklich notwendig ist, gibt es das in den anderen Antworten erwähnte Friend-Accessor-Muster.
Kein Schlüsselwort oder so verwenden.
Sie könnten mit Reflexion usw. "betrügen", aber ich würde "betrügen" nicht empfehlen.
Eine Methode, die ich zur Lösung dieses Problems gefunden habe, besteht darin, ein Accessor-Objekt wie folgt zu erstellen:
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();
}
}
Der erste Code, der getAccessor()
"Claims Ownership" des Accessors aufruft. Normalerweise ist dies Code, der das Objekt erstellt.
Foo bar = new Foo(); //This object is safe to share.
FooAccessor barAccessor = bar.getAccessor(); //This one is not.
Dies hat auch einen Vorteil gegenüber dem Freundschaftsmechanismus von C ++, da Sie den Zugriff auf Instanzebene und nicht auf Klassenebene beschränken können . Durch Steuern der Zugriffsreferenz steuern Sie den Zugriff auf das Objekt. Sie können auch mehrere Accessoren erstellen und jedem einen unterschiedlichen Zugriff gewähren, wodurch genau gesteuert werden kann, welcher Code auf was zugreifen kann:
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();
}
}
Wenn Sie möchten, dass die Dinge etwas besser organisiert sind, können Sie ein Referenzobjekt erstellen, das alles zusammenhält. Auf diese Weise können Sie alle Accessoren mit einem Methodenaufruf beanspruchen und sie zusammen mit ihrer verknüpften Instanz aufbewahren. Sobald Sie die Referenz haben, können Sie die Accessoren an den Code weitergeben, der sie benötigt:
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();
}
}
Nach vielem Headbangen (nicht die gute Art) war dies meine endgültige Lösung und ich mag es sehr. Es ist flexibel, einfach zu bedienen und ermöglicht eine sehr gute Kontrolle über den Klassenzugriff. (Der Zugriff nur mit Referenz ist sehr nützlich.) Wenn Sie für die Accessoren / Referenzen geschützt statt privat verwenden, können Unterklassen von Foo sogar erweiterte Referenzen von zurückgeben getReference
. Es erfordert auch keine Reflexion, so dass es in jeder Umgebung verwendet werden kann.
Ich bevorzuge Delegation oder Komposition oder Fabrikklasse (abhängig von dem Problem, das zu diesem Problem führt), um zu vermeiden, dass es eine öffentliche Klasse wird.
Wenn es sich um ein Problem mit "Schnittstellen- / Implementierungsklassen in verschiedenen Paketen" handelt, würde ich eine öffentliche Factory-Klasse verwenden, die sich im selben Paket wie das impl-Paket befindet, und die Offenlegung der impl-Klasse verhindern.
Wenn es sich um ein Problem "Ich hasse es, diese Klasse / Methode öffentlich zu machen, nur um diese Funktionalität für eine andere Klasse in einem anderen Paket bereitzustellen" handelt, würde ich eine öffentliche Delegatenklasse im selben Paket verwenden und nur diesen Teil der Funktionalität verfügbar machen benötigt von der "Außenseiter" Klasse.
Einige dieser Entscheidungen hängen von der Klassenladearchitektur des Zielservers (OSGi-Bundle, WAR / EAR usw.), der Bereitstellung und den Konventionen für die Paketbenennung ab. Zum Beispiel ist das oben vorgeschlagene Lösungsmuster "Friend Accessor" für normale Java-Anwendungen clever. Ich frage mich, ob es aufgrund des unterschiedlichen Klassenladestils schwierig wird, es in OSGi zu implementieren.
Ich weiß nicht, ob es für irgendjemanden von Nutzen ist, aber ich habe es folgendermaßen gehandhabt:
Ich habe eine Schnittstelle (AdminRights) erstellt.
Jede Klasse, die diese Funktionen aufrufen kann, sollte AdminRights implementieren.
Dann habe ich eine Funktion HasAdminRights wie folgt erstellt:
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;
}
}
Ich habe einmal eine reflexionsbasierte Lösung gesehen, die zur Laufzeit eine "Freundprüfung" mit Reflektion durchführte und den Aufrufstapel überprüfte, um festzustellen, ob die Klasse, die die Methode aufruft, dazu berechtigt war. Als Laufzeitprüfung hat es den offensichtlichen Nachteil.