A long time ago, when I switched from C to Java, I missed preprocessor macros, like:
#ifdef SOME_STUFF
...
#endif
This was annoying, especially on Android where there was no payment API yet, so the only way to make a paid application was to make a demo version alongside the paid one. Since both apps were essentially the same, you had to use some trickery to try to remove the functionality from the demo. Either a simple “if” condition, which could be easily changed by reverse engineering or trying to change the classes with some Gradle tricks.
Java has no preprocessor so you couldn’t do that. Back in the time I considered this as a drawback.
Well, turns out it’s actually a good thing. Because the main problem with conditional compilation is something I found out while trying to debug some C program recently:
Code rot within rarely used defines.
Indeed, I enabled some define to add additional logging and nothing worked: compilation errors everywhere. Why? Because the code around it changed and nobody bothered to check if the logging still worked.
Worse, sometimes when editing such code, the IDE (VSCode in that case) can become confused and show the wrong path since it cannot know for sure which define will be enabled or not, so you get some grey colored code which is actually the real one.
So, always remember that when you put code around a define, it’s no longer tested. The only legitimate use I see for them are for macros and for portability where you don’t have much choice anyway.