我希望能够在一个包中编写一个Java类,它可以访问另一个包中类的非公共方法,而不必使它成为另一个类的子类。这可能吗?


当前回答

提供的解决方案可能不是最简单的。另一种方法基于与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();
    }
}

最后:Entity类,它仅对类application.service.Service提供对包私有成员的友好访问。

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复制c++的朋友机制。

假设我有一个类罗密欧和另一个类朱丽叶。因为仇恨的原因,他们在不同的包裹(家庭)里。

罗密欧想要拥抱朱丽叶,而朱丽叶只想让罗密欧拥抱她。

在c++中,朱丽叶会宣布罗密欧是(恋人)朋友,但在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?");
    }

}

这就是朱丽叶的方法。拥抱是公开的,但你需要一个罗密欧。我喜欢这么称呼它。它用的是罗密欧。Love作为“签名安全”,以确保只有Romeo可以调用此方法并检查爱是否真实,以便运行时在它为空时抛出NullPointerException。

现在,孩子们:

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);
    }
}

罗密欧班。爱是公开的,但它的创造者却是私人的。因此,任何人都可以看到它,但只有罗密欧才能建造它。我用了一个静态的参考,所以罗密欧。从未使用过的爱只会被构建一次,不会影响优化。

因此,罗密欧可以拥抱朱丽叶,而且只有他可以,因为只有他可以构建和接近一个罗密欧。Love实例,Juliet需要它来拥抱她(否则她会用NullPointerException打你)。

据我所知,这是不可能的。

也许,你可以给我们更多关于你的设计的细节。像这样的问题很可能是设计缺陷造成的。

只考虑

如果这些类如此密切相关,为什么它们在不同的包中? A是否访问B的私有成员,或者操作是否应该移动到类B并由A触发? 这是真正的调用还是事件处理更好?

Java的设计者明确地拒绝了朋友的概念,因为它在c++中是有效的。你把你的"朋友"放在同一个包裹里。私有的、受保护的和打包的安全性是作为语言设计的一部分强制执行的。

James Gosling希望Java成为没有错误的c++。我相信他觉得这个朋友是一个错误,因为它违反了面向对象的原则。包提供了一种合理的方式来组织组件,而不会对OOP过于纯粹。

NR指出,你可以使用反射来作弊,但即使这样也只在你没有使用SecurityManager的情况下有效。如果您启用了Java标准安全性,那么您将无法通过反射进行欺骗,除非您编写了专门允许反射的安全策略。

Eirikma的回答简单而出色。我可能还要补充一点:与其使用一个公共可访问的方法getFriend()来获取一个不能使用的朋友,不如更进一步,不允许获取没有令牌的朋友:getFriend(Service.FriendToken)。这个FriendToken将是一个具有私有构造函数的内部公共类,因此只有Service可以实例化一个。

如果你想访问受保护的方法,你可以创建一个你想要使用的类的子类,它将你想要使用的方法公开为公共的(或者是更安全的命名空间内部的),并在你的类中有一个类的实例(将它用作代理)。

就私人方法而言(我认为)你运气不好。