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


当前回答

从Java 9开始,可以使用模块在许多情况下解决这个问题。

其他回答

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

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

NR指出,你可以使用反射来作弊,但即使这样也只在你没有使用SecurityManager的情况下有效。如果您启用了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中,有可能具有“包相关的友好性”。 这对于单元测试很有用。 如果你在一个方法前没有指定private/public/protected,它将是“friend in the package”。 同一个包中的类将能够访问它,但它在类外部是私有的。

这个规则并不总是为人所知,它很好地近似于c++的“朋友”关键字。 我觉得这是一个很好的替代品。

例如,“朋友”概念在Java中很有用,可以将API与其实现分开。实现类通常需要访问API类的内部内容,但这些内容不应该公开给API客户端。这可以使用“朋友访问器”模式来实现,如下所示:

通过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();
        }
    }
}

提供'friend'功能的类:

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

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

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