在方法签名中公开声明包私有类型

时间:2011-03-10 19:49:15

标签: java compiler-construction syntax package-private

这在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。我为什么这样做? 为什么编译器让我以一种我无法调用的方式声明方法?

5 个答案:

答案 0 :(得分:6)

Java的设计已经有很多想法,但有时候一些次优设计才会滑落。着名的Java Puzzlers清楚地证明了这一点。

另一个包仍然可以使用package-private参数调用该方法。最简单的方法是传递它null。但这不是因为你仍然可以称之为,这样的结构确实有意义。 它破坏了package-private背后的基本理念:只有包本身应该看到它。大多数人会同意任何使用这种结构的代码至少令人困惑,只是有一种难闻的气味。 最好不要允许它。

正如旁注,允许这样做的事实会打开更多的角落案例。例如,从执行Arrays.asList(new X().getY())编译的不同包中,但在执行时抛出IllegalAccessError,因为它尝试创建不可访问的Y类的数组。这只是表明这种无法访问的类型的泄漏不符合其他语言设计所做的假设

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

答案 1 :(得分:2)

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

一个非常重要的例子:

package x;
public class Z extends Y {}

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

但这不是重点。

重点是,Java试图禁止一些明显荒谬的设计,但它不能禁止所有。

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

  2. 如果它太严格,那么当物品仍然是塑料的时候,这对于开发是不利的。

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

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

答案 2 :(得分:2)

有些公共方法可以返回非public类型的实例,例如,有时很有用。如果此类型实现接口。工厂经常这样工作:

public interface MyInterface { }

class HiddenImpl implements MyInterface { }

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

当然有人可能会争辩说,在这种情况下,编译器可能会强制createInstance()的返回值为MyInterface。但是,允许HiddenImpl至少有两个优点。一个是,HiddenImpl可以实现几个单独的接口,并且调用者可以自由选择它想要使用返回值的类型。另一个是来自包内的调用者可以使用相同的方法来获取HiddenImpl的实例并使用它,而无需在工厂中进行转换或使用两个方法(一个公共MyInterface createInstance()和一个包保护HiddenImpl createPrivateInstance())做同样的事情。

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

答案 3 :(得分:1)

允许它的一个重要原因是允许opaque types。想象一下以下场景:

package x;

public interface Foo;

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

    class Y implements Foo { }
}

这里我们了解你的情况(通过公共API导出的受包保护的内部类),但这是有道理的,因为就调用者而言,返回的Y是一个opaque类型。也就是说,IIRC NetBeans确实对此类行为发出警告。

答案 4 :(得分:1)

你不能做类似的事情:

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

是的,它是丑陋的,愚蠢无知的,但你仍然可以做到。

那就是说,我相信你的orignal代码会产生编译器警告。