8

在我正在编写的程序中,我有一个类RestrictedUser和一个User派生RestrictedUser.自我试图通过强制转换来隐藏用户特定方法的类,RestrictedUser但是当我进行强制转换时,用户方法仍然可用。此外,当我运行调试器时,变量的类型显示为User.

RestrictedUser restricted = regularUser;

在 Java 中进行强制转换是否隐藏了子类方法和字段,还是我做错了什么?有解决方法吗?

谢谢

4

10 回答 10

17

如果您要尝试运行此代码:

User user = new User(...);
RestrictedUser restricted = user;
restricted.methodDefinedInTheUserClass(); // <--

你会得到一个编译错误。这在任何方式、形状或形式上都不会是安全的,因为即使您要传递RestrictedUser到另一个方法,例如,该方法也可以这样做:

if (restricted instanceof User) {
    User realUser = (User)restricted;
    realUser.methodDefinedInTheUserClass(); // !!
}

并且您的“受限制”用户不再受限制。

对象在调试器中显示为User对象,因为它是User对象,即使对象引用存储在RestrictedUser变量中。基本上,将子类的实例放在具有父类类型的变量中确实会“隐藏”子类的方法和字段,但不安全。

于 2009-04-03T04:23:59.753 回答
3

我不确定你到底在问什么,因为你使用的术语有点不清楚,但这里有。如果您有一个超类和一个子类,您可以通过将超类中的方法设置为私有来对子类隐藏超类中的方法。如果您需要超类上的公共方法不可见,那么您就不走运了。如果将子类转换为超类,则子类上的公共方法将不再可见。

一个可能对您有所帮助的建议是使用组合而不是继承,将敏感方法提取到单独的类中,然后根据需要将该类的适当实例插入到对象中。如果需要,您可以将类中的方法委托给注入的类。

于 2009-04-03T04:23:32.597 回答
3

您混淆了静态类型和动态类型。

静态类型是引用的类型。当您从 Derived 向上转换为 Base 时,您是在告诉编译器,据您所知,指向的东西是 Base。也就是说,您承诺指向的对象要么是 null,要么是 Base,或者是从 Base 派生的东西。也就是说,它有Base的公共接口。

然后您将无法调用在 Derived(而不是在 Base)中声明的方法,但是当您调用任何被 Derived 覆盖的 Base 方法时,您将获得 Derived 的覆盖版本。

于 2009-04-03T04:24:14.153 回答
3

如果需要保护子类,可以使用委托模式:

public class Protect extends Superclass {  // better implement an interface here
  private final Subclass subclass;

  public Protect(Subclass subclass) {
    this.subclass = subclass;
  }

  public void openMethod1(args) {
    this.subclass.openMethod1(args);
  }

  public int openMethod2(args) {
    return this.subclass.openMethod2(args);
  }
  ...
}

也可以考虑使用java.lang.reflect.Proxy
[]]

于 2009-04-03T08:58:14.633 回答
2

彼得的回答约翰的回答是正确的:如果对象的真实类型(客户端代码可以转换为,调用反射方法等)仍然可用,则仅转换静态类型将无济于事。您必须以某种方式掩盖真实类型(正如彼得的回答所提到的)。

实际上有一个这样做的模式:如果您可以访问 JDK 源代码,请查看类中的unconfigurable*方法。Executors

于 2009-04-03T04:52:11.810 回答
2

爪哇

在 Java 中,您永远无法从对象中“隐藏”某些内容。编译器可能会忘记您对特定实例的详细了解。(比如它是什么子类)调试器比编译器知道的多得多,所以它会告诉你当前实例是什么实际类型,这可能是你正在经历的。

面向对象

听起来您正在编写需要知道您正在使用的对象类型的逻辑,这在 OOP 中不推荐,但出于实际原因通常需要。

限制形容词

正如我在对该问题的评论中所说,您应该清楚这里的基类是什么。直觉上 RestrictedUser 应该是 User 的子类,因为名称暗示了一种更专业的类型。(名称上有一个额外的活动形容词。)在你的情况下,这很特别,因为这是一个限制形容词,它可以让你把 User 作为 RestrictedUser 的子类完全没问题。我建议将两者重命名为:BasicUser/UserWithId 和 NonRestrictedUser 以避免限制形容词,这是令人困惑的部分。

于 2009-04-03T12:24:37.400 回答
1

另外,也不要认为可以在匿名类中隐藏方法。例如,这不会提供您期望的隐私:

Runnable run = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello, world!");
    }

    public void secretSquirrel() {
        System.out.println("Surprise!");
    }
}

您可能无法命名的真实类型run(以便直接转换为),但您仍然可以使用 获取其类getClass(),并且可以secretSquirrel使用反射调用。(即使secretSquirrel是私有的,如果你没有SecurityManager,或者如果它被设置为允许可访问性,反射也可以调用私有方法。)

于 2009-04-03T05:00:12.787 回答
0

我认为您可能做出了一个糟糕的命名选择:我认为RestrictedUser将是 的子类User,而不是相反,所以我将UnrestrictedUser用作子类。

如果将 an 向上UnrestrictedUser转换为 a RestrictedUser,则您的引用将只能访问在 中声明的方法RestrictedUser。但是,如果您将其向下转换为UnrestrictedUser,您将可以访问所有方法。UnrestrictedUser由于 Java 对象知道它们真正是什么类型,因此无论您使用什么类型的引用(甚至是) ,任何实际上是 an 的东西都可以始终转换回它Object。有不可避免的和语言的一部分。但是,您可以将其隐藏在某种代理后面,但在您的情况下,这可能会违背目的。

此外,在 Java 中,所有非静态方法默认都是虚拟的。这意味着如果UnrestrictedUser覆盖在 中声明的某些方法RestrictedUser,则将使用该UnrestrictedUser版本,即使您通过RestrictedUser引用访问它也是如此。如果需要调用父类版本,可以通过调用来进行非虚调用RestrictedUser.variableName.someMethod()。但是,您可能不应该经常使用它(最不重要的原因是它破坏了封装)。

于 2009-04-03T12:56:26.653 回答
0

我也认为这个问题引出了另一个问题。你打算如何调用这个方法?似乎有一个类层次结构,其中子类化引入新的方法签名将需要在调用者中进行 instanceof 检查。

您能否更新您的问题以包含一个您会调用这些方法的场景?也许我们能提供更多帮助。

于 2009-04-03T13:20:12.290 回答
0

John Calsbeek 给出了技术答案。我想考虑设计问题。您要做的是阻止某些代码段或某些开发人员了解有关 User 类的任何信息。您希望他们将所有用户视为 RestrictedUser。

您这样做的方式是使用权限。您需要防止有问题的开发人员访问 User 类。您可以通过将其放入包中并限制对包的访问来做到这一点。

于 2009-04-03T16:56:52.487 回答