ah - compiler semantics vs launcher semantics, and the source code launcher lives in both worlds
javac refuses to compile two sources for the same class javac will compile a source file for a class that exists on the classpath (and even overwrite a class on the classpath) java accepts two class files for the same class on the classpath (favors the first) java -jar accepts a jar containing classes that also exist on the classpath (favors the jar) java refuses to launch a source file for a class that exists on the classpath the source code launcher "feels" like an outlier to me, but it could certainly go either way and i guess the source code launcher is an outlier anyway, as it decouples the first toplevel class name from the file name On Fri, Sep 14, 2018 at 1:49 PM, Jonathan Gibbons < jonathan.gibb...@oracle.com> wrote: > Seth, > > The reason for reporting an error is to prevent the "wrong" class being > used! > > -- Jon > > > On 09/14/2018 10:14 AM, seth lytle wrote: > >> this behavior of throwing an error if the class is found twice on the >> classpath strikes me as unusual - afaik, in all other cases, java is fine >> with finding multiple implementations on the classpath and uses the first >> found (similar to the unix path, which also allows overriding by providing >> an explicit path) and this is really one of the powerful features of java >> - >> allowing you to swap out one class for another seamlessly. and for rapid >> prototyping, "java -cp target/classes:$cp src/myPackage/Stuff.java" runs >> much faster than "mvn package -Dexec.mainClass=myPackage.Stuff", and >> doubly >> so for mvnDebug >> >> >> >> >> >> >> >> >> >> >> On Fri, Sep 14, 2018 at 3:33 AM, Peter Levart <peter.lev...@gmail.com> >> wrote: >> >> Hi Jaikiran, >>> >>> Forwarding to compiler-dev as the core of source file launcher feature is >>> produced there... >>> >>> The check for main class is performed after compilation (which actually >>> produces the main class name). >>> >>> I think it would be possible to check for all classes compiled from the >>> source file after compilation without to much complication. The >>> compilation >>> produces classes and stores them into a Map<String, byte[]>. The keySet() >>> of that map is a Set of compiled class names. Each of them could be >>> tested >>> via .getResource() invoked upon the application class loader. The error >>> could even point to the URL of the conflicting class file that way... >>> >>> Regards, Peter >>> >>> >>> On 09/14/2018 07:36 AM, Jaikiran Pai wrote: >>> >>> Please consider this trivial code C.java: >>> >>> public class C { >>> public static void main(String[] args) throws Exception { >>> System.out.println("main() execution started"); >>> } >>> } >>> >>> >>> >>> ls >>> >>> C.java >>> >>> Similar to a previous discussion[1] while doing random testing, I ended >>> up compiling C.java explicitly using javac: >>> >>> >>> javac C.java >>> ls >>> >>> C.java C.class >>> >>> and then at a later date tried to use the source file launcher feature >>> of Java 11 (without realizing C.class was already present in the dir): >>> >>> >>> java C.java >>> >>> This threw the error: >>> >>> error: class found on application class path: C >>> >>> Although the error isn't that clear for the reason I note in [2], having >>> run into this before, I was aware what this meant and deleted the >>> C.class and moved on. The important part here is that the source >>> launcher noticed this condition and aborted even before it auto >>> compiled(?) and launched and executed the main() of the program. >>> >>> Now consider a slight modification to that source file: >>> >>> public class C { >>> public static void main(String[] args) throws Exception { >>> System.out.println("main() execution started"); >>> final B b = new B(); >>> System.out.println("Done"); >>> } >>> >>> private static class B { >>> >>> } >>> } >>> >>> Again at some point I compiled this explicitly using javac, so my >>> directory is (among other things): >>> >>> >>> ls >>> >>> C$B.class C.class C.java >>> >>> Then ran the source file launcher feature: >>> >>> >>> java C.java >>> >>> error: class found on application class path: C >>> >>> As expected, ran into the same previous error. As before, in order to >>> move on, deleted C.class: >>> >>> >>> rm C.class >>> >>> but forgot to delete the nested static class that belonged to it. So the >>> directory now contained: >>> >>> >>> ls >>> >>> C$B.class C.java >>> >>> Now used the source launcher feature again: >>> >>> >>> java C.java >>> >>> This time it failed with: >>> >>> main() execution started >>> Exception in thread "main" java.lang.IllegalAccessError: failed to >>> access class C$B from class C (C$B is in unnamed module of loader 'app'; >>> C is in unnamed module of loader >>> com.sun.tools.javac.launcher.Main$MemoryClassLoader @1b1473ab) >>> at C.main(C.java:4) >>> >>> The error message isn't clear to pinpoint the issue, but at least the >>> reference to MemoryClassLoader was a hint that was enough for me to >>> understand where to look. It did take me a few minutes to realize that >>> C$B.class was lying around which I needed to remove too. >>> >>> However, IMO, the important part here is that unlike in the first case >>> where the program itself wasn't launched and instead was aborted early, >>> in this case the program did get executed (notice the System.out.println >>> "main() execution started" message that got printed) and failed at >>> runtime. >>> >>> Would it be possible to make these two behaviours consistent and detect >>> such cases and abort early here too? Or would that add too much >>> complexity to this feature? >>> >>> Finally, any thoughts on the error messages for this feature to make it >>> a bit easier in terms of debugging (classloading) issues like these? >>> >>> [1] http://mail.openjdk.java.net/pipermail/jdk-dev/2018-June/001425.html >>> [2] http://mail.openjdk.java.net/pipermail/jdk-dev/2018-June/001438.html >>> >>> -Jaikiran >>> >>> >>> >>> >>> >