有没有办法在 Java 中模拟 C++“朋友”概念?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/182278/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 10:59:37  来源:igfitidea点击:

Is there a way to simulate the C++ 'friend' concept in Java?

javac++friendaccessor

提问by Matthew Murdoch

I would like to be able to write a Java class in one package which can access non-public methods of a class in another package without having to make it a subclass of the other class. Is this possible?

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

采纳答案by Matthew Murdoch

The 'friend' concept is useful in Java, for example, to separate an API from its implementation. It is common for implementation classes to need access to API class internals but these should not be exposed to API clients. This can be achieved using the 'Friend Accessor' pattern as detailed below:

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

The class exposed through the 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();
        }
    }
}

The class providing the 'friend' functionality:

提供“朋友”功能的类:

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

Example access from a class in the 'friend' implementation package:

从“朋友”实现包中的类访问的示例:

package impl;

public final class FriendlyAccessExample {
    public static void main(String[] args) {
        Accessor accessor = Accessor.getInstance();
        Exposed exposed = accessor.createExposed();
        accessor.sayHello(exposed);
    }
}

回答by NR.

Not using a keyword or so.

不使用关键字左右。

You could "cheat" using reflection etc., but I wouldn't recommend "cheating".

您可以使用反射等“作弊”,但我不建议“作弊”。

回答by Black

As far as I know, it is not possible.

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

Maybe, You could give us some more details about Your design. Questions like these are likely the result of design flaws.

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

Just consider

只是考虑

  • Why are those classes in different packages, if they are so closely related?
  • Has A to access private members of B or should the operation be moved to class B and triggered by A?
  • Is this really calling or is event-handling better?
  • 为什么这些类在不同的包中,如果它们如此密切相关?
  • A 是否可以访问 B 的私有成员,还是应该将操作移到 B 类并由 A 触发?
  • 这真的是调用还是事件处理更好?

回答by Omar Kooheji

If you want to access protected methods you could create a subclass of the class you want to use that exposes the methods you want to use as public (or internal to the namespace to be safer), and have an instance of that class in your class (use it as a proxy).

如果要访问受保护的方法,可以创建要使用的类的子类,该子类公开要用作公共的方法(或命名空间内部以确保安全),并在类中有该类的实例(将其用作代理)。

As far as private methods are concerned (I think) you are out of luck.

就私有方法而言(我认为),您不走运。

回答by David G

The designers of Java explicitly rejected the idea of friend as it works in C++. You put your "friends" in the same package. Private, protected, and packaged security is enforced as part of the language design.

Java 的设计者明确拒绝了朋友的想法,因为它适用于 C++。你把你的“朋友”放在同一个包裹里。私有的、受保护的和打包的安全性作为语言设计的一部分被强制执行。

James Gosling wanted Java to be C++ without the mistakes. I believe he felt that friend was a mistake because it violates OOP principles. Packages provide a reasonable way to organize components without being too purist about OOP.

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

NR pointed out that you could cheat using reflection, but even that only works if you aren't using the SecurityManager. If you turn on Java standard security, you won't be able to cheat with reflection unless you write security policy to specifically allow it.

NR 指出您可以使用反射作弊,但即使这样也只有在您不使用 SecurityManager 时才有效。如果您打开 Java 标准安全性,除非您编写安全策略来明确允许反射,否则您将无法通过反射进行欺骗。

回答by Ran Biron

I once saw a reflection based solution that did "friend checking" at runtime using reflection and checking the call stack to see if the class calling the method was permitted to do so. Being a runtime check, it has the obvious drawback.

我曾经看到一个基于反射的解决方案,它在运行时使用反射进行“朋友检查”并检查调用堆栈以查看是否允许调用该方法的类这样做。作为运行时检查,它有明显的缺点。

回答by Matthew Murdoch

I prefer delegation or composition or factory class (depending upon the issue that results in this problem) to avoid making it a public class.

我更喜欢委托或组合或工厂类(取决于导致此问题的问题)以避免使其成为公共类。

If it is a "interface/implementation classes in different packages" problem, then I would use a public factory class that would in the same package as the impl package and prevent the exposure of the impl class.

如果是“不同包中的接口/实现类”问题,那么我会使用一个公共工厂类,它会与impl包在同一个包中,并防止impl类暴露。

If it is a "I hate to make this class/method public just to provide this functionality for some other class in a different package" problem, then I would use a public delegate class in the same package and expose only that part of the functionality needed by the "outsider" class.

如果是“我讨厌公开此类/方法只是为了为不同包中的其他类提供此功能”问题,那么我将在同一包中使用公共委托类并仅公开该部分功能“局外人”阶级所需要的。

Some of these decisions are driven by the target server classloading architecture (OSGi bundle, WAR/EAR, etc.), deployment and package naming conventions. For example, the above proposed solution, 'Friend Accessor' pattern is clever for normal java applications. I wonder if it gets tricky to implement it in OSGi due to the difference in classloading style.

其中一些决定是由目标服务器类加载架构(OSGi 包、WAR/EAR 等)、部署和包命名约定驱动的。例如,上面提出的解决方案,'Friend Accessor' 模式对于普通的 Java 应用程序来说很聪明。我想知道由于类加载风格的不同,在 OSGi 中实现它是否会变得棘手。

回答by eirikma

The provided solution was perhaps not the simplest. Another approach is based on the same idea as in C++: private members are not accessible outside the package/private scope, except for a specific class that the owner makes a friend of itself.

提供的解决方案可能不是最简单的。另一种方法基于与 C++ 相同的想法:私有成员不能在包/私有范围之外访问,除了所有者与自己成为朋友的特定类。

The class that needs friend access to a member should create a inner public abstract "friend class" that the class owning the hidden properties can export access to, by returning a subclass that implement the access-implementing methods. The "API" method of the friend class can be private so it is not accessible outside the class that needs friend access. Its only statement is a call to an abstract protected member that the exporting class implements.

需要对成员进行友元访问的类应该创建一个内部公共抽象“友元类”,拥有隐藏属性的类可以通过返回实现访问实现方法的子类来导出访问权。友元类的“API”方法可以是私有的,因此在需要友元访问的类之外无法访问它。它唯一的语句是对导出类实现的抽象受保护成员的调用。

Here's the code:

这是代码:

First the test that verifies that this actually works:

首先是验证这确实有效的测试:

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

Then the Service that needs friend access to a package private member of 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();
    }
}

Finally: the Entity class that provides friendly access to a package private member only to the class application.service.Service.

最后: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();
            }
        };
    }
}

Okay, I must admit it is a bit longer than "friend service::Service;" but it might be possible to shorten it while retaining compile-time checking by using annotations.

好吧,我必须承认它比“朋友服务::服务;”要长一些。但是可以通过使用注释在保留编译时检查的同时缩短它。

回答by daitangio

In Java it is possible to have a "package-related friendness". This can be userful for unit testing. If you do not specify private/public/protected in front of a method, it will be "friend in the package". A class in the same package will be able to access it, but it will be private outside the class.

在 Java 中,可能具有“与包相关的友好性”。这对于单元测试很有用。如果没有在方法前指定private/public/protected,它将是“包中的朋友”。同一个包中的类将能够访问它,但它在类之外是私有的。

This rule is not always known, and it is a good approximation of a C++ "friend" keyword. I find it a good replacement.

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

回答by Jeff Axelrod

There are two solutions to your question that don't involve keeping all classes in the same package.

您的问题有两种解决方案,它们不涉及将所有类保留在同一个包中。

The first is to use the Friend Accessor/Friend Packagepattern described in (Practical API Design, Tulach 2008).

第一种是使用(Practical API Design, Tulach 2008)中描述的朋友访问器/朋友包模式。

The second is to use OSGi. There is an article hereexplaining how OSGi accomplishes this.

二是使用OSGi。有文章在这里解释的OSGi是如何实现这一点。

Related Questions: 1, 2, and 3.

相关问题:123