11

我有以下课程

你好.java

package speak.hello;

import java.util.Map;

import speak.hi.CustomMap;
import speak.hi.Hi;

public class Hello {

    private Hi hi;

    Hello(Hi hi) {
        this.hi = hi;
    }

    public String sayHello() {
        return "Hello";
    }

    public String sayHi() {
        return hi.sayHi();
    }

    public Map<String, Object> getMap() {
        return hi.getMap();
    }

    public void clearMap() {
        hi.getMap().clear();
    }

    public void discardMap() {
        CustomMap map = (CustomMap) hi.getMap();
        map.discard();
    }

    public static void main(String[] args) {
        Hello hello = new Hello(new Hi());
        System.out.println(hello.sayHello());
        System.out.println(hello.sayHi());
        System.out.println(hello.getMap());
        hello.clearMap();
        System.out.println("--");
        hello.discardMap();
    }

}

你好.java

package speak.hi;

import java.util.HashMap;
import java.util.Map;

public class Hi {
    public String sayHi() {
        return "Hi";
    }

    public Map<String, Object> getMap() {
        return new CustomMap<String, Object>();
    }
}

CustomMap.java

package speak.hi;

import java.util.HashMap;

public class CustomMap<K, V> extends HashMap<K, V> {
    private static final long serialVersionUID = -7979398843650044928L;

    public void discard() {
        System.out.println("Discarding Map");
        this.clearCache();
        this.clear();
    }

    @Override
    public void clear() {
        System.out.println("Clearing Map");
        super.clear();
    }

    private void clearCache() {
        System.out.println("Clearing Map");
    }
}

这工作正常,直到我public从中删除访问说明符CustomMap

package speak.hi;

import java.util.HashMap;

class CustomMap<K, V> extends HashMap<K, V> {
    private static final long serialVersionUID = -7979398843650044928L;

    public void discard() {
        System.out.println("Discarding Map");
        this.clearCache();
        this.clear();
    }

    @Override
    public void clear() {
        System.out.println("Clearing Map");
        super.clear();
    }

    private void clearCache() {
        System.out.println("Clearing Map");
    }
}

编译器大喊

speak.hi.CustomMap 类型不可见

现在,如果我没有修改选项speak.hi.CustomMap(第三方 jar 等)。有什么方法我仍然可以使用CustomMapfromspeak.hello.Hello吗?


我知道的一种选择是移动speak.hello.Hellospeak.hi.HelloNow Hello 在包中speak.hi它可以访问包私有类Hi


有没有其他方法可以做到这一点?也许使用反射?


编辑:根据@StephenC 的要求更新了其他详细信息

4

10 回答 10

12

有没有其他方法可以做到这一点?也许使用反射?

是的。如果您的应用程序具有完全权限,则可以使用反射来绕过 Java 访问规则。

例如,private要从不同的类访问对象的字段,您需要:

  • 获取对象的Class对象。
  • 使用Class.getDeclaredField(...)方法获取Field字段的对象。
  • 调用Field.setAccessible(true)以关闭访问检查。
  • 调用Class.getField(object, Field)以获取字段的值(如果是原始类型,则为装箱值)。

如果类本身不可访问,则需要确保在源代码中没有引用类标识符...'因为这将导致编译错误。相反,将其引用分配给(比如说)类型变量Object或其他一些可见超类型的变量,并反射性地对实例执行更具体的操作。


正如您可能想象的那样,这很乏味且容易出错。建议您找到更好的方法,例如:

  • 让课程的供应商解决任何导致您需要打破可见性限制的问题,
  • 让课程的供应商改变他们的知名度,
  • 寻找另一种方式来使用不需要破坏其抽象的类,或者
  • 抛弃它们并找到(或写作)更好的东西。

(一般来说,如果您必须打开一个抽象,那么抽象本身或您使用它的方式有问题。)


最后,我应该补充一点,不受信任的代码(应该)在阻止使用关键反射操作的安全沙箱中运行。

于 2011-11-25T11:13:54.370 回答
7

default以下方法使用反射调用作用域类方法

public void discardMap() {
    //CustomMap map = (CustomMap) hi.getMap();
    //map.discard();
    try {
        Object o =hi.getClass().getMethod("getMap").invoke(hi);
        Method m = o.getClass().getMethod("discard");
        m.setAccessible(true);
        m.invoke(o);
    } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}
于 2012-01-17T15:34:44.113 回答
3

这可能是不可能的,因为:

班级 :

可以从同一个包访问类?

  • 公众:是的
  • 受保护:是
  • 默认:是
  • 私人:没有

可以从不同的包访问类?

  • 公众:是的
  • 受保护:没有
  • 默认值:除非它是子类
  • 私人:没有
于 2011-11-25T11:03:17.063 回答
1

为了完整起见,添加此解决方案。

我知道的一种选择是将 speak.hello.Hello 移动到 speak.hi.Hello 因为现在 Hello 在包 speak.hi 它可以访问包私有类 Hi

package speak.hi;

public class Hello {

    private Hi hi;

    Hello(Hi hi) {
        this.hi = hi;
    }

    public String sayHello() {
        return "Hello";
    }

    public String sayHi() {
        return hi.sayHi();
    }

    public static void main(String[] args) {
        Hello hello = new Hello(new Hi());
        System.out.println(hello.sayHello());
        System.out.println(hello.sayHi());
    }

}
于 2011-11-25T11:52:25.097 回答
1

I don't recommend to use non-API classes as they might change in any future version and can break your code.

How did you find out about this class? Is it an Open Source library?

Try to contact the authors of the library, tell them your use case and find a way with them to offer a public API. If it's an open source library you could help them by providing a patch.

于 2011-11-25T10:55:21.327 回答
0

我认为,如果库的作者没有将特定类作为公共 API 的一部分,那是因为他们不希望其他人使用它。你应该尊重这个决定,即使你可以使用反射来打破它。使用私有 API 简直就是糟糕的编程。

于 2011-11-25T11:17:06.700 回答
0

Not possible. The security model is this : a model to provide security :) If you designed class Hi and delivered it to customers with private access, you wouldn't like them to be able to bypass your restrictions, would you?

于 2011-11-25T10:51:52.237 回答
0

第一个类在包 abcclass1 中,但 class1 是私有的,抽象的第二个类在包 abcclass2 中扩展 class1 但 class2 是公共的

第三类在包 xyzclass3

为了访问第 3 类中的 class1,您可以编写如下内容:-

类 baseClass = (new class2()).getClass(); 并使用其超类的实例,然后使用:- baseClass.getSuperClass(); 并在任何你想要的地方使用它。

但是由于某种原因,基类再次被抽象和私有化,因此不建议这样做,但是这个解决方案再次可以用作解决方法。

于 2015-02-26T15:44:43.137 回答
-1

我认为默认情况下,该类将是“默认”(可以说是私有包),而不是“私有”。因此可以在同一个包中访问它。

此外,您不能在 Java 中将任何*顶级类设为私有。

如果你想让一个类默认并且仍然能够在其他包中访问它,那么拥有访问说明符(修饰符)的目的是什么?

您要么需要公开课程,要么需要移至同一个包。

于 2011-11-25T10:54:06.547 回答
-2

不可能,您不能将您的 Hi 类创建为私有的。

于 2011-11-25T10:53:15.867 回答