10

Is it really impossible to hide some classes in a jar file?

I wanted not to allow direct instantiation of the classes to keep it more flexible. Only the factory (or a facade) should be visible of this jar.

Is there any other way than solve this problem than creating two projects? (Two projects: the first one contains the classes (implementation) and the other one references to the first one and contains the factory; later only the second one will be referenced)

om-nom-nom
  • 60,231
  • 11
  • 174
  • 223
nrainer
  • 2,383
  • 1
  • 18
  • 33
  • 1
    I'm not sure what you mean by "hiding". Jar files are just .zip files, so anyone can open them and see your class files inside them. – jonescb Jan 21 '11 at 14:07
  • I just want to avoid that the classes can be used outside (by accident) for instantiation. – nrainer Jan 21 '11 at 14:16
  • 1
    I do think you should trust the user of your classes and provide decent enough documentation to what to use and what not to. There's no way you can prevent all the mistakes developers using your jars can come up with anyway. One alternative of course is just to make your own constructors so complex no-one can understand them and will fall back naturally to the factory classes :D – Mikko Wilkman Jan 21 '11 at 14:25
  • Also I don't think there's point in adding this kind of flexibility until it really is used for something. And just for flexibility, maybe you could look into some Inversion of Control frameworks like Spring (or something more lightweight depending on your needs). – Mikko Wilkman Jan 21 '11 at 14:28

8 Answers8

5

I'm understanding you're not looking to hide the actual classes, just prevent their construction outside a factory class. This I think can be quite easily achieved by using package private (default) visibility in the class constructors. The only limitation is that you'll need to have the classes and the factory in the same package so in a medium to large codebase things may get unnecessarily complex.

Mikko Wilkman
  • 1,435
  • 10
  • 8
  • 4
    Oh actually, this way anyone can still call your constructors by creating their own factory classes and placing them in the same package.. Maybe it would be better to start with _why_ you want to hide these things? – Mikko Wilkman Jan 21 '11 at 14:21
  • 1
    You got it. But the limitation is exactly the problem. There are many classes and I really don't want to put all in one package. – nrainer Jan 21 '11 at 14:21
  • 1
    @ Mikko: To your comment: That could be solved by sealing the package. – nrainer Jan 21 '11 at 14:22
4

If I understand your question correctly, you would like to make sure that users of your library are forced to use your factory to instantiate their objects rather than using the constructors themselves.

As I see it there are two possibilities, one of which is silly but usable in few, specific cases, and the other one is the most practical and probably most commonly used way of doing it.

  1. You could make all your classes into private inner classes of the factory. This would work if you had one factory per class, but is hardly workable if you have a lot of different classes being managed through one factory.
  2. You could use the protected access modifier to restrict access to your class constructors. This is common practice when using the factory pattern.
Mia Clarke
  • 7,802
  • 3
  • 45
  • 61
  • The first one is - as you said - silly. The second one has the problem that the factory has to be in the same package. – nrainer Jan 21 '11 at 14:23
  • @nrainer :) I've used it in cases when you have a factory for just one single class with many instantiation options, but other than in that specific case, it is not advisable. – Mia Clarke Jan 21 '11 at 14:25
  • I find this answer perfectly suiting my requirement when i want to hide the complete business logic of my library from the consuming application. – Gurunath Sripad May 06 '20 at 16:38
3

I think you will have either compiler failure or warning if your public factory method try to return something which is "hidden".

No, you can not hide a public class without reimplementing your own ClassLoader or using OSGi or anything similar.

What you can do is to separate interface api from the implementation, e.g. have one project which contains only the interfaces and another porject which contains the implmentations. However, you still cannot hide the implementation classes.

gigadot
  • 8,743
  • 7
  • 33
  • 49
  • Thats what I meant by creating two projects. I think I will do that even if it it increases the number of projects. – nrainer Jan 21 '11 at 14:35
2

Obfuscation can help you somehow.

jmj
  • 225,392
  • 41
  • 383
  • 426
2

With standard classloaders and plain old jar files, this is not possible. OSGi has this concept of making visible only some packages to another bundle(i.e. separation of public api and internal implementation).

If you are using eclipse, you may enforce such rules with this

Gurunath Sripad
  • 487
  • 1
  • 6
  • 14
lweller
  • 10,533
  • 3
  • 31
  • 37
1

If I understand you correctly when you say "not to allow direct instantiation of the classes to keep it more flexible", a properly executed facade pattern will handle this.

Restrict the constructors of all the classes you want to hide to package scope. Open the facade class to public scope.

http://mindprod.com/jgloss/packagescope.html

"If you have a variable or method in your class that you don’t want clients of your class directly accessing, don’t give it a public, protected or private declaration. Due to an oversight in the design of Java, you can’t explicitly declare the default “package” accessibility. Other members of the package will be able to see it, but classes outside the package that inherit from yours, won’t. The protected accessibility attribute offers slightly more visibibily. A protected method is visible to inheriting classes, even not part of the same package. A package scope (default) method is not. That is the only difference between protected and package scope. "

Speck
  • 2,109
  • 1
  • 19
  • 29
  • Unfortunately then the facade has to be in the same package. As there are many classes, I don't want that. – nrainer Jan 21 '11 at 14:31
  • I haven't experimented, but you should be able to put you're facade class in one package and have your implementing classes in children packages. That way the facade and implementing classes are all in the same package and you get the organization of having many packages. – Speck Jan 21 '11 at 15:00
  • 1
    As far as I know pck1.subPck1 has nothing to do with pck1. Therefore I don't think it will work. (The namespaces in C# would allow that. Anyway, if I used C# I would not have this problem because of the internal modifier which would solve my problem.) – nrainer Jan 21 '11 at 15:21
  • +1 this seems to be the easiest way. just simply do 'String str;' instead of 'public/private/protected String str;' – greenhouse Feb 19 '15 at 22:40
1

There are two solutions to your question that don't involve keeping all classes in the same package.

The first is to use the Friend Accessor/Friend Package pattern described in (Practical API Design, Tulach 2008).

The second is to use OSGi. There is an article here explaining how OSGi accomplishes this.

Related Questions: 1, 2, 3, and 4.

Community
  • 1
  • 1
Jeff Axelrod
  • 25,625
  • 29
  • 137
  • 239
0

You can do such magics with a custom class loader but:

  • the correct separation will be available only in a project staffed with your class loader;
  • it's really doubtful that the effort to create such loader is worthy.

In such situations I would do something similar to what we may see in the standard Java. E.g.you see javax.xml.stream.XMLInputFactory but somewhere you have com.sun.xml.internal.stream.XMLInputFactoryImpl. It is perfectly compilable if you write:

new com.sun.xml.internal.stream.XMLInputFactoryImpl()

though you will hardly do it :-) With a system property you may control the actual implementation that is being loaded. To me such approach is fine in many situations.

I hope I have understood your question correctly ;)

Cheers!

Lachezar Balev
  • 10,090
  • 7
  • 44
  • 66