5

I have a Java class that is about 4,000 lines long (lots of methods). This class then uses about 200 small classes that only it needs, so another 4,000 lines of code.

If this was C# I would put those other in a partial class file so different file, but they would remain private nested classes only visible to the parent class.

Is there a way to do this in Java? I'm not asking for some methods to be in a distinct file, but for private nested classes to be in a distinct file.

thanks - dave

David Thielen
  • 28,723
  • 34
  • 119
  • 193
  • I did a search and could not find this here, although I'm guessing it's been asked before. – David Thielen Jan 14 '18 at 13:57
  • No, there's not partial classes in Java. Although you can use some sort of aggregation to achieve similar benefits/functionality. – Ousmane D. Jan 14 '18 at 14:01
  • 2
    Any reason not to just have them as classes in the same package? (By the way, a class file that's 4000 lines long, is about 3800 lines TOO LONG) – Stewart Jan 14 '18 at 14:02
  • 1
    Are the classes static, or do they use some of the "lots of methods" in a 4000-line class? – Sergey Kalinichenko Jan 14 '18 at 14:05
  • 3
    Put this class and all of the child classes in a separate package, then give the child classes package-private visibility. – Andy Turner Jan 14 '18 at 14:08
  • By "child" classes, you mean nested classes, right? I would suggest editing the question to indicate that, as otherwise "child" sounds like "subclass". But no, you can't write nested classes in a different source file. – Jon Skeet Jan 14 '18 at 14:12
  • 2
    @Stewart: I don't think it's a good idea to put arbitrary limits on the lengths of classes. Without context, we really can't know whether that's too much or not. You'll find plenty of types that are about a thousand lines long in https://github.com/nodatime/nodatime/tree/master/src/NodaTime, but I generally don't feel they're too large. If you've got a fair number of methods, each of which has significant amounts of documentation, it can easily mount up. – Jon Skeet Jan 14 '18 at 14:15
  • @dasblinkenlight - it's a ton of static classes, most of them under 10 lines of code. – David Thielen Jan 14 '18 at 14:35
  • @Stewart - This is code that parses a very complex XML file turning it into an internal object. We have a nested class for every node type to process that node. And we have a ton of utility methods so we don't have duplicate code across all those nested classes. This would be a complex mess if we tried to reduce the main handler down to a bunch of 200 line classes. As to the second file, yes it's a ton of distinct classes but all in one file so we don't have 200 source files (also a mess). – David Thielen Jan 14 '18 at 14:41
  • @AndyTurner - I don't think the package-private gives me anything. The default visibility is a bit more restrictive than that and it works fine. The separate folder might help, need to think that through. Thanks. – David Thielen Jan 14 '18 at 14:43
  • 1
    @David default visibility *is* package private. What do you mean? – Andy Turner Jan 14 '18 at 14:48
  • @AndyTurner - sorry, right. I bounce between JavaScript, C# and Java so much I get them conflated at times – David Thielen Jan 14 '18 at 15:10
  • @DavidThielen If you have a ton of utility methods, sounds like at least they should go in a class of their own. – Stewart Jan 14 '18 at 18:03

3 Answers3

3

You can't make a class private to only another class while putting it in a different file.

Use no class access modifier

What you can do is put the classes in separate files with no access modifiers (omit "public"), which will make them package-private, i.e. visible only within its own package. See also the official Access Control tutorial.

UtilClasses.java:

package OurPackage;

class UtilClass1
{
}
class UtilClass2
{
}

MainClass.java:

package OurPackage;

public class MainClass
{
   UtilClass1 iAmAUtilClass;
}

Use interfaces or inheritance

You can also achieve something similar with either interfaces or inheritance, by omitting the access modifier from the nested class. This would also be package-private, but this might be preferable to the above in some circumstances, since it avoids having all the nested classes at the top level.

BaseInterface.java:

package OurPackage;

interface BaseInterface
{
   class UtilClass1
   {
   }
}

MainClass.java:

package OurPackage;

public class MainClass implements BaseInterface
{
   UtilClass1 iAmAUtilClass;
}

You can also use a base class instead of an interface and extend that with roughly the same effect.

You don't need to implement BaseInterface gain access to its nested classes, but, if you don't, you'd need to use BaseClass.UtilClass1 instead of just UtilClass1.

Community
  • 1
  • 1
Bernhard Barker
  • 54,589
  • 14
  • 104
  • 138
1

Inner private classes can't be "extracted" and still be visible only to one particular class. One solution is already mentioned in the comments: Create a package that contains the "main" class and all the previously inner classes and make the inner classes package visible. This would also allow you to create unit tests testing for the correct functionalities of the inner classes, which is something that is most likely currently not happening simply because the inner classes can't be "reached" by a unit test at the moment.

Concepts like declaring "friendships" between classes like in C++ don't exist in Java.

Lothar
  • 5,323
  • 1
  • 11
  • 27
0

You can replace the inner classes with top-level ones, but you'll have to rewrite a lot of things by hand that the compiler auto-wires for you with the inner-class relationship. To the Virtual Machine, an inner class is nothing special, it's just another class in the same package as the outer class with a fancy name. But the compiler creates a lot of helper constructs under the hood, that you have to reconstruct by hand (or have some refactoring tool do that for you):

  • The inner class can refer to the outer this instance, by prefixing it with the outer class name. You need to pass the outer this into your inner constructor and store it in a field like outerThis to get access.
  • In the source code, you can call the outer-class methods directly. You need to rewrite it like outerThis.method(). The same applies to fields.
  • For private outer methods and fields to become accessible, the compiler creates bridge constructs for you. You have to either change access modifiers or create package-private bridge methods yourself.

In the end, you'll have the former inner classes at least package-visible and being more verbose than the original ones, but on the other hand you'll get better isolation and testability.

Ralf Kleberhoff
  • 6,990
  • 1
  • 13
  • 7