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


当前回答

这里有一个小技巧,我用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打你)。

其他回答

例如,“朋友”概念在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);
    }
}

我更喜欢委托、组合或工厂类(取决于导致这个问题的问题),以避免将其设置为公共类。

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

如果这是一个“我讨厌让这个类/方法公开,只是为了在不同的包中为其他类提供这个功能”的问题,那么我会在同一个包中使用一个公共委托类,并且只公开“外部”类所需的部分功能。

其中一些决策是由目标服务器类加载体系结构(OSGi包、WAR/EAR等)、部署和包命名约定驱动的。例如,上面提出的解决方案“朋友访问器”模式对于普通的java应用程序非常聪明。我想知道在OSGi中实现它是否会因为类加载风格的不同而变得棘手。

没有使用关键字左右。

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

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

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

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

相关问题:1、2和3。

下面是一个具有可重用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类有一个私有构造函数,因此这是一种更安全的方式。