开发者

Publicly declare a package private type in a method signature

This is possible in Java:

package x;

public class X {

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

class Y {}

So what's a good reason the Java compiler lets me declare the getY() method as public? What's bothering me is: the class Y is package private, but the accessor getY() declares it in its method signature. But outside of the xpackage, I can only assign the method's results to Object:

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

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

OK. Now I can somehow try to make up an example where this could somehow be explained with method result covariance. But to make things worse, I can also do this:

package x;
开发者_Go百科
public class X {
    public Y getY(Y result) {
        return result;
    }
}

class Y {}

Now I could never call getY(Y result) from outside of the x package. Why can I do that? Why does the compiler let me declare a method in a way that I cannot call it?


A lot of thinking has gone into the design of Java, but sometimes some sub-optimal design just slips through. The famous Java Puzzlers clearly demonstrate that.

Another package can still call the method with the package-private parameter. The easiest way is to pass it null. But it's not because you can still call it, that such a construct really makes sense. It breaks the basic idea behind package-private: only the package itself should see it. Most people would agree that any code that makes use of this construct is at least confusing and just has a bad smell to it. It would have been better not to allow it.

Just as a side note, the fact that it's allowed opens up some more corner cases. For example, from a different package doing Arrays.asList(new X().getY()) compiles, but throws an IllegalAccessError when executing because it tries to create an array of the inaccessible Y class. That just shows that this leaking of inaccessible types doesn't fit into the assumptions the rest of the language design makes.

But, like other unusual rules in Java, it was allowed in the first versions of Java. Because it's not such a big deal, and because backwards compatibility is more important for Java, improving this situation (disallowing it) simply isn't worth it anymore.


First of all, you could call the method. The trivial example is calling it within the same package.

A non trivial example:

package x;
public class Z extends Y {}

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

But that is not the point.

The point is, Java tries to forbid some of the obviously nonsensical designs, but it cannot forbid all.

  1. what is nonsensical? that is very subjective.

  2. if it's too strict, it's bad for development when things are still plastic.

  3. language spec is already way too complicated; adding more rules is beyond human capacity. [1]

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


It is sometimes useful to have public methods that return an instance of a type that is not public, e.g. if this type implements an interface. Often factories work like this:

public interface MyInterface { }

class HiddenImpl implements MyInterface { }

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

Of course one could argue that the compiler could force the return value of createInstance() to be MyInterface in this case. However, there are at least two advantages of allowing it to be HiddenImpl. One is, that HiddenImpl could implement several separate interfaces, and the caller is free to choose as which type it wants to use the return value. The other is that callers from inside the package can use the same method to get an instance of HiddenImpl and use it as such, without the need for casting it or having two methods in the factory (one public MyInterface createInstance() and one package-protected HiddenImpl createPrivateInstance()) that do the same thing.

The reason for allowing something like public void setY(Y param) is similar. There may be public sub-types of Y, and callers from outside the package may pass instances of these types. Again, the same two avantages as above apply here (there may be several such sub-types, and callers from the same package may choose to pass Y instances directly).


A big reason to allow it is to allow for opaque types. Imagine the following scenario:

package x;

public interface Foo;

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

    class Y implements Foo { }
}

Here we have your situation (a package-protected inner class exported through public API), but this makes sense since as far as a caller is concerned the returned Y is an opaque type. That said, IIRC NetBeans does give a warning for this type of behaviour.


Couldn't you do something like:

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

Yes it is ugly and dumb and pointless but you can still do.

That said, I believe your orignal code would produce a compiler warning.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜