Publicly declare a package private type in a method signature
Asked Answered
K

5

9

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;

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?

Kussell answered 10/3, 2011 at 19:49 Comment(8)
We don't know what was in the brains of the Java language designers. For all we know, they never thought of it, and didn't find it important enough to fix if it ever came up later.Broderick
@bmargulies: Well if that is true, then that's an answer as well. Once it's in the specs, you can't remove it again, or you'll break plenty of code. I was just wondering if there is a reason, such as some sort of covariance or whatever...Kussell
@??? who wants to close it? It's a real question. Why does the compiler let me do something that leads to unusable methods? There I'll update the question like that...Kussell
If the question is 'why did the human beings who speced Java decide to permit this', then this forum can't answer it. They can. Unless they've published an explanation, in which perhaps Google can.Broderick
You're assuming that you are right and no one can easily find out about this. But maybe we're just overlooking something very interesting. I think it's an interesting question and I don't expect it to be answered today. Maybe in 2-3 months, someone will stumble upon this and know the answer. I didn't think that stackoverflow was just for easy questions? Because the easy questions can always be answered on Google, not the hard ones... Unless you can show me the Google query to find such an explanation? :-)Kussell
No, I'm proposing that this question falls under a long-standing convention for questions like this. And unless four other people agree with me, that's where it will sit.Broderick
I confess, I have a strong knee-jerk reaction to questions that try to read people's minds. I think that you have a point that it is unfair to characterize your question under that heading, which is why it's a good thing that I only get one vote.Broderick
You are excused! :-) The good answers did come in a lot earlier than I expected... After all, this still is a high quality forum, also for tough ones.Kussell
O
6

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.

Ohg answered 10/3, 2011 at 22:16 Comment(4)
+1 Very nice answer. Interesting observation about the Arrays.asList(...) construct!Kussell
Heh. Slightly edited the description... I was trying to give a short example of a bug I reported long ago (sun bug 6495506) that has already been fixed, but accidentally came up with another variant that's still there.Ohg
Hmm, that's very interesting. So we can really say that it shouldn't be possible to declare my getY() method public, but also that this isn't going to be fixed...Kussell
The asList() example is a javac bug. per 15.12.4.2 it shouldn't compile. The getY() method can't be responsible.Cummins
C
2

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

Cummins answered 10/3, 2011 at 21:32 Comment(1)
+1! Thanks man. The non-trivial example makes sense. I completely missed that. In any case, it makes my example just a little less nonsensical. The reason I came up with this question is because I found this very example in my own code (with more meaningful names than X, Y, of course) and I was just astonished. I didn't even mean to write it that way, though...Kussell
E
2

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).

Eaglewood answered 10/3, 2011 at 21:37 Comment(1)
+1: Nice answer, see also irreputable's answer. It makes sense, I didn't think of the possibility of having public subtypes of YKussell
W
1

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.

Wardroom answered 10/3, 2011 at 19:59 Comment(3)
Hmm... here, Foo is a public type. The public method has a public type in its signature, which is entirely OK. But in my case, a public method has a package private or opaque type in its signature. I don't understand, how that can be OK.Kussell
@Lukas: to my way of thinking, it's largely the same. I agree: using a public type in the signature is a much better/cleaner approach, but the language allows you to do it without the public type if you wish.Wardroom
OK. But we still don't know why that is so... (i.e. why the language designers didn't restrict these things)Kussell
B
1

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.

Bedspring answered 10/3, 2011 at 21:3 Comment(3)
Did you turn them on? I know have seen a warning for "public method exposes non-public API" but perhaps that was from my IDE then...Bedspring
Hmm, I turned on pretty much all the possible warnings in my Eclipse Helios, I don't have anything like that. Would be nice though! What are you using?Kussell
NetBeans netbeans.org Also open-source and I very much prefer it; YMMV.Bedspring

© 2022 - 2024 — McMap. All rights reserved.