한 패키지에 Java 클래스를 작성하여 다른 클래스의 서브 클래스로 만들 필요없이 다른 패키지에있는 클래스의 비공개 메소드에 액세스 할 수 있기를 원합니다. 이게 가능해?
답변
다음은 JAVA에서 C ++ 친구 메커니즘을 복제하는 데 사용하는 작은 트릭입니다.
클래스 Romeo
와 다른 클래스 가 있다고 가정 해 봅시다 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 표준 보안을 설정하면 보안 정책을 작성하여 명시 적으로 허용하지 않는 한 리플렉션을 사용하여 부정 행위를 할 수 없습니다.
답변
‘friend’개념은 예를 들어, API를 구현에서 분리하는 데 Java에서 유용합니다. 구현 클래스는 일반적으로 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);
}
}
답변
귀하의 질문에 대한 모든 솔루션을 동일한 패키지에 유지하는 것을 포함하지 않는 두 가지 솔루션이 있습니다.
첫 번째는 (Practical API Design, Tulach 2008)에 설명 된 Friend Accessor / Friend Package 패턴 을 사용하는 것 입니다.
두 번째는 OSGi를 사용하는 것입니다. OSGi가이를 수행하는 방법을 설명하는 기사가 있습니다 .
답변
내가 아는 한 불가능합니다.
아마도, 당신은 당신의 디자인에 대한 좀 더 자세한 정보를 우리에게 줄 수 있습니다. 이와 같은 질문은 디자인 결함의 결과 일 수 있습니다.
그냥 고려
- 클래스가 서로 밀접하게 관련되어있는 이유는 무엇입니까?
- A가 B의 개인 멤버에 액세스해야합니까, 아니면 조작을 클래스 B로 이동시키고 A에 의해 트리거되어야합니까?
- 이것이 실제로 전화를 걸거나 이벤트 처리가 더 낫습니까?
답변
eirikma의 대답은 쉽고 우수합니다. 공개적으로 접근 가능한 메소드 인 getFriend ()를 사용하여 친구를 사용할 수 없게하는 대신 한 단계 더 나아가서 토큰없이 친구를 얻지 못하게 할 수 있습니다 : getFriend (Service.FriendToken). 이 FriendToken은 개인 생성자가있는 내부 공개 클래스이므로 서비스 만 인스턴스화 할 수 있습니다.
답변
재사용 가능한 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
클래스 따라서이 그 일을 훨씬 더 안전하게 만들고, 개인 생성자가 있습니다.