9

这在 Java 中是可能的:

package x;

public class X {

    // How can this method be public??
    public Y getY() {
        return new Y();
    }
}

class Y {}

那么,Java 编译器让我将getY()方法声明为的充分理由是public什么?困扰我的是:该类Y是包私有的,但访问器getY()在其方法签名中声明了它。但是在x包之外,我只能将方法的结果分配给Object

// OK
Object o = new X().getY();

// Not OK:
Y y = new X().getY();

好的。现在我可以尝试编造一个例子,用方法结果协方差来解释这个例子。但更糟糕的是,我也可以这样做:

package x;

public class X {
    public Y getY(Y result) {
        return result;
    }
}

class Y {}

现在我再也不能getY(Y result)x包裹外面打电话了。为什么我可以这样做?为什么编译器让我以我无法调用的方式声明一个方法?

4

5 回答 5

6

Java 的设计已经进行了很多思考,但有时一些次优的设计会漏掉。著名的 Java Puzzlers 清楚地证明了这一点。

另一个包仍然可以使用 package-private 参数调用该方法。最简单的方法是通过它null。但这并不是因为你仍然可以调用它,所以这样的构造真的有意义。它打破了 package-private 背后的基本思想:只有包本身才能看到它。大多数人都会同意,任何使用这种结构的代码至少是令人困惑的,只是有一种难闻的气味。最好不要允许它。

顺便说一句,它被允许的事实开辟了更多的极端案例。例如,从不同的包进行Arrays.asList(new X().getY())编译,但在执行时抛出 IllegalAccessError,因为它试图创建一个不可访问的 Y 类的数组。这只是表明这种不可访问类型的泄漏不符合语言设计的其余部分所做的假设

但是,就像 Java 中其他不寻常的规则一样,它在 Java 的第一个版本中是允许的。因为这没什么大不了的,而且因为向后兼容性对 Java 来说更重要,所以改善这种情况(禁止它)根本不值得了。

于 2011-03-10T22:16:57.123 回答
2

首先,您可以调用该方法。一个简单的例子是在同一个包中调用它。

一个不平凡的例子:

package x;
public class Z extends Y {}

package x2;
    x.getY( new Z() ); // compiles

但这不是重点。

关键是,Java 试图禁止一些明显荒谬的设计,但它不能全部禁止。

  1. 什么是荒谬的?这是非常主观的。

  2. 如果它太严格,那么当事物仍然是可塑的时候,对发展不利。

  3. 语言规范已经太复杂了;添加更多规则超出了人类的能力。[1]

[1] http://java.sun.com/docs/books/jls/third_edition/html/names.html#6.6

于 2011-03-10T21:32:56.890 回答
2

有时使用返回非 类型实例的公共方法很有用public,例如,如果该类型实现了接口。工厂通常是这样工作的:

public interface MyInterface { }

class HiddenImpl implements MyInterface { }

public class Factory {
    public HiddenImpl createInstance() {
        return new HiddenImpl();
     }
}

当然,有人可能会争辩说,在这种情况下,编译器createInstance()可能会强制返回 的值。MyInterface但是,允许它至少有两个优点HiddenImpl。一种是,它HiddenImpl可以实现几个独立的接口,调用者可以自由选择它想要使用哪种类型的返回值。另一个是来自包内部的调用者可以使用相同的方法来获取 HiddenImpl 的实例并按原样使用它,而无需强制转换它或在工厂中有两个方法(一个 publicMyInterface createInstance()和一个 package-protected HiddenImpl createPrivateInstance())一样的东西。

允许类似的原因public void setY(Y param)是相似的。可能有 的公共子类型Y,并且来自包外部的调用者可能会传递这些类型的实例。再次,与上面相同的两个优点在这里适用(可能有几个这样的子类型,来自同一个包的调用者可以选择Y直接传递实例)。

于 2011-03-10T21:37:32.197 回答
1

允许它的一个重要原因是允许不透明类型。想象以下场景:

package x;

public interface Foo;

class X
{
    public Foo getFoo ( )
    {
        return new Y( );
    }

    class Y implements Foo { }
}

在这里,我们遇到了您的情况(通过公共 API 导出的受包保护的内部类),但这是有道理的,因为就调用者而言,返回的 Y 是不透明类型。也就是说,IIRC NetBeans 确实对这种行为发出了警告。

于 2011-03-10T19:59:41.487 回答
1

你不能做这样的事情:

Object answer = foo1.getY();  
foo2.setY(  foo1.getY().getClass().cast(answer)  );

是的,它丑陋、愚蠢、毫无意义,但你仍然可以这样做。

也就是说,我相信您的原始代码会产生编译器警告。

于 2011-03-10T21:03:54.763 回答